PDI+VC · Exercício de Programação

EP07_07 — ⚫ Classificação Real de um Mosaico de Texturas via LBP + k-NN

7.18.7 EP07_07 ⚫ Classificação Real de um Mosaico de Texturas via LBP + k-NN

Nos exercícios anteriores, o descritor LBP (EP07_04) e o classificador k-NN multiclasse (EP07_06) foram estudados separadamente, sempre a partir de dados já fornecidos na entrada — vizinhanças \(3\times3\) isoladas ou histogramas previamente extraídos. Neste exercício de encerramento do capítulo, o programa deverá ler uma imagem real, no formato PGM ASCII (P2), calcular o descritor LBP diretamente a partir dos pixels e, em seguida, classificar cada região por meio do k-NN, reproduzindo, em escala reduzida, o fluxo completo de um sistema de reconhecimento de texturas. Essa abordagem também antecipa a ideia de classificação por mosaico de regiões, relacionada à segmentação semântica estudada em um capítulo posterior.

O simulador interativo do EP07_06 utilizava apenas três classes (granular, listrada e manchada) representadas por pontos bidimensionais fictícios. Neste exercício, acrescenta-se uma quarta classe, xadrez, e os pontos são substituídos por histogramas LBP extraídos de uma imagem real.

A imagem de entrada é um mosaico formado por uma grade \(G\times G\) de blocos quadrados de \(S\times S\) pixels. Cada bloco contém uma amostra de uma das quatro classes de textura sintética do capítulo: granular, listrada, manchada ou xadrez (padrão de tabuleiro com intensidades alternadas). Como nos demais exercícios do livro, o carregamento da imagem é realizado pela função didática mm.readImg.

DicaPor que um mosaico único, e não várias imagens?

A entrada reúne as \(G \times G\) amostras de textura em um único arquivo PGM, apenas para simplificar a leitura dos dados e evitar a abertura de vários arquivos. Para o algoritmo, isso não altera o processamento: cada bloco é tratado de forma independente, como se fosse uma imagem isolada. A única exceção é a exclusão da borda (item 4 a seguir).

7.18.7.1 📋 Diretrizes de Implementação

  1. Leitura das dimensões da imagem

    Ler, por meio da entrada padrão, duas linhas contendo, respectivamente, o número de linhas \(L\) e o número de colunas \(C\) do mosaico (ambos múltiplos do tamanho de bloco \(S\), com \(L=C\)).

  2. Carregamento da imagem

    Utilizar a função didática

    f = mm.readImg(L, C)

    para ler os \(L \times C\) valores de intensidade (tons de cinza, uint8) do mosaico.

  3. Parâmetros da grade

    Ler o inteiro \(G\) (número de blocos por lado) e o inteiro \(S\) (tamanho do lado de cada bloco, em pixels), satisfazendo \(L = C = G \times S\).

  4. Cálculo do código LBP por pixel

    Para cada pixel interior da imagem (ou seja, que não esteja na borda global de f — linha ou coluna \(0\) ou \(L-1\)/\(C-1\)), calcule o código LBP com \(P=8\) vizinhos e raio \(R=1\), percorrendo os vizinhos em sentido horário a partir do canto superior esquerdo, exatamente como no 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].

    Pixels na borda global da imagem não possuem vizinhança completa e devem ser ignorados (não contribuem para nenhum histograma). Isso inclui pixels de borda que caem no interior de um bloco (a exclusão é sempre em relação à borda da imagem inteira, não à borda de cada bloco).

  5. Histograma LBP uniforme por bloco (10 compartimentos)

    Para cada bloco \((i,j)\) da grade (\(i,j = 0,\ldots,G-1\)), acumule, entre seus pixels válidos (item 4), um histograma \(H^{(i,j)}\) de \(10\) compartimentos:

    • Considerando a sequência circular de bits \(s_0,\ldots,s_7\) do pixel (mesma regra de transições do EP07_04): se o número de transições for \(\le 2\) (padrão uniforme), o pixel contribui para o compartimento \(\operatorname{popcount}(s_0,\ldots,s_7) \in \{0,\ldots,8\}\) (número de bits iguais a 1);
    • Caso contrário (padrão não uniforme), o pixel contribui para o compartimento \(9\).

    Ao final, normalize o histograma de cada bloco dividindo pelo número de pixels válidos nele contidos, obtendo \(\hat H^{(i,j)}\), com \(\sum_{b=0}^{9} \hat H^{(i,j)}[b] = 1\).

  6. Protótipos de treinamento

    Ler o inteiro \(Ncl\) (número de classes) seguido de \(Ncl\) nomes de classe (ordem que define a matriz de confusão e o desempate de votação, como no EP07_06); em seguida, ler a string \(M\) (métrica: euclidiana ou manhattan) e o inteiro ímpar \(k\); por fim, ler o inteiro \(N\) (número de protótipos) e, para cada um, o nome da classe seguido de \(10\) valores reais (histograma protótipo já normalizado).

  7. Classificação k-NN de cada bloco

    Para cada bloco, calcule a distância de \(\hat H^{(i,j)}\) a cada um dos \(N\) protótipos, usando a métrica \(M\) (mesmas fórmulas do EP07_06). Selecione os \(k\) protótipos mais próximos (desempate de distância pela ordem de leitura dos protótipos) e classifique pela classe majoritária (desempate de votação pela ordem das classes do item 6).

  8. Rótulos reais e avaliação

    Ler, em uma única linha, os \(G \times G\) nomes de classe reais de cada bloco, em ordem de leitura por linha da grade (bloco \((0,0)\), \((0,1)\), …, \((0,G-1)\), \((1,0)\), …). Construa a matriz de confusão \(Ncl \times Ncl\) (linha = classe real, coluna = classe prevista) e a acurácia global.

  9. Saída

    Imprimir, para cada bloco (na mesma ordem de leitura dos rótulos reais do item 8), a classe prevista. Em seguida, imprimir a matriz de confusão (uma linha por classe real, na ordem do item 6). Por fim, imprimir a acurácia, arredondada a 4 casas decimais.

