Transformaciones Afines con Python
Descubre el algoritmo detrás de las transformaciones afines, cómo implementarlo con numpy y opencv, y los mejores trucos para usarlo en proyectos reales.
1. ¿Qué es una Transformación Afín?
Una transformación afín es una función lineal que preserva colinealidad y razón de distancias. En el plano 2‑D se representa mediante una matriz 3×3 de la forma:
\[\begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{bmatrix}\]
Los parámetros a, b, d, e controlan rotación, escala y cizallamiento, mientras que c, f representan la traslación.
Esta representación permite combinar varias operaciones en una única multiplicación matricial, lo que la hace ideal para procesamiento de imágenes y visión por computadora.
2. Matemáticas de la Transformación Afín
Para un punto (x, y) en coordenadas homogéneas (x, y, 1), la transformación se define como:
\[\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix}=\begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{bmatrix}\begin{bmatrix} x \\ y \\ 1 \end{bmatrix}\]
Desglosando:
- Rotación:
a = cosθ, b = -sinθ, d = sinθ, e = cosθ - Escala:
a = sx, e = sy - Cizallamiento:
b = sh_x, d = sh_y - Traslación:
c = tx, f = ty
Al combinar estos valores se obtienen transformaciones compuestas sin perder precisión.
3. Implementación en Python
Usaremos numpy para la lógica matricial y opencv (cv2) para aplicar la transformación a imágenes.
3.1. Construcción de la Matriz Afín
import numpy as np
def affine_matrix(scale=(1,1), rotate=0, shear=(0,0), translate=(0,0)):
sx, sy = scale
shx, shy = shear
tx, ty = translate
theta = np.deg2rad(rotate)
# Rotación + escala
R = np.array([[np.cos(theta)*sx, -np.sin(theta)*sy, 0],
[np.sin(theta)*sx, np.cos(theta)*sy, 0],
[0, 0, 1]])
# Cizallamiento
S = np.array([[1, shx, 0],
[shy, 1, 0],
[0, 0, 1]])
# Traslación
T = np.array([[1, 0, tx],
[0, 1, ty],
[0, 0, 1]])
# Orden típico: T * S * R
return T @ S @ R
3.2. Aplicar la Transformación a una Imagen
import cv2
def apply_affine(img, M, output_shape=None):
if output_shape is None:
h, w = img.shape[:2]
else:
h, w = output_shape
# OpenCV espera una matriz 2x3 (elimina la fila [0 0 1])
M_cv2 = M[:2, :]
return cv2.warpAffine(img, M_cv2, (w, h), flags=cv2.INTER_LINEAR)
# Ejemplo de uso
img = cv2.imread('sample.jpg')
M = affine_matrix(scale=(1.2,1.2), rotate=30, shear=(0.2,0), translate=(50,30))
result = apply_affine(img, M)
cv2.imwrite('result.jpg', result)
4. Casos Prácticos
4.1. Rotación de un Cuadro
M = affine_matrix(rotate=45)
rotated = apply_affine(img, M)
Ideal para data‑augmentation en redes neuronales de visión.
4.2. Corrección de Perspectiva (aproximación afín)
Cuando la cámara está ligeramente desviada, una transformación afín puede alinear la escena sin la sobrecarga de una homografía completa.
pts_src = np.float32([[30,30],[200,30],[30,200]])
pts_dst = np.float32([[0,0],[220,20],[20,210]])
M = cv2.getAffineTransform(pts_src, pts_dst)
corrected = cv2.warpAffine(img, M, (250,250))
5. Comparativa: Transformación Afín vs. Transformación Proyectiva (Homografía)
Transformación Afín
- Preserva paralelismo.
- Menor coste computacional (O(1) por píxel).
- Ideal para rotación, escala, cizallado y traslación.
- Limitada cuando se requiere corrección de perspectiva fuerte.
Transformación Proyectiva (Homografía)
- Preserva la colinealidad pero no el paralelismo.
- Requiere una matriz
3×3con ocho grados de libertad. - Capaz de corregir distorsiones de cámara (p.ej., vista de plano inclinado).
- Mayor coste y mayor sensibilidad a ruido en los puntos de referencia.
6. Buenas Prácticas y Optimización
- Pre‑cálculo de la matriz: Si aplicas la misma transformación a cientos de imágenes, calcula la matriz una sola vez.
- Uso de tipos de datos adecuados:
float32es suficiente para la mayoría de los casos y reduce el consumo de memoria. - Interpolación:
cv2.INTER_LINEARes balanceado; para bordes nítidos usaINTER_NEAREST, y para calidad máximaINTER_CUBIC. - Thread‑pool o multiprocessing para procesar lotes de imágenes en paralelo (p.ej.,
concurrent.futures). - Evitar bordes vacíos: Calcula el tamaño de salida con la fórmula de la caja delimitadora del nuevo rectángulo.
7. Troubleshooting Común
| Síntoma | Causa típica | Solución |
|---|---|---|
| Imagen recortada o con bordes negros | Salida demasiado pequeña | Calcular la dimensión de salida con la transformación de los cuatro vértices y usar esa caja como output_shape. |
| Distorsión inesperada | Orden incorrecto de operaciones (p.ej., traslación antes de rotación) | Construir la matriz en el orden deseado (usualmente T·S·R). |
| Rendimiento bajo en lotes grandes | Procesamiento secuencial | Implementar procesamiento en paralelo o usar cv2.cuda.warpAffine si la GPU está disponible. |
| Pixelado al ampliar | Interpolación por defecto (nearest) | Usar INTER_LINEAR o INTER_CUBIC. |
8. Compatibilidad y Escalabilidad
Las funciones mostradas son compatibles con Python 3.8+ y funcionan en Windows, macOS y distribuciones Linux. Para proyectos de gran escala (p.ej., pipelines de datos de cientos de TB), considere:
- Uso de
DaskoApache Beampara distribuir la carga. - Almacenamiento de matrices pre‑calculadas en
ParquetoArrowpara acceso rápido. - Vectorización con
Numbacuando se requiera manipular píxeles a nivel de numpy sin OpenCV.
Transformaciones Afines con Python: Algoritmo, Ejemplos y Mejores Prácticas