Transformaciones de imagen afines en Python con Numpy, Pillow y OpenCV

En este artículo describiré lo que significa aplicar una transformación afín a una imagen y cómo hacerlo en Python. Primero demostraré las operaciones de bajo nivel en Numpy para dar una implementación geométrica detallada. Luego los voy a seguir para un uso más práctico de Python Pillow y OpenCV. bibliotecas .
Este artículo se escribió usando un cuaderno Jupyter y la fuente se puede encontrar en mi repositorio de GitHub , por lo tanto, siéntase libre de clonarlo y experimentarlo con el código.

¿Qué es una transformación afín?

Segun wikipedia una transformación afín es un mapeo funcional entre dos espacios geométricos (afines) que conservan puntos, líneas rectas y paralelas, así como relaciones entre puntos. Toda esa redacción abstracta matemática se reduce a una transformación lineal que, en términos generales, se traduce, al menos en el contexto del procesamiento de imágenes, en una o más manipulaciones, como girar, voltear, escalar o esquilar aplicando una matriz de transformación.
Una cosa buena es que, dado que se trata esencialmente de una operación geométrica 2D, podemos visualizarla. Permítanme comenzar dando una tabla de transformaciones afines que describen cada tipo de manipulación geométrica.
Tipo de transformaciónMatriz de transformaciónEcuación de mapeo de píxeles
IdentidadEl100010001]yX=X
y=y
EscaladaEldoX000doy0001]yX=doXX
y=doyy
Rotación*EldoosΘsyonorteΘ0-syonorteΘdoosΘ0001]X=XdoosΘ-ysyonorteΘ
y=XdoosΘ+ysyonorteΘ
TraducciónEl10tX01ty001]X=X+tX
y=y+ty
Cizalla horizontalEl1sh0010001]X=X+svy
y=y
Cizalla verticalEl100sv10001]X=X
y=Xsh+y
La transformación afín utiliza un ángulo de rotación en el sentido de las agujas del reloj que contrasta con el círculo de ángulos de la unidad de geometría típica que se mide en sentido contrario a las agujas del reloj con 0 a partir del eje X positivo, por lo tanto, verá que a menudo se aplica el negativo del ángulo. .
' La notación aquí solo se refiere a la coordenada de salida transformada de x o y no a la notación de cálculo para una derivada
A modo de demostración simple, aplicaré un par de transformaciones para manipular las coordenadas x e y de los siguientes puntos que tienen componentes tridimensionales de x, y e índice de caracteres ascii similar a la forma en que un píxel de imagen tiene componentes tridimensionales de x, y , y frecuencia (o intensidad).
a = (0, 1, 0) 
b = (1, 0, 1) 
c = (0, -1, 2) 
d = (-1, 0, 3)
Las transformaciones para este ejemplo serán escalado en 2 en todas las direcciones y rotación de 90 grados en el sentido de las agujas del reloj. Primero realizaré las transformaciones individualmente para mostrar el efecto directo que cada una tiene al mover los puntos, luego combinaré las transformaciones y las aplicaré en una acción.
Para comenzar, quiero construir una matriz Numpy (algunos pueden llamar a esto una matriz) con cada fila que representa el punto donde la primera columna es la x, la segunda la y, y la tercera es el índice de su letra en el conjunto de caracteres ascii similar a la tabla que se muestra a continuación. A continuación, uso Matplotlib para trazar los puntos (después de aplicar la transformación de identidad sin cambios) para dar una línea de base visual de dónde estamos.
Puntox (fila)y (columna)índice de ascii
una010
segundo101
do0-12
re-103
import matplotlib.pyplot as plt  
import numpy as np  
import string

# points a, b and, c
a, b, c, d = (0, 1, 0), (1, 0, 1), (0, -1, 2), (-1, 0, 3)

# matrix with row vectors of points
A = np.array([a, b, c, d])

# 3x3 Identity transformation matrix
I = np.eye(3)  
color_lut = 'rgbc'  
fig = plt.figure()  
ax = plt.gca()  
xs = []  
ys = []  
for row in A:  
    output_row = I @ row
    x, y, i = output_row
    xs.append(x)
    ys.append(y)
    i = int(i) # convert float to int for indexing
    c = color_lut[i]
    plt.scatter(x, y, color=c)
    plt.text(x + 0.15, y, f"{string.ascii_letters[i]}")
