tutoriales.com

Aprende a Manipular Imágenes con Pillow en Python: Una Guía para Procesamiento Digital

Este tutorial te guiará a través de la biblioteca Pillow en Python, una potente herramienta para el procesamiento de imágenes. Aprenderás desde la apertura y guardado de imágenes hasta la aplicación de transformaciones complejas, permitiéndote automatizar y mejorar tus tareas de edición.

Principiante15 min de lectura13 views
Reportar error

La manipulación de imágenes es una tarea común en muchos campos, desde el desarrollo web y móvil hasta la ciencia de datos y la inteligencia artificial. Python, con su vasta colección de bibliotecas, ofrece una solución robusta para esta necesidad: Pillow. Pillow es una fork amigable de la legendaria biblioteca PIL (Python Imaging Library), que proporciona poderosas capacidades de procesamiento de imágenes.

En este tutorial, exploraremos las funcionalidades clave de Pillow, desde las operaciones básicas como abrir y guardar imágenes, hasta transformaciones más avanzadas como redimensionar, rotar, recortar y aplicar filtros.

🔥 **Importante:** Asegúrate de tener Python instalado en tu sistema. Pillow es compatible con Python 3.6 y versiones superiores.

🚀 ¿Por Qué Pillow?

Pillow es la elección de facto para la manipulación de imágenes en Python por varias razones:

  • Facilidad de Uso: Su API es intuitiva y fácil de aprender.
  • Amplias Funcionalidades: Soporta una gran variedad de formatos de imagen y ofrece numerosas operaciones de manipulación.
  • Rendimiento: Está optimizada para trabajar eficientemente con imágenes.
  • Comunidad Activa: Cuenta con una comunidad vibrante y documentación excelente.

🛠️ Instalación de Pillow

Antes de empezar a programar, necesitamos instalar la biblioteca Pillow. Abre tu terminal o línea de comandos y ejecuta el siguiente comando:

pip install Pillow
💡 Consejo: Si estás usando un entorno virtual, actívalo antes de instalar Pillow para mantener tus dependencias organizadas.

📖 Conceptos Básicos de Imágenes

Antes de sumergirnos en el código, es útil entender algunos conceptos fundamentales de las imágenes digitales:

  • Modo (Mode): Describe el tipo y profundidad de píxeles de una imagen. Ejemplos comunes son L (escala de grises), RGB (color verdadero), RGBA (color verdadero con transparencia), CMYK.
  • Tamaño (Size): Representado como una tupla (ancho, alto) en píxeles.
  • Formato (Format): El tipo de archivo de imagen (JPEG, PNG, GIF, BMP, etc.).

🎯 Primeros Pasos: Abriendo y Guardando Imágenes

El primer paso en cualquier tarea de manipulación de imágenes es cargar la imagen en la memoria y, una vez modificada, guardarla de nuevo.

Abrir una Imagen

Usamos la función Image.open() para cargar una imagen. Asegúrate de tener una imagen de prueba en el mismo directorio que tu script de Python, o proporciona la ruta completa.

from PIL import Image

try:
    # Reemplaza 'imagen_original.jpg' con la ruta de tu imagen
    img = Image.open('imagen_original.jpg')
    print(f"Formato: {img.format}")
    print(f"Modo: {img.mode}")
    print(f"Tamaño: {img.size}")
    img.show() # Abre la imagen con el visor predeterminado del sistema
except FileNotFoundError:
    print("Error: La imagen 'imagen_original.jpg' no se encontró.")
except Exception as e:
    print(f"Ocurrió un error al abrir la imagen: {e}")
📌 Nota: `img.show()` es útil para depurar y ver rápidamente el resultado de tus operaciones. En entornos de servidor, es posible que no funcione o requiera configuración adicional.

Guardar una Imagen

Para guardar una imagen modificada, usamos el método save(). Puedes especificar el formato de salida simplemente cambiando la extensión del archivo.

from PIL import Image

# Suponiendo que 'img' es una imagen abierta previamente
try:
    img = Image.open('imagen_original.jpg')
    img.save('imagen_copia.png') # Guarda como PNG
    print("Imagen guardada como imagen_copia.png")
except FileNotFoundError:
    print("Error: La imagen 'imagen_original.jpg' no se encontró.")
