Equalizzazione dell’istogramma from Scratch in Python
Articolo in lingua originale di Rohit Krishna
Cosa è un istogramma?
È un modo per rappresentare visivamente la distribuzione di un insieme di dati dividendolo in intervalli uguali (o “bins”) e contando il numero dati che rientrano in ciascun bin. Le barre risultanti nell’istogramma mostrano la frequenza dei dati che appartengono a ciascun intervallo.
Equalizzazione dell’istogramma/immagine
Spesso le immagini possono essere difficili da decifrare per l’osservatore umano, ad esempio perché troppo scure. È stato stabilito che l’uomo è in grado di distinguere tra 700 e 900 sfumature di grigio in condizioni di visione ottimali.
Tuttavia nelle sezioni molto scure o molto luminose di un’immagine la differenza appena percettibile (just noticeable difference, JND) si riduce notevolmente. Ciònonostante, è anche chiaro che per l’uomo è più facile distinguere differenze maggiori, quindi se la distribuzione delle scale di grigio in un’immagine viene migliorata, ciò facilita la comprensione da parte degli osservatori umani.
Una tecnica per migliorare la distribuzione delle scale di grigio in un’immagine è l’equalizzazione dell’istogramma.
Questa tecnica cerca di distribuire uniformemente le scale di grigio di un’immagine, in modo che l’istogramma risultante sia quasi piatto, con il risultato di aumentare il contrasto dell’immagine.
Ora che avete idea di cosa sia, passiamo all’implementazione in Python.
1. Istogramma dell’immagine in scala di grigi
Quando si equalizza un’immagine a colori, in genere si equalizza solo il canale della luminanza (scala di grigi), altrimenti i colori possono risultare distorti.
A tale scopo, è necessario convertire l’immagine RGB o CMY in formato YUV o HSV.
Nel formato YUV il canale Y è la luminanza, U e V sono la crominanza.
Nel formato HSV il canale V è la luminanza, gli altri sono la saturazione (S) e la tonalità (hue, H).
image = cv2.imread("<path-to-img>") image = cv2.cvtColor(image, cv2.COLOR_BGR2YUV)
2. Calcolare la funzione di densità cumulata dell’istogramma
L’algoritmo cerca di distribuire in modo efficace il numero di intensità di ciascun valore dei pixel nell’immagine in modo quasi uniforme, senza rimuovere le informazioni contenenti la luminanza.
Nota: nel diagramma qua sopra la funzione di densità cumulata (cdf) è normalizzata tra 0 e 10.000
Bene, se ora mi chiedeste “Ok, il nostro obiettivo è quello di provare a equalizzare il numero di pixel, ma perché dobbiamo calcolare la cdf?”
Per mappare le nuove intensità dell’immagine che risultano in un’equalizzazione del canale.
Avrete una comprensione più chiara osservando i plot sottostanti.
Come si può vedere, non ci sono molti valori di pixel tra 40 e 65, quindi se mettiamo quasi lo stesso valore (la cdf in quella regione) in quella regione, i pixel di quelle posizioni diventeranno quasi uguali (secondo la cdf), aumentando il contrasto.
hist, _ = np.histogram(luminance_channel.flatten(), 256, [0, 255]) cdf = hist.cumsum()
3. Normalizzare la somma cumulata della distribuzione tra 0 e 255
Quando mappiamo l’intensità della cdf nell’immagine, è ovvio che in un’immagine a 8 bit il valore minimo del pixel che si può osservare è 0 e il massimo è 255, ma la funzione di densità cumulativa sarà molto più grande di questo, quindi dobbiamo normalizzarla tra 0 e 255.
cdf_normalized = ((cdf - cdf.min()) * 255) / (cdf.max() - cdf.min())
4. Mappare l’intensità della somma cumulata all’immagine
E un processo facile e semplice.
# NOTE: cdf_normalized is a np.ndarray channel_result = cdf_normalized[luminance_channel.flatten()] channel_result = np.reshape(channel_result, luminance_channel.shape)
Il codice completo
import numpy as np import cv2 def histEqualization(channel: np.ndarray) -> np.ndarray: hist, _ = np.histogram(channel.flatten(), 256, [0, 255]) cdf = hist.cumsum() cdf_norm = ((cdf - cdf.min()) * 255) / (cdf.max() - cdf.min()) channel_new = cdf_norm[channel.flatten()] channel_new = np.reshape(channel_new, channel.shape) return channel_new img = cv2.imread("./lungs.jpg") result = cv2.cvtColor(img, cv2.COLOR_BGR2YUV) result[..., 0] = histEqualization(result[..., 0]) result = cv2.cvtColor(result, cv2.COLOR_YUV2BGR) cv2.imshow("res", result) cv2.waitKey(0)
Limiti dell’equalizzazione globale dell’istogramma
Se un’immagine contiene multiple luminosità variabili, allora questo metodo non funzionerà come ci si aspetta. In questi casi serve utilizzare l’equalizzazione adattativa dell’istogramma o l’equalizzazione adattativa limitata al contrasto (CLAHE).
Conclusione
Spero che ora abbiate una comprensione chiara dell’algoritmo di equalizzazione dell’istogramma. Lavorando a progetti reali non serve fare tutti questi sforzi, si possono usare librerie come OpenCV. In python, attraverso OpenCV, si possono fare equalizzazioni dell’istogramma come segue:
import cv2 img = cv2.imread("<path-to-img>") result = cv2.equalizeHist(img)
Fonti
- https://en.wikipedia.org/wiki/Histogram_equalization
- https://medium.com/hackernoon/histogram-equalization-in-python-from-scratch-ebb9c8aa3f23