EP07_07 — ⚫ Real Classification of a Texture Mosaic via LBP + k-NN
7.18.7 EP07_07 ⚫ Real Classification of a Texture Mosaic via LBP + k-NN
In the previous exercises, the LBP descriptor (EP07_04) and the multiclass k-NN classifier (EP07_06) were studied separately, always based on data already provided as input — isolated \(3\times3\) neighborhoods or previously extracted histograms. In this final exercise of the chapter, the program should read a real image, in ASCII PGM (P2) format, compute the LBP descriptor directly from the pixels, and then classify each region using k-NN, reproducing, on a reduced scale, the complete flow of a texture recognition system. This approach also anticipates the idea of region mosaic classification, related to semantic segmentation studied in a later chapter.
The interactive simulator from EP07_06 used only three classes (granular, striped, and blotchy) represented by fictitious two-dimensional points. In this exercise, a fourth class, checkered, is added, and the points are replaced by LBP histograms extracted from a real image.
The input image is a mosaic formed by a \(G\times G\) grid of square blocks of \(S\times S\) pixels. Each block contains a sample of one of the four synthetic texture classes from the chapter: granular, striped, blotchy, or checkered (checkerboard pattern with alternating intensities). As in the other exercises in the book, image loading is performed by the didactic function mm.readImg.
TipWhy a single mosaic, rather than several images?
The input gathers the \(G \times G\) texture samples into a single PGM file, merely to simplify data reading and avoid opening multiple files. For the algorithm, this does not alter the processing: each block is treated independently, as if it were an isolated image. The only exception is the border exclusion (item 4 below).
7.18.7.1 📋 Implementation Guidelines
Reading image dimensions
Read, from standard input, two lines containing, respectively, the number of rows \(L\) and the number of columns \(C\) of the mosaic (both multiples of the block size \(S\), with \(L=C\)).
Image loading
Use the didactic function
f = mm.readImg(L, C)
to read the \(L \times C\) intensity values (grayscale, uint8) of the mosaic.
Grid parameters
Read the integer \(G\) (number of blocks per side) and the integer \(S\) (side length of each block, in pixels), satisfying \(L = C = G \times S\).
LBP code computation per pixel
For each interior pixel of the image (i.e., one not on the global border of f — row or column \(0\) or \(L-1\)/\(C-1\)), compute the LBP code with \(P=8\) neighbors and radius \(R=1\), traversing the neighbors clockwise starting from the upper-left corner, exactly as in 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 on the global border of the image do not have a complete neighborhood and should be ignored (they do not contribute to any histogram). This includes border pixels that fall within the interior of a block (exclusion is always relative to the border of the entire image, not to the border of each block).
Uniform LBP histogram per block (10 bins)
For each block \((i,j)\) of the grid (\(i,j = 0,\ldots,G-1\)), accumulate, among its valid pixels (item 4), a histogram \(H^{(i,j)}\) of \(10\) bins:
Considering the circular bit sequence \(s_0,\ldots,s_7\) of the pixel (same transition rule as in EP07_04): if the number of transitions is \(\le 2\) (uniform pattern), the pixel contributes to bin \(\operatorname{popcount}(s_0,\ldots,s_7) \in \{0,\ldots,8\}\) (number of bits equal to 1);
Otherwise (non-uniform pattern), the pixel contributes to bin \(9\).
At the end, normalize each block’s histogram by dividing by the number of valid pixels it contains, obtaining \(\hat H^{(i,j)}\), with \(\sum_{b=0}^{9} \hat H^{(i,j)}[b] = 1\).
Training prototypes
Read the integer \(Ncl\) (number of classes) followed by \(Ncl\) class names (the order defining the confusion matrix and voting tie-break, as in EP07_06); then read the string\(M\) (metric: euclidean or manhattan) and the odd integer \(k\); finally, read the integer \(N\) (number of prototypes) and, for each one, the class name followed by \(10\) real values (already normalized prototype histogram).
k-NN classification of each block
For each block, compute the distance from \(\hat H^{(i,j)}\) to each of the \(N\) prototypes, using metric \(M\) (same formulas as in EP07_06). Select the \(k\) closest prototypes (distance tie-break by prototype reading order) and classify by majority class (voting tie-break by the class order from item 6).
True labels and evaluation
Read, on a single line, the \(G \times G\)true class names of each block, in row-major order of the grid (block \((0,0)\), \((0,1)\), …, \((0,G-1)\), \((1,0)\), …). Build the \(Ncl \times Ncl\) confusion matrix (row = true class, column = predicted class) and the overall accuracy.
Output
Print, for each block (in the same reading order of the true labels from item 8), the predicted class. Then print the confusion matrix (one row per true class, in the order of item 6). Finally, print the accuracy, rounded to 4 decimal places.
7.18.7.2 📌 Computational Constraints
Fixed descriptor:\(P=8\), \(R=1\), and \(10\) bins (per item 5) are fixed in this exercise — they are not read from the input.
Global border exclusion, not per-block: a pixel at the boundary between two blocks, but within the image interior, is valid and contributes normally to the histogram of the block to which it belongs.
Reading order as tie-break criterion: both distance tie-breaking (item 7) and voting tie-breaking (item 7) follow exactly the same conventions as EP07_01 and EP07_06.
Prototypes as input, not learned: unlike Practical Project 2, the training histograms are provided directly as input; the program must not generate synthetic textures.
7.18.7.3 🧠 Theoretical Foundation
Exercise step
Corresponding step in the chapter
Image reading via mm.readImg
Image acquisition in the pattern recognition pipeline
descritor_lbp function from Practical Project 2 (bins=10, range=(0, P+2))
k-NN classification with selectable metric (EP07_06)
KNeighborsClassifier trained on X_textura
\(Ncl\times Ncl\) confusion matrix and accuracy
confusion_matrix and accuracy_score on yt_teste
This exercise highlights, with real pixels rather than synthetic values, a limitation discussed in the final section of the chapter: texture classes visually distinct to a human observer — such as granular and blotchy — may produce similar LBP histograms when the considered neighborhood is small (\(R=1\)), since both exhibit a high frequency of non-uniform patterns at the scale of a single pixel. The checkered class, on the other hand, due to its regular and repetitive edges, tends to be separated more easily. The confusion matrix produced is expected to reflect precisely this pattern of partial confusion.
7.18.7.4 📦 Input and Output Specification (VPL)
Input:
L
C
[L x C image matrix]
G S
Ncl class_name_1 ... class_name_Ncl
M k
N
class_name h0 h1 ... h9 (repeated N times)
label(0,0) label(0,1) ... label(G-1,G-1)
Output:
\(G \times G\) lines with the predicted class of each block, in grid reading order.
\(Ncl\) lines with the confusion matrix (one line per true class, values separated by spaces).
Last line: Accuracy: <value>.
7.18.7.5 📌 Example (manual verification)
To check the descriptor implementation before testing it on a complete mosaic, consider a \(6\times6\)homogeneous image, with all pixels at intensity \(100\), treated as a single block (\(G=1\), \(S=6\)). Since every interior pixel has its 8 neighbors with intensity equal to the center (\(g_p \ge g_c\) in all cases), all bits \(s_p\) are 1, the number of transitions is \(0\) (uniform), and the bin is \(\operatorname{popcount}(11111111)=8\). The histogram of the single block is therefore 0 0 0 0 0 0 0 0 1 0.
Distance from the block to the uniform prototype is exactly \(0\); the other class does not appear in the true label, so its row in the confusion matrix is null.
7.18.7.6 📌 Reference Files (.pgm)
For local debugging, two test mosaics in ASCII P2 format are provided (attached to this assignment; when integrating them into the chapter repository, save them in all/cap07/data/EP07/):
📥 Case 1 — Simple mosaic (Caso1_Mosaico_Simples.pgm): \(2\times2\) grid of \(24\times24\)-pixel blocks, one sample of each of the four classes, with low noise — useful for validating image reading and classification logic in a controlled scenario.
📥 Case 2 — Mixed mosaic (Caso2_Mosaico_Misto.pgm): \(3\times3\) grid of \(16\times16\)-pixel blocks, with repeated classes and greater variability — a scenario in which the confusion between granular and blotchy discussed in the Theoretical Foundation tends to manifest.
Figure 7.27 displays both mosaics for visual inspection before implementation.
import osimport urllib.requestimport numpy as npdef garantir_e_baixar_arquivo(nome_arquivo): diretorio_local ="dados/EP07" caminho_local = os.path.join(diretorio_local, nome_arquivo)# Create the local directory if it does not existifnot os.path.exists(diretorio_local): os.makedirs(diretorio_local)# If the file does not exist locally, download from the remote repositoryifnot 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"Downloading {nome_arquivo} from GitHub...")try: urllib.request.urlretrieve(url_arquivo, caminho_local)exceptExceptionas e:raiseIOError(f"Erro ao baixar {nome_arquivo} do GitHub. ","Verifique a conexão ou a URL. Detalhes: {e}")return caminho_localdef ler_pgm_p2(caminho):withopen(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)# Ensures the download and obtains the correct patharq_caso1 = garantir_e_baixar_arquivo("Caso1_Mosaico_Simples.pgm")arq_caso2 = garantir_e_baixar_arquivo("Caso2_Mosaico_Misto.pgm")# Reads the PGM matricescaso1 = ler_pgm_p2(arq_caso1)caso2 = ler_pgm_p2(arq_caso2)mm.show( [caso1, caso2], titles=["Case 1: Simple Mosaic\n(2x2 blocks, 1 sample/class)","Case 2: Mixed Mosaic\n(3x3 blocks, repeated classes)", ], cols=2, figsize=(8, 4),)
Figure 7.27: Reference mosaics (ASCII PGM format) used in EP07_07. Case 1: 2x2 grid with one sample of each class. Case 2: 3x3 grid with repeated classes and greater variability.
🎮 EP07_07 Simulator: Mosaic Classification via LBP + k-NN⚫ full pipeline
3x3 mosaic of 12x12 blocks (L=C=36). LBP (P=8,R=1) computed pixel by pixel, with global border exclusion. Adjust k and the metric and observe the classification of each block against 8 prototypes (2 per class).
⚠️ Synthetic textures generated by code, not the real .pgm files from EP07_07. Use this simulator to understand the algorithm flow, not as a difficulty reference among the classes.
1
Figure 7.28: Simulator EP07_07: Classification of a Texture Mosaic via LBP + k-NN
%%writefile EP07_07.py# Python code
Overwriting EP07_07.py
TestSuite("EP07_07.py").run()
✔️ EP07_07.cases already exists in casos/
📋 2 case(s) loaded from casos/EP07_07.cases
🔍 Testing Python: EP07_07.py
⚠️ EP07_07.py: Empty file (fewer than 3 lines). Tests skipped.