Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 4 de 7

Visualización: depurar el pipeline con tus ojos

¿Qué es la visualización con matplotlib?

Matplotlib es la librería base para dibujar gráficos en Python: líneas, barras, histogramas y, lo que más nos importa aquí, mostrar imágenes con imshow. En visión por computador, "ver" un array intermedio es la forma más rápida de entender qué está haciendo (o rompiendo) tu código.

¿Cómo funciona?

Matplotlib organiza todo en una figura (figure) que contiene uno o varios ejes (axes, cada subgráfico). Tú creas la figura, dibujas sobre los ejes y la muestras con plt.show() o la guardas con plt.savefig(). Como una imagen es un array de NumPy, imshow la pinta directamente: cada número se traduce a un color o a un nivel de gris.

import numpy as np
import matplotlib.pyplot as plt

# Una "hoja" de juguete: blanca con dos burbujas rellenas
img = np.full((20, 20), 255, dtype=np.uint8)
img[4:8, 4:8] = 0 # burbuja 1 (rellena)
img[4:8, 12:16] = 0 # burbuja 2 (rellena)

plt.imshow(img, cmap="gray") # cmap="gray" -> escala de grises real
plt.title("Hoja de juguete")
plt.colorbar() # muestra la escala 0..255
plt.show()

Si no pones cmap="gray", matplotlib pinta el array en su mapa de color por defecto (morado-amarillo) y verás colores raros. Para imágenes en gris, siempre cmap="gray".

¿Para qué sirve? (y qué pieza del OMR construye)

En el proyecto OMR, el pipeline tiene varios pasos: imagen original → escala de grises → umbralizada (blanco y negro puro) → contornos detectados. Si algo falla (no detecta burbujas, las cuenta mal), necesitas ver cada paso por separado. Esta es la pieza de depuración visual: ponemos varios estados de la imagen lado a lado para encontrar dónde se rompe el pipeline.

Comparar pasos lado a lado con subplots

import numpy as np
import matplotlib.pyplot as plt

img = np.full((20, 20), 255, dtype=np.uint8)
img[4:8, 4:8] = 0
img[4:8, 12:16] = 0

# Simulamos un paso de umbralización: todo lo oscuro -> 0, lo claro -> 255
umbral = np.where(img < 128, 0, 255).astype(np.uint8)

# Dos paneles en una fila
fig, axes = plt.subplots(1, 2, figsize=(8, 4))

axes[0].imshow(img, cmap="gray")
axes[0].set_title("Original")
axes[0].axis("off") # sin ejes numéricos, es una imagen

axes[1].imshow(umbral, cmap="gray")
axes[1].set_title("Umbralizada")
axes[1].axis("off")

plt.tight_layout() # evita que se solapen los títulos
plt.savefig("debug_pipeline.png", dpi=120)
plt.show()

plt.subplots(1, 2) devuelve la figura y un array de ejes. Indexas axes[0], axes[1]… Para una rejilla 2x2, usarías axes[fila][columna]. axis("off") quita los números de los ejes, que no aportan nada cuando muestras una imagen.

El histograma: elegir el umbral con datos

¿Cómo sabemos qué valor usar para separar marca de papel? Un histograma del brillo nos lo dice: muestra cuántos píxeles hay de cada nivel. En una hoja típica verás dos montañas: muchos píxeles claros (papel) y unos pocos oscuros (marcas).

import numpy as np
import matplotlib.pyplot as plt

img = np.full((20, 20), 255, dtype=np.uint8)
img[4:8, 4:8] = 0
img[4:8, 12:16] = 0

plt.hist(img.ravel(), bins=50, range=(0, 255)) # ravel = aplanar a 1D
plt.axvline(128, color="red", linestyle="--", label="umbral propuesto")
plt.xlabel("Nivel de gris")
plt.ylabel("Nº de píxeles")
plt.legend()
plt.title("Distribución de brillo")
plt.show()

El valle entre las dos montañas del histograma es el mejor umbral. En el módulo de OpenCV veremos que el método Otsu encuentra ese valle automáticamente; aquí lo elegimos a ojo para entenderlo.

Visualizar resultados de Pandas

También sirve para el reporte final: una gráfica de barras con las notas del módulo anterior.

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({"estudiante": ["Ana", "Luis", "Marta"], "nota": [5.0, 3.0, 4.0]})

plt.bar(df["estudiante"], df["nota"], color="steelblue")
plt.axhline(3.0, color="red", linestyle="--", label="mínimo aprobatorio")
plt.ylabel("Nota")
plt.legend()
plt.title("Resultados del examen")
plt.show()

Tech English: figure = figura/lienzo; axes = ejes/subgráfico; to plot = graficar; histogram = histograma; threshold = umbral; bins = intervalos.

Ejercicios

  1. Crea una hoja de juguete con tres burbujas, una de ellas a medio rellenar (valor 100 en vez de 0). Muestra original, umbralizada y la diferencia (original - umbral con astype(int16)) en tres paneles con subplots(1, 3).
  2. Dibuja el histograma de esa imagen y traza con axvline el umbral que tú elegirías mirando el valle. Justifica tu elección en un comentario.
Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.