¿Es un producto tensor de interpolación de Lagrange cúbico lo mismo que la interpolación bicúbica?

11

Acabo de implementar un muestreo de textura interpolado al muestrear los píxeles 4x4 más cercanos y luego hacer la interpolación de Lagrange en el eje x para obtener cuatro valores para usar la interpolación de Lagrange en el eje y.

¿Es esto lo mismo que la interpolación bicúbica o es diferente? ¿O hay diferentes tipos de interpolación bicúbica, y este es quizás uno de ellos?

Implementación de Webgl Shadertoy aquí y código GLSL (WebGL) relevante a continuación: https://www.shadertoy.com/view/MllSzX

¡Gracias!

float c_textureSize = 64.0;

float c_onePixel = 1.0 / c_textureSize;
float c_twoPixels = 2.0 / c_textureSize;

float c_x0 = -1.0;
float c_x1 =  0.0;
float c_x2 =  1.0;
float c_x3 =  2.0;

//=======================================================================================
vec3 CubicLagrange (vec3 A, vec3 B, vec3 C, vec3 D, float t)
{
    return
        A * 
        (
            (t - c_x1) / (c_x0 - c_x1) * 
            (t - c_x2) / (c_x0 - c_x2) *
            (t - c_x3) / (c_x0 - c_x3)
        ) +
        B * 
        (
            (t - c_x0) / (c_x1 - c_x0) * 
            (t - c_x2) / (c_x1 - c_x2) *
            (t - c_x3) / (c_x1 - c_x3)
        ) +
        C * 
        (
            (t - c_x0) / (c_x2 - c_x0) * 
            (t - c_x1) / (c_x2 - c_x1) *
            (t - c_x3) / (c_x2 - c_x3)
        ) +       
        D * 
        (
            (t - c_x0) / (c_x3 - c_x0) * 
            (t - c_x1) / (c_x3 - c_x1) *
            (t - c_x2) / (c_x3 - c_x2)
        );
}

//=======================================================================================
vec3 BicubicTextureSample (vec2 P)
{
    vec2 pixel = P * c_textureSize + 0.5;

    vec2 frac = fract(pixel);
    pixel = floor(pixel) / c_textureSize - vec2(c_onePixel/2.0);

    vec3 C00 = texture2D(iChannel0, pixel + vec2(-c_onePixel ,-c_onePixel)).rgb;
    vec3 C10 = texture2D(iChannel0, pixel + vec2( 0.0        ,-c_onePixel)).rgb;
    vec3 C20 = texture2D(iChannel0, pixel + vec2( c_onePixel ,-c_onePixel)).rgb;
    vec3 C30 = texture2D(iChannel0, pixel + vec2( c_twoPixels,-c_onePixel)).rgb;

    vec3 C01 = texture2D(iChannel0, pixel + vec2(-c_onePixel , 0.0)).rgb;
    vec3 C11 = texture2D(iChannel0, pixel + vec2( 0.0        , 0.0)).rgb;
    vec3 C21 = texture2D(iChannel0, pixel + vec2( c_onePixel , 0.0)).rgb;
    vec3 C31 = texture2D(iChannel0, pixel + vec2( c_twoPixels, 0.0)).rgb;    

    vec3 C02 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_onePixel)).rgb;
    vec3 C12 = texture2D(iChannel0, pixel + vec2( 0.0        , c_onePixel)).rgb;
    vec3 C22 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_onePixel)).rgb;
    vec3 C32 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_onePixel)).rgb;    

    vec3 C03 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_twoPixels)).rgb;
    vec3 C13 = texture2D(iChannel0, pixel + vec2( 0.0        , c_twoPixels)).rgb;
    vec3 C23 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_twoPixels)).rgb;
    vec3 C33 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_twoPixels)).rgb;    

    vec3 CP0X = CubicLagrange(C00, C10, C20, C30, frac.x);
    vec3 CP1X = CubicLagrange(C01, C11, C21, C31, frac.x);
    vec3 CP2X = CubicLagrange(C02, C12, C22, C32, frac.x);
    vec3 CP3X = CubicLagrange(C03, C13, C23, C33, frac.x);

    return CubicLagrange(CP0X, CP1X, CP2X, CP3X, frac.y);
}
Alan Wolfe
fuente
2
Podrías publicar el código de sombreador relevante aquí en caso de bitrot, ¿no?
joojaa
1
deberíamos tener un marcado de código más bonito para el código de sombreador, ¡publicaré en meta si alguien no me ha golpeado!
Alan Wolfe el
¿Es ese un idioma de sombreador específico que no está disponible en la lista de idiomas cubiertos por nuestro resaltado de sintaxis?
trichoplax
No estoy seguro. Es solo GLSL (de webgl para ser exactos). Acabo de hacer 4 espacios antes de cada línea de código, no estoy seguro de si hay una mejor manera de marcarlo ...
Alan Wolfe

Respuestas:

8

Resulta que no, aunque puede usar la interpolación bicúbica de Lagrange para el muestreo de textura bicúbica, no es la opción de mayor calidad y probablemente no sea probable que se use.

Las estrías de hermita cúbicas son una mejor herramienta para el trabajo.

