Este artigo explora as principais bibliotecas e técnicas para processamento de imagens em Python, com foco prático em manipulação, transformação, análise e filtragem de imagens digitais. Abordamos ferramentas essenciais como PIL/Pillow, NumPy, Matplotlib e SciPy, demontsrando como combiná-las para resolver tarefas comuns em visão computacional.
Manipulação Básica com Pillow
A biblioteca Pillow (sucessora ativa do PIL) é a escolha padrão para operações fundamentais de imagem. Ela permite carregar, converter, redimensionar e salvar imagens em diversos formatos.
Para abrir uma imagem e convertê-la para tons de cinza:
from PIL import Image
img = Image.open("landscape.jpg")
gray_img = img.convert("L")
O método convert("L") aplica uma conversão ponderada dos canais RGB para luminância, gerando uma imagem monocromática. Para salvar em outro formato — por exemplo, converter todos os arquivos PNG de um diretório para JPEG — podemos usar:
import os
from pathlib import Path
from PIL import Image
input_dir = Path("raw_images")
output_dir = Path("converted_jpegs")
output_dir.mkdir(exist_ok=True)
for path in input_dir.glob("*.png"):
with Image.open(path) as im:
jpeg_path = output_dir / f"{path.stem}.jpg"
im.save(jpeg_path, quality=95)
Geração de Miniaturas
Criar miniaturas preservando a proporção é simples com o método thumbnail():
# Redimensiona in-place mantendo a proporção máxima de 200×200
img.thumbnail((200, 200))
Corte e Colagem com Precisão
O corte usa uma tupla (esquerda, superior, direita, inferior), com origem no canto superior esquerdo:
# Extrai região central de 150×150 pixels
w, h = img.size
box = (w//2 - 75, h//2 - 75, w//2 + 75, h//2 + 75)
region = img.crop(box)
# Rotaciona 90 graus e cola de volta na posição original
rotated = region.rotate(90, expand=True)
img.paste(rotated, box)
Visualização com Matplotlib
O Matplotlib oferece controle refinado sobre a exibição de imagens e sobreposições gráficas. Ao contrário do Pillow, ele opera sobre arrays NumPy — ideal para análise quantitativa.
Para exibir uma imagem com anotações interativas:
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
# Carrega como array NumPy
data = np.array(Image.open("portrait.jpg"))
plt.figure(figsize=(8, 6))
plt.imshow(data)
plt.title("Imagem Original com Pontos Marcados")
plt.axis("off") # Oculta eixos para visualização limpa
# Adiciona três pontos vermelhos
points = [(120, 85), (240, 110), (180, 200)]
for x, y in points:
plt.plot(x, y, "ro", markersize=8)
plt.show()
Contornos e Histogramas
Para gerar contornos de nível de intensidade (útil em segmentação):
gray_data = np.array(Image.open("portrait.jpg").convert("L"))
plt.figure()
plt.contour(gray_data, levels=12, cmap="viridis", alpha=0.8)
plt.axis("equal")
plt.axis("off")
plt.show()
E para visualizar a distribuição de intensidades:
plt.figure()
plt.hist(gray_data.ravel(), bins=256, range=(0, 255), density=True, color="steelblue", alpha=0.7)
plt.xlabel("Intensidade de Pixel")
plt.ylabel("Densidade de Probabilidade")
plt.title("Histograma de Intensidade")
plt.grid(True, alpha=0.3)
plt.show()
Transformações com NumPy
O NumPy fornece a base numérica para operações vetoriais eficientes em imagens. Cada imagem é representada como um array multidimensional — por exemplo, (altura, largura, canais) para RGB ou (altura, largura) para tons de cinza.
Exemplos de transformações de brilho e contraste:
arr = np.array(Image.open("portrait.jpg").convert("L"), dtype=np.float32)
# Inversão de tons
inverted = 255.0 - arr
# Ajuste linear para intervalo [40, 220]
scaled = np.clip((arr - arr.min()) / (arr.max() - arr.min()) * 180 + 40, 0, 255)
# Elevação ao quadrado (realce de sombras)
quadratic = np.clip((arr / 255.0) ** 2 * 255, 0, 255)
Equalização de Histograma
Implementação eficiente usando funções NumPy nativas:
def equalize_histogram(image_array):
"""Equaliza o histograma de uma imagem em tons de cinza."""
flat = image_array.ravel()
hist, _ = np.histogram(flat, bins=256, range=(0, 256), density=False)
cdf = hist.cumsum().astype(np.float64)
cdf = 255 * cdf / cdf[-1] # Normaliza para 0–255
# Mapeia cada valor de pixel para seu novo valor
equalized = np.interp(flat, range(256), cdf)
return equalized.reshape(image_array.shape).astype(np.uint8)
# Uso
eq_img = equalize_histogram(gray_data)
Filtragem com SciPy
O módulo scipy.ndimage oferece filtros especializados para suavização, detecção de bordas e morfologia matemática.
Suavização Gaussiana
Aplica um kernel gaussiano para reduzir ruído:
from scipy.ndimage import gaussian_filter
blurred = gaussian_filter(gray_data, sigma=3.0)
Detecção de Bordas com Gradientes
Calcula derivadas parciais usando filtros Sobel:
from scipy.ndimage import sobel
grad_x = sobel(gray_data, axis=1, mode="constant")
grad_y = sobel(gray_data, axis=0, mode="constant")
magnitude = np.hypot(grad_x, grad_y) # Magnitude do gradiente
Análise Morfológica em Imagens Binárias
Para contar objetos após limiarização:
from scipy.ndimage import label, binary_opening
from scipy.ndimage.morphology import generate_binary_structure
# Converte para binária com limiar fixo
binary = gray_data > 128
# Remove pequenos ruídos com abertura morfológica
structure = generate_binary_structure(2, 1)
cleaned = binary_opening(binary, structure, iterations=2)
# Rotula componentes conectados
labeled, num_objects = label(cleaned)
print(f"Objetos detectados: {num_objects}")
Remoção de Ruído com Modelo ROF
O modelo Rudin-Osher-Fatemi (ROF) preserva bordas enquanto remove ruído. Sua implementação baseada em algoritmo de Chambolle:
def rof_denoise(image, weight=10, tolerance=1e-3, step=0.125):
"""Implementação do algoritmo ROF para remoção de ruído."""
u = image.astype(np.float64)
px, py = np.zeros_like(u), np.zeros_like(u)
while True:
u_old = u.copy()
# Gradientes
ux = np.roll(u, -1, axis=1) - u
uy = np.roll(u, -1, axis=0) - u
# Atualiza variáveis duais
px_new = px + (step / weight) * ux
py_new = py + (step / weight) * uy
norm = np.maximum(1, np.sqrt(px_new**2 + py_new**2))
px, py = px_new / norm, py_new / norm
# Atualiza primal
rx = np.roll(px, 1, axis=1)
ry = np.roll(py, 1, axis=0)
div = (px - rx) + (py - ry)
u = image + weight * div
# Verifica convergência
error = np.linalg.norm(u - u_old) / u.size
if error < tolerance:
break
return np.clip(u, 0, 255).astype(np.uint8)
# Aplicação
noisy = gray_data + 20 * np.random.normal(size=gray_data.shape)
denoised = rof_denoise(noisy, weight=15)