7.18.7.2 📌 Restrições Computacionais

  • Descritor fixo: \(P=8\), \(R=1\) e \(10\) compartimentos (conforme item 5) são fixos neste exercício — não são lidos da entrada.
  • Exclusão de borda global, não de bloco: um pixel no limite entre dois blocos, mas no interior da imagem, é válido e contribui normalmente para o histograma do bloco ao qual pertence.
  • Ordem de leitura como critério de desempate: tanto o desempate de distância (item 7) quanto o de votação (item 7) seguem exatamente as mesmas convenções do EP07_01 e do EP07_06.
  • Protótipos como entrada, não aprendidos: diferentemente do Projeto Prático 2, os histogramas de treinamento são fornecidos diretamente na entrada; o programa não deve gerar texturas sintéticas.

7.18.7.3 🧠 Fundamentação Teórica

Etapa do exercício Etapa correspondente no capítulo
Leitura da imagem via mm.readImg Aquisição da imagem no pipeline de reconhecimento de padrões
Código LBP por pixel (EP07_04) local_binary_pattern(imagem, P=8, R=1, method="uniform")
Histograma de 10 compartimentos por bloco Função descritor_lbp do Projeto Prático 2 (bins=10, range=(0, P+2))
Classificação k-NN com métrica selecionável (EP07_06) KNeighborsClassifier treinado sobre X_textura
Matriz de confusão \(Ncl\times Ncl\) e acurácia confusion_matrix e accuracy_score sobre yt_teste

Este exercício evidencia, com pixels reais em vez de valores sintéticos, uma limitação discutida na seção final do capítulo: classes de textura visualmente distintas para um observador humano — como granular e manchada — podem produzir histogramas LBP semelhantes quando a vizinhança considerada é pequena (\(R=1\)), pois ambas apresentam alta frequência de padrões não uniformes na escala de um único pixel. Já a classe xadrez, por possuir bordas regulares e repetitivas, tende a ser separada com maior facilidade. Espera-se que a matriz de confusão produzida reflita exatamente esse padrão de confusão parcial.

7.18.7.4 📦 Especificação de Entrada e Saída (VPL)

Entrada:

L
C
[matriz L x C da imagem]
G S
Ncl nome_classe_1 ... nome_classe_Ncl
M k
N
nome_classe h0 h1 ... h9      (repetida N vezes)
rotulo(0,0) rotulo(0,1) ... rotulo(G-1,G-1)

