Blog
Ecualización de histogramas
- Publicado por: Alberto Sosa-Costa
- Categoría: Blog OpenCV Procesamiento de Imagenes

Aprender el concepto de ecualización de histogramas y su utilidad para aumentar el contraste de las imágenes.
Teoría
Considere una imagen cuyos valores de píxeles están limitados solo a un rango específico de valores. Por ejemplo, una imagen muy brillante tendrá todos los píxeles confinados en valores altos. Sin embargo, una buena imagen (con mayor contraste) tendrá diferentes valores de píxeles en todas las regiones de la imagen. Por lo tanto, una técnica usualmente empleada para aumentar el contraste de una imagen es estirar su histograma hacia cualquiera de los extremos (como se muestra en la imagen inferior, tomada de wikipedia). Esto es precisamente lo que hace la ecualización de histogramas.
En la práctica, luego de aplicar la ecualización no se obtiene un histogram completamente plano como el mostrado en la figura anterior. Esto se debe a que los valores que pueden tomar los píxeles son discretos. Para más información sobre la transformación matemática utilizada para la ecualización de histogramas, se recomienda leer el artículo de wikipedia: Ecualización de histogramas.
Aquí nos limitaremos a ver un ejemplo de su implementación utilizando las funciones de Numpy. Más adelante veremos la función correspondiente en OpenCV.
import numpy as np from matplotlib import pyplot as plt img1 = cv2.imread('wikimage.jpg',0) #Genera el historama de la imagen hist,bins = np.histogram(img1.flatten(),256,[0,256]) #Genera la función de distribución acumulada (cdf por sus siglas en inglés) cdf = hist.cumsum() cdf_normalized = cdf * hist.max()/ cdf.max() #Genera los gráficos del histograma y de la función de distribución acumulada plt.plot(cdf_normalized, color = 'b') plt.hist(img1.flatten(),256,[0,256], color = 'r') plt.xlim([0,256]) plt.legend(('cdf','histograma'), loc = 'upper right') plt.show()
El comando img.flatten() transforma el arreglo bidimensional de la imagen en un vector unidimensional
En el histograma se observa que la mayoría de los píxeles tienen valores cercanos a cero (consistente con una imagen oscura). Para aumentar el contraste de esta imagen necesitamos ecualizar el histograma, o sea expandirlo en todo el rango de valores de 0 a 255.
Para esto encontramos los valores máximo y mínimo de la función de distribución (excluyendo los ceros) y aplicamos la ecuación de ecualización del histograma tal y como aparece en wikipedia. Para excluir los ceros utilizaremos la función np.ma.masked_equal(), que enmascara (ignora) los valores iguales al número que se pase en el segundo argumento.
#Enmascara los valores iguales a cero cdf_m = np.ma.masked_equal(cdf,0) #Aplica la transformación de ecualización cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min()) #Rellena los valores previamente enmascarados con ceros cdf = np.ma.filled(cdf_m,0).astype('uint8') #Aplica la ecualización a los píxeles de la imagen original img2 = cdf[img1] #Grafica la imagen resultante de aplicar la ecualización del histograma cv2.imshow('image',img2) cv2.waitKey(0) cv2.destroyAllWindows()
Otra característica importante es que, incluso si utilizaremos la misma imagen anterior pero más brillante (en lugar de tan oscura como la que hemos utilizado), después de la ecualización tendremos casi la misma imagen que obtuvimos luego de ecualizar la oscura. Como resultado, esto se usa como una “herramienta de referencia” para igualar las condiciones de iluminación de imágenes diferentes. Esto es útil en muchos casos. Por ejemplo, en el reconocimiento facial, antes de entrenar los datos de la cara, las imágenes de las caras se ecualizan para hacer que todas tengan las mismas condiciones de iluminación.
Ecualización de histogramas en OpenCV
OpenCV tiene una función para hacer esto, cv2.equalizeHist(). Su entrada es solo una imagen en escala de grises y la salida es nuestra imagen luego de ecualizado su histograma.
A continuación se muestra un fragmento de código que muestra el uso de esta función para la misma imagen utilizada anteriormente.
img = cv2.imread('wikimage.jpg',0) equ = cv2.equalizeHist(img) res = np.hstack((img,equ)) #Agrupa las imágenes una al lado de la otra cv2.imwrite('res.png',res)
Ahora puedes utilizar diferentes imágenes con diferentes condiciones de luz, ecualizarlas y verificar los resultados.
La ecualización del histograma es buena cuando el histograma de la imagen está confinado a una región en particular. No funcionará bien en lugares donde hay grandes variaciones de intensidad donde el histograma cubre una región grande, es decir, que están presentes píxeles brillantes y oscuros.
Contraste de ecualización adaptable del histograma (CLAHE por sus siglas en inglés)
La ecualización de histograma que hemos visto anteriormente considera el contraste global de la imagen. Sin emabrgo, en muchos casos, esto no es una buena idea. Por ejemplo, la imagen inferior muestra una imagen de entrada y su resultado después de la ecualización del histograma global.
Es cierto que el contraste de fondo ha mejorado después de la ecualización del histograma. Sin embargo, si comparamos la cara de la estatua en ambas imágenes, notamos que hemos perdido la mayor parte de la información debido al exceso de brillo. Esto es debido a que su histograma no está confinado a una región en particular como vimos en casos anteriores (Trate de trazar el histograma de la imagen de entrada, obtendrá más intuición).
Para resolver este problema, se utiliza la ecualización de histograma adaptativo. En este caso, la imagen se divide en pequeños bloques llamados “tiles” (tileSize es 8×8 por defecto en OpenCV). Luego, cada uno de estos bloques se ecualiza como siempre. Entonces, en un área pequeña, el histograma se limitaría a una región pequeña (a menos que haya ruido). Si hay ruido, se amplificará. Para evitar esto, se aplica la limitación de contraste. Si cualquier BIN del histograma está por encima del límite de contraste especificado (por defecto 40 en OpenCV), esos píxeles se recortan y se distribuyen uniformemente a otros BINS antes de aplicar la ecualización de histograma. Después de la ecualización, para eliminar artefactos en los bordes de los mosaicos, se aplica la interpolación bilineal.
A continuación, el fragmento de código muestra cómo aplicar CLAHE en OpenCV:
import numpy as np import cv2 img = cv2.imread('ejemplo.png',0) # Crea un objeto CLAHE (los argumentos son opcionales). clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) cl1 = clahe.apply(img) cv2.imwrite('clahe.jpg',cl1)

Felicidades, cada vez sabes más sobre histogramas con OpenCV.
Aprende mucho mas de Histogramas en nuestro curso Python de OpenCV:
[…] ➡ Ecualización de Histogramas […]