except Exception as e:
    print(f"Ocurrió un error al guardar la imagen: {e}")

Tabla de Formatos Comunes Soportados:

FormatoExtensión ComúnDescripción
---------
JPEG.jpg, .jpegCompresión con pérdida, ideal para fotografías.
PNG.pngCompresión sin pérdida, soporta transparencia.
---------
GIF.gifSoporta animaciones y transparencia, ideal para gráficos simples.
BMP.bmpFormato sin compresión, grande.
---------
TIFF.tif, .tiffAlta calidad, usado en impresión.

📏 Transformaciones Básicas de Imágenes

Pillow ofrece una gran variedad de transformaciones para modificar el aspecto y el tamaño de tus imágenes.

Redimensionar Imágenes

El método resize() te permite cambiar el tamaño de una imagen. Recibe una tupla (ancho, alto) como argumento.

from PIL import Image

try:
    img = Image.open('imagen_original.jpg')
    # Redimensionar a 300x200 píxeles
    img_redimensionada = img.resize((300, 200))
    img_redimensionada.save('imagen_redimensionada.jpg')
    print("Imagen redimensionada y guardada.")
    img_redimensionada.show()
except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")
⚠️ Advertencia: Redimensionar imágenes a un tamaño mucho mayor al original puede resultar en pérdida de calidad y pixelación.

Rotar Imágenes

El método rotate() permite girar una imagen un número específico de grados. Puedes especificar un ángulo de rotación.

from PIL import Image

try:
    img = Image.open('imagen_original.jpg')
    # Rotar la imagen 90 grados en sentido horario
    img_rotada = img.rotate(90)
    img_rotada.save('imagen_rotada_90.jpg')
    print("Imagen rotada 90 grados y guardada.")
    img_rotada.show()

    # Rotar con expansión (para que la imagen completa sea visible)
    img_rotada_expandida = img.rotate(45, expand=True)
    img_rotada_expandida.save('imagen_rotada_45_expandida.jpg')
    print("Imagen rotada 45 grados (expandida) y guardada.")
    img_rotada_expandida.show()
except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")

Recortar Imágenes (Cropping)

El método crop() te permite extraer una sección rectangular de una imagen. Necesita una tupla de 4 valores (izquierda, arriba, derecha, abajo) para definir el área a recortar.

from PIL import Image

try:
    img = Image.open('imagen_original.jpg')
    ancho, alto = img.size

    # Definir el área de recorte: un cuadrado de 100x100 en el centro
    left = (ancho - 100) / 2
    top = (alto - 100) / 2
    right = (ancho + 100) / 2
    bottom = (alto + 100) / 2

    # Asegurarse de que las coordenadas sean enteros
    area_recorte = (int(left), int(top), int(right), int(bottom))

    img_recortada = img.crop(area_recorte)
    img_recortada.save('imagen_recortada.jpg')
    print("Imagen recortada y guardada.")
    img_recortada.show()
except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")
except Exception as e:
    print(f"Ocurrió un error: {e}")

Voltear Imágenes (Flipping)

Puedes voltear una imagen horizontal o verticalmente usando transpose() junto con Image.FLIP_LEFT_RIGHT o Image.FLIP_TOP_BOTTOM.

from PIL import Image

try:
    img = Image.open('imagen_original.jpg')

    # Voltear horizontalmente
    img_flip_h = img.transpose(Image.FLIP_LEFT_RIGHT)
    img_flip_h.save('imagen_volteada_h.jpg')
    print("Imagen volteada horizontalmente y guardada.")
    img_flip_h.show()

    # Voltear verticalmente
    img_flip_v = img.transpose(Image.FLIP_TOP_BOTTOM)
    img_flip_v.save('imagen_volteada_v.jpg')
    print("Imagen volteada verticalmente y guardada.")
    img_flip_v.show()
except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")
Inicio Abrir Imagen Aplicar Transformación (Resize / Rotate / Crop) Guardar Imagen Fin

✨ Aplicando Filtros y Mejoras

Pillow incluye el módulo ImageFilter que proporciona una variedad de filtros predefinidos para mejorar o modificar el aspecto visual de las imágenes.

Desenfoque (Blur)

El filtro BLUR es útil para suavizar una imagen o para efectos artísticos.

from PIL import Image, ImageFilter

