四辺形を形成する4つの2Dポイント、p0 =(x0、y0)、p1 =(x1、y1)などがあります。私の場合、クワッドは長方形ではありませんが、少なくとも凸面である必要があります。
p2 --- p3
| |
t | p |
| |
p0 --- p1
s
バイリニア補間を使用しています。 SとTは[0..1]内にあり、補間された点は次の式で与えられます。
bilerp(s,t) = t*(s*p3+(1-s)*p2) + (1-t)*(s*p1+(1-s)*p0)
ここに問題があります。クワッドの内側にあることがわかっている2Dポイントpがあります。バイリニア補間を使用するときにそのポイントを与えるs、tを見つけたいと思います。
バイリニア補間を逆にする簡単な式はありますか?
ソリューションをありがとう。 Naaffのソリューションの実装をwikiとして投稿しました。
あなたの問題を交差問題と考えるのが最も簡単だと思います。点pがp0、p1、p2、p3で定義される任意の2D双一次曲面と交差するパラメータの位置(s、t)は何ですか。
この問題を解決するために私が取るアプローチは、tspauldの提案に似ています。
Xとyに関する2つの方程式から始めます。
x = (1-s)*( (1-t)*x0 + t*x2 ) + s*( (1-t)*x1 + t*x3 )
y = (1-s)*( (1-t)*y0 + t*y2 ) + s*( (1-t)*y1 + t*y3 )
Tを解く:
t = ( (1-s)*(x0-x) + s*(x1-x) ) / ( (1-s)*(x0-x2) + s*(x1-x3) )
t = ( (1-s)*(y0-y) + s*(y1-y) ) / ( (1-s)*(y0-y2) + s*(y1-y3) )
これで、これら2つの方程式を互いに等しく設定して、tを削除できます。すべてを左側に移動して単純化すると、次の形式の方程式が得られます。
A*(1-s)^2 + B*2s(1-s) + C*s^2 = 0
どこ:
A = (p0-p) X (p0-p2)
B = ( (p0-p) X (p1-p3) + (p1-p) X (p0-p2) ) / 2
C = (p1-p) X (p1-p3)
2D外積 を表すために演算子Xを使用したことに注意してください(例:p0 X p1 = x0 * y1-y0 * x1)。この方程式を2次方程式としてフォーマットしました バーンスタイン多項式 これにより、物事がよりエレガントになり、数値的に安定します。 sの解は、この方程式の根です。バーンスタイン多項式の2次方程式を使用して、根を見つけることができます。
s = ( (A-B) +- sqrt(B^2 - A*C) ) / ( A - 2*B + C )
二次方程式は、+-のために2つの答えを与えます。 pが双線形表面内にある解のみに関心がある場合は、sが0から1の間にない解を破棄できます。tを見つけるには、上記の2つの方程式のいずれかにsを代入して、tを解きます。 sの観点から。
重要な特殊なケースを1つ指摘しておきます。分母A-2 * B + C = 0の場合、2次多項式は実際には線形です。この場合、sを見つけるためにはるかに単純な方程式を使用する必要があります。
s = A / (A-C)
これにより、AC = 0でない限り、正確に1つの解が得られます。A= Cの場合、2つのケースがあります。A= C = 0は、allの値を意味します。 sにはpが含まれます。それ以外の場合、sのno値にはpが含まれます。
ニュートン法 を使用して、次の非線形連立方程式を繰り返し解くことができます。
p = p0*(1-s)*(1-t) + p1*s*(1-t) + p2*s*t + p3*(1-s)*t.
2つの方程式(方程式のx成分とy成分の両方に等しい)と2つの未知数(sとt)があることに注意してください。
ニュートン法を適用するには、 残余 rが必要です。これは次のとおりです。
r = p - (p0*(1-s)*(1-t) + p1*s*(1-t) + p2*s*t + p3*(1-s)*t),
ヤコビ行列 Jは、残余を微分することによって求められます。私たちの問題では、ヤコビアンは次のとおりです。
J(:,1) = dr/ds = -p0*(1-t) + p1*(1-t) + p2*t - p3*t (first column of matrix)
J(:,2) = dr/dt = -p0*(1-s) - p1*s + p2*s + p3*(1-s) (second column).
ニュートン法を使用するには、最初の推測(s、t)から始めて、次の反復を数回実行します。
(s,t) = (s,t) - J^-1 r,
sとtの新しい値を使用して、反復ごとにJとrを再計算します。各反復での支配的なコストは、係数行列としてJを、右辺としてrを使用して2x2線形システムを解くことにより、ヤコビアンの逆行列を残差(J ^ -1 r)に適用することです。
メソッドの直感:
直感的には、四辺形が 平行四辺形 の場合、問題を解決するのははるかに簡単です。ニュートン法は、平行四辺形近似を連続して四辺形問題を解くことです。各反復で、
点(s、t)で局所微分情報を使用して、平行四辺形で四辺形を近似します。
線形システムを解くことにより、平行四辺形近似の下で正しい(s、t)値を見つけます。
この新しいポイントにジャンプして繰り返します。
メソッドの利点:
ニュートンタイプの方法で予想されるように、収束は非常に高速です。反復が進むにつれて、メソッドは真の点にどんどん近づいていくだけでなく、局所的な平行四辺形の近似もより正確になるため、収束率自体が増加します(反復法の専門用語では、ニュートン法は 二次収束 )。実際には、これは正しい桁数が反復ごとに約2倍になることを意味します。
これは、私が行ったランダム試行での反復とエラーの代表的な表です(コードについては以下を参照)。
Iteration Error
1 0.0610
2 9.8914e-04
3 2.6872e-07
4 1.9810e-14
5 5.5511e-17 (machine epsilon)
2回の反復後、エラーは事実上気付かれないほど小さく、ほとんどの実用的な目的には十分です。5回の反復後、結果は マシン精度 の限界まで正確です。
反復回数を固定すると(たとえば、ほとんどの実用的なアプリケーションでは3回の反復、非常に高い精度が必要な場合は8回の反復)、アルゴリズムには非常に単純で単純なロジックがあり、その構造は高精度に適しています。パフォーマンスの計算。あらゆる種類の特殊なエッジケース*をチェックし、結果に応じて異なるロジックを使用する必要はありません。これは、いくつかの簡単な式を含む単なるforループです。以下に、ここやインターネット上の他の回答に見られる従来の数式ベースのアプローチに対するこのアプローチのいくつかの利点を強調します。
コーディングが簡単。 forループを作成し、いくつかの数式を入力するだけです。
条件や分岐はありません(if/then)。これにより、通常、はるかに優れた パイプライン効率 が可能になります。
平方根はなく、反復ごとに1つの除算のみが必要です(適切に記述されている場合)。他のすべての演算は、単純な加算、減算、および乗算です。平方根と除算は通常、加算/乗算/乗算よりも数倍遅く、特定のアーキテクチャ(特に特定の組み込みシステム)では効率が低下する可能性があります キャッシュ 。実際、 平方根 と 除算 が実際に最新のプログラミング言語によってどのように計算されるかを内部で見ると、どちらもニュートン法の変形を使用しています。アーキテクチャに応じたソフトウェア。
簡単にベクトル化して、一度に膨大な数の四辺形を持つ配列を処理できます。これを行う方法の例については、以下のベクトル化されたコードを参照してください。
任意の次元に拡張します。アルゴリズムは、任意の数の次元(2d、3d、4d、...)で逆多重線形補間に簡単な方法で拡張されます。以下に3Dバージョンを含めましたが、単純な再帰バージョンを作成したり、自動微分ライブラリを使用してn次元に移動したりすることを想像できます。ニュートン法は通常、次元に依存しない収束率を示すため、原則として、この方法は現在のハードウェアで数千次元(!)にスケーラブルである必要があります(その後、n行n列の行列Jの構築と解決が制限になる可能性があります因子)。
もちろん、これらのほとんどはハードウェア、コンパイラ、および他の多くの要因にも依存するため、マイレージは異なる場合があります。
コード:
とにかく、これが私のMatlabコードです:(私はここですべてをパブリックドメインにリリースします)
基本的な2Dバージョン:
function q = bilinearInverse(p,p1,p2,p3,p4,iter)
%Computes the inverse of the bilinear map from [0,1]^2 to the convex
% quadrilateral defined by the ordered points p1 -> p2 -> p3 -> p4 -> p1.
%Uses Newton's method. Inputs must be column vectors.
q = [0.5; 0.5]; %initial guess
for k=1:iter
s = q(1);
t = q(2);
r = p1*(1-s)*(1-t) + p2*s*(1-t) + p3*s*t + p4*(1-s)*t - p;%residual
Js = -p1*(1-t) + p2*(1-t) + p3*t - p4*t; %dr/ds
Jt = -p1*(1-s) - p2*s + p3*s + p4*(1-s); %dr/dt
J = [Js,Jt];
q = q - J\r;
q = max(min(q,1),0);
end
end
使用例:
% Test_bilinearInverse.m
p1=[0.1;-0.1];
p2=[2.2;-0.9];
p3=[1.75;2.3];
p4=[-1.2;1.1];
q0 = Rand(2,1);
s0 = q0(1);
t0 = q0(2);
p = p1*(1-s0)*(1-t0) + p2*s0*(1-t0) + p3*s0*t0 + p4*(1-s0)*t0;
iter=5;
q = bilinearInverse(p,p1,p2,p3,p4,iter);
err = norm(q0-q);
disp(['Error after ',num2str(iter), ' iterations: ', num2str(err)])
出力例:
>> test_bilinearInverse
Error after 5 iterations: 1.5701e-16
高速ベクトル化2Dバージョン:
function [ss,tt] = bilinearInverseFast(px,py, p1x,p1y, p2x,p2y, p3x,p3y, p4x,p4y, iter)
%Computes the inverse of the bilinear map from [0,1]^2 to the convex
% quadrilateral defined by the ordered points p1 -> p2 -> p3 -> p4 -> p1,
% where the p1x is the x-coordinate of p1, p1y is the y-coordinate, etc.
% Vectorized: if you have a lot of quadrilaterals and
% points to interpolate, then p1x(k) is the x-coordinate of point p1 on the
% k'th quadrilateral, and so forth.
%Uses Newton's method. Inputs must be column vectors.
ss = 0.5 * ones(length(px),1);
tt = 0.5 * ones(length(py),1);
for k=1:iter
r1 = p1x.*(1-ss).*(1-tt) + p2x.*ss.*(1-tt) + p3x.*ss.*tt + p4x.*(1-ss).*tt - px;%residual
r2 = p1y.*(1-ss).*(1-tt) + p2y.*ss.*(1-tt) + p3y.*ss.*tt + p4y.*(1-ss).*tt - py;%residual
J11 = -p1x.*(1-tt) + p2x.*(1-tt) + p3x.*tt - p4x.*tt; %dr/ds
J21 = -p1y.*(1-tt) + p2y.*(1-tt) + p3y.*tt - p4y.*tt; %dr/ds
J12 = -p1x.*(1-ss) - p2x.*ss + p3x.*ss + p4x.*(1-ss); %dr/dt
J22 = -p1y.*(1-ss) - p2y.*ss + p3y.*ss + p4y.*(1-ss); %dr/dt
inv_detJ = 1./(J11.*J22 - J12.*J21);
ss = ss - inv_detJ.*(J22.*r1 - J12.*r2);
tt = tt - inv_detJ.*(-J21.*r1 + J11.*r2);
ss = min(max(ss, 0),1);
tt = min(max(tt, 0),1);
end
end
速度を上げるために、このコードは2x2行列の逆行列に対して次の式を暗黙的に使用します。
[a,b;c,d]^-1 = (1/(ad-bc))[d, -b; -c, a]
使用例:
% test_bilinearInverseFast.m
n_quads = 1e6; % 1 million quads
iter = 8;
% Make random quadrilaterals, ensuring points are ordered convex-ly
n_randpts = 4;
pp_xx = zeros(n_randpts,n_quads);
pp_yy = zeros(n_randpts,n_quads);
disp('Generating convex point ordering (may take some time).')
for k=1:n_quads
while true
p_xx = randn(4,1);
p_yy = randn(4,1);
conv_inds = convhull(p_xx, p_yy);
if length(conv_inds) == 5
break
end
end
pp_xx(1:4,k) = p_xx(conv_inds(1:end-1));
pp_yy(1:4,k) = p_yy(conv_inds(1:end-1));
end
pp1x = pp_xx(1,:);
pp1y = pp_yy(1,:);
pp2x = pp_xx(2,:);
pp2y = pp_yy(2,:);
pp3x = pp_xx(3,:);
pp3y = pp_yy(3,:);
pp4x = pp_xx(4,:);
pp4y = pp_yy(4,:);
% Make random interior points
ss0 = Rand(1,n_quads);
tt0 = Rand(1,n_quads);
ppx = pp1x.*(1-ss0).*(1-tt0) + pp2x.*ss0.*(1-tt0) + pp3x.*ss0.*tt0 + pp4x.*(1-ss0).*tt0;
ppy = pp1y.*(1-ss0).*(1-tt0) + pp2y.*ss0.*(1-tt0) + pp3y.*ss0.*tt0 + pp4y.*(1-ss0).*tt0;
pp = [ppx; ppy];
% Run fast inverse bilinear interpolation code:
disp('Running inverse bilinear interpolation.')
tic
[ss,tt] = bilinearInverseFast(ppx,ppy, pp1x,pp1y, pp2x,pp2y, pp3x,pp3y, pp4x,pp4y, 10);
time_elapsed = toc;
disp(['Number of quadrilaterals: ', num2str(n_quads)])
disp(['Inverse bilinear interpolation took: ', num2str(time_elapsed), ' seconds'])
err = norm([ss0;tt0] - [ss;tt],'fro')/norm([ss0;tt0],'fro');
disp(['Error: ', num2str(err)])
出力例:
>> test_bilinearInverseFast
Generating convex point ordering (may take some time).
Running inverse bilinear interpolation.
Number of quadrilaterals: 1000000
Inverse bilinear interpolation took: 0.5274 seconds
Error: 8.6881e-16
3Dバージョン:
収束の進行状況を表示するためのコードが含まれています。
function ss = trilinearInverse(p, p1,p2,p3,p4,p5,p6,p7,p8, iter)
%Computes the inverse of the trilinear map from [0,1]^3 to the box defined
% by points p1,...,p8, where the points are ordered consistent with
% p1~(0,0,0), p2~(0,0,1), p3~(0,1,0), p4~(1,0,0), p5~(0,1,1),
% p6~(1,0,1), p7~(1,1,0), p8~(1,1,1)
%Uses Gauss-Newton method. Inputs must be column vectors.
tol = 1e-9;
ss = [0.5; 0.5; 0.5]; %initial guess
for k=1:iter
s = ss(1);
t = ss(2);
w = ss(3);
r = p1*(1-s)*(1-t)*(1-w) + p2*s*(1-t)*(1-w) + ...
p3*(1-s)*t*(1-w) + p4*(1-s)*(1-t)*w + ...
p5*s*t*(1-w) + p6*s*(1-t)*w + ...
p7*(1-s)*t*w + p8*s*t*w - p;
disp(['k= ', num2str(k), ...
', residual norm= ', num2str(norm(r)),...
', [s,t,w]= ',num2str([s,t,w])])
if (norm(r) < tol)
break
end
Js = -p1*(1-t)*(1-w) + p2*(1-t)*(1-w) + ...
-p3*t*(1-w) - p4*(1-t)*w + ...
p5*t*(1-w) + p6*(1-t)*w + ...
-p7*t*w + p8*t*w;
Jt = -p1*(1-s)*(1-w) - p2*s*(1-w) + ...
p3*(1-s)*(1-w) - p4*(1-s)*w + ...
p5*s*(1-w) - p6*s*w + ...
p7*(1-s)*w + p8*s*w;
Jw = -p1*(1-s)*(1-t) - p2*s*(1-t) + ...
-p3*(1-s)*t + p4*(1-s)*(1-t) + ...
-p5*s*t + p6*s*(1-t) + ...
p7*(1-s)*t + p8*s*t;
J = [Js,Jt,Jw];
ss = ss - J\r;
end
end
使用例:
%test_trilinearInverse.m
h = 0.25;
p1 = [0;0;0] + h*randn(3,1);
p2 = [0;0;1] + h*randn(3,1);
p3 = [0;1;0] + h*randn(3,1);
p4 = [1;0;0] + h*randn(3,1);
p5 = [0;1;1] + h*randn(3,1);
p6 = [1;0;1] + h*randn(3,1);
p7 = [1;1;0] + h*randn(3,1);
p8 = [1;1;1] + h*randn(3,1);
s0 = Rand;
t0 = Rand;
w0 = Rand;
p = p1*(1-s0)*(1-t0)*(1-w0) + p2*s0*(1-t0)*(1-w0) + ...
p3*(1-s0)*t0*(1-w0) + p4*(1-s0)*(1-t0)*w0 + ...
p5*s0*t0*(1-w0) + p6*s0*(1-t0)*w0 + ...
p7*(1-s0)*t0*w0 + p8*s0*t0*w0;
ss = trilinearInverse(p, p1,p2,p3,p4,p5,p6,p7,p8);
disp(['error= ', num2str(norm(ss - [s0;t0;w0]))])
出力例:
test_trilinearInverse
k= 1, residual norm= 0.38102, [s,t,w]= 0.5 0.5 0.5
k= 2, residual norm= 0.025324, [s,t,w]= 0.37896 0.59901 0.17658
k= 3, residual norm= 0.00037108, [s,t,w]= 0.40228 0.62124 0.15398
k= 4, residual norm= 9.1441e-08, [s,t,w]= 0.40218 0.62067 0.15437
k= 5, residual norm= 3.3548e-15, [s,t,w]= 0.40218 0.62067 0.15437
error= 4.8759e-15
逆多重線形補間は、形状の体積が正の場合にのみ明確に定義されるため、入力ポイントの順序に注意する必要があります。3Dでは、形状を裏返しにするポイントを選択する方がはるかに簡単です。
これが、コミュニティwikiとしてのNaaffのソリューションの実装です。再度、感謝します。
これはC実装ですが、C++で動作するはずです。ファジングテスト機能が含まれています。
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int equals( double a, double b, double tolerance )
{
return ( a == b ) ||
( ( a <= ( b + tolerance ) ) &&
( a >= ( b - tolerance ) ) );
}
double cross2( double x0, double y0, double x1, double y1 )
{
return x0*y1 - y0*x1;
}
int in_range( double val, double range_min, double range_max, double tol )
{
return ((val+tol) >= range_min) && ((val-tol) <= range_max);
}
/* Returns number of solutions found. If there is one valid solution, it will be put in s and t */
int inverseBilerp( double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double x, double y, double* sout, double* tout, double* s2out, double* t2out )
{
int t_valid, t2_valid;
double a = cross2( x0-x, y0-y, x0-x2, y0-y2 );
double b1 = cross2( x0-x, y0-y, x1-x3, y1-y3 );
double b2 = cross2( x1-x, y1-y, x0-x2, y0-y2 );
double c = cross2( x1-x, y1-y, x1-x3, y1-y3 );
double b = 0.5 * (b1 + b2);
double s, s2, t, t2;
double am2bpc = a-2*b+c;
/* this is how many valid s values we have */
int num_valid_s = 0;
if ( equals( am2bpc, 0, 1e-10 ) )
{
if ( equals( a-c, 0, 1e-10 ) )
{
/* Looks like the input is a line */
/* You could set s=0.5 and solve for t if you wanted to */
return 0;
}
s = a / (a-c);
if ( in_range( s, 0, 1, 1e-10 ) )
num_valid_s = 1;
}
else
{
double sqrtbsqmac = sqrt( b*b - a*c );
s = ((a-b) - sqrtbsqmac) / am2bpc;
s2 = ((a-b) + sqrtbsqmac) / am2bpc;
num_valid_s = 0;
if ( in_range( s, 0, 1, 1e-10 ) )
{
num_valid_s++;
if ( in_range( s2, 0, 1, 1e-10 ) )
num_valid_s++;
}
else
{
if ( in_range( s2, 0, 1, 1e-10 ) )
{
num_valid_s++;
s = s2;
}
}
}
if ( num_valid_s == 0 )
return 0;
t_valid = 0;
if ( num_valid_s >= 1 )
{
double tdenom_x = (1-s)*(x0-x2) + s*(x1-x3);
double tdenom_y = (1-s)*(y0-y2) + s*(y1-y3);
t_valid = 1;
if ( equals( tdenom_x, 0, 1e-10 ) && equals( tdenom_y, 0, 1e-10 ) )
{
t_valid = 0;
}
else
{
/* Choose the more robust denominator */
if ( fabs( tdenom_x ) > fabs( tdenom_y ) )
{
t = ( (1-s)*(x0-x) + s*(x1-x) ) / ( tdenom_x );
}
else
{
t = ( (1-s)*(y0-y) + s*(y1-y) ) / ( tdenom_y );
}
if ( !in_range( t, 0, 1, 1e-10 ) )
t_valid = 0;
}
}
/* Same thing for s2 and t2 */
t2_valid = 0;
if ( num_valid_s == 2 )
{
double tdenom_x = (1-s2)*(x0-x2) + s2*(x1-x3);
double tdenom_y = (1-s2)*(y0-y2) + s2*(y1-y3);
t2_valid = 1;
if ( equals( tdenom_x, 0, 1e-10 ) && equals( tdenom_y, 0, 1e-10 ) )
{
t2_valid = 0;
}
else
{
/* Choose the more robust denominator */
if ( fabs( tdenom_x ) > fabs( tdenom_y ) )
{
t2 = ( (1-s2)*(x0-x) + s2*(x1-x) ) / ( tdenom_x );
}
else
{
t2 = ( (1-s2)*(y0-y) + s2*(y1-y) ) / ( tdenom_y );
}
if ( !in_range( t2, 0, 1, 1e-10 ) )
t2_valid = 0;
}
}
/* Final cleanup */
if ( t2_valid && !t_valid )
{
s = s2;
t = t2;
t_valid = t2_valid;
t2_valid = 0;
}
/* Output */
if ( t_valid )
{
*sout = s;
*tout = t;
}
if ( t2_valid )
{
*s2out = s2;
*t2out = t2;
}
return t_valid + t2_valid;
}
void bilerp( double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double s, double t, double* x, double* y )
{
*x = t*(s*x3+(1-s)*x2) + (1-t)*(s*x1+(1-s)*x0);
*y = t*(s*y3+(1-s)*y2) + (1-t)*(s*y1+(1-s)*y0);
}
double randrange( double range_min, double range_max )
{
double range_width = range_max - range_min;
double Rand01 = (Rand() / (double)Rand_MAX);
return (Rand01 * range_width) + range_min;
}
/* Returns number of failed trials */
int fuzzTestInvBilerp( int num_trials )
{
int num_failed = 0;
double x0, y0, x1, y1, x2, y2, x3, y3, x, y, s, t, s2, t2, orig_s, orig_t;
int num_st;
int itrial;
for ( itrial = 0; itrial < num_trials; itrial++ )
{
int failed = 0;
/* Get random positions for the corners of the quad */
x0 = randrange( -10, 10 );
y0 = randrange( -10, 10 );
x1 = randrange( -10, 10 );
y1 = randrange( -10, 10 );
x2 = randrange( -10, 10 );
y2 = randrange( -10, 10 );
x3 = randrange( -10, 10 );
y3 = randrange( -10, 10 );
/*x0 = 0, y0 = 0, x1 = 1, y1 = 0, x2 = 0, y2 = 1, x3 = 1, y3 = 1;*/
/* Get random s and t */
s = randrange( 0, 1 );
t = randrange( 0, 1 );
orig_s = s;
orig_t = t;
/* bilerp to get x and y */
bilerp( x0, y0, x1, y1, x2, y2, x3, y3, s, t, &x, &y );
/* invert */
num_st = inverseBilerp( x0, y0, x1, y1, x2, y2, x3, y3, x, y, &s, &t, &s2, &t2 );
if ( num_st == 0 )
{
failed = 1;
}
else if ( num_st == 1 )
{
if ( !(equals( orig_s, s, 1e-5 ) && equals( orig_t, t, 1e-5 )) )
failed = 1;
}
else if ( num_st == 2 )
{
if ( !((equals( orig_s, s , 1e-5 ) && equals( orig_t, t , 1e-5 )) ||
(equals( orig_s, s2, 1e-5 ) && equals( orig_t, t2, 1e-5 )) ) )
failed = 1;
}
if ( failed )
{
num_failed++;
printf("Failed trial %d\n", itrial);
}
}
return num_failed;
}
int main( int argc, char** argv )
{
int num_failed;
srand( 0 );
num_failed = fuzzTestInvBilerp( 100000000 );
printf("%d of the tests failed\n", num_failed);
getc(stdin);
return 0;
}
2Dで作業しているため、bilerp
関数は実際には2つの方程式であり、1つはx、1つはyです。それらは次の形式で書き直すことができます。
x = t * s * A.x + t * B.x + s * C.x + D.x
y = t * s * A.y + t * B.y + s * C.y + D.y
どこ:
A = p3 - p2 - p1 + p0
B = p2 - p0
C = p1 - p0
D = p0
最初の方程式を書き直して、t
をs
で取得し、2番目の方程式に代入して、s
を解きます。
これは私の実装です...私はそれがtfinigaよりも速いと思います
void invbilerp( float x, float y, float x00, float x01, float x10, float x11, float y00, float y01, float y10, float y11, float [] uv ){
// substition 1 ( see. derivation )
float dx0 = x01 - x00;
float dx1 = x11 - x10;
float dy0 = y01 - y00;
float dy1 = y11 - y10;
// substitution 2 ( see. derivation )
float x00x = x00 - x;
float xd = x10 - x00;
float dxd = dx1 - dx0;
float y00y = y00 - y;
float yd = y10 - y00;
float dyd = dy1 - dy0;
// solution of quadratic equations
float c = x00x*yd - y00y*xd;
float b = dx0*yd + dyd*x00x - dy0*xd - dxd*y00y;
float a = dx0*dyd - dy0*dxd;
float D2 = b*b - 4*a*c;
float D = sqrt( D2 );
float u = (-b - D)/(2*a);
// backsubstitution of "u" to obtain "v"
float v;
float denom_x = xd + u*dxd;
float denom_y = yd + u*dyd;
if( abs(denom_x)>abs(denom_y) ){ v = -( x00x + u*dx0 )/denom_x; }else{ v = -( y00y + u*dy0 )/denom_y; }
uv[0]=u;
uv[1]=v;
/*
// do you really need second solution ?
u = (-b + D)/(2*a);
denom_x = xd + u*dxd;
denom_y = yd + u*dyd;
if( abs(denom_x)>abs(denom_y) ){ v = -( x00x + u*dx0 )/denom_x; }else{ v2 = -( y00y + u*dy0 )/denom_y; }
uv[2]=u;
uv[3]=v;
*/
}
と派生
// starting from bilinear interpolation
(1-v)*( (1-u)*x00 + u*x01 ) + v*( (1-u)*x10 + u*x11 ) - x
(1-v)*( (1-u)*y00 + u*y01 ) + v*( (1-u)*y10 + u*y11 ) - y
substition 1:
dx0 = x01 - x00
dx1 = x11 - x10
dy0 = y01 - y00
dy1 = y11 - y10
we get:
(1-v) * ( x00 + u*dx0 ) + v * ( x10 + u*dx1 ) - x = 0
(1-v) * ( y00 + u*dy0 ) + v * ( y10 + u*dy1 ) - y = 0
we are trying to extract "v" out
x00 + u*dx0 + v*( x10 - x00 + u*( dx1 - dx0 ) ) - x = 0
y00 + u*dy0 + v*( y10 - y00 + u*( dy1 - dy0 ) ) - y = 0
substition 2:
x00x = x00 - x
xd = x10 - x00
dxd = dx1 - dx0
y00y = y00 - y
yd = y10 - y00
dyd = dy1 - dy0
// much nicer
x00x + u*dx0 + v*( xd + u*dxd ) = 0
y00x + u*dy0 + v*( yd + u*dyd ) = 0
// this equations for "v" are used for back substition
v = -( x00x + u*dx0 ) / ( xd + u*dxd )
v = -( y00x + u*dy0 ) / ( yd + u*dyd )
// but for now, we eliminate "v" to get one eqution for "u"
( x00x + u*dx0 ) / ( xd + u*dxd ) = ( y00y + u*dy0 ) / ( yd + u*dyd )
put denominators to other side
( x00x + u*dx0 ) * ( yd + u*dyd ) = ( y00y + u*dy0 ) * ( xd + u*dxd )
x00x*yd + u*( dx0*yd + dyd*x00x ) + u^2* dx0*dyd = y00y*xd + u*( dy0*xd + dxd*y00y ) + u^2* dy0*dxd
// which is quadratic equation with these coefficients
c = x00x*yd - y00y*xd
b = dx0*yd + dyd*x00x - dy0*xd - dxd*y00y
a = dx0*dyd - dy0*dxd
一部の回答では、質問が少し誤解されています。すなわち。彼らは、(s、t)座標を見つけたいクワッド内の補間位置p(x、y)ではなく、未知の補間関数のvalueが与えられていると想定しています。これはより単純な問題であり、クワッドを通る2本の直線の交点である解決策があることが保証されています。
軸に沿った場合と同様に、一方の線はセグメントp0p1とp2p3を通り、もう一方の線はp0p2とp1p3を通ります。これらの線はp(x、y)の位置によって一意に定義され、この点で明らかに交差します。
P0p1とp2p3を切断する線だけを考えると、選択したさまざまなs値ごとに、それぞれが異なる幅でクワッドを切断する、そのような線のファミリーを想像できます。 s値を固定する場合、t = 0とt = 1を設定することで2つのエンドポイントを見つけることができます。
したがって、最初にその線が次の形式であると仮定します:y = a0 * x + b0
次に、与えられたs値を修正すると、この線の2つの端点がわかります。彼らです:
(1-s)p0 +(s)p1
(1-s)p2 +(s)p3
これらの2つのエンドポイントが与えられた場合、それらを線の方程式に接続し、a0とb0 sの関数としてを解くことにより、線のファミリーを決定できます。 s値を設定すると、特定の行の式が得られます。ここで必要なのは、このファミリのどの線が点p(x、y)に当たるかを把握することだけです。 p(x、y)の座標を直線の公式に代入するだけで、sの目標値を解くことができます。
対応するアプローチを実行して、tを見つけることもできます。
Pが正方形の四隅の最小値と最大値の間にあるようなpの単一の値しかない場合、いいえ、一般に、次のような単一の解(s、t)を見つけることはできません。バイリニア補間はその値を与えます。
一般に、正方形の内部には無限の数の解(s、t)があります。それらは、正方形を通る湾曲した(双曲線)経路に沿って配置されます。
関数がベクトル値の1である場合、正方形の未知の点に2つの既知の値がありますか?正方形の各コーナーにある2つのパラメータの既知の値が与えられると、解決策が存在する可能性がありますが、その保証はありません。これは、2つの別個の独立した問題と見なすことができることを忘れないでください。それらのそれぞれの解決策は、正方形を通る双曲線の等高線に沿って存在します。輪郭のペアが正方形の内側で交差する場合、解決策が存在します。それらが交差しない場合、解決策は存在しません。
また、問題を解決するための簡単な式が存在するかどうかを尋ねます。申し訳ありませんが、実際にはそうではありません。私が言ったように、曲線は双曲線です。
1つの解決策は、別の補間方法に切り替えることです。したがって、双線形の代わりに、正方形を三角形のペアに分割します。各三角形内で、真の線形補間を使用できるようになりました。これで、各三角形内の2つの未知数の2つの方程式の線形連立方程式を解くことができます。対応する区分的線形等高線が偶然に一致するまれな縮退の場合を除いて、各三角形に1つの解が存在する可能性があります。
そうですね、pが2Dポイントの場合、そうです、簡単に取得できます。その場合、SはTでの四辺形の全幅の小数部分であり、Tは同様にSでの四辺形の全高の小数部分です。
ただし、pがスカラーの場合、双一次内挿関数は必ずしもモノリシックではないため、必ずしも可能であるとは限りません。