xs.append(xs[0])  
ys.append(ys[0])  
plt.plot(xs, ys, color="gray", linestyle='dotted')  
ax.set_xticks(np.arange(-2.5, 3, 0.5))  
ax.set_yticks(np.arange(-2.5, 3, 0.5))  
plt.grid()  
plt.show()  
png
Los tres puntos a, b y c se trazan en una cuadrícula después de aplicarles la transformación de identidad a través de un simple producto de puntos de matriz vectorial que los deja sin cambios.
Ts
Ts=El200020001]
Ahora pasaré a trazar los puntos transformados similares a lo que se hizo con los puntos originales no alterados por la transformación de Identidad, pero esta vez aplicaré la matriz de transformación de escala definida anteriormente. Para una mejor visualización, trazo una línea de puntos que conecta los puntos.
# create the scaling transformation matrix
T_s = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]])

fig = plt.figure()  
ax = plt.gca()  
xs_s = []  
ys_s = []  
for row in A:  
    output_row = T_s @ row
    x, y, i = row
    x_s, y_s, i_s = output_row
    xs_s.append(x_s)
    ys_s.append(y_s)
    i, i_s = int(i), int(i_s) # convert float to int for indexing
    c, c_s = color_lut[i], color_lut[i_s] # these are the same but, its good to be explicit
    plt.scatter(x, y, color=c)
    plt.scatter(x_s, y_s, color=c_s)
    plt.text(x + 0.15, y, f"{string.ascii_letters[int(i)]}")
    plt.text(x_s + 0.15, y_s, f"{string.ascii_letters[int(i_s)]}'")

xs_s.append(xs_s[0])  
ys_s.append(ys_s[0])  
plt.plot(xs, ys, color="gray", linestyle='dotted')  
plt.plot(xs_s, ys_s, color="gray", linestyle='dotted')  
ax.set_xticks(np.arange(-2.5, 3, 0.5))  
ax.set_yticks(np.arange(-2.5, 3, 0.5))  
plt.grid()  
plt.show()  
png
De la gráfica anterior, debe quedar muy claro que las dimensiones x e y simplemente se ampliaron en un factor de dos, mientras que la tercera dimensión responsable del índice de letras ASCII se mantuvo sin cambios. De hecho, aquellos familiarizados con el álgebra matricial habrán notado que para todas las transformaciones afines enumeradas en la primera tabla, el valor representado en la tercera dimensión siempre se deja sin alterar, como lo indican los ceros y un solo valor en la tercera dimensión. Indice de la última columna.
Ahora déjame describir cómo interpretar la transformación de rotación. Comenzaré resolviendo las dos funciones trigonométricas para el ángulo de rotación deseado de 90 grados, luego simplemente las inserto en la matriz de transformación de rotación que se indica en la tabla anterior.
syonorte(90o)=1
doos(90o)=0
Tr=El010-100001]
Ahora todo lo que necesito hacer es aplicar la misma lógica para transformar y trazar los puntos, de esta manera:
# create the rotation transformation matrix
T_r = np.array([[0, 1, 0], [-1, 0, 0], [0, 0, 1]])

fig = plt.figure()  
ax = plt.gca()  
for row in A:  
    output_row = T_r @ row
    x_r, y_r, i_r = output_row
    i_r = int(i_r) # convert float to int for indexing
    c_r = color_lut[i_r] # these are the same but, its good to be explicit
    letter_r = string.ascii_letters[i_r]
    plt.scatter(x_r, y_r, color=c_r)
    plt.text(x_r + 0.15, y_r, f"{letter_r}'")

plt.plot(xs, ys, color="gray", linestyle='dotted')  
ax.set_xticks(np.arange(-2.5, 3, 0.5))  
ax.set_yticks(np.arange(-2.5, 3, 0.5))  
plt.grid()  
plt.show()  
png
Con suerte, se puede ver en la gráfica que todos los puntos se giraron 90 grados alrededor de un eje de rotación en el origen.
Lo bueno de que las transformaciones afines sean transformaciones esencialmente lineales es que puede combinar las transformaciones y aplicarlas en un solo paso. Para demostrar esto, aplicaré el producto punto (multiplicación de matrices) de mis dos matrices de transformación, como:
Tdoometrosegundo=El010-100001]El200020001]=El020-200001]
Ahora puedo aplicar esta matriz de transformación combinada a los puntos y replantearlos para mostrar una combinación de escalado en dos y rotación en 90 grados.
# create combined tranformation matrix
T = T_s @ T_r

fig = plt.figure()  
ax = plt.gca()