try:
    img = Image.open('imagen_original.jpg')

    # Aplicar un filtro de desenfoque
    img_blur = img.filter(ImageFilter.BLUR)
    img_blur.save('imagen_desenfocada.jpg')
    print("Imagen desenfocada y guardada.")
    img_blur.show()
except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")

Nitidez (Sharpen)

Para realzar los bordes y detalles de una imagen, puedes usar el filtro SHARPEN.

from PIL import Image, ImageFilter

try:
    img = Image.open('imagen_original.jpg')

    # Aplicar un filtro de nitidez
    img_sharpen = img.filter(ImageFilter.SHARPEN)
    img_sharpen.save('imagen_nitida.jpg')
    print("Imagen nítida y guardada.")
    img_sharpen.show()
except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")

Otros Filtros Comunes

Aquí hay algunos otros filtros populares disponibles en ImageFilter:

  • CONTOUR: Encuentra los contornos de la imagen.
  • EMBOSS: Crea un efecto de relieve.
  • FIND_EDGES: Detecta los bordes en la imagen.
  • SMOOTH: Suaviza la imagen.
💡 Consejo: Experimenta con diferentes filtros para ver sus efectos. Cada filtro puede tener un impacto visual muy distinto.

Ajuste de Color, Brillo, Contraste y Nitidez (ImageEnhance)

El módulo ImageEnhance permite realizar ajustes más finos en propiedades como el brillo, el contraste o la saturación.

from PIL import Image, ImageEnhance

try:
    img = Image.open('imagen_original.jpg')

    # Ajustar el brillo (factor > 1 para más brillo, < 1 para menos)
    enhancer_brillo = ImageEnhance.Brightness(img)
    img_brillo = enhancer_brillo.enhance(1.5) # Aumentar brillo 50%
    img_brillo.save('imagen_brillo.jpg')
    print("Brillo ajustado.")
    img_brillo.show()

    # Ajustar el contraste
    enhancer_contraste = ImageEnhance.Contrast(img)
    img_contraste = enhancer_contraste.enhance(1.8) # Aumentar contraste 80%
    img_contraste.save('imagen_contraste.jpg')
    print("Contraste ajustado.")
    img_contraste.show()

    # Ajustar la saturación (color)
    enhancer_color = ImageEnhance.Color(img)
    img_saturacion = enhancer_color.enhance(2.0) # Duplicar saturación
    img_saturacion.save('imagen_saturacion.jpg')
    print("Saturación ajustada.")
    img_saturacion.show()

except FileNotFoundError:
    print("Error: Asegúrate de que 'imagen_original.jpg' existe.")

🎨 Dibujando Formas y Texto en Imágenes

Pillow también te permite dibujar formas geométricas y añadir texto a tus imágenes usando el módulo ImageDraw.

Dibujar Rectángulos y Círculos

from PIL import Image, ImageDraw

# Crear una imagen en blanco (fondo blanco, RGB, 400x300)
img_dibujo = Image.new('RGB', (400, 300), color = 'white')
draw = ImageDraw.Draw(img_dibujo)

# Dibujar un rectángulo rojo
# Coordenadas: (x1, y1, x2, y2)
draw.rectangle((50, 50, 200, 150), fill='red', outline='black', width=2)

# Dibujar un círculo azul
# Coordenadas: (x1, y1, x2, y2) para la caja delimitadora
draw.ellipse((250, 100, 350, 200), fill='blue', outline='green', width=3)

img_dibujo.save('imagen_con_formas.png')
print("Imagen con formas dibujadas guardada.")
img_dibujo.show()

Añadir Texto

Para añadir texto, también usas ImageDraw, pero necesitas una fuente. Pillow puede cargar fuentes TrueType (.ttf).

from PIL import Image, ImageDraw, ImageFont

# Crear una imagen en blanco
img_texto = Image.new('RGB', (500, 200), color = 'lightblue')
draw = ImageDraw.Draw(img_texto)

# Cargar una fuente (asegúrate de que la ruta sea correcta o usa una fuente del sistema)
# En Linux/macOS, puedes usar 'arial.ttf' o 'Roboto-Regular.ttf' si lo tienes
# En Windows, puedes probar con 'C:/Windows/Fonts/arial.ttf'
# Si la fuente no se encuentra, Pillow usará una fuente predeterminada
try:
    # Intenta cargar una fuente común
    font = ImageFont.truetype("arial.ttf", 30)
