Equalizzazione dell’istogramma from Scratch in Python

Articolo in lingua originale di Rohit Krishna

immagine di Tumisu da Pixabay

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.

Una radiografia sbiadita in scala di grigi e l’istogramma delle sue intensità di pixel | Immagine dell’autore

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.

Illustrazione con e senza equalizzazione dell’istogramma | immagine dell’autore

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.

Plot con e senza equalizzazione | immagine dell’autore

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.

Istogramma con e senza zoom dell’immagine e della cdf | Immagine dell’autore

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

  1. https://en.wikipedia.org/wiki/Histogram_equalization
  2. https://medium.com/hackernoon/histogram-equalization-in-python-from-scratch-ebb9c8aa3f23

Share:

Contenuti
Torna in alto