xs_comb = []  
ys_comb = []  
for row in A:  
    output_row = T @ row
    x, y, i = row
    x_comb, y_comb, i_comb = output_row
    xs_comb.append(x_comb)
    ys_comb.append(y_comb)
    i, i_comb = int(i), int(i_comb) # convert float to int for indexing
    c, c_comb = color_lut[i], color_lut[i_comb] # these are the same but, its good to be explicit
    letter, letter_comb = string.ascii_letters[i], string.ascii_letters[i_comb]
    plt.scatter(x, y, color=c)
    plt.scatter(x_comb, y_comb, color=c_comb)
    plt.text(x + 0.15 , y, f"{letter}")
    plt.text(x_comb + 0.15, y_comb, f"{letter_comb}'")
xs_comb.append(xs_comb[0])  
ys_comb.append(ys_comb[0])  
plt.plot(xs, ys, color="gray", linestyle='dotted')  
plt.plot(xs_comb, ys_comb, color="gray", linestyle='dotted')  
ax.set_xticks(np.arange(-2.5, 3, 0.5))  
ax.set_yticks(np.arange(-2.5, 3, 0.5))  
plt.grid()  
plt.show()  
png

Trabajando con una imagen

A estas alturas, espero haber podido desarrollar cierta intuición acerca de cómo se utilizan las transformaciones afines para simplemente mover los puntos en el espacio 2D, por lo que me gustaría comenzar a trabajar con datos de imágenes reales para Dar una demostración más concreta de cómo funciona todo esto.
Esto también me permite cubrir otro tema importante de transformaciones afines que trata con la tercera dimensión. La tercera dimensión de los datos en una imagen representa el valor de píxel real, o algunas veces se denomina dominio de intensidad, mientras que la ubicación física 2D de los píxeles en las otras dos dimensiones se denomina dominio espacial.
Para comenzar, leeré y mostraré una imagen usando matplotlib, que es simplemente una letra mayúscula grande R.
img = plt.imread('letterR.jpg')  
img.shape #  (1000, 1000, 4)  
Usando el imread(...)método, puedo leer en la imagen JPG, que representa la letra R mayúscula, en un ndarray numpy. Luego muestro las dimensiones de la matriz, que son de 1000 filas por 1000 columnas, y juntas forman hasta 1,000,000 píxeles en el dominio espacial. Los datos de píxeles individuales están en la forma de una matriz de 4 enteros sin signo que representan un canal (o muestra) rojo, verde, azul y alfa que juntos proporcionan los datos de intensidad de cada píxel.
plt.figure(figsize=(5, 5))  
plt.imshow(img)  
png
A continuación, me gustaría aplicar la escala y rotación anteriores al dominio espacial de los datos de imagen, transformando así las ubicaciones de píxeles similares a lo que demostré anteriormente con los datos de puntos. Sin embargo, debo adoptar un enfoque ligeramente diferente porque los datos de la imagen están organizados de una manera diferente a la de las filas de puntos de datos con los que trabajé anteriormente. Con los datos de imagen necesito mapear los índices para cada píxel de los datos de entrada a los índices de salida transformados usando la matriz de transformación T, definida anteriormente.
# 2x scaling requires a tranformation image array 2x the original image
img_transformed = np.empty((2000, 2000, 4), dtype=np.uint8)  
for i, row in enumerate(img):  
    for j, col in enumerate(row):
        pixel_data = img[i, j, :]
        input_coords = np.array([i, j, 1])
        i_out, j_out, _ = T @ input_coords
        img_transformed[i_out, j_out, :] = pixel_data

plt.figure(figsize=(5, 5))  
plt.imshow(img_transformed)  
png
El trazado de la imagen después de aplicar la transformación muestra claramente que la imagen original se ha girado 90 grados en el sentido de las agujas del reloj y se ha ampliado 2 veces. Sin embargo, el resultado ahora está obviamente disminuido, ya que puede ver fácilmente la discontinuidad en las intensidades de píxeles.
Para entender la razón de esto, utilizaré nuevamente un sencillo diagrama de cuadrícula para la demostración. Considere una gráfica de 4 cuadrados en una cuadrícula de 2x2 similar al dominio espacial de una imagen de 2x2.
def plot_box(plt, x0, y0, txt, w=1, h=1):  
    plt.scatter(x0, y0)
    plt.scatter(x0, y0 + h)
    plt.scatter(x0 + w, y0 + h)
    plt.scatter(x0 + w, y0)
    plt.plot([x0, x0, x0 + w, x0 + w, x0], [y0, y0 + h, y0 + h, y0, y0], color="gray", linestyle='dotted')
    plt.text(x0 + (.33 * w), y0 + (.5 * h), txt)

