TNI+VO · Exercice de Programmation

EP07_07 — ⚫ Classification réelle d’une mosaïque de textures via LBP + k-NN

7.18.7 EP07_07 ⚫ Classification réelle d’une mosaïque de textures via LBP + k-NN

Dans les exercices précédents, le descripteur LBP (EP07_04) et le classifieur k-NN multiclasse (EP07_06) ont été étudiés séparément, toujours à partir de données déjà fournies en entrée — voisinages \(3\times3\) isolés ou histogrammes préalablement extraits. Dans cet exercice de clôture du chapitre, le programme devra lire une image réelle, au format PGM ASCII (P2), calculer le descripteur LBP directement à partir des pixels, puis classer chaque région au moyen du k-NN, reproduisant ainsi, à échelle réduite, le flux complet d’un système de reconnaissance de textures. Cette approche anticipe également l’idée de classification par mosaïque de régions, liée à la segmentation sémantique étudiée dans un chapitre ultérieur.

Le simulateur interactif de l’EP07_06 n’utilisait que trois classes (granular, listrada et manchada) représentées par des points bidimensionnels fictifs. Dans cet exercice, une quatrième classe, xadrez, est ajoutée, et les points sont remplacés par des histogrammes LBP extraits d’une image réelle.

L’image d’entrée est une mosaïque formée d’une grille \(G\times G\) de blocs carrés de \(S\times S\) pixels. Chaque bloc contient un échantillon de l’une des quatre classes de texture synthétique du chapitre : granular, listrada, manchada ou xadrez (motif en damier avec intensités alternées). Comme dans les autres exercices du livre, le chargement de l’image est réalisé par la fonction didactique mm.readImg.

AstucePourquoi une mosaïque unique, et non plusieurs images ?

L’entrée regroupe les \(G \times G\) échantillons de texture dans un seul fichier PGM, uniquement pour simplifier la lecture des données et éviter l’ouverture de plusieurs fichiers. Pour l’algorithme, cela ne modifie pas le traitement : chaque bloc est traité de manière indépendante, comme s’il s’agissait d’une image isolée. La seule exception est l’exclusion de la bordure (point 4 ci-dessous).

7.18.7.1 📋 Directives d’implémentation

  1. Lecture des dimensions de l’image

    Lire, via l’entrée standard, deux lignes contenant respectivement le nombre de lignes \(L\) et le nombre de colonnes \(C\) de la mosaïque (tous deux multiples de la taille de bloc \(S\), avec \(L=C\)).

  2. Chargement de l’image

    Utiliser la fonction didactique

    f = mm.readImg(L, C)

    pour lire les \(L \times C\) valeurs d’intensité (niveaux de gris, uint8) de la mosaïque.

  3. Paramètres de la grille

    Lire l’entier \(G\) (nombre de blocs par côté) et l’entier \(S\) (taille du côté de chaque bloc, en pixels), satisfaisant \(L = C = G \times S\).

  4. Calcul du code LBP par pixel

    Pour chaque pixel intérieur de l’image (c’est-à-dire ne se trouvant pas sur la bordure globale de f — ligne ou colonne \(0\) ou \(L-1\)/\(C-1\)), calculer le code LBP avec \(P=8\) voisins et rayon \(R=1\), en parcourant les voisins dans le sens horaire à partir du coin supérieur gauche, exactement comme dans l’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].

    Les pixels sur la bordure globale de l’image n’ont pas de voisinage complet et doivent être ignorés (ils ne contribuent à aucun histogramme). Cela inclut les pixels de bordure qui tombent à l’intérieur d’un bloc (l’exclusion est toujours relative à la bordure de l’image entière, et non à celle de chaque bloc).

  5. Histogramme LBP uniforme par bloc (10 compartiments)

    Pour chaque bloc \((i,j)\) de la grille (\(i,j = 0,\ldots,G-1\)), accumuler, parmi ses pixels valides (point 4), un histogramme \(H^{(i,j)}\) de \(10\) compartiments :

    • En considérant la séquence circulaire de bits \(s_0,\ldots,s_7\) du pixel (même règle de transitions que l’EP07_04) : si le nombre de transitions est \(\le 2\) (motif uniforme), le pixel contribue au compartiment \(\operatorname{popcount}(s_0,\ldots,s_7) \in \{0,\ldots,8\}\) (nombre de bits égaux à 1) ;
    • Sinon (motif non uniforme), le pixel contribue au compartiment \(9\).

    À la fin, normaliser l’histogramme de chaque bloc en le divisant par le nombre de pixels valides qu’il contient, obtenant \(\hat H^{(i,j)}\), avec \(\sum_{b=0}^{9} \hat H^{(i,j)}[b] = 1\).

  6. Prototypes d’apprentissage

    Lire l’entier \(Ncl\) (nombre de classes) suivi de \(Ncl\) noms de classes (ordre définissant la matrice de confusion et le départage des votes, comme dans l’EP07_06) ; ensuite, lire la chaîne \(M\) (métrique : euclidiana ou manhattan) et l’entier impair \(k\) ; enfin, lire l’entier \(N\) (nombre de prototypes) et, pour chacun, le nom de la classe suivi de \(10\) valeurs réelles (histogramme prototype déjà normalisé).

  7. Classification k-NN de chaque bloc

    Pour chaque bloc, calculer la distance de \(\hat H^{(i,j)}\) à chacun des \(N\) prototypes, en utilisant la métrique \(M\) (mêmes formules que l’EP07_06). Sélectionner les \(k\) prototypes les plus proches (départage des distances par l’ordre de lecture des prototypes) et classer selon la classe majoritaire (départage des votes par l’ordre des classes du point 6).

  8. Étiquettes réelles et évaluation

    Lire, sur une seule ligne, les \(G \times G\) noms de classes réels de chaque bloc, dans l’ordre de lecture par ligne de la grille (bloc \((0,0)\), \((0,1)\), …, \((0,G-1)\), \((1,0)\), …). Construire la matrice de confusion \(Ncl \times Ncl\) (ligne = classe réelle, colonne = classe prédite) et l’exactitude globale.

  9. Sortie

    Imprimer, pour chaque bloc (dans le même ordre de lecture que les étiquettes réelles du point 8), la classe prédite. Ensuite, imprimer la matrice de confusion (une ligne par classe réelle, dans l’ordre du point 6). Enfin, imprimer l’exactitude, arrondie à 4 décimales.

