PDI+VC · Ejercicio de Programación

EP07_07 — ⚫ Clasificación Real de un Mosaico de Texturas mediante LBP + k-NN

7.18.7 EP07_07 ⚫ Clasificación Real de un Mosaico de Texturas mediante LBP + k-NN

En los ejercicios anteriores, el descriptor LBP (EP07_04) y el clasificador k-NN multiclase (EP07_06) se estudiaron por separado, siempre a partir de datos ya proporcionados en la entrada: vecindades \(3\times3\) aisladas o histogramas previamente extraídos. En este ejercicio de cierre del capítulo, el programa deberá leer una imagen real, en formato PGM ASCII (P2), calcular el descriptor LBP directamente a partir de los píxeles y, a continuación, clasificar cada región mediante el k-NN, reproduciendo, a escala reducida, el flujo completo de un sistema de reconocimiento de texturas. Este enfoque también anticipa la idea de clasificación por mosaico de regiones, relacionada con la segmentación semántica estudiada en un capítulo posterior.

El simulador interactivo del EP07_06 utilizaba solo tres clases (granular, listrada y manchada) representadas por puntos bidimensionales ficticios. En este ejercicio, se añade una cuarta clase, xadrez, y los puntos se sustituyen por histogramas LBP extraídos de una imagen real.

La imagen de entrada es un mosaico formado por una cuadrícula \(G\times G\) de bloques cuadrados de \(S\times S\) píxeles. Cada bloque contiene una muestra de una de las cuatro clases de textura sintética del capítulo: granular, listrada, manchada o xadrez (patrón de tablero con intensidades alternadas). Como en los demás ejercicios del libro, la carga de la imagen se realiza mediante la función didáctica mm.readImg.

Tip¿Por qué un mosaico único y no varias imágenes?

La entrada reúne las \(G \times G\) muestras de textura en un único archivo PGM, solo para simplificar la lectura de los datos y evitar la apertura de varios archivos. Para el algoritmo, esto no altera el procesamiento: cada bloque se trata de forma independiente, como si fuera una imagen aislada. La única excepción es la exclusión del borde (ítem 4 a continuación).

7.18.7.1 📋 Directrices de Implementación

  1. Lectura de las dimensiones de la imagen

    Leer, mediante la entrada estándar, dos líneas que contengan, respectivamente, el número de filas \(L\) y el número de columnas \(C\) del mosaico (ambos múltiplos del tamaño de bloque \(S\), con \(L=C\)).

  2. Carga de la imagen

    Utilizar la función didáctica

    f = mm.readImg(L, C)

    para leer los \(L \times C\) valores de intensidad (tonos de gris, uint8) del mosaico.

  3. Parámetros de la cuadrícula

    Leer el entero \(G\) (número de bloques por lado) y el entero \(S\) (tamaño del lado de cada bloque, en píxeles), satisfaciendo \(L = C = G \times S\).

  4. Cálculo del código LBP por píxel

    Para cada píxel interior de la imagen (es decir, que no esté en el borde global de f — fila o columna \(0\) o \(L-1\)/\(C-1\)), calcule el código LBP con \(P=8\) vecinos y radio \(R=1\), recorriendo los vecinos en sentido horario a partir de la esquina superior izquierda, exactamente como en el EP07_04: [lin-1][col-1], [lin-1][col], [lin-1][col+1], [lin][col+1], [lin+1][col+1], [lin+1][col], [lin+1][col-1], [lin][col-1].

    Los píxeles en el borde global de la imagen no poseen vecindad completa y deben ser ignorados (no contribuyen a ningún histograma). Esto incluye píxeles de borde que caen en el interior de un bloque (la exclusión es siempre respecto al borde de la imagen completa, no al borde de cada bloque).

  5. Histograma LBP uniforme por bloque (10 compartimentos)

    Para cada bloque \((i,j)\) de la cuadrícula (\(i,j = 0,\ldots,G-1\)), acumule, entre sus píxeles válidos (ítem 4), un histograma \(H^{(i,j)}\) de \(10\) compartimentos:

    • Considerando la secuencia circular de bits \(s_0,\ldots,s_7\) del píxel (misma regla de transiciones del EP07_04): si el número de transiciones es \(\le 2\) (patrón uniforme), el píxel contribuye al compartimento \(\operatorname{popcount}(s_0,\ldots,s_7) \in \{0,\ldots,8\}\) (número de bits iguales a 1);
    • En caso contrario (patrón no uniforme), el píxel contribuye al compartimento \(9\).

    Al final, normalice el histograma de cada bloque dividiendo por el número de píxeles válidos contenidos en él, obteniendo \(\hat H^{(i,j)}\), con \(\sum_{b=0}^{9} \hat H^{(i,j)}[b] = 1\).

  6. Prototipos de entrenamiento

    Leer el entero \(Ncl\) (número de clases) seguido de \(Ncl\) nombres de clase (orden que define la matriz de confusión y el desempate de votación, como en el EP07_06); a continuación, leer la cadena \(M\) (métrica: euclidiana o manhattan) y el entero impar \(k\); por último, leer el entero \(N\) (número de prototipos) y, para cada uno, el nombre de la clase seguido de \(10\) valores reales (histograma prototipo ya normalizado).

  7. Clasificación k-NN de cada bloque

    Para cada bloque, calcule la distancia de \(\hat H^{(i,j)}\) a cada uno de los \(N\) prototipos, usando la métrica \(M\) (mismas fórmulas del EP07_06). Seleccione los \(k\) prototipos más cercanos (desempate de distancia por el orden de lectura de los prototipos) y clasifique por la clase mayoritaria (desempate de votación por el orden de las clases del ítem 6).

  8. Etiquetas reales y evaluación

    Leer, en una única línea, los \(G \times G\) nombres de clase reales de cada bloque, en orden de lectura por fila de la cuadrícula (bloque \((0,0)\), \((0,1)\), …, \((0,G-1)\), \((1,0)\), …). Construya la matriz de confusión \(Ncl \times Ncl\) (fila = clase real, columna = clase predicha) y la precisión global.

  9. Salida

    Imprimir, para cada bloque (en el mismo orden de lectura de las etiquetas reales del ítem 8), la clase predicha. A continuación, imprimir la matriz de confusión (una fila por clase real, en el orden del ítem 6). Por último, imprimir la precisión, redondeada a 4 decimales.