#             x0, y0, letter
a = np.array((0,  1,  0))  
b = np.array((1,  1,  1))  
c = np.array((0,  0,  2))  
d = np.array((1,  0,  3))

A = np.array([a, b, c, d])  
fig = plt.figure()  
ax = plt.gca()  
for pt in A:  
    x0, y0, i = I @ pt
    x0, y0, i = int(x0), int(y0), int(i)
    plot_box(plt, x0, y0, f"{string.ascii_letters[int(i)]} ({x0}, {y0})")

ax.set_xticks(np.arange(-1, 5, 1))  
ax.set_yticks(np.arange(-1, 5, 1))  
plt.grid()  
plt.show()  
png
Ahora observe lo que sucede cuando aplico una transformación de escalado 2X como se muestra a continuación. Recordar que:
Ts=El200020001]
TsTs
fig = plt.figure()  
ax = plt.gca()  
for pt in A:  
    xt, yt, i = T_s @ pt
    xt, yt, i = int(xt), int(yt), int(i)
    plot_box(plt, xt, yt, f"{string.ascii_letters[i]}' ({xt}, {yt})")

delta_w, delta_h = 0.33, 0.5  
plt.text(0 + delta_w, 1 + delta_h, "? (0, 1)")  
plt.text(1 + delta_w, 0 + delta_h, "? (1, 0)")  
plt.text(1 + delta_w, 1 + delta_h, "? (1, 1)")  
plt.text(1 + delta_w, 2 + delta_h, "? (1, 2)")  
plt.text(2 + delta_w, 1 + delta_h, "? (2, 1)")

ax.set_xticks(np.arange(-1, 5, 1))  
ax.set_yticks(np.arange(-1, 5, 1))  
plt.grid()  
plt.show()  
png
¿Queda la pregunta de qué hacer con las brechas que se han introducido? Un pensamiento intuitivo sería simplemente mirar a la imagen original para la respuesta. Sucede que si aplicamos el inverso de la transformación a una coordenada en la salida, obtendré la ubicación correspondiente de la entrada original.
En operaciones matriciales, como el mapeo hacia atrás, se ve así:
(X,y,1)=Ts-1(Xy1)
Ts-1Ts
Ts-1=El1/20001/20001]-1
Sin embargo, pronto se dará cuenta de que hay un pequeño problema que aún debe solucionarse debido al hecho de que cada una de las coordenadas de la brecha se corresponde con los valores fraccionarios del sistema de coordenadas 2x2. En el caso de los datos de imagen, realmente no se puede tener una fracción de un píxel. Esto será más claro con un ejemplo de mapeo de la brecha (2, 1) al espacio original de 2x2, así:
Ts-1(2,1,1)=(1,1/2,1)
En este caso redondearé la y '= 1/2 hasta 0 y diré que se asigna a (1, 0). En el sentido general, este método de selección de un valor en la cuadrícula 2x2 original para colocar en los huecos de la cuadrícula 3x3 transformada se conoce como interpolación, y en este ejemplo específico, estoy usando una versión simplificada del método de interpolación vecino más cercano.
Ok, ahora volvemos a los datos de la imagen. Debería estar bastante claro qué se debe hacer ahora para corregir esos vacíos en la versión escalada y rotada de la letra R. Debo desarrollar una implementación de la interpolación del vecino más cercano basada en el mapeo hacia atrás, utilizando el inverso de la matriz de transformación T, de las coordenadas de píxeles en la imagen transformada para encontrar la coincidencia exacta o el vecino más cercano en la imagen original.
T_inv = np.linalg.inv(T)

# nearest neighbors interpolation
def nearest_neighbors(i, j, M, T_inv):  
    x_max, y_max = M.shape[0] - 1, M.shape[1] - 1
    x, y, _ = T_inv @ np.array([i, j, 1])
    if np.floor(x) == x and np.floor(y) == y:
        x, y = int(x), int(y)
        return M[x, y]
    if np.abs(np.floor(x) - x) < np.abs(np.ceil(x) - x):
        x = int(np.floor(x))
    else:
        x = int(np.ceil(x))
    if np.abs(np.floor(y) - y) < np.abs(np.ceil(y) - y):
        y = int(np.floor(y))
    else:
        y = int(np.ceil(y))
    if x > x_max:
        x = x_max
    if y > y_max:
        y = y_max
    return M[x, y,]