7.18.7.2 📌 Contraintes informatiques

  • Descripteur fixe : \(P=8\), \(R=1\) et \(10\) compartiments (selon le point 5) sont fixes dans cet exercice — ils ne sont pas lus depuis l’entrée.
  • Exclusion de bordure globale, non de bloc : un pixel à la limite entre deux blocs, mais à l’intérieur de l’image, est valide et contribue normalement à l’histogramme du bloc auquel il appartient.
  • Ordre de lecture comme critère de départage : tant le départage des distances (point 7) que celui des votes (point 7) suivent exactement les mêmes conventions que l’EP07_01 et l’EP07_06.
  • Prototypes en entrée, non appris : contrairement au Projet Pratique 2, les histogrammes d’apprentissage sont fournis directement en entrée ; le programme ne doit pas générer de textures synthétiques.

7.18.7.3 🧠 Fondements théoriques

Étape de l’exercice Étape correspondante dans le chapitre
Lecture de l’image via mm.readImg Acquisition de l’image dans le pipeline de reconnaissance de formes
Code LBP par pixel (EP07_04) local_binary_pattern(image, P=8, R=1, method="uniform")
Histogramme de 10 compartiments par bloc Fonction descritor_lbp du Projet Pratique 2 (bins=10, range=(0, P+2))
Classification k-NN avec métrique sélectionnable (EP07_06) KNeighborsClassifier entraîné sur X_textura
Matrice de confusion \(Ncl\times Ncl\) et exactitude confusion_matrix et accuracy_score sur yt_teste

Cet exercice met en évidence, avec des pixels réels plutôt que des valeurs synthétiques, une limitation discutée dans la section finale du chapitre : des classes de texture visuellement distinctes pour un observateur humain — comme granular et manchada — peuvent produire des histogrammes LBP similaires lorsque le voisinage considéré est petit (\(R=1\)), car toutes deux présentent une fréquence élevée de motifs non uniformes à l’échelle d’un seul pixel. La classe xadrez, quant à elle, possédant des bordures régulières et répétitives, tend à être séparée plus facilement. On s’attend à ce que la matrice de confusion produite reflète exactement ce schéma de confusion partielle.

7.18.7.4 📦 Spécification d’entrée et de sortie (VPL)

Entrée :

L
C
[matrice L x C de l’image]
G S
Ncl nom_classe_1 ... nom_classe_Ncl
M k
N
nom_classe h0 h1 ... h9      (répétée N fois)
etiquette(0,0) etiquette(0,1) ... etiquette(G-1,G-1)

Sortie :

  • \(G \times G\) lignes avec la classe prédite de chaque bloc, dans l’ordre de lecture de la grille.
  • \(Ncl\) lignes avec la matrice de confusion (une ligne par classe réelle, valeurs séparées par des espaces).
  • Dernière ligne : Acuracia: <valeur>.

