Questo capitolo apre la Parte 1 del libro, dedicata ai fondamenti dell’Elaborazione Digitale delle Immagini (EDI). Vengono presentati la rappresentazione matematica delle immagini digitali e i principali metodi per la loro manipolazione.
Si utilizza il linguaggio C++ e la libreria morph.hpp, una porta didattica della morph.py (ZAMPIROLLI, 2025).
1.1 Obiettivi
Al termine di questo capitolo, sarai in grado di:
Comprendere la natura fisica e matematica dell’immagine digitale \(f(x,y)\).
Identificare le bande dello spettro elettromagnetico rilevanti per l’elaborazione digitale delle immagini (PDI).
Eseguire operazioni di base: lettura, visualizzazione e salvataggio delle immagini.
Accedere e modificare le intensità dei pixel singolarmente.
Applicare la sogliatura manuale.
Configurare l’ambiente di sviluppo in C++.
Gestire le strutture di array (std::vector) senza cadere in insidie di copia/riferimento.
1.2 Prima di iniziare: Notebooks interattivi
Questo materiale è stato realizzato secondo il concetto di Literate Programming (Programmazione Letteraria), ideato da Donald Knuth negli anni ’80 (KNUTH, 1984). Knuth — anche creatore del sistema TeX per la tipografia digitale — propose che i programmi fossero scritti come una narrazione logica per gli esseri umani, alternando codice e documentazione.
Per eseguire una cella, premere Shift + Invio oppure fare clic sul pulsante ▶️.
NotaNota sul formato
Nelle versioni renderizzate (PDF o HTML), il codice è presentato in blocchi statici a scopo di lettura e riferimento. L’esecuzione interattiva richiede l’accesso tramite Google Colab (disponibile in cima alla pagina) o in ambiente locale con VSCode o Jupyter Notebook.
1.3 Fondamenti
Lo studio dei sistemi basati su immagini comprende un ecosistema di discipline integrate che trasformano dati visivi grezzi in conoscenza strutturata. Mentre alcune aree si concentrano sulla generazione di rappresentazioni, altre si dedicano al trattamento e all’analisi di tali dati per supportare applicazioni tecnologiche complesse.
Il diagramma presentato in Figura 1.1 stabilisce la distinzione e la complementarità tra l’Elaborazione Digitale delle Immagini (EDI) e la Visione Artificiale (VA). L’EDI, evidenziata in verde, si concentra sulla trasformazione da immagine a immagine, mirando al miglioramento della qualità o alla pre-elaborazione, come la rimozione del rumore e l’accentuazione del contrasto.
Al contrario, la VA, contrassegnata in blu, si concentra sull’interpretazione del contenuto visivo per estrarre modelli o informazioni, come il riconoscimento di oggetti e gesti. La regione di intersezione illustra la sinergia tra le aree, dove l’EDI prepara i dati visivi per l’interpretazione da parte della VA. La mappa dimostra inoltre le interconnessioni di entrambe le discipline con aree quali la Robotica, la Grafica Computerizzata, l’Intelligenza Artificiale (IA) e le Neuroscienze.
Figura 1.1: Diagramma relazionale che illustra le distinzioni fondamentali, le sinergie e le interconnessioni tra EDI e VA nel contesto dei sistemi basati su immagini.
1.3.1 👁️ Visione Computazionale
Focus:Immagine → Modello (percorso inverso rispetto alla Computer Grafica).
Obiettivo: Estrarre informazioni di alto livello da immagini o video.
Applicazioni tipiche:
Robotica: rilevamento di ostacoli, localizzazione e navigazione autonoma.
Sorveglianza e ispezione: riconoscimento di eventi, lettura di targhe, controllo qualità.
Telerilevamento: analisi di immagini satellitari, mappatura ambientale.
Imaging medico: rilevamento di tumori, segmentazione di organi, supporto alla diagnosi.
Interazione uomo-computer: riconoscimento di gesti, espressioni facciali, eye tracking.
Relazione con altri ambiti: utilizza tecniche di Apprendimento Automatico e IA per classificare e interpretare le scene; funge da “occhi” della Robotica.
1.3.2 🖼️ Elaborazione Digitale delle Immagini (PDI)
Obiettivo:Immagine → Immagine (generalmente - trasformazione di un’immagine in un’altra).
Scopo: Migliorare la qualità visiva o estrarre caratteristiche di basso livello.
Applicazioni comuni:
Rimozione del rumore (filtri media, mediana, gaussiano).
Miglioramento del contrasto (equalizzazione dell’istogramma, regolazione gamma).
Rilevamento dei bordi (Sobel, Canny, Laplaciano).
Segmentazione (sogliatura, crescita delle regioni, watershed).
È la base per la maggior parte dei sistemi di visione artificiale (pre-elaborazione).
La Computer Grafica applica spesso PDI per la post-elaborazione (es.: smussatura, miglioramento).
Le tecniche di IA possono ottimizzare i parametri di elaborazione (es.: apprendimento di filtri).
Tabella 1.1: Connessione tra PDI, VC e altre aree della scienza.
Área
Relação com PDI e VC
Intelligenza Artificiale
Fornisce modelli (reti neurali, SVM) che interpretano le uscite della VC.
Robotica
Consuma dati di VC per prendere decisioni (navigazione, manipolazione).
Apprendimento Automatico
Utilizza descrittori estratti da PDI/VC per addestrare classificatori.
Computer Grafica
Percorso inverso: modello → immagine; spesso applica PDI per una resa realistica.
Neuroscienze
Ispira modelli di PDI (es.: filtri simili alle cellule gangliari della retina).
1.4 Fasi del PDI
Le fasi del PDI sono presentate nella Figura 1.2 e possono essere interpretate come una catena di trasformazioni che riduce la ridondanza dei dati per ricercarne il significato:
Basso Livello: opera direttamente sui pixel dell’immagine disturbata per apportare miglioramenti e filtraggi, producendo come output un’immagine pulita o esaltata.
Medio Livello: riceve l’immagine trattata ed esegue la segmentazione e la descrizione, trasformando la matrice dei pixel in attributi strutturati (forma, dimensione e texture).
Alto Livello: utilizza la tabella degli attributi per alimentare processi di logica e intelligenza artificiale, portando alla decisione o al riconoscimento finale (come la diagnosi medica).
Figura 1.2: Rappresentazione del flusso sequenziale di elaborazione: l’output di ciascun livello diventa l’input del livello successivo.
La Figura 1.3 descrive l’intera sequenza dell’elaborazione delle immagini digitali (PDI), dall’acquisizione all’interpretazione. Il flusso inizia con l’Acquisizione dell’Immagine (1) e prosegue con il Miglioramento (2) e il Restauro (3). Successivamente, il contenuto viene isolato tramite la Segmentazione (4) e raffinato mediante la Morfologia (5). La transizione cruciale avviene nella Rappresentazione e Descrizione (6), dove gli oggetti visivi vengono convertiti in dati matematici (area, perimetro, ecc.), consentendo il Riconoscimento (7). I processi ausiliari includono l’Elaborazione dell’Immagine a Colori e la Compressione, che contribuiscono all’efficienza dell’archiviazione e dell’analisi.
Figura 1.3: Flusso dettagliato del PDI: dall’acquisizione sensoriale all’estrazione di attributi e al riconoscimento automatizzato, includendo l’elaborazione a colori e la compressione.
1.5 Formazione dell’Immagine e Spettro
Il processo di formazione di un’immagine si basa sull’interazione tra materia ed energia radiante. Essenzialmente, un’immagine viene concepita quando un sensore registra la radiazione risultante dall’interazione con un oggetto fisico. Nel contesto della visione umana e della fotografia convenzionale, questo fenomeno dipende da una sorgente luminosa che illumina la scena; le caratteristiche degli oggetti vengono quindi codificate attraverso le variazioni di intensità e colore della luce che raggiunge il sensore, come illustrato nella Figura 1.4.
Figura 1.4: Rappresentazione dello spettro visibile e della sua posizione rispetto alle altre radiazioni elettromagnetiche, evidenziando la variazione delle lunghezze d’onda da 380 nm a 750 nm.
La luce visibile occupa solo una piccola banda dello spettro elettromagnetico — tra 380 nm (viola) e 750 nm (rosso) — come illustrato nella Figura 1.5. I sensori digitali convenzionali operano in questa stessa finestra, ma apparecchiature specializzate possono captare radiazioni invisibili all’occhio umano, come l’infrarosso e i raggi X. In PDI, l’immagine formata dipende direttamente dalla sensibilità spettrale del sensore utilizzato.
Figura 1.5: (A) Spettro elettromagnetico completo in scala logaritmica, con evidenza della fascia visibile. (B) Dettaglio della luce visibile (380-750 nm) e dei suoi colori. (C) Scomposizione della luce bianca tramite prisma: la lunghezza d’onda minore subisce una rifrazione maggiore, separando UV, visibile e infrarosso.
A partire da questo processo fisico di acquisizione, diventa possibile modellare matematicamente l’immagine digitale come una funzione bidimensionale discreta, nella quale ogni punto della scena è rappresentato da campioni numerici di intensità luminosa, formalizzando così i concetti di pixel e di immagine digitale presentati nella sezione successiva.
1.6 Cos’è un’Immagine Digitale?
Un’immagine digitale è formata da una griglia di pixel (Picture Elements), dove ogni pixel è la più piccola unità elementare dell’immagine.
ConsiglioCos’è un Pixel?
Un pixel è la più piccola unità indirizzabile che compone un’immagine digitale. Ogni pixel occupa una posizione unica nella griglia e memorizza uno o più valori numerici che rappresentano la sua intensità o colore.
Rappresentazione Matematica
A differenza di una funzione continua, il dominio di un’immagine digitale è un piano rettangolare finito \(\mathbb{E} \subset \mathbb{Z}^2\), che rappresenta la griglia di campionamento. Questo dominio è indicizzato da coordinate intere:
\[
\mathbb{E} = \{ (x, y) \in \mathbb{Z}^2 \mid 0 \le x < L,\; 0 \le y < H \}
\tag{1.1}\]
Dove:
\(L\): rappresenta la larghezza dell’immagine (numero di colonne).
\(H\): rappresenta l’altezza dell’immagine (numero di righe).
L’immagine digitale è una funzione che associa a ogni coppia di coordinate \((x,y)\) uno o più valori che descrivono l’aspetto del pixel.
\[
f: \mathbb{E} \to \mathcal{V}
\tag{1.2}\]
L’insieme \(\mathcal{V}\) definisce i valori possibili per il pixel (codominio), variando a seconda del tipo di immagine, come dimostrato nella Tabella 1.2.
Tabella 1.2: Principali tipi di immagine digitale e i rispettivi insiemi di valori possibili per ogni pixel.
Tipo di immagine
\(\mathcal{V}\) (valori del pixel)
Rappresentazione
Binaria
\(\{0, 1\}\) o \(\{0, 255\}\)
⬛◻️
Scala di grigi
\(\{0, 1, \dots, 255\}\)
░▒▓█
A colori (RGB)
\(\{0, \dots, 255\}^3\) (terne ordinate di valori)
🟥🟩🟦
Esempio pratico: Un’immagine a colori nel modello RGB può essere rappresentata matematicamente da una funzione che associa tre valori di intensità a ogni pixel. Computazionalmente, questa rappresentazione corrisponde a tre matrici sovrapposte — i canali rosso (Red), verde (Green) e blu (Blue) — nelle quali ogni elemento memorizza l’intensità luminosa del rispettivo canale in una determinata posizione dell’immagine.
Per rendere possibili gli esperimenti pratici di PDI e VC, questo libro utilizza C++17 e un insieme minimo di librerie orientate alla manipolazione matriciale, all’elaborazione delle immagini e alla visualizzazione dei risultati, presentate di seguito.
1.7 Configurazione dell’Ambiente
Questo materiale utilizza C++17 e la libreria a singolo header morph.hpp, originariamente proposta per Python in Zampirolli (2025). Qui viene utilizzata una versione minima e didattica della morph.py, adattata per una compilazione semplice con g++, come presentato in Tabella 1.3.
Ogni cella di codice viene compilata ed eseguita come un processo isolato (%%writefile file.cpp seguito da g++). Diversamente dal kernel Python, non esiste uno stato persistente tra le celle: le immagini prodotte da una cella vengono salvate in file per essere lette dalla successiva.
Gli Esercizi di Programmazione (EP) presentati alla fine dei capitoli possono essere validati dallo stesso modulo testsuite.py utilizzato nel percorso Python, che compila la soluzione con g++ e confronta l’output con i casi di test. In questo modo, gli stessi casi di test (.cases) possono validare soluzioni in C++, Python e altri linguaggi, sia nei notebook che in Moodle/VPL.
Tabella 1.3: Principali librerie e strumenti utilizzati nel percorso C++ di questo libro.
Libreria / Strumento
Funzione principale
g++ (C++17)
Compilazione di ogni cella di codice
morph.hpp
Astrazione didattica delle operazioni di PDI
stb_image.h / stb_image_write.h
Lettura/scrittura di PNG/JPEG (senza OpenCV)
testsuite.py
Esecuzione e validazione automatica degli EP
1.8 Informazioni sulla morph.hpp
La morph.hpp è una versione C++ minima e didattica della morph.py: implementa le funzioni utilizzate in questo capitolo (read, gray, randomImage, show, write, threshold, drawImg), oltre alle operazioni di morfologia (dil/ero e varianti dil0/ero0/dil1/ero1) preparate per i capitoli successivi. Non è garantita una parità numerica con l’implementazione Python — il criterio è “compila e produce un’immagine plausibile”, non “risultato bit-a-bit identico a Python”.
Le librerie stb_image.h/stb_image_write.h (dominio pubblico/MIT) sono vendorizzate insieme al repository — cioè, il loro codice sorgente è già copiato all’interno del progetto stesso, invece di essere installato separatamente tramite apt install — evitando così di dipendere dai pacchetti di sistema durante la compilazione su Colab. Le operazioni dil()/ero() utilizzano cv::dilate/cv::erode quando compilate con -DMM_USE_OPENCV; senza questa flag (predefinita, anche su Moodle/VPL), si ricade sulla versione didattica equivalente (dil1/ero1) senza richiedere OpenCV.
import os, urllib.requestos.makedirs("tmp/state", exist_ok=True) # artefatti di build della pista C++ (.cpp, binario, PNG)url ="https://raw.githubusercontent.com/fzampirolli/pdi-vc/master/morph/config.py"ifnot os.path.exists("config.py"): urllib.request.urlretrieve(url, "config.py")# Il kernel è comunque Python anche nella pista C++: `mm` (morph.py) è usato dai# simulatori, dalla visualizzazione delle figure che il binario C++ genera e dallo# stato mm::Image tra le celle. cpp=True scarica anche la pista compilata# (morph.hpp + stb_image*.h), usata nell'#include delle celle %%writefile *.cpp.import configconfig.setup(testsuite=True, cpp=True)from morph import mmfrom testsuite import TestSuite
1.9 Fondamenti delle Matrici — attenzione alla copia dei riferimenti
Poiché un’immagine digitale può essere rappresentata da una matrice, è importante comprendere come creare e manipolare correttamente le matrici. In C++, std::vector ha semantica di valore: copiare il vettore copia i suoi dati. Pertanto, std::vector<std::vector<int>> m(3, std::vector<int>(2, 0)) crea effettivamente tre righe indipendenti — il costruttore di riempimento copia la riga-modello tre volte.
AvvisoAttenzione alla copia dei riferimenti
La trappola compare quando si usano puntatori o riferimenti invece di copie. In std::vector<int> riga(2, 0); std::vector<std::vector<int>*> m(3, &riga);, i tre elementi di m puntano alla stessa riga, e modificare (*m[0])[0] modifica tutte. Lo stesso accade con auto& riga = m[0]; (riferimento — modifica la matrice), in contrasto con auto riga = m[0]; (copia — lascia la matrice intatta).
Per visualizzare questo comportamento, si può eseguire il codice su Python Tutor (che esegue anche C++ passo dopo passo) e confrontare l’effetto di copie e di riferimenti.
Nella pratica, per l’elaborazione digitale delle immagini (PDI), si utilizza la struttura mm::Image della morph.hpp, che ha anch’essa semantica di valore: mm::Image b = a; copia i pixel, mentre mm::Image& b = a; crea solo un alias per la stessa immagine. Il codice seguente presenta diverse modalità di creazione di immagini sintetiche, i cui risultati sono mostrati nella Figura 1.6..
%%writefile tmp/fig_imagens_sinteticas.cpp#define MM_OUT "tmp/fig_imagens_sinteticas.png"// Compile: g++-std=c++17-o programa programa.cpp -I. -lpng -ljpeg#include "morph.hpp"#include <iostream>#include <filesystem>//| quarto-raw: false//| label: fig-imagens-sinteticas//| fig-cap: "Exemplos de imagens sintéticas representadas matricialmente."//| echo: true//| output: trueint main() {// Criando uma imagem preta (zeros) de 4, 6 pixels mm::Image img_preta(4, 6); img_preta.at(0, 0) =255;// pixel branco no canto superior esquerdo// Criando uma imagem branca (255) de 4, 6 pixels mm::Image img_branca(4, 6); std::fill(img_branca.data.begin(), img_branca.data.end(), 255); img_branca.at(3, 5) =0;// pixel preto no canto inferior direito// Criando uma imagem aleatória para testes (ruído) mm::Image img_random = mm::randomImage(4, 6, 255); std::cout <<"Matriz aleatória gerada:\n"; std::cout << mm::drawImg(img_random) <<"\n"; mm::show( std::vector<mm::Image>{img_preta, img_branca, img_random}, MM_OUT, std::vector<std::string>{"Predominantemente preta\n(com 1 pixel branco em (0,0))","Predominantemente branca\n(com 1 pixel preto em (3,5))","Imagem aleatória\n(simulação de ruído)" },3 );// [pdi:panel-io] auto-generated — do not edit by handstd::filesystem::create_directories("tmp");mm::write(img_preta, "tmp/fig_imagens_sinteticas_0.png");mm::write(img_branca, "tmp/fig_imagens_sinteticas_1.png");mm::write(img_random, "tmp/fig_imagens_sinteticas_2.png");// [pdi:panel-io:end]return0;}
Overwriting tmp/fig_imagens_sinteticas.cpp
!g++-I. -std=c++17 tmp/fig_imagens_sinteticas.cpp -o tmp/fig_imagens_sinteticas \&& ./tmp/fig_imagens_sinteticas \&& test -f "tmp/fig_imagens_sinteticas.png"\|| echo "⚠ mm::show não gravou tmp/fig_imagens_sinteticas.png"
try: mm.show( [ mm.read("tmp/fig_imagens_sinteticas_0.png"), mm.read("tmp/fig_imagens_sinteticas_1.png"), mm.read("tmp/fig_imagens_sinteticas_2.png"), ], titles=['Predominantemente preta\n(com 1 pixel branco em (0,0))','Predominantemente branca\n(com 1 pixel preto em (3,5))','Imagem aleatória\n(simulação de ruído)', ], cols=3, axis=True, figsize=(9, 3), )exceptExceptionas _e:print("figura indisponivel nesta trilha (C++): "+repr(_e) +" tmp/fig_imagens_sinteticas_0.png (ver a versao Python)")
Figura 1.6: Exemplos de imagens sintéticas representadas matricialmente.
1.10 Lettura e Visualizzazione delle Immagini
In biblioteche come OpenCV (cv::Mat), le immagini digitali sono rappresentate computazionalmente come matrici multidimensionali. In morph.hpp, la struttura mm::Image adotta un approccio più semplice: un buffer lineare di byte (std::vector<unsigned char>), con altezza, larghezza e numero di canali espliciti.
Una delle operazioni fondamentali nell’elaborazione delle immagini digitali è la lettura delle immagini.
In morph.hpp, la funzione mm::read() consente di caricare immagini sia da file locali che da URL, come illustrato nella Figura 2.7..
!g++-I. -std=c++17 tmp/fig_01_natureza.cpp -o tmp/fig_01_natureza \&& ./tmp/fig_01_natureza \&& test -f "tmp/fig_01_natureza.png"\|| echo "⚠ mm::show não gravou tmp/fig_01_natureza.png"
Dimensões (H, W, Canais): (1365, 2048, 3)
Tipo de dado: uint8
Exemplo: Captura RGB
try: mm.show(mm.read("tmp/fig_01_natureza.png"))exceptExceptionas _e:print("figura indisponivel nesta trilha (C++): "+repr(_e) +" tmp/fig_01_natureza.png (ver a versao Python)")
Figura 1.7: Mandrill (Mandrillus sphinx) em ambiente natural. Crédito: Julien Renoult (CC BY 4.0).
Alternativa: Download dell’immagine per archiviazione locale
In ambienti in cui la lettura diretta degli URL non è disponibile — a causa di restrizioni di rete, policy di firewall o assenza di connettività — un’alternativa consiste nel scaricare preventivamente l’immagine nel file system locale e quindi caricarla con mm::read(). Nel percorso C++, mm::read accetta anche URL direttamente (download interno tramite curl/wget); questa alternativa copre il caso di scaricare una volta e riutilizzare il file locale. Nell’esempio seguente, il file viene salvato come mandrill.png.
wget -O mandrill.png <URL> esegue il download dell’immagine e la archivia localmente con il nome specificato;
mm::read("mandrill.png") effettua la lettura del file direttamente dal file system, senza necessità di richieste HTTP aggiuntive;
questa strategia riduce la dipendenza dalla connettività durante l’esecuzione degli esperimenti ed evita download ripetuti della stessa immagine.
ConsiglioNota
Il prefisso ! è utilizzato in ambienti basati su notebook, come Jupyter Notebook, JupyterLab e Google Colab, per eseguire comandi del sistema operativo direttamente in celle di codice. In terminali convenzionali, il comando deve essere utilizzato senza il prefisso !.
Se l’utilità wget non è installata, si può utilizzare in alternativa:
Come abbiamo visto, un’immagine a colori nello spazio RGB è rappresentata dalla funzione:
\[
f: \mathbb{E} \to \{0,1,\dots,255\}^3
\]
Cioè, per ogni pixel \((x,y)\), abbiamo tre valori \((R,G,B)\) che ne definiscono il colore.
Conversione in Scala di Grigi
Per convertire un’immagine RGB in scala di grigi (grayscale), è necessario combinare i tre canali in un unico valore di intensità \(g\), che rappresenta la luminosità percepita. Poiché l’occhio umano non è ugualmente sensibile al rosso, al verde e al blu, si utilizza una media ponderata. Lo standard ITU-R BT.601 ({ITU-R}, 2011) definisce i seguenti pesi:
\[
g = 0.299\,R + 0.587\,G + 0.114\,B
\tag{1.3}\]
Dopo il calcolo, il valore \(g\) viene arrotondato all’intero più vicino e adattato all’intervallo \([0, 255]\). Il risultato è una nuova immagine, ora in scala di grigi, rappresentata da:
A partire dall’immagine in scala di grigi \(f_{\text{grigi}}(x,y)\), un’operazione fondamentale è la soglia, che produce un’immagine binaria (solo bianco e nero). A tale scopo, si sceglie un valore di taglio \(T\) (generalmente nell’intervallo \([0,255]\)) e si definisce:
Esempio: Con \(T = 128\), i pixel con intensità superiore a 128 diventano bianchi (255); gli altri diventano neri (0).
La soglia è ampiamente utilizzata per segmentare gli oggetti dallo sfondo, estrarre i bordi o creare maschere binarie per l’elaborazione successiva.
Nota: Il valore 255 rappresenta il bianco massimo nelle immagini a 8 bit, mentre 0 rappresenta il nero assoluto.
Esempio pratico di conversione e soglia
La Figura 1.8 illustra i passaggi principali per trasformare un’immagine a colori in scala di grigi e, successivamente, convertirla in un’immagine binaria mediante soglia. Il codice seguente implementa questi passaggi:
%%writefile tmp/fig_01_processamento_basico.cpp#define MM_OUT "tmp/fig_01_processamento_basico.png"#include "morph.hpp"#include <iostream>#include <vector>#include <string>#include <filesystem>int main() {// [pdi:state-io] auto-generated — do not edit by handmm::Image img = mm::_read_state("tmp/state/img_34.png");// [pdi:state-io:end]// Leitura da imagem já fornecida: img//1. Converter para Tons de Cinza mm::Image img_gray = mm::gray(img);//2. Aplicar limiar (Pixels >128 tornam-se 255, outros 0)int limiar =128; mm::Image img_binaria = mm::threshold(img_gray, limiar);// Uso da nova função mm::show( std::vector<mm::Image>{img, img_gray, img_binaria}, MM_OUT, std::vector<std::string>{"Original", "Tons de Cinza", "Binária (T=128)"},3 );// [pdi:state-io] auto-generated — do not edit by handstd::filesystem::create_directories("tmp/state");mm::write(img_gray, "tmp/state/img_gray_38.png");// [pdi:state-io:end]// [pdi:panel-io] auto-generated — do not edit by handstd::filesystem::create_directories("tmp");mm::write(img, "tmp/fig_01_processamento_basico_0.png");mm::write(img_gray, "tmp/fig_01_processamento_basico_1.png");mm::write(img_binaria, "tmp/fig_01_processamento_basico_2.png");// [pdi:panel-io:end]return0;}
Overwriting tmp/fig_01_processamento_basico.cpp
!g++-I. -std=c++17 tmp/fig_01_processamento_basico.cpp -o tmp/fig_01_processamento_basico \&& ./tmp/fig_01_processamento_basico \&& test -f "tmp/fig_01_processamento_basico.png"\|| echo "⚠ mm::show não gravou tmp/fig_01_processamento_basico.png"
[1] Original
[2] Tons de Cinza
[3] Binária (T=128)
try: mm.show( [ mm.read("tmp/fig_01_processamento_basico_0.png"), mm.read("tmp/fig_01_processamento_basico_1.png"), mm.read("tmp/fig_01_processamento_basico_2.png"), ], titles=['Original','Tons de Cinza','Binária (T=128)', ], cols=3, )exceptExceptionas _e:print("figura indisponivel nesta trilha (C++): "+repr(_e) +" tmp/fig_01_processamento_basico_0.png (ver a versao Python)")
Figura 1.8: Processamento básico de imagens: (a) imagem original, (b) imagem em tons de cinza, (c) imagem binarizada por limiar (T=128).
1.12 Soglia con il metodo di Otsu
Come presentato in Equazione 1.4, la sogliatura converte un’immagine in scala di grigi in binaria utilizzando un valore di taglio \(T\). Finora abbiamo fissato \(T = 128\) manualmente.
Tuttavia, la scelta manuale di \(T\) non è sempre banale. La libreria mm offre un’alternativa automatica: quando la soglia non viene fornita, la funzione mm::threshold(img_gray) calcola il valore di \(T\) tramite il metodo di Otsu (OTSU, 1979). Questo metodo, che verrà dettagliato nei capitoli successivi, massimizza la varianza tra le classi dell’istogramma (frequenza di ogni tono di grigio), separando automaticamente i pixel dell’oggetto e dello sfondo.
Il codice seguente confronta la sogliatura manuale (\(T=128\)) con quella automatica (Otsu). La binarizzazione con Otsu viene eseguita con mm::threshold(img_gray); poiché l’API minima di morph.hpp non restituisce il \(T\) calcolato, il valore mostrato nell’etichetta della figura viene recuperato con cv2.threshold nella fase di visualizzazione (stesso algoritmo, stesso \(T\)).
%%writefile tmp/fig_01_otsu.cpp#define MM_OUT "tmp/fig_01_otsu.png"//| quarto-raw: false//| label: fig-01-otsu//| fig-cap: "Comparação entre limiarização manual (T=128) e automática (Otsu) sobre a imagem em tons de cinza."//| echo: true//| output: true#include "morph.hpp"#include <iostream>#include <vector>#include <filesystem>int main() {// [pdi:state-io] auto-generated — do not edit by handmm::Image img_gray = mm::_read_state("tmp/state/img_gray_38.png");// [pdi:state-io:end]// Limiarização com T fixo (manual)int T_fixo =128; mm::Image img_bin_fixo = mm::threshold(img_gray, T_fixo);// Limiarização pelo método de Otsu (T automático)int T_otsu = mm::otsu(img_gray); mm::Image img_bin_otsu = mm::threshold(img_gray);// mesmo T de Otsu std::cout <<"Limiar calculado por Otsu: T = "<< T_otsu <<"\n";// ou simplesmente:// img_bin_otsu = mm::threshold(img_gray);// Exibição lado a lado mm::show( std::vector<mm::Image>{img_gray, img_bin_fixo, img_bin_otsu}, MM_OUT, std::vector<std::string>{"Tons de Cinza", "Binária (T=128)", "Binária (Otsu, T="+ std::to_string(T_otsu) +")"},3 );// [pdi:panel-io] auto-generated — do not edit by handstd::filesystem::create_directories("tmp");mm::write(img_gray, "tmp/fig_01_otsu_0.png");mm::write(img_bin_fixo, "tmp/fig_01_otsu_1.png");mm::write(img_bin_otsu, "tmp/fig_01_otsu_2.png");// [pdi:panel-io:end]return0;}
Overwriting tmp/fig_01_otsu.cpp
!g++-I. -std=c++17 tmp/fig_01_otsu.cpp -o tmp/fig_01_otsu \&& ./tmp/fig_01_otsu \&& test -f "tmp/fig_01_otsu.png"\|| echo "⚠ mm::show não gravou tmp/fig_01_otsu.png"
Limiar calculado por Otsu: T = 95
[1] Tons de Cinza
[2] Binária (T=128)
[3] Binária (Otsu, T=95)
Figura 1.9: Comparação entre limiarização manual (T=128) e automática (Otsu) sobre a imagem em tons de cinza.
La Figura 1.9 mostra che la soglia ottenuta mediante Otsu si adatta automaticamente all’immagine, producendo una binarizzazione più efficace rispetto a un valore fisso, soprattutto quando le intensità dell’oggetto e dello sfondo sono ben separate nell’istogramma. Questa tecnica è ampiamente utilizzata nei sistemi di visione artificiale (VC) per la binarizzazione di documenti, il rilevamento di oggetti e il pre-processing delle immagini.
la morph.hpp astrae tutta questa complessità: basta chiamare mm::threshold(img_gray). La soglia di Otsu viene calcolata automaticamente e l’immagine binaria viene restituita direttamente. Questo approccio consente di concentrarsi sul concetto, non sui dettagli implementativi.
1.13 Accesso ai Pixel
In morph.hpp, l’immagine è un mm::Image con un buffer lineare data (riga dopo riga, canali interallacciati) e il metodo at(y, x, c) per l’accesso individuale, seguendo la convenzione matriciale riga (asse Y) e colonna (asse X): img.at(riga, colonna) per i toni di grigio e img.at(riga, colonna, canale) per ogni canale di un’immagine RGB.
Il codice della Figura 1.10 dimostra come estrarre questi valori in immagini a colori (RGB) e in toni di grigio, oltre a isolare l’intorno immediato del punto di interesse.
# Not yet ported to this language in this version — conceptual reference in Python.# 1. Coordinate del pixel bersaglio (riga, colonna)r, c =600, 800# 2. Accesso diretto ai valori del pixelpixel_cinza = img_gray[r, c]print(f"Pixel bersaglio ({r}, {c}):")print(f" Toni di grigio (scalare) : {pixel_cinza}")print(f" Colorato (canali RGB) : R={img[r, c, 0]}, G={img[r, c, 1]}, B={img[r, c, 2]}")# 3. Intorno 3x3 attorno a (r, c); il pixel centrale va tra parentesi quadreprint("Matrice dell'intorno 3x3 (toni di grigio):")for dy inrange(-1, 2): linha =""for dx inrange(-1, 2): v = img_gray[r + dy, c + dx]if dy ==0and dx ==0: linha +=f"[{v:3d}]"else: linha +=f" {v:3d} "print(" "+ linha)# 4. Marca la posizione del pixel con un quadrato rosso vuoto (bordo di# 3 px) su una copia dell'immagine e visualizza (equivalente al punto del matplotlib).marcada = img.copy()lado =20# metà lato del quadrato, in pixelesp =5# spessore del bordofor x inrange(c - lado, c + lado +1):for w inrange(esp): marcada[r - lado + w, x, 0] =255 marcada[r - lado + w, x, 1] =0 marcada[r - lado + w, x, 2] =0 marcada[r + lado - w, x, 0] =255 marcada[r + lado - w, x, 1] =0 marcada[r + lado - w, x, 2] =0for y inrange(r - lado, r + lado +1):for w inrange(esp): marcada[y, c - lado + w, 0] =255 marcada[y, c - lado + w, 1] =0 marcada[y, c - lado + w, 2] =0 marcada[y, c + lado - w, 0] =255 marcada[y, c + lado - w, 1] =0 marcada[y, c + lado - w, 2] =0mm.show(marcada, title=f"Posizione del pixel ({r}, {c})")
Figura 1.10
Accesso ai pixel in C++ (mm::Image):
Scala di grigi:img_gray.at(r, c) restituisce un unsigned char (da 0 a 255) con l’intensità del grigio nel pixel.
RGB:img.at(r, c, 0), img.at(r, c, 1), img.at(r, c, 2) accedono ai canali R, G e B — un indice di canale alla volta; non esiste un vettore [R, G, B].
Intorno \(3\times 3\): esaminato tramite due cicli annidati su img_gray.at(r + dy, c + dx), con dy, dx in \(\{-1, 0, 1\}\) — non esiste lo slicing come in NumPy. Questa scansione è alla base per filtri spaziali e convoluzioni.
Non esiste plottaggio interattivo (equivalente a plt.plot): per marcare la posizione del pixel, la cella di codice seguente disegna un quadrato rosso vuoto attorno ad esso in una copia dell’immagine (marcata = img.copy(), poi marcata.at(...)) e la visualizza con mm::show.
L’indicizzazione è zero-based: (0,0) è l’angolo in alto a sinistra. La prima dimensione controlla l’altezza (righe/Y) e la seconda la larghezza (colonne/X).
1.14 Riassunto
In questo capitolo sono stati presentati i fondamenti della rappresentazione delle immagini digitali: la definizione di pixel, la strutturazione delle immagini in matrici e l’impatto del campionamento e della quantizzazione sulla qualità finale:
Immagine digitale = funzione \(f(x,y)\) che mappa coordinate in intensità (scalari o vettoriali).
Dominio: insieme finito \(\mathbb{E} = \{(x,y) \in \mathbb{Z}^2 \mid 0 \le x < L,\; 0 \le y < H\}\).
Tipi principali: binaria (\(\mathcal{V} = \{0, 255\}\)), toni di grigio (\(\mathcal{V} = [0,255]\)) e RGB (\(\mathcal{V} = [0,255]^3\)).
Sogliatura converte i toni di grigio in immagine binaria; il metodo di Otsu determina automaticamente il valore di soglia massimizzando la varianza tra le classi.
La morph.hpp (o mm::) offre funzioni didattiche per operazioni di base dell’elaborazione delle immagini, come mm::gray(), mm::threshold(), mm::show() (sovraccaricata per 1 immagine o più).
Trappola della copia: std::vector ha semantica di valore, ma puntatori/riferimenti condivisi tra “righe” di una matrice reintroducono lo stesso problema di NumPy — preferisci mm::Image, che copia anch’essa per valore.
Accesso ai pixel tramite img.at(riga, colonna), con indicizzazione zero-based.
Il Capitolo 2 tratterà istogrammi ed equalizzazione del contrasto.
1.15 🤖 Uso del Gemini Notebook come Tutor Complementare
In questa edizione, oltre ai notebook interattivi su Google Colab, il Gemini Notebook è disponibile come strumento complementare di studio. La piattaforma utilizza esclusivamente i documenti forniti dall’autore come base di conoscenza, garantendo risposte coerenti con il contenuto del libro.
Importante🎓 Studia con il Tutor Intelligente
Accedi all’ambiente del capitolo tramite il link qui sotto ed esplora in particolare le opzioni Guida allo Studio e Conversazione per approfondire la tua comprensione.
Il progetto di questo capitolo nel Gemini Notebook è stato costruito esclusivamente con il testo in portoghese e gli esempi di codice in Python. Se stai studiando dall’edizione in inglese o francese, oppure seguendo il percorso in C++, le risposte del tutor potrebbero non corrispondere esattamente alla versione che stai leggendo.
⚠️ Avvertenza sul Contenuto Generato dall’IA
L’IA è un potente alleato nello studio, ma il contenuto generato può contenere errori o imprecisioni. Consulta sempre libri, articoli scientifici e altre fonti accademiche affidabili per validare le informazioni. Quando possibile, esegui gli esempi pratici forniti in questo capitolo per verificare i risultati.
Funzionalità Disponibili sulla Piattaforma
Il Gemini Notebook offre una suite avanzata di strumenti basati sull’IA per trasformare il contenuto statico del libro in un’esperienza di apprendimento dinamica e multimediale. La piattaforma utilizza tecniche di RAG (Retrieval-Augmented Generation), fondate sul lavoro di Lewis (2020), per basare le risposte strettamente sui documenti forniti e minimizzare il verificarsi di allucinazioni.
Le principali funzionalità includono:
Sintesi Multimodali (Audio e Video): Generazione di conversazioni naturali tra esperti nel formato di Sintesi Audio (stile podcast) e Sintesi Video, che discutono i temi centrali del capitolo, come le differenze tra PDI e VC, o l’interpretazione di trasformazioni come la sogliatura e il metodo di Otsu.
Visualizzazione di Strutture (Mappa Mentale e Infografica): Creazione automatica di diagrammi che collegano visivamente i concetti, ad esempio, il flusso di elaborazione dalla cattura dell’immagine digitale, passando per la conversione in toni di grigio, la sogliatura e la segmentazione binaria.
Strumenti di Valutazione (Test e Schede Didattiche): Generazione di Test a scelta multipla e Schede Didattiche (flashcards) per il consolidamento delle conoscenze, basati sul testo d’autore (es.: domande sulla formula di conversione RGB→grigio o sul funzionamento della soglia globale e di Otsu).
Supporto alla Presentazione (Slide e Relazioni): Assistenza nella strutturazione di Presentazioni di Slide e nella redazione di Relazioni tecniche, facilitando la comunicazione dei risultati di esperimenti con le immagini.
Analisi dei Dati (Tabella Dati): Organizzazione dei dati estratti dal testo in tabelle strutturate, aiutando la comprensione di esempi pratici, come il confronto tra diversi valori di soglia.
Chat Contestualizzata: Consente di porre domande dirette sul codice e sulla teoria, come: “Come implementare la conversione da RGB a toni di grigio utilizzando i pesi dello standard ITU‑R BT.601?” o “Cosa succede all’immagine binaria se scelgo una soglia T=200 invece di T=128?”.
1.16 Elenco di Esercizi
(15%) Con le tue parole, definisci immagine digitale e pixel. Fornisci un esempio concreto di come un’immagine a colori (RGB) è rappresentata in forma matriciale nel computer.
(15%) Spiega le differenze tra immagine binaria, toni di grigio (8 bit) e a colori RGB, indicando l’intervallo di valori possibili per ogni pixel in ciascun tipo.
(20%) Considerando la formula di conversione RGB → toni di grigio dello standard ITU‑R BT.601: \[g = 0.299\,R + 0.587\,G + 0.114\,B\] Calcola il valore del pixel in toni di grigio per \((R,G,B) = (80, 180, 30)\). Arrotonda al numero intero più vicino.
(20%) Che cos’è la sogliatura (thresholding)? Spiega la differenza tra scegliere una soglia \(T\) fissa (es.: \(T=128\)) e utilizzare il metodo di Otsu per la determinazione automatica della soglia. In poche parole, come sceglie la soglia il metodo di Otsu?
(15%) Nel contesto della libreria didattica mm discussa nel capitolo, rispondi:
(7,5%) Come si accede al valore del pixel nella posizione (riga=50, colonna=60) di un’immagine in scala di grigi img_gray?
(7,5%) Qual è il vantaggio di usare mm::threshold(img_gray) senza passare la soglia? Confronta con la chiamata equivalente in OpenCV (cv::threshold).
(15%) Cosa rappresentano i campi h, w e channels di un mm::Image per un’immagine RGB? Fornisci un esempio concreto con un’immagine di 640×480 pixel.
Riferimenti del Capitolo
La base teorica di questo capitolo comprende le seguenti opere di PDI e VC:
Gonzalez (2018) per i fondamenti di Elaborazione Digitale delle Immagini (EDI).
Singh (2019) per l’implementazione pratica di metodi di elaborazione e analisi delle immagini.
Szeliski (2022) per lo studio di VC e algoritmi fondamentali.
Bradski (2008) per l’applicazione della libreria OpenCV in ambiente Python.
Lewis (2020) per il concetto di generazione aumentata da recupero (RAG), utilizzato a supporto dell’elaborazione delle informazioni di questo materiale.
1.17 💻 Parte pratica con esercizi di programmazione
🎯 Obiettivo di questo Quaderno
Gli Esercizi di Programmazione (EPs) presentati di seguito possono essere sottomessi anche in attività Moodle (attività VPL) che forniscono feedback automatico.
Questo quaderno è stato sviluppato per superare le limitazioni d’uso di Moodle. Con esso, si deve:
Sviluppare: Scrivere e modificare la propria soluzione direttamente nell’ambiente Colab.
Validare: Testare il proprio codice localmente utilizzando gli stessi casi di test di Moodle.
Organizzare: Salvare in modo sicuro i propri codici delle attività VPL.
Valutare: Quando si è connessi a Moodle, basta copiare la propria soluzione e cliccare su Valuta in Moodle (se si è nella rete UFABC) per registrare il proprio voto ufficiale.
⚙️ Istruzioni Passo a Passo
In un ambiente di esecuzione (come VSCode, Jupyter o Colab), seguire l’ordine seguente per configurare l’ambiente e validare i propri esercizi:
Preparazione dell’Ambiente
Eseguire la cella di codice seguente per scaricare morph.py e testsuite.py dal repository della disciplina — solo se non sono già presenti nella directory locale. Con entrambi in ./, il notebook e i sottoprocessi del TestSuite trovano il modulo senza configurazioni aggiuntive di percorso.
Nota: Lo script testsuite.py cercherà automaticamente i casi di test in all/{cap}/cases su GitHub.
Scrittura del Codice
Salvare la propria soluzione in una cella di codice utilizzando il comando magico %%writefile. Il nome del file deve seguire il pattern EPX_Y.*, dove X è il capitolo, Y è l’esercizio e * è l’estensione del linguaggio.
Esempio:%%writefile EP01_01.py
Download
Scaricare morph.py e testsuite.py eseguendo la cella seguente:
import os, urllib.requestos.makedirs("tmp/state", exist_ok=True) # artefatti di build della pista C++ (.cpp, binario, PNG)url ="https://raw.githubusercontent.com/fzampirolli/pdi-vc/master/morph/config.py"ifnot os.path.exists("config.py"): urllib.request.urlretrieve(url, "config.py")# Il kernel è comunque Python anche nella pista C++: `mm` (morph.py) è usato dai# simulatori, dalla visualizzazione delle figure che il binario C++ genera e dallo# stato mm::Image tra le celle. cpp=True scarica anche la pista compilata# (morph.hpp + stb_image*.h), usata nell'#include delle celle %%writefile *.cpp.import configconfig.setup(testsuite=True, cpp=True)from morph import mmfrom testsuite import TestSuite
Dopo aver salvato il file con la tua soluzione, esegui il comando seguente (in una nuova cella) per valutare i test automatici:
TestSuite("EP01_01.extensão").run()
Sostituisci l’estensione in base al linguaggio utilizzato:
Linguaggio
Estensione
Python
.py
Java
.java
C
.c
C++
.cpp
JavaScript
.js
R
.r
Come funziona: Il TestSuite scarica i casi di test da GitHub, esegue il tuo programma con ciascun input e confronta l’output con quello atteso – calcolando automaticamente il tuo voto.
Per testare codice Python direttamente, senza salvare file, usa run_code(codice) passando il codice come stringa in una variabile codice:
codice ="""from morph import mm# ... il tuo codice qui ..."""TestSuite("EP04_01").run_code(codice)
⚠️ Importante: Regole e Buone Pratiche
🔹 Sull’Inserimento dei Dati
Il tuo programma deve leggere l’input standard (tastiera).
Python: Utilizza input().
Altri linguaggi: Utilizza il comando di lettura standard equivalente (cin, Scanner, ecc.).
🔹 Configurazione dell’IA su Colab
Per un migliore apprendimento, si consiglia di disattivare il completamento automatico del codice tramite IA, poiché non sarà disponibile durante le valutazioni. Ad esempio, nel browser Chrome:
Vai su: Strumenti > Impostazioni > IA generativa
Deseleziona: Abilita generazione di codice
🔹 Integrità Accademica (Plagio)
Questa risorsa di test locali si applica a ES senza variazioni. Tuttavia:
Individualità: Ogni studente deve sviluppare la propria soluzione.
Rilevamento di Similarità: Il professore utilizza strumenti che rilevano copie, anche con modifica di nomi di variabili o spazi bianchi.
1.17.1 EP01_01 📏 Tre metriche di distanza nella PDA (Processamento Digitale delle Immagini)
In questa attività, devi scrivere un programma che calcoli le tre distanze classiche nella PDA: Euclidea (L2), City-Block (L1) e Chessboard (L∞).
Leggi 4 numeri reali che rappresentano le coordinate: \(A_x, A_y, B_x, B_y\).
Stampa i tre risultati, ciascuno su una riga, formattati con due cifre decimali, nell’ordine: Euclidea, City-block, Chessboard.
📌 Importante:
Utilizza le funzioni matematiche standard del tuo linguaggio: math.sqrt, abs (o fabs) e max.
L’output deve contenere solo i numeri (uno per riga), senza testi aggiuntivi.
Consulta un simulatore interattivo per questo esercizio nella Figura 1.11 (grafico con trascinamento dei punti e visualizzazione delle tre metriche).
1.17.1.1 🖼️ Perché è importante? – Costo computazionale
In un’immagine 1000×1000 pixel (1 milione di pixel), calcolare la distanza di ogni pixel da un punto di riferimento richiede 1 milione di operazioni. La scelta della metrica influisce sulle prestazioni:
Metrica
Operazioni per pixel
Costo relativo (1M pixel)
Quando utilizzarla
Euclidea (L2)
2 sottrazioni, 2 moltiplicazioni, 1 somma, 1 sqrt
🔴 Più costosa – sqrt è dispendiosa
Distanza “reale” nello spazio continuo
City-block (L1)
2 sottrazioni, 2 abs, 1 somma
🟡 Moderata – senza radice quadrata
Griglie, robotica, immagini binarie
Chessboard (L∞)
2 sottrazioni, 2 abs, 1 max
🟢 Più efficiente
Movimenti di pezzi, morfologia
La funzione sqrt è computazionalmente più costosa rispetto ad operazioni come addizione, sottrazione, moltiplicazione e valore assoluto. Nelle CPU moderne, la differenza può essere piccola (circa 1,5× a 3×), ma nei sistemi embedded o nei cicli di milioni di iterazioni, qualsiasi guadagno conta. Per questo motivo, quando l’obiettivo è solo confrontare le distanze (es.: trovare il punto più vicino), utilizza la distanza euclidea al quadrato.
1.17.1.2 📋 Compito (specifica per VPL)
Input:
Un’unica riga con quattro numeri reali: Ax Ay Bx By
Output:
Tre righe, ciascuna con un numero reale con due cifre decimali (Euclidea, City-block, Chessboard).
1.17.1.3 📌 Esempi
Input
Output
Osservazione
0 0 3 4
5.00 7.00 4.00
Triangolo 3-4-5
0 0 1 1
1.41 2.00 1.00
Diagonale unitaria
Esempio di test di sqrt in Python, con timeit che isola ogni operazione:
%%writefile tmp/mm_out_1.cpp// Benchmark: soma simples vs soma + sqrt#include <chrono>#include <cmath>#include <iostream>int main() { const longlong N =50000000;//50 milhões// Mide o tempo apenas da soma auto t0 = std::chrono::high_resolution_clock::now();for (longlong i =0; i < N;++i) { double a =3.0, b =4.0; volatile double resultado = a + b;// evita otimização } auto t1 = std::chrono::high_resolution_clock::now(); double t_soma = std::chrono::duration<double>(t1 - t0).count();// Mide o tempo da soma + sqrt t0 = std::chrono::high_resolution_clock::now();for (longlong i =0; i < N;++i) { double a =3.0, b =4.0; volatile double resultado = std::sqrt(a*a + b*b);// evita otimização } t1 = std::chrono::high_resolution_clock::now(); double t_sqrt = std::chrono::duration<double>(t1 - t0).count(); std::cout <<"Soma simples : "<< t_soma <<" s\n"; std::cout <<"Soma + sqrt : "<< t_sqrt <<" s\n"; std::cout <<"Razão (sqrt/soma) : "<< (t_sqrt/t_soma) <<"x\n";return0;}
Soma simples : 0.113353 s
Soma + sqrt : 0.156901 s
Razão (sqrt/soma) : 1.38418x
🎮 Simulatore EP01_01: Metriche di Distanza nello Spazio DiscretoEuclidea vs City-block vs Scacchiera
Clicca e trascina i punti A o B nel piano cartesiano o regola le loro coordinate qui sotto per confrontare le tre metriche di distanza in tempo reale.
📐 EUCLIDEA (L2)
5.00
√(Δx² + Δy²)
🧱 CITY-BLOCK (L1)
7.00
|Δx| + |Δy|
🏁 SCACCHIERA (L∞)
4.00
max(|Δx|, |Δy|)
👆 Trascina i punti A (Viola) o B (Arancione) sulla griglia.
Punto A
Punto B
Legenda Geometrica: Linea tratteggiata (Euclidea), Percorso ortogonale a L (City-block) ed Evidenziazione della dimensione massima (Scacchiera).
Euclidea City-block Scacchiera (Max)
Figura 1.11: Simulatore EP01_01: Distanze Euclidea, City-block e Chessboard
1.17.1.4 🐍 Python
Basta creare una normale cella di codice e inserire il codice Python. L’input può essere simulato usando input(), che funziona normalmente.
Esempio di cella:
%%writefile EP01_01.py# Codice Pythonx1,y1,x2,y2 =int(input()), int(input()), int(input()), int(input())# Calcolo delle differenzedx =abs(x2 - x1)dy =abs(y2 - y1)# 1. Distanza Euclidea (L2)dist_euclidiana = (dx**2+ dy**2)**0.5# 2. Distanza City-block / Manhattan (L1)dist_city_block = dx + dy# 3. Distanza Scacchiera / Chebyshev (Linf)dist_chessboard =max(dx, dy)# Output formattato secondo i casi di testprint(f"{dist_euclidiana:.2f}")print(f"{dist_city_block:.2f}")print(f"{dist_chessboard:.2f}")
Overwriting EP01_01.py
# Aspetta che tu inserisca 4 numeri interi eseguendo questa cella.# In Jupyter o Google Colab, la magia %run -i permette allo script di leggere dalla tastiera.# In un terminale comune, useresti: python3 EP01_01.py (senza il '!' e senza '%run').# %run -i EP01_01.py
# Invia 4 interi come input standard (stdin) allo script EP01_01.py usando una pipe!echo -e "0\n0\n4\n4"| python3 EP01_01.py
5.66
8.00
4.00
TestSuite("EP01_01.py").run()
✔️ EP01_01.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_01.cases
🔍 Test di Python: EP01_01.py
✔️ Caso 1: OK✔️ Caso 2: OK✔️ Caso 3: OK✔️ Caso 4: OK✔️ Caso 5: OK
📊 Risultato: 5/5 (100.0%)
🎉 Complimenti! Tutti i test sono stati superati.
1.17.1.5 ☕ Java
Per eseguire Java in Colab, è necessario utilizzare una cella con il prefisso %%writefile per salvare il codice in un file, compilarlo ed eseguirlo.
✔️ EP01_01.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_01.cases
🔍 Test di Java: EP01_01.java
✔️ Caso 1: OK✔️ Caso 2: OK✔️ Caso 3: OK✔️ Caso 4: OK✔️ Caso 5: OK
📊 Risultato: 5/5 (100.0%)
🎉 Complimenti! Tutti i test sono stati superati.
1.17.1.6 💻 C
Analogamente, usa %%writefile per salvare il codice, poi compila ed esegui. Per C, utilizziamo il compilatore GCC.
# Installare il compilatore GCC e gli strumenti di build# build-essential include gcc, g++, make, ecc.import platform, shutil, subprocessdef instalar_gcc():if shutil.which("gcc"):print("✅ GCC già disponibile.");returnif platform.system() !="Linux":print("⚠️ Mac: xcode-select --install | Windows: WSL o MinGW");returntry: import google.colab; cmd = ["apt-get", "install", "-y", "build-essential"]exceptImportError: cmd = ["sudo", "apt-get", "install", "-y", "build-essential"] subprocess.run(cmd, check=True)print("✅ build-essential installato!")instalar_gcc()
# Compila il file .c generando l'eseguibile EP01_01# -lm viene usato per linkare la libreria matematica (math.h) se necessario!gcc EP01_01.c -o EP01_01 -lm!echo -e "0\n0\n4\n4"| ./EP01_01
5.66
8.00
4.00
TestSuite("EP01_01.c").run()
✔️ EP01_01.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_01.cases
🔍 Test di C: EP01_01.c
✔️ Caso 1: OK✔️ Caso 2: OK✔️ Caso 3: OK✔️ Caso 4: OK✔️ Caso 5: OK
📊 Risultato: 5/5 (100.0%)
🎉 Complimenti! Tutti i test sono stati superati.
1.17.1.7 💻 C++
Analogamente al Java, usa %%writefile per salvare il codice, poi compila ed esegui. Ricorda che, in Colab, è necessario installare anche quanto segue:
# Installa il compilatore G++ per C++# Il build-essential include g++, make, ecc.import platform, shutil, subprocessdef instalar_gpp():if shutil.which("g++"):print("✅ G++ già disponibile.");returnif platform.system() !="Linux":if platform.system() =="Darwin":print("⚠️ Mac: xcode-select --install")else:print("⚠️ Windows: usa WSL o MinGW (https://www.mingw-w64.org)")returntry: import google.colab; cmd = ["apt-get", "install", "-y", "build-essential"]exceptImportError: cmd = ["sudo", "apt-get", "install", "-y", "build-essential"] subprocess.run(cmd, check=True)print("✅ Compilatore C++ pronto.")instalar_gpp()
✔️ EP01_01.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_01.cases
🔍 Test di C++: EP01_01.cpp
✔️ Caso 1: OK✔️ Caso 2: OK✔️ Caso 3: OK✔️ Caso 4: OK✔️ Caso 5: OK
📊 Risultato: 5/5 (100.0%)
🎉 Complimenti! Tutti i test sono stati superati.
1.17.1.8 🌐 JavaScript (Node.js)
Per JavaScript, usa %%writefile per creare il file ed eseguilo con Node:
✔️ EP01_01.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_01.cases
🔍 Test di Node.js: EP01_01.js
✔️ Caso 1: OK✔️ Caso 2: OK✔️ Caso 3: OK✔️ Caso 4: OK✔️ Caso 5: OK
📊 Risultato: 5/5 (100.0%)
🎉 Complimenti! Tutti i test sono stati superati.
1.17.1.9 📊 R
In Colab è possibile eseguire codice R direttamente utilizzando la magia %%R.
Il programma deve leggere quattro numeri (x1, y1, x2, y2) e mostrare la distanza euclidea con due cifre decimali.
✔️ EP01_01.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_01.cases
🔍 Test di R: EP01_01.r
✔️ Caso 1: OK✔️ Caso 2: OK✔️ Caso 3: OK✔️ Caso 4: OK✔️ Caso 5: OK
📊 Risultato: 5/5 (100.0%)
🎉 Complimenti! Tutti i test sono stati superati.
1.17.2 EP01_02 📊 Prestazioni Predittive — Metriche di ML in VC
In questa attività entrerai nel mondo dell’Apprendimento Automatico (Machine Learning). Il tuo obiettivo è valutare le prestazioni di un classificatore binario calcolando le metriche a partire da una Matrice di Confusione.
1.17.2.1 🧠 Perché la metrica giusta è importante?
Immagina un rilevatore di monete da R$ 1,00. L’impatto dell’errore definisce la metrica prioritaria:
Metrica
Esempio Pratico
Importanza in PDI/VC
Accuratezza
Conteggio dei Grani
Utile quando le classi sono bilanciate (es: metà dei grani difettosi, metà sani).
Precisione
Sicurezza/Biometria
Fondamentale per evitare Falsi Positivi (es: non consentire a un impostore di accedere a un sistema per un errore di riconoscimento).
Sensibilità
Salute (Tumori)
Fondamentale per evitare Falsi Negativi (es: non lasciare che un tumore passi inosservato in una radiografia).
F1-score
Banconote
Ideale per un equilibrio tra non rifiutare banconote autentiche e non accettare banconote false.
Stampa i risultati formattati con due cifre decimali, uno per riga.
📌 Importante:
Usa la divisione in virgola mobile per evitare risultati troncati.
L’ordine di uscita deve essere: Accuratezza, Precisione, Sensibilità e F1-score.
Consulta la simulazione interattiva di questo EP alla Figura 1.12.
1.17.2.3 📌 Esempio di Esecuzione
Input
Output
Osservazione
40
0.75
Accuratezza
10
0.73
Precisione
15
0.80
Sensibilità
35
0.76
F1-score
(Nota: VP=40, FN=10, FP=15, VN=35. Totale dei casi = 100)
🎮 Simulatore EP01_02: Prestazioni Predittive — Metriche di MLMatrice di Confusione & Metriche
Seleziona uno scenario predefinito o regola i cursori per osservare l'impatto in tempo reale sulla matrice di confusione e sulle metriche di valutazione.
INPUT (PARAMETRI DELLA MATRICE)
40
10
15
35
MATRICE DI CONFUSIONE DISCRETA
Pred +
Pred −
Reale +
VP
40
FN
10
Reale −
FP
15
VN
35
METRICHE CALCOLATE IN TEMPO REALE
Accuratezza0.75
(VP + VN) / Totale
Precisione0.73
VP / (VP + FP)
Sensibilità (Recall)0.80
VP / (VP + FN)
F1-Score (Media Armonica)0.76
2 · (P · R) / (P + R)
Totale Campioni: 100
Figura 1.12: Simulatore EP01_02: Prestazioni Predittive — Metriche di ML
%%writefile EP01_02.cpp// your solution
Overwriting EP01_02.cpp
TestSuite("EP01_02.cpp").run()
✔️ EP01_02.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_02.cases
🔍 Test di C++: EP01_02.cpp
⚠️ EP01_02.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.3 EP01_03 📈 Mean Average Precision (mAP) — Curva Precisione-Sensibilità
Nella EP01_02 hai visto che la scelta della soglia altera significativamente Precisione e Sensibilità. L’mAP (Mean Average Precision) risolve questo problema: valuta il modello su più soglie (ogni soglia deve generare una matrice di confusione diversa) e riassume le prestazioni tramite l’area sotto la curva Precisione-Sensibilità (P-S).
Mentre l’F1‑Score considera un singolo punto di equilibrio, l’mAP considera l’intera curva. Più il valore è vicino a 1,0, migliore è il rilevatore su tutte le soglie e classi (es.: monete da 25, 50 centesimi e 1 euro).
Per ogni soglia (t), classifica i campioni: predito = 1 se confiança ≥ t, altrimenti 0.
Calcola VP, FP, FN, VN e ottieni Precisione((t)) e Sensibilità((t)).
Costruisci la curva P-S: coppie (Sensibilità((t)), Precisione((t))), ordinate per Sensibilità crescente.
Rendi monotona la Precisione: \[P_{\text{mono}}[i] = \max_{j \ge i} P[j]\]
Calcola l’AP (area sotto la curva monotona) usando la regola del trapezio (approssimazione più accurata rispetto alla semplice somma di Riemann): \[AP = \sum_{i=1}^{m-1} \frac{P_{\text{mono}}[i-1] + P_{\text{mono}}[i]}{2} \cdot (S[i] - S[i-1])\]
mAP = media delle AP di tutte le classi. In questa EP c’è solo 1 classe, quindi mAP = AP.
Nota
📐 Differenza riassunta:
La somma di Riemann approssima l’area tramite rettangoli, potendo sottostimare o sovrastimare. La regola del trapezio usa trapezi, riducendo l’errore considerando la media tra i valori agli estremi dell’intervallo, risultando generalmente più precisa per funzioni continue a tratti, come la curva Precisione-Sensibilità.
1.17.3.3 📋 Compito
Leggi un intero n (quantità di campioni). Poi leggi n righe, ciascuna con: verdade (0 o 1) e confiança (float 0.0–1.0).
Calcola e stampa, per la soglia 0.85 (indice 9 della lista):
Matrice di Confusione (VP, FN, FP, VN)
Accuratezza, Precisione, Sensibilità e F1‑Score
Successivamente, per tutte le soglie, stampa:
Precisioni grezze, Precisioni monotone e Sensibilità, separate da ,
mAP finale
1.17.3.4 📌 Importante
Soglia fissa per le metriche individuali: 0.85
Divisione sicura: se il denominatore è zero, usa 0
Formattazione: due cifre decimali
Rendi monotona da destra verso sinistra
La Figura 1.13 presenta una simulazione di questa domanda
1.17.3.6 🐍 Suggerimento per calcolare l’AP (con regola del trapezio)
def calcular_AP(verdades, confiancas, limiares): m =len(limiares) precisoes = [0.0] * m sensibilidades = [0.0] * mfor i inrange(m): p, s = calcular_metricas(verdades, confiancas, limiares[i]) precisoes[m-1-i] = p sensibilidades[m-1-i] = s prec_mono = precisoes.copy()for i inrange(m-2, -1, -1):if prec_mono[i] < prec_mono[i+1]: prec_mono[i] = prec_mono[i+1] AP =0.0for i inrange(1, m):# Regola del trapezio: media delle altezze per la base area_trapezio = (prec_mono[i-1] + prec_mono[i]) /2.0 AP += area_trapezio * (sensibilidades[i] - sensibilidades[i-1])return precisoes, prec_mono, sensibilidades, AP
Modifica i campioni (classe reale e confidenza) oppure scegli uno scenario predefinito per visualizzare la matrice di confusione, la curva P-S e il valore di mAP in tempo reale.
Figura 1.13: Simulatore EP01_03: Mean Average Precision (mAP) e Curva P-S
%%writefile EP01_03.cpp// your solution
Overwriting EP01_03.cpp
TestSuite("EP01_03.cpp").run()
✔️ EP01_03.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_03.cases
🔍 Test di C++: EP01_03.cpp
⚠️ EP01_03.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.4 EP01_04 🖼️ Lettura e Informazioni di un’Immagine in Matrice
In questa attività, devi scrivere un programma che elabori un’immagine digitale rappresentata come una matrice di pixel in scala di grigi.
Leggi due interi L e C, che rappresentano il numero di righe e di colonne.
Leggi i L * C valori interi che compongono la matrice dell’immagine (ogni valore tra 0 e 255).
Calcola e stampa le seguenti informazioni:
Il numero di righe.
Il numero di colonne.
Il valore del pixel più grande (Massimo).
Il valore del pixel più piccolo (Minimo).
La Media aritmetica di tutti i pixel.
📌 Importante:
L’output deve seguire esattamente il formato etichettato (es: Righe: X).
Il valore della media deve essere formattato con due cifre decimali.
Vedi un simulatore interattivo per questa domanda nella Figura 1.14 (griglia interattiva per la visualizzazione delle intensità e dei calcoli in tempo reale).
1.17.4.1 🧠 Perché è importante? – L’Immagine come Dato
Ogni immagine digitale è, in fondo, una struttura dati. In scala di grigi a 8 bit, ogni pixel è un valore scalare. Estrarre statistiche di base è il primo passo per:
Operazione
Utilità Pratica
Massimo/Minimo
Identificare se l’immagine è “slavata” (basso contrasto) o saturata.
Media
Calcolare la luminosità globale della scena per regolazioni dell’esposizione.
Normalizzazione
Ridimensionare i valori in intervalli come \([0, 1]\) nelle reti neurali.
1.17.4.2 📋 Attività (specifica per VPL)
Input:
La prima riga contiene l’intero L (righe).
La seconda riga contiene l’intero C (colonne).
Le righe successive contengono gli elementi della matrice.
Output:
Cinque righe formattate secondo l’esempio:
Righe: L
Colonne: C
Max: V
Min: V
Media: V.VV
1.17.4.3 📌 Esempi
Input
Output
Osservazione
2
3
0 128 255
50 100 200
Righe: 2
Colonne: 3
Max: 255
Min: 0
Media: 122.17
Immagine piccola ad alto contrasto
📊 Simulatore EP01_04: Statistiche Locali dei PixelMatrice 5x5
Fai clic su qualsiasi pixel della matrice per incrementare il suo livello di grigio (passo +51) oppure usa le azioni predefinite qui sotto per osservare i limiti e la media globale.
Media Globale (µ)
0.00
Valore Max
0
Valore Min
0
Figura 1.14: Simulatore EP01_04: Statistiche dei pixel in matrice discreta 5x5
%%writefile EP01_04.cpp// your solution
Overwriting EP01_04.cpp
TestSuite("EP01_04.cpp").run()
✔️ EP01_04.cases esiste già in casos/
📋 5 caso/i caricato/i da casos/EP01_04.cases
🔍 Test di C++: EP01_04.cpp
⚠️ EP01_04.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.5 EP01_05 🔄 Negativo di un’Immagine in Ton di Grigio
In questa attività, si deve scrivere un programma che calcoli il negativo di un’immagine digitale.
Leggere due interi L e C, che rappresentano il numero di righe e colonne.
Leggere i valori interi che compongono la matrice dell’immagine.
Per ogni pixel, applicare la trasformazione di inversione:
\[pixel_{negativo} = 255 - pixel_{originale}\]
Stampare la matrice risultante, mantenendo il formato originale (L righe e C colonne).
📌 Importante:
I valori di ciascuna riga nell’output devono essere separati da uno spazio bianco.
L’output deve contenere solo i numeri della matrice risultante.
Vedere un simulatore interattivo per questa questione nella Figura 1.15 (confronto in tempo reale tra la matrice originale e il suo negativo).
1.17.5.1 🧠 Perché è importante? – Inversione di Intensità
Il negativo è una trasformazione lineare di base che inverte la scala di luminosità. È uno strumento essenziale per l’occhio umano per identificare dettagli chiari “nascosti” in sfondi più scuri, ed è ampiamente utilizzato in:
Applicazione
Utilità
Immagini Mediche
Migliora la visualizzazione di anomalie in tessuti densi (es. Radiografie).
Astronomia
Evidenziare galassie e nebulose deboli contro il vuoto dello spazio.
Arti Digitali
Effetti estetici e preparazione di maschere di selezione.
1.17.5.2 📋 Compito (specifica per VPL)
Input:
La prima riga contiene l’intero L.
La seconda riga contiene l’intero C.
Le righe successive contengono gli elementi della matrice.
Output:
La matrice invertita con L righe e C colonne.
1.17.5.3 📌 Esempi
Input
Output
Osservazione
2
3
0 128 255
50 100 255
255 127 0
205 155 55
Dove c’era 0 (nero) diventa 255 (bianco)
🌓 Simulatore EP01_05: Trasformazione del Negativo di Immaginep' = 255 - p
Fai clic sui pixel della matrice Originale (p) per modificarne i livelli di grigio (passo di +51) e osserva l'effetto dell'inversione complementare sulla matrice Negativo (255 - p).
ORIGINALE (p)
NEGATIVO (255 - p)
💡La trasformazione del negativo mappa i toni scuri (vicini a 0) su toni chiari (vicini a 255) e viceversa, essendo utile per evidenziare dettagli scuri su sfondi chiari.
Figura 1.15: Simulatore EP01_05: Trasformazione del Negativo dell’Immagine (Inversione di Intensità)
%%writefile EP01_05.cpp// your solution
Overwriting EP01_05.cpp
TestSuite("EP01_05.cpp").run()
✔️ EP01_05.cases esiste già in casos/
📋 7 caso/i caricato/i da casos/EP01_05.cases
🔍 Test di C++: EP01_05.cpp
⚠️ EP01_05.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.6 EP01_06 🎨 Conversione RGB → Toni di Grigio (ITU-R BT.601)
In questa attività, devi scrivere un programma che converta pixel colorati (RGB) in toni di grigio utilizzando la ponderazione fisiologica dello standard ITU-R BT.601.
Leggi due interi L e C, che rappresentano il numero di righe e colonne.
Leggi L × C triple di interi, dove ogni terna rappresenta i canali R (Rosso), G (Verde) e B (Blu) di un pixel.
Per ogni pixel, calcola il valore di grigio (\(g\)) usando la formula:
\[g = \text{round}(0.299 \times R + 0.587 \times G + 0.114 \times B)\]
Stampa la matrice risultante (L righe e C colonne) contenente i valori interi convertiti.
📌 Importante:
Utilizza la funzione round() del tuo linguaggio per garantire l’arrotondamento corretto all’intero più vicino.
L’output deve contenere solo i valori di grigio, mantenendo la struttura a matrice (separati da spazio sulla riga).
Vedi un simulatore interattivo per questa domanda nella Figura 1.16 (regola gli slider per osservare come ogni colore contribuisce alla luminosità finale).
1.17.6.1 🧠 Perché non usare semplicemente la media?
L’occhio umano non percepisce tutti i colori con la stessa intensità. Siamo molto più sensibili al Verde che al Blu a causa della nostra evoluzione biologica. Lo standard ITU-R BT.601 utilizza pesi specifici per creare un’immagine in grigio che appaia naturalmente corretta alla nostra visione:
Canale
Peso
Percezione Umana
🟢 Verde
58.7%
Massima sensibilità (distinzione del fogliame).
🔴 Rosso
29.9%
Sensibilità media.
🔵 Blu
11.4%
Bassa sensibilità (toni più scuri).
1.17.6.2 📋 Compito (specifica per VPL)
Input:
La prima riga contiene l’intero L.
La seconda riga contiene l’intero C.
Le righe successive contengono triple di interi R G B per ogni pixel.
Output:
La matrice di grigi con L righe e C colonne.
1.17.6.3 📌 Esempi
Input
Output
Osservazione
1
3
255 0 0 0 255 0 0 0 255
76 150 29
Nota come il Verde (150) sia più luminoso del Blu (29)
🎨 Simulatore EP01_06: Percezione del Colore & Pesi ITU-R BT.601RGB → Scala di Grigi
Regola l'intensità dei canali Rosso (R), Verde (G) e Blu (B) per osservare come ciascuna componente contribuisce in modo ponderato al valore finale di luminanza nei livelli di grigio.
REGOLAZIONE DEI CANALI COLORE
80
180
30
RGB Originale
Toni di Grigio
💡Il canale Verde (G) ha il peso maggiore (0.587) a causa della maggiore sensibilità spettrale del sistema visivo umano alle lunghezze d'onda del verde.
Figura 1.16: Simulatore EP01_06: Conversione RGB in Scala di Grigi (Ponderazione Percettiva ITU-R BT.601)
%%writefile EP01_06.cpp// your solution
Overwriting EP01_06.cpp
TestSuite("EP01_06.cpp").run()
✔️ EP01_06.cases esiste già in casos/
📋 7 caso/i caricato/i da casos/EP01_06.cases
🔍 Test di C++: EP01_06.cpp
⚠️ EP01_06.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.7 EP01_07 ⚫ Soglia Manuale: Immagine Binaria
In questa attività, devi scrivere un programma che esegua la segmentazione di un’immagine tramite sogliatura (thresholding).
Leggi due interi L e C, che rappresentano le dimensioni della matrice.
Leggi un intero T, che sarà il valore della soglia (taglio).
Leggi i valori interi della matrice.
Per ogni pixel \(p\), applica la seguente regola di binarizzazione:
\[\text{risultato} = \begin{cases} 255 & \text{se } p > T \\ 0 & \text{se } p \le T \end{cases}\]
Stampa la matrice risultante contenente solo i valori 0 o 255.
📌 Importante:
Presta attenzione all’operatore: il pixel diventa bianco (255) solo se è strettamente maggiore di \(T\).
L’output deve mantenere la struttura della matrice (L righe e C colonne).
Consulta un simulatore interattivo per questo problema su Figura 1.17 (regola il cursore di \(T\) per osservare come gli oggetti vengono isolati dallo sfondo).
1.17.7.1 🧠 Cos’è la Segmentazione?
La sogliatura è il metodo più semplice per separare gli oggetti di interesse dallo sfondo dell’immagine. Trasformando i toni di grigio in bianco e nero puro, creiamo una mappa binaria che facilita il conteggio degli oggetti o l’identificazione delle forme:
Valore del Pixel (\(p\))
Condizione
Risultato Finale
Scuro (\(p \le T\))
Sfondo/Rumore
0 (Nero)
Chiaro (\(p > T\))
Oggetto/In evidenza
255 (Bianco)
1.17.7.2 📋 Compito (specifica per VPL)
Input:
La prima riga contiene l’intero L.
La seconda riga contiene l’intero C.
La terza riga contiene l’intero T (soglia).
Le righe successive contengono gli elementi della matrice.
Output:
La matrice binarizzata (0 o 255) con L righe e C colonne.
1.17.7.3 📌 Esempi
Input
Output
Osservazione
2
4
128
0 100 128 200
50 129 255 64
0 0 0 255
0 255 255 0
Nota che il valore 128 è diventato 0 (poiché \(128 \le 128\))
🎛️ Simulatore EP01_07: Soglia Interattiva dell'ImmagineBinario: 0 o 255
128
Ingresso (Scala di Grigi)
Uscita (Maschera Binaria)
Pixel con intensità maggiore di 128 (p > 128) diventano bianchi (255); altrimenti, diventano neri (0).
Figura 1.17: Simulatore EP01_07: Soglia Globale Interattiva (Binarizzazione p > T)
%%writefile EP01_07.cpp// your solution
Overwriting EP01_07.cpp
TestSuite("EP01_07.cpp").run()
✔️ EP01_07.cases esiste già in casos/
📋 7 caso/i caricato/i da casos/EP01_07.cases
🔍 Test di C++: EP01_07.cpp
⚠️ EP01_07.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.8 EP01_08 🎨 Rimappatura per Intervallo di Intensità
In questa attività, devi scrivere un programma che applichi trasformazioni lineari distinte a diverse regioni di intensità dell’immagine.
Leggi due interi L e C, che rappresentano le dimensioni della matrice.
Leggi gli interi T (soglia), δ₁ (delta 1) e δ₂ (delta 2).
Leggi i valori interi della matrice.
Per ogni pixel \(p\), applica la regola di rimappatura condizionale:
\[\text{risultato} = \begin{cases} p + \delta_1 & \text{se } p < T \\ p + \delta_2 & \text{se } p \ge T \end{cases}\]
Stampa la matrice risultante con i nuovi valori di intensità.
📌 Importante:
δ₁ è l’offset applicato ai pixel scuri (al di sotto della soglia).
δ₂ è l’offset applicato ai pixel chiari (maggiori o uguali alla soglia).
I casi di test garantiscono che il risultato sia sempre nell’intervallo valido da 0 a 255, quindi non è necessario gestire saturazione o arrotondamenti.
L’output deve mantenere la struttura di matrice (L righe e C colonne).
1.17.8.1 🧠 Trasformazione Condizionale dei Pixel
Nell’elaborazione digitale delle immagini (PDI), spesso dobbiamo trattare le regioni in modo indipendente. Questa tecnica consente, ad esempio, di schiarire solo le ombre di una fotografia (aumentando i pixel scuri) senza bruciare la luminosità delle aree già chiare, o viceversa.
Intervallo di Intensità
Condizione
Operazione
Pixel scuri
\(p < T\)
\(p + \delta_1\)
Pixel chiari
\(p \ge T\)
\(p + \delta_2\)
1.17.8.2 📋 Compito (specifica per VPL)
Input:
La prima riga contiene gli interi L e C.
La seconda riga contiene gli interi T, δ₁ e δ₂.
Le righe successive contengono gli elementi della matrice.
Output:
La matrice trasformata con L righe e C colonne, con valori separati da spazi.
1.17.8.3 📌 Esempi
Input
Output
Osservazione
2 4 128 60 -40 0 100 150 255 80 128 200 30
60 160 110 215 140 88 160 90
I pixel < 128 sommano 60. I pixel ≥ 128 sottraggono 40.
🎛️ Simulatore EP01_08: Rimappatura per Intervallo Condizionalep < T → p + δ₁ | p ≥ T → p + δ₂
Regola la soglia di separazione (T) e gli spostamenti di luminosità (δ₁ e δ₂) per applicare trasformazioni di intensità differenziate nelle regioni scure e chiare dell'immagine.
128
+60
-40
Input Originale (p)
Risultato Trasformato
Regola attiva: p < 128 → p + (+60) | p ≥ 128 → p + (-40)
Figura 1.18: Simulatore EP01_08: Rimappatura per Intervallo Condizionale (Luminosità e Contrasto per Soglia)
%%writefile EP01_08.cpp// your solution
Overwriting EP01_08.cpp
TestSuite("EP01_08.cpp").run()
✔️ EP01_08.cases esiste già in casos/
📋 8 caso/i caricato/i da casos/EP01_08.cases
🔍 Test di C++: EP01_08.cpp
⚠️ EP01_08.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.9 EP01_09 🏁 Schema a Scacchiera: Matrice a Quadretti
In questa attività, devi scrivere un programma che generi un’immagine sintetica con il motivo di una scacchiera.
Leggi due interi L (righe) e C (colonne).
Genera una matrice in cui i valori alternano tra 0 (nero) e 1 (bianco).
La logica di riempimento deve seguire la regola di parità:
L’elemento nella posizione \((0,0)\) è sempre 0.
Un pixel nella posizione \((i, j)\) sarà 1 se la somma degli indici \((i + j)\) è dispari.
Un pixel nella posizione \((i, j)\) sarà 0 se la somma degli indici \((i + j)\) è pari.
📌 Importante:
I colori devono alternarsi correttamente sia orizzontalmente che verticalmente.
L’output deve essere la matrice stampata riga per riga, con gli elementi separati da uno spazio.
Consulta un simulatore interattivo per questo problema su Figura 1.19 (regola le dimensioni per visualizzare la costruzione della griglia e l’output testuale corrispondente).
1.17.9.1 🧠 Motivi Sintetici
Creare motivi geometrici è un esercizio fondamentale per padroneggiare la logica degli indici nelle matrici. Nell’elaborazione digitale delle immagini, il motivo a scacchiera non è solo estetico; è ampiamente utilizzato per:
Applicazione
Utilità
Calibrazione della fotocamera
Stimare i parametri intrinseci ed estrinseci dell’obiettivo.
Correzione della distorsione
Identificare e correggere l’effetto “a barile” o “a cuscino” negli obiettivi grandangolari.
Mappatura 3D
Proiettare motivi noti per ricostruire superfici in sistemi a luce strutturata.
1.17.9.2 📋 Compito (specifica per VPL)
Input:
Una riga contenente l’intero L (righe).
Una riga contenente l’intero C (colonne).
Output:
La matrice a scacchiera con L righe e C colonne, stampata con spazi tra gli elementi.
1.17.9.3 📌 Esempi
Input
Output
Osservazione
3
4
0 1 0 1
1 0 1 0
0 1 0 1
Nota che ogni riga inizia con l’inverso della precedente
🏁 Simulatore EP01_09: Generatore di Matrice a Scacchiera(i + j) % 2
Modifica il numero di righe (L) e colonne (C) per osservare come l'alternanza di parità delle coordinate della griglia costruisce la matrice binaria a scacchiera.
×
GRIGLIA GRAFICA
USCITA ATTESA (VALORI)
Figura 1.19: Simulatore EP01_09: Generatore di Motivo a Scacchiera (Logica di Parità (i + j) % 2)
%%writefile EP01_09.cpp// your solution
Overwriting EP01_09.cpp
TestSuite("EP01_09.cpp").run()
✔️ EP01_09.cases esiste già in casos/
📋 7 caso/i caricato/i da casos/EP01_09.cases
🔍 Test di C++: EP01_09.cpp
⚠️ EP01_09.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.10 EP01_10 📄 Metadati: Lettura di File PGM
In questa attività, devi leggere un file immagine nel formato PGM (Portable Gray Map) ed estrarne le dimensioni dall’intestazione.
Il formato PGM (P2) è un file di testo semplice (ASCII) che memorizza immagini in scala di grigi.
Il file possiede un’intestazione strutturata nel seguente modo:
Versione: L’identificatore P2.
Commenti: Righe opzionali che iniziano con # (devono essere ignorate).
Dimensioni: Due interi che rappresentano Larghezza e Altezza.
Massimo: Un intero che rappresenta l’intensità massima (generalmente 255).
Dopo l’intestazione, seguono i dati dei pixel.
📌 Importante:
Lettura del File: Devi aprire il file indicato nell’esempio utilizzando la funzione open() di Python.
Ordine di Uscita: Contrariamente all’ordine presente nel file, l’output atteso deve essere nel formato di tupla: (Altezza, Larghezza, Canali).
Poiché i file PGM sono in scala di grigi, il numero di Canali è sempre 1.
Vedi un simulatore interattivo per questa domanda alla Figura 1.20 (regola le dimensioni per vedere come viene generata l’intestazione ASCII).
1.17.10.1 🧠 Comprendere il formato PGM
Il formato PGM è uno dei più semplici per l’elaborazione delle immagini. Essendo in testo puro, consente di visualizzare i metadati e persino i valori dei pixel aprendo il file in un blocco note:
Componente
Esempio
Significato
Numero Magico
P2
Identifica che è un PGM in formato testo (ASCII).
Commento
# CREATOR...
Riga informativa ignorata dal processore.
Dimensioni
397 343
397 colonne (Larghezza) e 343 righe (Altezza).
Intensità
255
Definisce il valore del bianco puro (scala da 0 a 255).
1.17.10.2 📋 Compito (specifica per VPL)
Input:
Nessun input da tastiera. Il programma deve leggere il file "aula01fig03b.pgm" presente nella directory di esecuzione.
Output:
Una tupla contenente (Altezza, Larghezza, 1).
1.17.10.3 📌 Esempi
Nome del File
Output Atteso
Osservazione
“aula01fig03b.pgm”
(343, 397, 1)
Nota l’inversione dell’ordine: Altezza prima
📄 Simulatore EP01_10: Struttura del File PGMASCII P2 & Formato (H, W, C)
Modifica le dimensioni di larghezza (W) e altezza (H) per osservare l'assemblaggio dinamico dell'intestazione PGM e il formato della tupla dell'array risultante in Python (Righe × Colonne × Canali).
DEFINIZIONI DELL'IMMAGINE
💡 Attenzione alla convenzione: L'intestazione PGM dichiara prima W H (Larghezza × Altezza), mentre l'array in Python/NumPy riporta la tupla come (H, W, C) (Righe × Colonne × Canali).
CONTENUTO DEL FILE (.PGM)
P2
# CREATOR: UFABC PDI / EP01_10
397343
255
120 134 210 0 85 255 ...
Uscita della Funzione mm.readImg (Formato dell'Array):
(343, 397, 1)
Figura 1.20: Simulatore EP01_10: Struttura del file PGM (Intestazione ASCII P2 e mappatura a tupla Python)
NotaNota
Il file necessario per questo EP verrà scaricato automaticamente dal repository tramite il seguente codice:
✔️ EP01_10.cases esiste già in casos/
📋 1 caso/i caricato/i da casos/EP01_10.cases
🔍 Test di C++: EP01_10.cpp
⚠️ EP01_10.cpp: file vuoto (meno di 3 righe). Test saltati.
1.17.11 EP01_11 📈 Analisi di Vicinato: Filtro di Massimo 1D
In questa attività, devi implementare un semplice filtro morfologico di massimo che opera su un segnale unidimensionale (vettore).
Leggi un intero n, che rappresenta la dimensione del vettore.
Leggi i n elementi interi che compongono il vettore originale v1.
Crea un nuovo vettore v2, in cui ogni posizione \(i\) è il risultato del confronto tra l’elemento corrente e i suoi vicini immediati:
\[v2[i] = \max(v1[i-1],\; v1[i],\; v1[i+1])\]
📌 Importante:
Bordi: Alle estremità del vettore (indici \(0\) e \(n-1\)), il vicinato ha solo due elementi (quello stesso e l’unico vicino disponibile). All’indice \(0\), confronta solo \(v1[0]\) e \(v1[1]\). All’ultimo indice, confronta solo \(v1[n-2]\) e \(v1[n-1]\).
Output: Stampa l’intestazione “v2:” seguita dai valori del vettore risultante, uno per riga.
Per un simulatore interattivo su questo problema, vedi Figura 1.21 (passa il mouse sui risultati per visualizzare la finestra di vicinato utilizzata nel calcolo).
1.17.11.1 🧠 Perché analizzare i vicini?
Nell’elaborazione delle immagini, il valore di un pixel raramente è isolato; dipende dal contesto circostante. Il Filtro di Massimo è la base dell’operazione di Dilatazione nella morfologia matematica e serve a:
Funzione
Effetto Visivo
Enfatizzazione
Espande strutture luminose e “ingrossa” oggetti chiari.
Rimozione del Rumore
Elimina piccoli punti neri (rumore “sale e pepe” scuro).
Riempimento
Chiude piccoli buchi o lacune in forme binarie.
1.17.11.2 📋 Compito (specifica per VPL)
Input:
Un intero n.
Nelle righe successive, i n elementi interi del vettore.
Output:
La stringav2: sulla prima riga.
Nelle righe successive, ogni elemento di v2 (uno per riga).
1.17.11.3 📌 Esempi
Input
Output
Osservazione
5
10
20
5
30
15
v2:
20
20
30
30
30
All’indice 1: max(10, 20, 5) = 20
📈 Simulatore EP01_11: Filtro di Massimo Locale 1DFinestra 1x3
Clicca sugli elementi di v1 (Ingresso) per generare nuovi valori individuali oppure passa il mouse sulle celle di v2 (Risultato) per ispezionare la finestra locale di vicinanza.
Vettore v1 (Ingresso)
⬇️
Vettore v2 (Risultato del Massimo)
Passa il cursore del mouse su una cella del vettore v2 per analizzare la finestra di massimo locale.
Figura 1.21: Simulatore EP01_11: Filtro di Massimo Locale 1D (Vicinato 1x3 con Condizione di Bordo)
%%writefile EP01_11.cpp// your solution
Overwriting EP01_11.cpp
TestSuite("EP01_11.cpp").run()
✔️ EP01_11.cases esiste già in casos/
📋 7 caso/i caricato/i da casos/EP01_11.cases
🔍 Test di C++: EP01_11.cpp
⚠️ EP01_11.cpp: file vuoto (meno di 3 righe). Test saltati.