except IOError:
    print("Fuente 'arial.ttf' no encontrada. Usando fuente predeterminada.")
    font = ImageFont.load_default() # Carga la fuente por defecto si no encuentra 'arial.ttf'

# Añadir texto
# Coordenadas: (x, y) del texto
draw.text((50, 70), "¡Hola, Pillow!", fill=(0, 0, 0), font=font)

img_texto.save('imagen_con_texto.png')
print("Imagen con texto guardada.")
img_texto.show()
📌 Nota: La disponibilidad de fuentes `.ttf` puede variar entre sistemas operativos. Si `arial.ttf` no funciona, busca otra fuente en tu sistema o descarga una.

🔄 Procesamiento por Lotes (Batch Processing)

Una de las mayores ventajas de Pillow es su capacidad para automatizar tareas. Podemos aplicar las mismas operaciones a múltiples imágenes de una sola vez.

from PIL import Image
import os

def procesar_imagenes_en_carpeta(ruta_entrada, ruta_salida, nuevo_ancho=None, nuevo_alto=None, aplicar_filtro=False):
    if not os.path.exists(ruta_salida):
        os.makedirs(ruta_salida)

    for filename in os.listdir(ruta_entrada):
        if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
            filepath_entrada = os.path.join(ruta_entrada, filename)
            filepath_salida = os.path.join(ruta_salida, f"procesada_{filename}")

            try:
                img = Image.open(filepath_entrada)

                # Redimensionar si se especifica
                if nuevo_ancho and nuevo_alto:
                    img = img.resize((nuevo_ancho, nuevo_alto))
                elif nuevo_ancho:
                    # Mantener la proporción si solo se especifica el ancho
                    ancho_original, alto_original = img.size
                    nuevo_alto = int((nuevo_ancho / ancho_original) * alto_original)
                    img = img.resize((nuevo_ancho, nuevo_alto))
                elif nuevo_alto:
                    # Mantener la proporción si solo se especifica el alto
                    ancho_original, alto_original = img.size
                    nuevo_ancho = int((nuevo_alto / alto_original) * ancho_original)
                    img = img.resize((nuevo_ancho, nuevo_alto))

                # Aplicar filtro si se especifica
                if aplicar_filtro:
                    from PIL import ImageFilter # Importar aquí para evitar circular imports si no se usa
                    img = img.filter(ImageFilter.SHARPEN)

                img.save(filepath_salida)
                print(f"Procesada: {filename} -> {os.path.basename(filepath_salida)}")

            except Exception as e:
                print(f"Error al procesar {filename}: {e}")

# Uso del procesamiento por lotes
# Crea una carpeta 'imagenes_originales' con tus imágenes de prueba
# y una carpeta 'imagenes_procesadas' vacía
ruta_entrada = 'imagenes_originales'
ruta_salida = 'imagenes_procesadas'

# Ejemplo 1: Redimensionar todas las imágenes a 600px de ancho (manteniendo proporción)
procesar_imagenes_en_carpeta(ruta_entrada, ruta_salida + '_redimensionadas_600px', nuevo_ancho=600)

# Ejemplo 2: Aplicar filtro de nitidez a todas las imágenes
procesar_imagenes_en_carpeta(ruta_entrada, ruta_salida + '_nitidas', aplicar_filtro=True)

# Ejemplo 3: Redimensionar y aplicar filtro
procesar_imagenes_en_carpeta(ruta_entrada, ruta_salida + '_redimensionadas_y_nitidas', nuevo_ancho=400, nuevo_alto=300, aplicar_filtro=True)

Para ejecutar este script, crea una carpeta llamada imagenes_originales en el mismo directorio que tu script de Python y coloca algunas imágenes dentro. Luego, ejecuta el script. Verás cómo se crean carpetas nuevas con las imágenes procesadas.

⚠️ Advertencia: Siempre trabaja con copias de tus imágenes originales cuando experimentes con procesamiento por lotes para evitar la pérdida accidental de datos.

📈 Optimización de Imágenes para Web

Una tarea muy común es optimizar imágenes para su uso en la web, reduciendo el tamaño del archivo sin sacrificar demasiada calidad. Pillow facilita esto.

from PIL import Image
import os