7.18.7.5 📌 Exemple (vérification manuelle)

Pour vérifier l’implémentation du descripteur avant de la tester sur une mosaïque complète, considérons une image \(6\times6\) homogène, avec tous les pixels d’intensité \(100\), traitée comme un seul bloc (\(G=1\), \(S=6\)). Comme tout pixel intérieur a ses 8 voisins d’intensité égale à celle du centre (\(g_p \ge g_c\) dans tous les cas), tous les bits \(s_p\) valent 1, le nombre de transitions est \(0\) (uniforme) et le compartiment est \(\operatorname{popcount}(11111111)=8\). L’histogramme de l’unique bloc est donc 0 0 0 0 0 0 0 0 1 0.

Entrée (résumée) Sortie Observation
6
6
[36 valeurs égales à 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
La distance du bloc au prototype uniforme est exactement \(0\) ; la classe outra n’apparaît pas dans l’étiquette réelle, c’est pourquoi sa ligne dans la matrice de confusion est nulle.

7.18.7.6 📌 Fichiers de référence (.pgm)

Pour le débogage local, deux mosaïques de test au format ASCII P2 sont fournies (annexées à cette livraison ; lors de leur intégration au référentiel du chapitre, enregistrez-les dans all/cap07/dados/EP07/) :

  • 📥 Cas 1 — Mosaïque simple (Caso1_Mosaico_Simples.pgm) : grille \(2\times2\) de blocs de \(24\times24\) pixels, un échantillon de chacune des quatre classes, avec faible bruit — utile pour valider la lecture de l’image et la logique de classification dans un scénario contrôlé.
  • 📥 Cas 2 — Mosaïque mixte (Caso2_Mosaico_Misto.pgm) : grille \(3\times3\) de blocs de \(16\times16\) pixels, avec classes répétées et plus grande variabilité — scénario dans lequel la confusion entre granular et manchada discutée dans les Fondements théoriques tend à se manifester.

La Figure 7.27 affiche les deux mosaïques, pour une inspection visuelle avant l’implémentation.

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)
    
    # Créer le répertoire local s'il n'existe pas
    if not os.path.exists(diretorio_local):
        os.makedirs(diretorio_local)
        
    # Si le fichier n'existe pas localement, le télécharger depuis le dépôt distant
    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"Téléchargement {nome_arquivo} depuis 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)

# Garantit le téléchargement et obtient le chemin correct
arq_caso1 = garantir_e_baixar_arquivo("Caso1_Mosaico_Simples.pgm")
arq_caso2 = garantir_e_baixar_arquivo("Caso2_Mosaico_Misto.pgm")

# Lit les matrices PGM
caso1 = ler_pgm_p2(arq_caso1)
caso2 = ler_pgm_p2(arq_caso2)

mm.show(
    [caso1, caso2],
    titles=[
        "Cas 1 : Mosaïque Simple\n(blocs 2x2, 1 échantillon/classe)",
        "Cas 2 : Mosaïque Mixte\n(blocs 3x3, classes répétées)",
    ],
    cols=2,
    figsize=(8, 4),
)
Figure 7.27: Mosaïques de référence (format PGM ASCII) utilisées dans l’EP07_07. Cas 1 : grille 2x2 avec un échantillon de chaque classe. Cas 2 : grille 3x3 avec classes répétées et plus grande variabilité.
🎮 Simulateur EP07_07 : Classification de mosaïque via LBP + k-NN ⚫ pipeline complet

Mosaïque 3x3 de blocs 12x12 (L=C=36). LBP (P=8,R=1) calculé pixel par pixel, avec exclusion de la bordure globale. Ajustez k et la métrique et observez la classification de chaque bloc face à 8 prototypes (2 par classe).

⚠️ Textures synthétiques générées par code, pas les fichiers .pgm réels de l'EP07_07. Utilisez ce simulateur pour comprendre le flux de l'algorithme, pas comme référence de difficulté entre les classes.
1
Figure 7.28: Simulateur EP07_07 : Classification d’une mosaïque de textures via LBP + k-NN
%%writefile EP07_07.py
# Code Python
Overwriting EP07_07.py
TestSuite("EP07_07.py").run()
✔️ EP07_07.cases existe déjà dans casos/
📋 2 cas chargé(s) depuis casos/EP07_07.cases

🔍 Test de Python : EP07_07.py
⚠️ EP07_07.py : fichier vide (moins de 3 lignes). Tests ignorés.