img_nn = np.empty((2000, 2000, 4), dtype=np.uint8)  
for i, row in enumerate(img_transformed):  
    for j, col in enumerate(row):
        img_nn[i, j, :] = nearest_neighbors(i, j, img, T_inv)

plt.figure(figsize=(5, 5))  
plt.imshow(img_nn)  
png
No está mal, ¿verdad?
Debo tener en cuenta que en la mayoría de los casos, el método del vecino más cercano no será suficiente. Existen otros dos métodos de interpolación más comunes conocidos como interpolación bilineal y bicúbica que generalmente proporcionan resultados mucho mejores. Hablaré más sobre estos otros algoritmos de interpolación al presentar las bibliotecas Pillow y OpenCV en las últimas secciones. El propósito de esta sección es simplemente construir una comprensión intuitiva de cómo funcionan las cosas.

Transformaciones afines con almohada

En esta sección, trataré brevemente cómo usar la excelente almohada de la biblioteca de procesamiento de imágenes de Python para realizar transformaciones afines.
En primer lugar, Pillow tendrá que ser instalado. Usé pip para lograr esto, así:
$ pip install pillow
Ahora el primer paso es importar la Imageclase desde el módulo PIL (PIL es el nombre del módulo Python asociado a Pillow) y leer en mi imagen.
from PIL import Image  
Para leer el nombre de archivo de la imagen de muestra "letterR.jpg", llamo al método de la clase Image.open(...), pasándole el nombre del archivo, que devuelve una instancia de la Imageclase, que luego convierto en una matriz numpy y visualizo con matplotlib.
img = Image.open('letterR.jpg')  
plt.figure(figsize=(5, 5))  
plt.imshow(np.asarray(img))  
png
La Imageclase Pillow tiene un método práctico llamado transform(...)que te permite realizar transformaciones afines de grano fino, pero hay algunas rarezas que debo discutir primero antes de saltar a una demostración de ello. El transform(...)método comienza con dos parámetros requeridos que representan sizeuna tupla de altura y anchura, seguidos de la methodtransformación que se aplicará, que será Image.AFFINEen este caso.
Los parámetros restantes son argumentos de palabras clave opcionales que controlan cómo se realizará la transformación. En el caso de este ejemplo, usaré el dataparámetro, que toma las dos primeras filas de una matriz de transformación afín.
Por ejemplo, la matriz de transformación de escalado 2x con la que he estado trabajando se redujo a solo las dos primeras filas:
Ts=El200020]
El último parámetro que usaré con el transform(...)método es resample, que se usa para indicar el tipo de algoritmo de interpolación de píxeles para aplicar fuera de las posibles opciones de Image.NEAREST(vecino más cercano) Image.BILINEAR, o Image.BICUBICEsta elección a menudo variará dependiendo de la transformación que se aplique. Sin embargo, bilineal y bicúbico generalmente dan mejores resultados que el vecino más cercano, pero como ya se demostró en este ejemplo, el vecino más cercano funciona bastante bien.
Hay algunas peculiaridades que me sirvieron de verdaderas trampas la primera vez que utilicé el Image.transform(...)método, particularmente alrededor de la construcción de la matriz de transformación afín con la fila extrañamente truncada en la última fila. Por lo tanto, me gustaría pasar un tiempo repasando por qué las cosas funcionan como lo hacen porque es un poco un proceso.
Lo primero que debe suceder es que la imagen se debe traducir para que el origen (0, 0) esté en el centro de la imagen. En el caso de la imagen 1000 x 1000 de la letra R en este ejemplo, significa una traducción de -500 en la x y la y.
TtrunanorteslunatmiTnortemisol500
Ttrunanorteslunatmi=El10tX01ty001]Tnortemisol500=El10-50001-500001]
TsdounalmiTrotunatmi
Trotunatmi=El0-10100001]Tsdounalmi=El200020001]
A continuación, se debe aplicar otra matriz de traducción que actúa para reposicionar el dominio espacial de los píxeles, negando esencialmente el primero que centró el origen. En este caso, necesito una traducción positiva de 1000 en x e y, donde 1000 viene del doble del original porque se ha aumentado en dos.
Tpagos1000=El101000011000001]
Estos constituyen los pasos de transformación individuales que se requieren, por lo que todo lo que queda es multiplicar las matrices en orden (es decir, de derecha a izquierda), de esta manera:
T=Tpagos1000TrotunatmiTsdounalmiTnortemisol500
Ok, entonces en realidad hay una última rareza. En Image.transform(...)realidad, el método requiere que se suministre la inversa de la matriz de transformación al dataparámetro como una matriz aplanada (o tupla) excluyendo la última fila.
Tyonortev=T-1
En código todo esto funciona de la siguiente manera:
# recenter resultant image
T_pos1000 = np.array([  
    [1, 0, 1000],
    [0, 1, 1000],
    [0, 0, 1]])