Saída:

  • \(G \times G\) linhas com a classe prevista de cada bloco, na ordem de leitura da grade.
  • \(Ncl\) linhas com a matriz de confusão (uma linha por classe real, valores separados por espaço).
  • Última linha: Acuracia: <valor>.

7.18.7.5 📌 Exemplo (verificação manual)

Para conferir a implementação do descritor antes de testá-la sobre um mosaico completo, considere uma imagem \(6\times6\) homogênea, com todos os pixels de intensidade \(100\), tratada como um único bloco (\(G=1\), \(S=6\)). Como todo pixel interior tem os 8 vizinhos com intensidade igual à do centro (\(g_p \ge g_c\) em todos os casos), todos os bits \(s_p\) valem 1, o número de transições é \(0\) (uniforme) e o compartimento é \(\operatorname{popcount}(11111111)=8\). O histograma do único bloco é, portanto, 0 0 0 0 0 0 0 0 1 0.

Entrada (resumida) Saída Observação
6
6
[36 valores iguais 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
Distância do bloco ao protótipo uniforme é exatamente \(0\); a classe outra não aparece no rótulo real, por isso sua linha na matriz de confusão é nula.

7.18.7.6 📌 Arquivos de Referência (.pgm)

Para depuração local, dois mosaicos de teste no padrão ASCII P2 são disponibilizados (anexados a esta entrega; ao integrá-los ao repositório do capítulo, salve-os em all/cap07/dados/EP07/):

  • 📥 Caso 1 — Mosaico simples (Caso1_Mosaico_Simples.pgm): grade \(2\times2\) de blocos de \(24\times24\) pixels, uma amostra de cada uma das quatro classes, com baixo ruído — útil para validar a leitura da imagem e a lógica de classificação em um cenário controlado.
  • 📥 Caso 2 — Mosaico misto (Caso2_Mosaico_Misto.pgm): grade \(3\times3\) de blocos de \(16\times16\) pixels, com classes repetidas e maior variabilidade — cenário em que a confusão entre granular e manchada discutida na Fundamentação Teórica tende a se manifestar.

A Figura 7.27 exibe os dois mosaicos, para inspeção visual antes da implementação.

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)
    
    # Criar o diretório local se ele não existir
    if not os.path.exists(diretorio_local):
        os.makedirs(diretorio_local)
        
    # Se o arquivo não existir localmente, baixa do repositório 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"Baixando {nome_arquivo} do 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)

# Garante o download e obtém o caminho correto
arq_caso1 = garantir_e_baixar_arquivo("Caso1_Mosaico_Simples.pgm")
arq_caso2 = garantir_e_baixar_arquivo("Caso2_Mosaico_Misto.pgm")

# Lê as matrizes PGM
caso1 = ler_pgm_p2(arq_caso1)
caso2 = ler_pgm_p2(arq_caso2)

mm.show(
    [caso1, caso2],
    titles=[
        "Caso 1: Mosaico Simples\n(2x2 blocos, 1 amostra/classe)",
        "Caso 2: Mosaico Misto\n(3x3 blocos, classes repetidas)",
    ],
    cols=2,
    figsize=(8, 4),
)
Figura 7.27: Mosaicos de referência (formato PGM ASCII) utilizados no EP07_07. Caso 1: grade 2x2 com uma amostra de cada classe. Caso 2: grade 3x3 com classes repetidas e maior variabilidade.
🎮 Simulador EP07_07: Classificação de Mosaico via LBP + k-NN ⚫ pipeline completo

Mosaico 3x3 de blocos 12x12 (L=C=36). LBP (P=8,R=1) calculado pixel a pixel, com exclusão da borda global. Ajuste k e a métrica e observe a classificação de cada bloco frente a 8 protótipos (2 por classe).

⚠️ Texturas sintéticas geradas por código, não os arquivos .pgm reais do EP07_07. Use este simulador para entender o fluxo do algoritmo, não como referência de dificuldade entre as classes.
1
Figura 7.28: Simulador EP07_07: Classificação de um Mosaico de Texturas via LBP + k-NN
%%writefile EP07_07.py
# Código Python
Writing EP07_07.py
TestSuite("EP07_07.py").run()
✔️ EP07_07.cases já existe em casos/
📋 2 caso(s) carregado(s) de casos/EP07_07.cases

🔍 Testando Python: EP07_07.py
⚠️ EP07_07.py: Arquivo sem conteúdo (menos de 3 linhas). Testes ignorados.