Compresión de Imágenes con SVD
Una guía práctica, paso a paso, para comprimir imágenes usando la Descomposición en Valores Singulares (SVD) y Python.
1. ¿Qué es la SVD?
La Descomposición en Valores Singulares (Singular Value Decomposition, SVD) es una técnica lineal que factoriza cualquier matriz A en tres matrices:
A = U Σ Vᵀ
U: matriz ortogonal (columnas = vectores singulares izquierdos).Σ: matriz diagonal con los valores singulares (ordenados de mayor a menor).Vᵀ: transpuesta de una matriz ortogonal (vectores singulares derechos).
En el contexto de imágenes, A es la matriz de intensidad de píxeles (gris o canal de color). Los valores singulares más grandes capturan la mayor parte de la energía visual; los menores pueden descartarse sin perder calidad perceptual.
2. Principio de Compresión mediante SVD
Al truncar la descomposición a k valores singulares, obtenemos una aproximación de rango k:
Aₖ ≈ Uₖ Σₖ Vₖᵀ
Donde Uₖ tiene k columnas, Σₖ es k×k y Vₖᵀ tiene k filas. El número de datos almacenados se reduce de m·n a k·(m+n+1), logrando compresión.
El factor de compresión (FC) se calcula como:
FC = (m·n) / (k·(m + n + 1))
Elegir k es un compromiso entre calidad y tamaño.
3. Implementación en Python
A continuación, un script completo que carga una imagen, la comprime con SVD y visualiza resultados.
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
def load_image(path, as_gray=True):
img = Image.open(path)
if as_gray:
img = img.convert('L') # escala de grises
return np.array(img, dtype=np.float64)
def svd_compress(matrix, k):
# Descomposición SVD completa
U, s, VT = np.linalg.svd(matrix, full_matrices=False)
# Truncado a k componentes
U_k = U[:, :k]
s_k = s[:k]
VT_k = VT[:k, :]
# Reconstrucción
return np.dot(U_k, np.dot(np.diag(s_k), VT_k))
def plot_results(original, compressed, k):
fig, axs = plt.subplots(1, 2, figsize=(10,5))
axs[0].imshow(original, cmap='gray')
axs[0].set_title('Original')
axs[0].axis('off')
axs[1].imshow(np.clip(compressed, 0, 255), cmap='gray')
axs[1].set_title(f'Compresión SVD (k={k})')
axs[1].axis('off')
plt.tight_layout()
plt.show()
if __name__ == '__main__':
img_path = 'ruta/a/tu/imagen.jpg'
A = load_image(img_path)
for k in [5, 20, 50, 100]:
A_k = svd_compress(A, k)
plot_results(A, A_k, k)
El script permite experimentar con diferentes valores de k y observar el efecto visual inmediato.
4. Comparativa: SVD vs Algoritmos Tradicionales (JPEG, JPEG‑2000)
SVD (Compresión basada en rango)
- Ventajas: Control fino de calidad, sin artefactos de bloque, ideal para análisis científico.
- Desventajas: Computacionalmente más costoso (O(mn·min(m,n))) y menos eficiente en hardware especializado.
JPEG (DCT)
- Ventajas: Amplio soporte, compresión rápida, hardware acelerado.
- Desventajas: Artefactos de bloque y pérdida de alta frecuencia a altas tasas.
| Característica | SVD | JPEG | JPEG‑2000 |
|---|---|---|---|
| Tipo de compresión | Lossy (controlable por k) | Lossy (cuantización) | Lossy / Lossless |
| Artefactos típicos | Ninguno (solo suavizado) | Bloques, ringing | Blur, ringing |
| Velocidad de compresión | Media‑Alta (SVD completa) | Alta | Media |
| Escalabilidad | Excelente (incrementar k) | Limitada | Buena |
| Uso en visión por computadora | Alto (preserva estructura) | Moderado | Alto |
5. Buenas Prácticas y Optimización
- Pre‑procesado: Normaliza la imagen a
float64y centra los valores (resta la media) para mejorar la convergencia de SVD. - Selección de k: Analiza la energía acumulada:
cum_energy = np.cumsum(s) / np.sum(s). Elige el menorkque supere, por ejemplo, el 90 % de energía. - Uso de SVD truncada: En lugar de calcular la SVD completa, emplea
scipy.sparse.linalg.svdspara obtener sólo loskvalores más grandes, reduciendo tiempo y memoria. - Paralelismo: Para imágenes muy grandes, divide la matriz en bloques y procesa cada bloque en paralelo (multiprocessing o Dask).
- Formato de salida: Guarda la representación comprimida como
.npz(U_k, s_k, VT_k) y reconstruye bajo demanda, evitando la generación de archivos JPEG.
6. Solución de Problemas Comunes
Problema: Memoria insuficiente al cargar imágenes de alta resolución.
Solución: Usa numpy.memmap o procesa la imagen en tiles (p.ej., 1024×1024) y combina los resultados.
Problema: La imagen reconstruida aparece más oscura.
Solución: Asegúrate de volver a añadir la media que se restó durante la normalización y de clipear los valores al rango [0,255] antes de guardar.
Problema: Tiempo de compresión excesivo (> minutes).
Solución: Cambia a SVD truncada (scipy.sparse.linalg.svds) y/o reduce la resolución temporalmente con cv2.resize antes de comprimir.
7. Casos de Uso Reales
- Investigación médica: Compresión de imágenes de resonancia magnética donde la preservación de detalles finos es crítica.
- Visión por computadora en drones: Transmisión de frames comprimidos con SVD para reducir ancho de banda sin sacrificar detección de objetos.
- Almacenamiento de catálogos astronómicos: Reducción de petabytes de imágenes del cielo manteniendo la fidelidad espectral.
Compresión de Imágenes con SVD: Guía Completa y Ejemplos en Python