Алгоритм интерполяции сплайнов во фреймворке GPUImage

Недавно я использовал структуру GPUImage (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;
}

Надеюсь, что эти объяснения помогут выяснить, какой алгоритм использовал Брэд Л. для интерполяции кривой, которая дает результат, как это сделал Photoshop.


person Casper Chang    schedule 12.10.2017    source источник
comment
хоть поделитесь ссылками которые вы описываете... мы не знаем методов о которых вы пишете поэтому без конкретики помочь не сможем...   -  person Spektre    schedule 12.10.2017
comment
@Spektre, спасибо. Я добавил некоторую информацию в конце своего поста.   -  person Casper Chang    schedule 13.10.2017


Ответы (1)


Я не узнаю этот язык (кроме 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 результатом является следующая точка. Остальное представляет собой какую-то суперпонированную кривую с определенными параметрами, вероятно, создающую некую выпуклость для градиента, масштабированного по расстоянию между точками (поэтому для него используются дельты). Обратить его было бы сложно (и я слишком ленив для этого), но термы напоминают мне полиномы Бернштейна (используются для БЕЗЬЕ и СПЛАЙН). a^3 и b^3 предполагают кубическую кривую. Вы должны начать с построения графика, который он создает, а затем попытаться создать полиномиальную интерполяцию, которая будет иметь аналогичные свойства. Если результат соответствует термину, который вы нашли, вы отвечаете. Для получения дополнительной информации см. это:

И все ссылки там (особенно How to construct own interpolation).

person Spektre    schedule 15.10.2017
comment
Эти термины и пояснения — именно то, что мне нужно, чтобы начать изучение в этой области. Я читал другие статьи по предоставленной вами ссылке не только для дальнейшего математического обсуждения, но также многое узнал о рендеринге кривых и точек в других ваших статьях (например, с использованием GDI, VCL, OpenGL и т. д.). Спасибо. - person Casper Chang; 17.10.2017
comment
@CasperChang рад помочь. После того, как вы закончите с интерполяцией и перейдете к ее использованию для графики или чего-то еще, взгляните на это Понимание матриц гомогенного преобразования 4x4, они обычно используется для размещения объектов/кривых в 3D-пространстве и для двунаправленных преобразований координат, что необходимо для 3D-графики. Вы можете расширить все это до любого количества измерений, таких как методы 4D-рендеринга и 4D-повороты - person Spektre; 17.10.2017