反双线性插值?

时间:2009-04-30 18:38:57

标签: math graphics geometry 2d bilinear-interpolation

我有四个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解决方案的实现发布为维基。

8 个答案:

答案 0 :(得分:23)

我认为将问题视为交叉问题是最容易的:参数位置(s,t)是什么,其中点p与由p0,p1,p2和p3定义的任意2D双线性曲面相交。

我将采取的解决此问题的方法与tspauld的建议类似。

从x和y的两个方程开始:

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) )

我们现在可以设置这两个方程彼此相等以消除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)

请注意,我使用运算符X来表示2D cross product(例如,p0 X p1 = x0 * y1 - y0 * x1)。我已经将这个等式格式化为二次Bernstein polynomial,因为这使得事物更加优雅并且在数值上更稳定。 s的解决方案是这个等式的根源。我们可以使用伯恩斯坦多项式的二次公式找到根:

s = ( (A-B) +- sqrt(B^2 - A*C) ) / ( A - 2*B + C )

由于+ - ,二次公式给出了两个答案。如果您只对p位于双线性表面内的解决方案感兴趣,那么您可以丢弃任何s不在0和1之间的答案。要找到t,只需将s替换回上面我们解决的两个方程之一就s而言。

我应该指出一个重要的特例。如果分母A - 2 * B + C = 0,则二次多项式实际上是线性的。在这种情况下,您必须使用更简单的公式来查找s:

s = A / (A-C)

这将为您提供一个解决方案,除非AC = 0.如果A = C,那么您有两种情况:A = C = 0意味着所有值包含p,否则 s的值不包含p。

答案 1 :(得分:9)

可以使用Newton's method迭代求解以下非线性方程组:

p = p0*(1-s)*(1-t) + p1*s*(1-t) + p2*s*t + p3*(1-s)*t.

请注意,有两个方程(方程的x和y分量都相等)和两个未知数(s和t)。

要应用牛顿方法,我们需要residual r,即:

r = p - (p0*(1-s)*(1-t) + p1*s*(1-t) + p2*s*t + p3*(1-s)*t),

Jacobian matrix 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).

要使用Newton的方法,首先从初始猜测(s,t)开始,然后执行以下迭代几次:

(s,t) = (s,t) - J^-1 r,

使用s和t的新值重新计算J和r的每次迭代。在每次迭代中,主要成本是通过求解以J为系数矩阵且r为右手边的2x2线性系统,将雅可比行列式的逆应用于残差(J ^ -1 r)。

该方法的直觉:

直观地说,如果四边形是parallelogram,那么解决问题会容易得多。牛顿方法用连续的平行四边形近似解决四边形问题。在每次迭代中我们

  1. 在点(s,t)处使用局部导数信息来近似平行四边形的四边形。

  2. 通过求解线性系统,在平行四边形近似下找到正确的(s,t)值。

  3. 跳到这个新点并重复。

  4. 方法的优点:

    正如牛顿型方法所预期的那样,收敛速度非常快。随着迭代的进行,方法不仅越来越接近真实点,而且局部平行四边形近似也变得更加准确,因此收敛速度本身也在增加(在迭代方法术语中,我们说Newton&#39 ; s方法是quadratically convergent)。实际上,这意味着每次迭代时正确数字的数量大约加倍。

    这是我做过的随机试验的迭代与错误的代表性表格(见下面的代码):

    Iteration  Error
    1          0.0610
    2          9.8914e-04
    3          2.6872e-07
    4          1.9810e-14
    5          5.5511e-17 (machine epsilon)
    

    经过两次迭代后,误差足够小,对于大多数实际用途而言实际上是不可理解的,并且在5次迭代后,结果精确到machine precision的极限。

    如果你确定了迭代次数(比如说,对于大多数实际应用程序来说是3次迭代,如果需要非常高的精度,则需要8次迭代),那么算法就有一个非常简单明了的逻辑,其结构非常适合自己高性能计算。无需检查各种特殊边缘情况*,并根据结果使用不同的逻辑。它只是一个包含一些简单公式的for循环。下面我将重点介绍这种方法的优点,而不是传统的基于公式的方法,这些方法可以在互联网的其他答案中找到:

    • 易于编码。只需制作一个for循环并输入几个公式。

    • 没有条件或分支(if / then),这通常会让pipelining efficiency更好。

    • 没有平方根,每次迭代只需要1个分区(如果写得好)。所有其他操作都是简单的加法,减法和乘法。平方根和除法通常比加法/乘法/乘法慢几倍,并且可以在某些架构上(特别是在某些嵌入式系统上)提高cache效率。实际上,如果你深入了解现代编程语言实际上如何计算square rootsdivisions,他们都使用牛顿方法的变体,有时在硬件中,有时在软件中取决于建筑。

    • 可以轻松地进行矢量化,以便同时处理具有大量四边形的阵列。请参阅下面的矢量化代码,了解如何执行此操作的示例。

    • 扩展到任意维度。该算法以直接的方式扩展到任意数量维度(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中,更容易选择使形状从内向外变换​​的点本身。

答案 2 :(得分:6)

这是我对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;
}

答案 3 :(得分:5)

由于你在2D工作,你的bilerp函数实际上是2个方程,x表示1,y表示1。它们可以用以下形式重写:

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,替换为第二个,并求解s

答案 4 :(得分:3)

这是我的实施......我猜它比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

答案 5 :(得分:1)

如果你只有p的单个值,使得p在广场的四个角上的最小值和最大值之间,那么不,通常不可能找到一个单一的解(s,t) )这样双线性插值会给你这个值。