7.18.7.2 📌 Restricciones Computacionales

  • Descriptor fijo: \(P=8\), \(R=1\) y \(10\) compartimentos (según el ítem 5) son fijos en este ejercicio — no se leen de la entrada.
  • Exclusión de borde global, no de bloque: un píxel en el límite entre dos bloques, pero en el interior de la imagen, es válido y contribuye normalmente al histograma del bloque al que pertenece.
  • Orden de lectura como criterio de desempate: tanto el desempate de distancia (ítem 7) como el de votación (ítem 7) siguen exactamente las mismas convenciones del EP07_01 y del EP07_06.
  • Prototipos como entrada, no aprendidos: a diferencia del Proyecto Práctico 2, los histogramas de entrenamiento se proporcionan directamente en la entrada; el programa no debe generar texturas sintéticas.

7.18.7.3 🧠 Fundamentación Teórica

Etapa del ejercicio Etapa correspondiente en el capítulo
Lectura de la imagen mediante mm.readImg Adquisición de la imagen en el pipeline de reconocimiento de patrones
Código LBP por píxel (EP07_04) local_binary_pattern(imagen, P=8, R=1, method="uniform")
Histograma de 10 compartimentos por bloque Función descritor_lbp del Proyecto Práctico 2 (bins=10, range=(0, P+2))
Clasificación k-NN con métrica seleccionable (EP07_06) KNeighborsClassifier entrenado sobre X_textura
Matriz de confusión \(Ncl\times Ncl\) y precisión confusion_matrix y accuracy_score sobre yt_teste

Este ejercicio evidencia, con píxeles reales en lugar de valores sintéticos, una limitación discutida en la sección final del capítulo: clases de textura visualmente distintas para un observador humano — como granular y manchada — pueden producir histogramas LBP similares cuando la vecindad considerada es pequeña (\(R=1\)), pues ambas presentan alta frecuencia de patrones no uniformes en la escala de un único píxel. En cambio, la clase xadrez, por poseer bordes regulares y repetitivos, tiende a separarse con mayor facilidad. Se espera que la matriz de confusión producida refleje exactamente ese patrón de confusión parcial.

7.18.7.4 📦 Especificación de Entrada y Salida (VPL)

Entrada:

L
C
[matriz L x C de la imagen]
G S
Ncl nombre_clase_1 ... nombre_clase_Ncl
M k
N
nombre_clase h0 h1 ... h9      (repetida N veces)
etiqueta(0,0) etiqueta(0,1) ... etiqueta(G-1,G-1)

Salida:

  • \(G \times G\) líneas con la clase predicha de cada bloque, en el orden de lectura de la cuadrícula.
  • \(Ncl\) líneas con la matriz de confusión (una fila por clase real, valores separados por espacio).
  • Última línea: Acuracia: <valor>.

7.18.7.5 📌 Ejemplo (verificación manual)

Para comprobar la implementación del descriptor antes de probarla sobre un mosaico completo, considere una imagen \(6\times6\) homogénea, con todos los píxeles de intensidad \(100\), tratada como un único bloque (\(G=1\), \(S=6\)). Como todo píxel interior tiene los 8 vecinos con intensidad igual a la del centro (\(g_p \ge g_c\) en todos los casos), todos los bits \(s_p\) valen 1, el número de transiciones es \(0\) (uniforme) y el compartimento es \(\operatorname{popcount}(11111111)=8\). El histograma del único bloque es, por tanto, 0 0 0 0 0 0 0 0 1 0.