def optimizar_imagen_para_web(input_path, output_path, calidad=85, max_ancho=1200):
    try:
        img = Image.open(input_path)
        ancho_original, alto_original = img.size

        # Redimensionar si es más grande que el ancho máximo
        if ancho_original > max_ancho:
            nuevo_alto = int((max_ancho / ancho_original) * alto_original)
            img = img.resize((max_ancho, nuevo_alto), Image.LANCZOS)
        
        # Convertir a RGB si la imagen tiene canal alfa (RGBA) para JPG
        if img.mode == 'RGBA':
            img = img.convert('RGB')

        # Guardar con calidad reducida (solo para JPEG)
        img.save(output_path, quality=calidad, optimize=True)
        print(f"Imagen optimizada: {os.path.basename(input_path)} -> {os.path.basename(output_path)}")
    except Exception as e:
        print(f"Error al optimizar {input_path}: {e}")

# Ejemplo de uso:
input_folder = 'imagenes_originales'
output_folder = 'imagenes_web'

if not os.path.exists(output_folder):
    os.makedirs(output_folder)

for filename in os.listdir(input_folder):
    if filename.lower().endswith(('.jpg', '.jpeg')):
        input_filepath = os.path.join(input_folder, filename)
        output_filepath = os.path.join(output_folder, f"web_{filename}")
        optimizar_imagen_para_web(input_filepath, output_filepath, calidad=75, max_ancho=800)

    elif filename.lower().endswith('.png'): # Los PNGs se tratan diferente, no usan 'quality' de la misma forma
        input_filepath = os.path.join(input_folder, filename)
        output_filepath = os.path.join(output_folder, f"web_{filename}")
        try:
            img = Image.open(input_filepath)
            if img.width > 800: # Redimensionar PNG si es grande
                ancho_original, alto_original = img.size
                nuevo_alto = int((800 / ancho_original) * alto_original)
                img = img.resize((800, nuevo_alto), Image.LANCZOS)
            img.save(output_filepath, optimize=True) # optimize=True para PNG reduce el tamaño de archivo
            print(f"PNG optimizado: {os.path.basename(input_filepath)} -> {os.path.basename(output_filepath)}")
        except Exception as e:
            print(f"Error al optimizar PNG {input_filepath}: {e}")

En este ejemplo, ajustamos la quality para JPGs (un valor entre 1 y 95 es común, con 75-85 siendo un buen equilibrio). Para PNGs, optimize=True ayuda a reducir el tamaño del archivo sin pérdida de calidad. También se usa Image.LANCZOS como filtro de remuestreo de alta calidad para el redimensionado.

💡 Consejo: La mejor calidad para web suele ser JPG para fotografías y PNG para imágenes con transparencia o gráficos con pocos colores sólidos.

❓ Preguntas Frecuentes (FAQ)

¿Pillow reemplaza por completo a PIL? Sí, Pillow es el sucesor *de facto* de PIL. Mantiene la misma API y es activamente mantenida, mientras que PIL ha dejado de tener soporte oficial. Si encuentras código que usa `PIL`, lo más probable es que funcione sin cambios con `Pillow` instalado.
¿Cómo puedo convertir una imagen a escala de grises? Es muy sencillo. Solo tienes que usar el método `convert()` con el modo `'L'`: ```python from PIL import Image img = Image.open('imagen_original.jpg') img_gris = img.convert('L') img_gris.save('imagen_gris.jpg') ```
¿Pillow soporta imágenes GIF animadas? Pillow puede abrir y guardar GIFs animados, pero su capacidad para manipular *fotogramas individuales* dentro de un GIF animado es más limitada que la de otras bibliotecas específicas para GIFs. Puedes iterar a través de los fotogramas para aplicar operaciones a cada uno.
Tutorial Completo

✅ Conclusión

Has llegado al final de esta guía sobre cómo manipular imágenes con Pillow en Python. Ahora tienes las herramientas y el conocimiento para realizar una amplia gama de operaciones, desde básicas hasta más complejas, en tus imágenes. Ya sea para automatizar el redimensionamiento, aplicar efectos creativos o preparar imágenes para la web, Pillow es un aliado invaluable en tu kit de herramientas de Python.

¡Experimenta, crea y sigue explorando el vasto mundo del procesamiento de imágenes con Python!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!