通常,正方形内部会有无数个解(s,t)。它们将沿着弯曲的(双曲线)路径穿过广场。

如果你的函数是一个值为1的向量,那么你在广场的某个未知点有两个已知值?给定广场每个角落的两个参数的已知值,则可能存在解决方案,但无法保证。请记住,我们可以将其视为两个独立的独立问题。它们中的每一个的解决方案将沿着通过正方形的双曲线轮廓线。如果这对轮廓在正方形内交叉,则存在解决方案。如果他们没有交叉,那么就没有解决方案。

您还会问是否存在一个简单的公式来解决问题。对不起,但不是我看到的。正如我所说,曲线是双曲线的。

一种解决方案是切换到不同的插值方法。因此,不是双线性,而是将正方形分成一对三角形。在每个三角形内,我们现在可以使用真正的线性插值。所以现在我们可以解决每个三角形内2个未知数的2个方程的线性系统。每个三角形可能有一个解决方案,除了罕见的退化情况,其中相应的分段线性轮廓线碰巧是共同发生的。

答案 6 :(得分:1)

有些回复略微误解了您的问题。即。他们假设给你一个未知插值函数的,而不是你想要找到(s,t)坐标的四元组内的插值位置p(x,y)。这是一个更简单的问题,并且保证有一个解决方案是通过四边形的两条直线的交叉点。

其中一条线将穿过区段p0p1和p2p3,另一条线将穿过p0p2和p1p3,类似于轴对齐的情况。这些线由p(x,y)的位置唯一定义,并且在这一点上显然会相交。

考虑到切割p0p1和p2p3的线,我们可以想象一系列这样的线,对于我们选择的每个不同的s值,每个都以不同的宽度切割四边形。如果我们修正一个s值,我们可以通过设置t = 0和t = 1来找到两个端点。

首先假设该行具有以下形式: y = a0 * x + b0

如果我们修复给定的s值,那么我们知道这一行的两个端点。他们是:

(1-s)p0 +(s)p1

(1-s)p2 +(s)p3

给定这两个端点,我们可以通过将线插入线的等式来确定线的族,并将a0和b0 求解为s 的函数。设置s值可给出特定线的公式。我们现在需要的是弄清楚这个家族中的哪一行击中了我们的点p(x,y)。只需将p(x,y)的坐标插入到我们的线公式中,我们就可以求解s的目标值。

也可以通过相应的方法找到t。

答案 7 :(得分:0)

好吧,如果p是2D点,是的,你可以很容易地得到它。在这种情况下,S是T处四边形总宽度的分数分量,T同样是S处四边形总高度的分数分量。

但是,如果p是标量,则不一定可能,因为双线性插值函数不一定是单片的。