# rotate - opposite angle
T_rotate = np.array([  
    [0, -1, 0],
    [1, 0, 0],
    [0, 0, 1]])
# scale
T_scale = np.array([  
    [2, 0, 0],
    [0, 2, 0],
    [0, 0, 1]])
# center original to 0,0
T_neg500 = np.array([  
    [1, 0, -500],
    [0, 1, -500],
    [0, 0, 1]])
T = T_pos1000 @ T_rotate @ T_scale @ T_neg500  
T_inv = np.linalg.inv(T)  
img_transformed = img.transform((2000, 2000), Image.AFFINE, data=T_inv.flatten()[:6], resample=Image.NEAREST)  
plt.imshow(np.asarray(img_transformed))  
png

Transformaciones afines con OpenCV2

Continuando, me gustaría describir brevemente cómo llevar a cabo estas transformaciones afines con la popular biblioteca de procesamiento de imágenes y visión de computadora OpenCV. Utilizo la palabra breve aquí porque es en gran parte lo mismo que lo que se requiere en la demostración anterior con Pillow.
Lo primero es lo primero, debes instalarlo así:
$ pip install opencv-python
Como mencioné anteriormente, hay una superposición significativa en la metodología entre el enfoque de almohada y el uso de OpenCV. Por ejemplo, aún crea una matriz de transformación que primero centra la matriz de píxeles en el origen y solo usa las dos primeras filas de la matriz de transformación. La principal diferencia es que con OpenCV le da la matriz estándar en lugar de la inversa.
Entonces, con ese entendimiento establecido, saltaré al código comenzando con la importación del módulo opencv-python, que se llama cv2.
import cv2  
Leer la imagen es tan simple como llamar al cv2.imread(...)método, pasar el nombre del archivo como un argumento. Esto devuelve los datos de la imagen en forma de una matriz numpy 3D, similar a cómo funciona matplotlib, pero los datos de píxeles en la tercera dimensión se componen de una matriz de canales en el orden de azul, verde, rojo en lugar de rojo, verde Azul, alfa como era en el caso de leer con matplotlib.
Por lo tanto, para trazar los datos de la imagen numpy que se originan en la biblioteca OpenCV, se debe invertir el orden de los canales de píxeles. Afortunadamente, OpenCV proporciona un método convincente cvtColor(...)que se puede usar para hacer esto como se muestra a continuación (aunque es probable que los puristas entusiastas sepan que img[:,:,::-1]harán lo mismo).
img = cv2.imread('letterR.jpg')  
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))  
png
Algunos últimos elementos a mencionar son que OpenCV requiere que los datos en la matriz de transformación sean de tipo flotante de 32 bits en lugar del flotante predeterminado de 64 bits, así que asegúrese de convertir a 32 bits con numpy.float32(...)Además, la API cv2.warpAffine(...)no proporciona la capacidad de especificar qué tipo de algoritmo de interpolación de píxeles se debe aplicar y no pude determinar a partir de los documentos qué se utiliza. Si sabe o averigua por favor publicar en los comentarios a continuación.
T_opencv = np.float32(T.flatten()[:6].reshape(2,3))  
img_transformed = cv2.warpAffine(img, T_opencv, (2000, 2000))  
plt.imshow(cv2.cvtColor(img_transformed, cv2.COLOR_BGR2RGB))  
png

Conclusión

En este artículo he cubierto lo que es una transformación afín y cómo se puede aplicar al procesamiento de imágenes usando Python. Se utilizó numpy puro y matplotlib para dar una descripción intuitiva de bajo nivel de cómo funcionan las transformaciones afines. Concluí demostrando cómo se puede hacer lo mismo usando dos bibliotecas populares de Python, Pillow y OpenCV.
Gracias por leer y, como siempre, no tenga miedo de comentar o criticar a continuación.

Recursos

Acerca de: Programator

Somos Instinto Programador

0 comentarios:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Con tecnología de Blogger.