GPUImage框架中的样条插值算法

Spline Interpolation Algorithm in GPUImage framework

我最近使用GPUImage框架([​​=24=]GPUImageToneCurveFilter)读取Adobe acv文件并生成着色器纹理的LUT。在成功使用此实用程序在我的图像中呈现自定义色调后,我很好奇使用了哪种样条插值算法来实现这一点。在示例代码中,我可以发现有一些二阶导数计算。但是我不明白数学参数是从哪里来的。能否告诉我一些进一步插值研究的理论参考?谢谢。

[编辑]

正如Spektre在下面提到的,我将样条计算的代码粘贴在GPUImageToneCurveFilter.m中。这是 Objective-C 版本。

从ACV文件中获取控制点并将它们从(0, 1)转换为(0, 255)后,将这些点发送到splineCurve函数进行插值,如下所示:

NSMutableArray *splinePoints = [self splineCurve:convertedPoints];

两个插值函数为:

- (NSMutableArray *)splineCurve:(NSArray *)points
{
    NSMutableArray *sdA = [self secondDerivative:points];

    // [points count] is equal to [sdA count]
    NSInteger n = [sdA count];
    if (n < 1)
    {
        return nil;
    }
    double sd[n];

    // From NSMutableArray to sd[n];
    for (int i=0; i<n; i++) 
    {
        sd[i] = [[sdA objectAtIndex:i] doubleValue];
    }


    NSMutableArray *output = [NSMutableArray arrayWithCapacity:(n+1)];

    for(int i=0; i<n-1 ; i++) 
    {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
        CGPoint cur = [[points objectAtIndex:i] CGPointValue];
        CGPoint next = [[points objectAtIndex:(i+1)] CGPointValue];
#else
        NSPoint cur = [[points objectAtIndex:i] pointValue];
        NSPoint next = [[points objectAtIndex:(i+1)] pointValue];
#endif

        for(int x=cur.x;x<(int)next.x;x++) 
        {
            double t = (double)(x-cur.x)/(next.x-cur.x);

            double a = 1-t;
            double b = t;
            double h = next.x-cur.x;

            double y= a*cur.y + b*next.y + (h*h/6)*( (a*a*a-a)*sd[i]+ (b*b*b-b)*sd[i+1] );

            if (y > 255.0)
            {
                y = 255.0;   
            }
            else if (y < 0.0)
            {
                y = 0.0;   
            }
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
            [output addObject:[NSValue valueWithCGPoint:CGPointMake(x, y)]];
#else
            [output addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
#endif
        }
    }

    // The above always misses the last point because the last point is the last next, so we approach but don't equal it.
    [output addObject:[points lastObject]];
    return output;
}

- (NSMutableArray *)secondDerivative:(NSArray *)points
{
    const NSInteger n = [points count];
    if ((n <= 0) || (n == 1))
    {
        return nil;
    }

    double matrix[n][3];
    double result[n];
    matrix[0][1]=1;
    // What about matrix[0][1] and matrix[0][0]? Assuming 0 for now (Brad L.)
    matrix[0][0]=0;    
    matrix[0][2]=0;    

    for(int i=1;i<n-1;i++) 
    {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
        CGPoint P1 = [[points objectAtIndex:(i-1)] CGPointValue];
        CGPoint P2 = [[points objectAtIndex:i] CGPointValue];
        CGPoint P3 = [[points objectAtIndex:(i+1)] CGPointValue];
#else
        NSPoint P1 = [[points objectAtIndex:(i-1)] pointValue];
        NSPoint P2 = [[points objectAtIndex:i] pointValue];
        NSPoint P3 = [[points objectAtIndex:(i+1)] pointValue];
#endif

        matrix[i][0]=(double)(P2.x-P1.x)/6;
        matrix[i][1]=(double)(P3.x-P1.x)/3;
        matrix[i][2]=(double)(P3.x-P2.x)/6;
        result[i]=(double)(P3.y-P2.y)/(P3.x-P2.x) - (double)(P2.y-P1.y)/(P2.x-P1.x);
    }

    // What about result[0] and result[n-1]? Assuming 0 for now (Brad L.)
    result[0] = 0;
    result[n-1] = 0;

    matrix[n-1][1]=1;
    // What about matrix[n-1][0] and matrix[n-1][2]? For now, assuming they are 0 (Brad L.)
    matrix[n-1][0]=0;
    matrix[n-1][2]=0;

    // solving pass1 (up->down)
    for(int i=1;i<n;i++) 
    {
        double k = matrix[i][0]/matrix[i-1][1];
        matrix[i][1] -= k*matrix[i-1][2];
        matrix[i][0] = 0;
        result[i] -= k*result[i-1];
    }
    // solving pass2 (down->up)
    for(NSInteger i=n-2;i>=0;i--)
    {
        double k = matrix[i][2]/matrix[i+1][1];
        matrix[i][1] -= k*matrix[i+1][0];
        matrix[i][2] = 0;
        result[i] -= k*result[i+1];
    }

    double y2[n];
    for(int i=0;i<n;i++) y2[i]=result[i]/matrix[i][1];

    NSMutableArray *output = [NSMutableArray arrayWithCapacity:n];
    for (int i=0;i<n;i++) 
    {
        [output addObject:[NSNumber numberWithDouble:y2[i]]];
    }

    return output;
}

希望这些解释可以帮助您了解 Brad L. 使用何种算法对曲线进行插值,从而产生与 Photoshop 相同的结果。

我不认识这种语言(除了 C++ 之类的东西)所以很难读懂它(对我来说)但我认为这个

double y= a*cur.y + b*next.y + (h*h/6)*( (a*a*a-a)*sd[i]+ (b*b*b-b)*sd[i+1] );

是你问的。术语:

a*cur.y + b*next.y

是基本的线性插值,所以当 t=0.0 结果是当前点,如果 t=1.0 结果是下一个点。其余的是某种具有特定参数的叠加曲线,可能会为按点距离缩放的梯度产生某种凹凸图案(这就是为什么使用增量)。要扭转它会很难(而且我太懒了)但是热量让我想起 Bernstein 多项式(用于 BEZIER样条曲线)。 a^3b^3 表示三次曲线。您应该从绘制它生成的图形开始,然后尝试创建具有相似属性的多项式插值。如果结果与您找到的词匹配,您就回答。有关详细信息,请参阅:

以及那里的所有链接(尤其是 How to construct own interpolation 链接)。