Implementación
Suponiendo que ya tiene una rutina de dibujo lineal, solo necesita complementarla con algún tipo de interpolación. Las curvas se crean dibujando suficientes líneas cortas interpoladas para que el resultado se vea suave. Un buen punto de partida sería utilizar una rutina de interpolación existente, como las que da Paul Bourke aquí .
Ilustraré esto usando las rutinas cúbicas que proporciona, ya que esas son algunas de las más simples que aún darán resultados razonables. Aquí está el primero (traducido a python) como referencia:
def cubic(mu,y0,y1,y2,y3):
mu2 = mu*mu
a0 = y3 - y2 - y0 + y1
a1 = y0 - y1 - a0
a2 = y2 - y0
a3 = y1
return a0*mu*mu2 + a1*mu2 + a2*mu + a3
Cada rutina tiene un parámetro mu
que representa la parte fraccionaria del índice que desea interpolar. Dependiendo de la rutina, los otros parámetros serán cierto número de muestras que rodean el índice en cuestión. En el caso cúbico, necesitará cuatro muestras. Por ejemplo, si los datos es y[n]
, y desea que el valor en 10.3
, mu
sería .3
, y que le pasa en y[9]
, y[10]
, y[11]
, y y[12]
.
Luego, en lugar de dibujar una sola línea con puntos finales, digamos, ( 10 ,y10) → ( 11 ,y11), dibujaría un montón de valores más cortos utilizando los valores interpolados (p. ej. ( 10 ,y10) → ( 10.1 , cúbico ( .1 ,y9 9,y10,y11,y12) ) ...) Obviamente esos puntos tendrían que ser escalados aX y y dimensiones de la imagen a renderizar.
Teoría
Ahora, dado que la página / rutina a la que hice referencia no cita ninguna fuente, vale la pena explicar de dónde provienen esas rutinas cúbicas (y cómo funcionan). Tanto el que reproduje arriba como el spline Catmull-Rom muy similar que menciona justo debajo de él, son dos casos específicos de usar el siguiente núcleo de convolución cúbica:
ψ ( x ) =⎧⎩⎨⎪⎪( α + 2 ) | XEl |3- ( α + 3 ) | XEl |2+ 1 ,α | XEl |3- 5 α | XEl |2+ 8 α | x | - 4 α ,0 , si 0 ≤ | x | < 1 si 1 ≤ | x | < 2 si 2 ≤ | x |
La rutina mencionada anteriormente corresponde a un valor de α = - 1, y la spline Catmull-Rom corresponde a α = - 1 / 2. No entraré en demasiados detalles sobre cómo se deriva la forma general del núcleo, pero implica varias restricciones, como asegurarse de queψ ( x ) es uno en cero y cero en todos los demás enteros.
Esto es lo que parece:
Las dos opciones para el valor de αprovienen de intentos de igualar varios aspectos de la función sinc , el núcleo de reconstrucción ideal. Ajusteα = - 1 hace la derivada de ψ coincidir con la derivada de la función sinc en x = 1y haciéndolo igual a - 1 / 2proporciona la mejor aproximación de baja frecuencia en su lugar. Por todas las cuentas, un valor deα = - 1 / 2tiene propiedades mucho mejores en general, por lo que es probablemente el mejor valor para usar en la práctica. Se puede encontrar una discusión mucho más extensa en el siguiente documento, comenzando en la página 328:
Meijering, Erik. "Una cronología de la interpolación: de la astronomía antigua al procesamiento moderno de señales e imágenes". Actas del IEEE. vol. 90, no. 3, págs. 319-42. Marzo de 2002.
Visión
Ahora, solo mirando ese núcleo en relación con la implementación real del código de interpolación, puede que no esté claro cómo se relacionan los dos. Básicamente, el proceso de interpolación puede considerarse como la suma de copias desplazadas del núcleo, que se escalan por las muestras de los datos, así:
De hecho, si tiene una implementación del núcleo, puede usarlo directamente para hacer la interpolación, de la siguiente manera:
def kernel(x, a=-1.0):
x = abs(x)
if x >= 0.0 and x < 1.0:
return (a + 2.0)*x**3.0 - (a + 3.0)*x**2.0 + 1
elif x >= 1.0 and x < 2.0:
return a*x**3.0 - 5.0*a*x**2.0 + 8.0*a*x - 4.0*a
else:
return 0.0
def cubic(mu,y0,y1,y2,y3):
a = -1.0
result = y0 * kernel(mu + 1, a)
result += y1 * kernel(mu, a)
result += y2 * kernel(mu - 1, a)
result += y3 * kernel(mu - 2, a)
return result
Sin embargo, es mucho menos eficiente desde el punto de vista informático hacerlo de esa manera. Como un puente desde el enfoque de kernel directo al más simplificado anterior, considere que con un poco de manipulación algebraica, la primera implementación se puede poner de la siguiente forma:
def cubic(mu,y0,y1,y2,y3):
mu2 = mu*mu
mu3 = mu*mu2
c0 = -mu3 + 2*mu2 - mu
c1 = mu3 - 2*mu2 + 1
c2 = -mu3 + mu2 + mu
c3 = mu3 - mu2
return c0*y0 + c1*y1 + c2*y2 + c3*y3
En esta formulación, los c0...c3
valores pueden considerarse como los coeficientes de un filtro FIR que se aplica a los valores de la muestra. Ahora es mucho más fácil ver cómo derivar la rutina del núcleo. Considere el núcleo conα = - 1, al igual que:
ψ ( x ) =⎧⎩⎨⎪⎪El | XEl |3- 2 | XEl |2+ 1 ,- | XEl |3+ 5 | XEl |2- 8 | x | + 4 ,0 , si 0 ≤ | x | < 1 si 1 ≤ | x | < 2 si 2 ≤ | x |
Ahora evalúe ese núcleo simbólicamente en varios desplazamientos desplazados, teniendo en cuenta que mu
(μ) varía de 0
a 1
:
ψ ( μ + 1 )ψ ( μ )ψ ( μ - 1 )ψ ( μ - 2 )====- ( μ + 1)3+ 5 ( μ + 1)2- 8 ( μ + 1 ) + 4μ3- 2μ2+ 1( 1 - μ)3- 2 ( 1 - μ)2+ 1- ( 2 - μ)3+ 5 ( 2 - μ)2- 8 ( 2 - μ ) + 4====-μ3+ 2μ2- μμ3- 2μ2+ 1-μ3+μ2+ μμ3-μ2(C0 0)(C1)(C2)(C3)
Tenga en cuenta que μ - 1 , μ - 2 obtener "volteado" a 1 - μ , 2 - μ respectivamente debido al valor absoluto en Xen la definición del núcleo. Ahora tenemos los polinomios exactos que se usan en la "versión FIR" de la rutina de interpolación. La evaluación de esos polinomios se puede hacer más eficiente a través de técnicas estándar (por ejemplo, el método de Horner ). Se pueden hacer cosas similares con otros núcleos, y también hay otras formas de construir implementaciones eficientes (véase la página de inicio de muestreo de audio digital ).