Entrada (resumida) Salida Observación
6
6
[36 valores iguales a 100]
1 6
2 uniforme outra
euclidiana 1
2
uniforme 0 0 0 0 0 0 0 0 1 0
outra 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1
uniforme
uniforme
1 0
0 0
Acuracia: 1.0000
Distancia del bloque al prototipo uniforme es exactamente \(0\); la clase outra no aparece en la etiqueta real, por eso su fila en la matriz de confusión es nula.

7.18.7.6 📌 Archivos de Referencia (.pgm)

Para depuración local, dos mosaicos de prueba en el patrón ASCII P2 están disponibles (anexados a esta entrega; al integrarlos al repositorio del capítulo, guárdelos en all/cap07/dados/EP07/):

  • 📥 Caso 1 — Mosaico simple (Caso1_Mosaico_Simples.pgm): cuadrícula \(2\times2\) de bloques de \(24\times24\) píxeles, una muestra de cada una de las cuatro clases, con bajo ruido — útil para validar la lectura de la imagen y la lógica de clasificación en un escenario controlado.
  • 📥 Caso 2 — Mosaico mixto (Caso2_Mosaico_Misto.pgm): cuadrícula \(3\times3\) de bloques de \(16\times16\) píxeles, con clases repetidas y mayor variabilidad — escenario en el que la confusión entre granular y manchada discutida en la Fundamentación Teórica tiende a manifestarse.

La Figura 7.27 exhibe los dos mosaicos, para inspección visual antes de la implementación.

import os
import urllib.request
import numpy as np

def garantir_e_baixar_arquivo(nome_arquivo):
    diretorio_local = "dados/EP07"
    caminho_local = os.path.join(diretorio_local, nome_arquivo)
    
    # Crear el directorio local si no existe
    if not os.path.exists(diretorio_local):
        os.makedirs(diretorio_local)
        
    # Si el archivo no existe localmente, se descarga del repositorio remoto
    if not os.path.exists(caminho_local):
        url_base = "https://raw.githubusercontent.com/fzampirolli/"
        url_base += "pdi-vc/master/all/cap07/dados/EP07"
        url_arquivo = f"{url_base}/{nome_arquivo}"
        print(f"Descargando {nome_arquivo} desde GitHub...")
        try:
            urllib.request.urlretrieve(url_arquivo, caminho_local)
        except Exception as e:
            raise IOError(f"Erro ao baixar {nome_arquivo} do GitHub. ",
                          "Verifique a conexão ou a URL. Detalhes: {e}")
            
    return caminho_local

def ler_pgm_p2(caminho):
    with open(caminho) as f:
        linhas = [l for l in f.read().split() if l]
    assert linhas[0] == "P2"
    C, L = int(linhas[1]), int(linhas[2])
    maxv = int(linhas[3])
    valores = list(map(int, linhas[4:4 + L * C]))
    return np.array(valores, dtype=np.uint8).reshape(L, C)

# Garantiza la descarga y obtiene la ruta correcta
arq_caso1 = garantir_e_baixar_arquivo("Caso1_Mosaico_Simples.pgm")
arq_caso2 = garantir_e_baixar_arquivo("Caso2_Mosaico_Misto.pgm")

# Lee las matrices PGM
caso1 = ler_pgm_p2(arq_caso1)
caso2 = ler_pgm_p2(arq_caso2)

mm.show(
    [caso1, caso2],
    titles=[
        "Caso 1: Mosaico Sencillo\n(2x2 bloques, 1 muestra/clase)",
        "Caso 2: Mosaico Mixto\n(3x3 bloques, clases repetidas)",
    ],
    cols=2,
    figsize=(8, 4),
)
Figura 7.27: Mosaicos de referencia (formato PGM ASCII) utilizados en el EP07_07. Caso 1: cuadrícula 2x2 con una muestra de cada clase. Caso 2: cuadrícula 3x3 con clases repetidas y mayor variabilidad.
🎮 Simulador EP07_07: Clasificación de Mosaico mediante LBP + k-NN ⚫ pipeline completo

Mosaico 3x3 de bloques 12x12 (L=C=36). LBP (P=8,R=1) calculado píxel a píxel, con exclusión del borde global. Ajusta k y la métrica y observa la clasificación de cada bloque frente a 8 prototipos (2 por clase).

⚠️ Texturas sintéticas generadas por código, no los archivos .pgm reales del EP07_07. Usa este simulador para entender el flujo del algoritmo, no como referencia de dificultad entre las clases.
1
Figura 7.28: Simulador EP07_07: Clasificación de un Mosaico de Texturas vía LBP + k-NN
%%writefile EP07_07.py
# Código Python
Overwriting EP07_07.py
TestSuite("EP07_07.py").run()
✔️ EP07_07.cases ya existe en casos/
📋 2 caso(s) cargado(s) de casos/EP07_07.cases

🔍 Probando Python: EP07_07.py
⚠️ EP07_07.py: archivo vacío (menos de 3 líneas). Pruebas omitidas.