La interpolación de Lagrange formará una curva que atraviesa los puntos de datos, preservando así la continuidad de C0, pero las splines hermitas preservan las derivadas en los bordes al tiempo que pasan a través de los puntos de datos, preservando así la continuidad de C1 y luciendo mucho mejor.

Esta pregunta tiene una gran información sobre splines cúbicos de hermita: /signals/18265/bicubic-interpolation

Aquí está la versión hermita cúbica del código que publiqué en la pregunta:

//=======================================================================================
vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t)
{
    float t2 = t*t;
    float t3 = t*t*t;
    vec3 a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0;
    vec3 b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0;
    vec3 c = -A/2.0 + C/2.0;
    vec3 d = B;

    return a*t3 + b*t2 + c*t + d;
}

//=======================================================================================
vec3 BicubicHermiteTextureSample (vec2 P)
{
    vec2 pixel = P * c_textureSize + 0.5;

    vec2 frac = fract(pixel);
    pixel = floor(pixel) / c_textureSize - vec2(c_onePixel/2.0);

    vec3 C00 = texture2D(iChannel0, pixel + vec2(-c_onePixel ,-c_onePixel)).rgb;
    vec3 C10 = texture2D(iChannel0, pixel + vec2( 0.0        ,-c_onePixel)).rgb;
    vec3 C20 = texture2D(iChannel0, pixel + vec2( c_onePixel ,-c_onePixel)).rgb;
    vec3 C30 = texture2D(iChannel0, pixel + vec2( c_twoPixels,-c_onePixel)).rgb;

    vec3 C01 = texture2D(iChannel0, pixel + vec2(-c_onePixel , 0.0)).rgb;
    vec3 C11 = texture2D(iChannel0, pixel + vec2( 0.0        , 0.0)).rgb;
    vec3 C21 = texture2D(iChannel0, pixel + vec2( c_onePixel , 0.0)).rgb;
    vec3 C31 = texture2D(iChannel0, pixel + vec2( c_twoPixels, 0.0)).rgb;    

    vec3 C02 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_onePixel)).rgb;
    vec3 C12 = texture2D(iChannel0, pixel + vec2( 0.0        , c_onePixel)).rgb;
    vec3 C22 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_onePixel)).rgb;
    vec3 C32 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_onePixel)).rgb;    

    vec3 C03 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_twoPixels)).rgb;
    vec3 C13 = texture2D(iChannel0, pixel + vec2( 0.0        , c_twoPixels)).rgb;
    vec3 C23 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_twoPixels)).rgb;
    vec3 C33 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_twoPixels)).rgb;    

    vec3 CP0X = CubicHermite(C00, C10, C20, C30, frac.x);
    vec3 CP1X = CubicHermite(C01, C11, C21, C31, frac.x);
    vec3 CP2X = CubicHermite(C02, C12, C22, C32, frac.x);
    vec3 CP3X = CubicHermite(C03, C13, C23, C33, frac.x);

    return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y);
}

Aquí hay una imagen que muestra la diferencia entre los métodos de muestreo. De izquierda a derecha: vecino más cercano, Bilinear, Lagrange Bicubic, Hermite Bicubic

ingrese la descripción de la imagen aquí

Alan Wolfe
fuente
Aunque todas las splines cúbicas son, en cierto sentido, equivalentes, probablemente sea conceptualmente más fácil usar splines Catmull-Rom. por ejemplo, cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf
Simon F
¿Crees que el parámetro tau ayuda o dificulta en este caso? Podría estar equivocado, pero siento que catmull rom está demasiado "definido por el usuario" (y debe ajustarse), mientras que la spline hermite intenta simplemente usar la información de los datos que están allí. Parece que la hermita cúbica está más cerca de una "verdad fundamental", que supongo que sería algo así como un filtro de sinc ideal. ¿Qué piensas sin embargo?
Alan Wolfe
No veo cómo Catmull-Rom está "definido por el usuario". Una vez que tenga una secuencia de 4 puntos contiguos, P [i-1], P [i], P [i + 1], P [i + 2] (4x4 para el caso 2D), el segmento de curva se define entre P [i ] y P [i + 1] y es C1 continuo con los segmentos vecinos. Un filtro sinc está bien para audio pero no para video. Ver Mitchell y Netravali: cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/… IIRC Catmull-Rom es un caso especial de la familia de filtros que proponen, pero creo que ese filtro es una curva aproximada, así que, a diferencia de CR, podría no pasar por los puntos originales.
Simon F
Así es como funciona el spline hermite, excepto que el spline catmull rom tiene un parámetro adicional tau (tensión) definido por el usuario. Además, sinc se aplica al video, DSP es DSP: P
Alan Wolfe
Debo admitir que nunca antes había visto un parámetro de tensión asociado con las estrías de Catmull Rom, pero en realidad solo supe de ellas a través de Foley & van Dam (et al) o, digamos, Watt & Watt que, AFAICR, hacen sin mención de tal. En realidad, habiendo dicho eso, dado que hay cuatro restricciones, es decir, la curva tiene que pasar a través de 2 puntos y tener dos tangentes definidas ** en esos puntos y es cúbico: estoy un poco perdido en cuanto a cómo hay más grados de libertad para soportar un parámetro de tensión ... ** ¿A menos que quiera decir que las tangentes se pueden escalar?
Simon F