Por qué funciona: Unidad 1.2: Captura de la imagen, Unidad 1.3: La imagen como datos
Ecualización de histograma y CLAHE en OpenCV
Prueba las correcciones automáticas de contraste de OpenCV, cv2.equalizeHist y CLAHE, en una foto oscura con una lámpara encendida, y mide por qué quedan más lejos de la exposición objetivo (30,6 y 35,5) que un simple multiplicador (9,1).
Traducción asistida por máquina del original en inglés. Los números, el código y las referencias se comprobaron contra él; el texto no fue revisado por un hablante nativo. Leer el original. Los labs interactivos y el código están en inglés.
La ecualización de histograma es el botón de contraste automático: reparte el brillo de una foto de forma pareja por todo el rango, sin que nadie le diga qué tan oscura estaba la foto.

cv2.equalizeHist sobre su brillo. Fotograma del HDR Photographic Survey [5].Dónde ya lo has visto: los botones Contraste automático y Niveles automáticos de los editores de fotos; Colores › Automático › Ecualizar en GIMP.
TL;DR. La ecualización automática hace que cualquier foto use todo el rango de brillo, lo cual es distinto de hacer que parezca una mejor exposición de la escena. En la foto oscura deja una distancia de 30,6 hasta la exposición objetivo, y su variante local, CLAHE, 35,5, frente a 9,1 de un simple multiplicador.
El problema
Las lecciones 3 y 4 del proyecto corrigieron la foto con un solo número, elegido comparando con el objetivo. La mayoría de las veces no hay objetivo. Una corrección automática tiene que decidir solo a partir de la foto, y la regla obvia es dejar el histograma plano: usar cada nivel de brillo más o menos por igual.
Ecualiza el brillo
cv2.equalizeHist recibe un canal de 8 bits y lo reasigna para que su histograma quede tan plano como lo permitan los valores [1, 2]. Para conservar los colores, se aplica solo al brillo: convierte a YCrCb, cuyo canal Y es el brillo y Cr, Cb llevan el color, ecualiza Y y vuelve a convertir.
CLAHE (contrast-limited adaptive histogram equalization, ecualización adaptativa de histograma con contraste limitado) hace lo mismo en pequeños bloques de la imagen (8 × 8 por defecto) y limita cuánto puede estirarse cada bloque, para que el ruido en las zonas uniformes no se dispare [2]. La versión por bloques la describieron Pizer y sus colegas en 1987 [4], y la de contraste limitado es de Zuiderveld, publicada en 1994 [3].
import cv2
import numpy as np
img = cv2.imread("dark.webp")
ref = cv2.imread("reference.webp")
def distance(a, b):
return np.abs(a.astype(int) - b.astype(int)).mean()
def on_brightness(img, op):
# ecualiza solo el brillo: Y de YCrCb, color intacto
ycc = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
ycc[..., 0] = op(ycc[..., 0])
return cv2.cvtColor(ycc, cv2.COLOR_YCrCb2BGR)
equalized = on_brightness(img, cv2.equalizeHist)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
local = on_brightness(img, clahe.apply)
print("equalizeHist:", round(distance(equalized, ref), 1))
print("CLAHE: ", round(distance(local, ref), 1))equalizeHist: 30.6 CLAHE: 35.5
| Función | Argumentos | Qué hace |
|---|---|---|
cv2.equalizeHist(channel) | un canal uint8 | aplana su histograma |
cv2.createCLAHE(clipLimit, tileGridSize) | cuánto puede estirarse un bloque; la rejilla de bloques | un objeto CLAHE |
clahe.apply(channel) | un canal uint8 | CLAHE sobre ese canal |
cv2.COLOR_BGR2YCrCb | — | brillo (Y) separado del color (Cr, Cb) |
Los dos superan a no hacer nada (47,0) y los dos pierden frente a un solo multiplicador (9,1).
def half(x):
return cv2.resize(x, None, fx=0.5, fy=0.5)
small = [half(x) for x in (equalized, local, ref)]
cv2.imshow("equalizeHist | CLAHE | reference", np.hstack(small))
La ecualización gasta el rango de brillo en lo que más abunda, y esta foto es casi toda pared y mesa oscuras. Esas zonas se estiran por casi todo el rango y quedan en gris medio, mientras que la lámpara y la zona iluminada, una pequeña parte de los píxeles, quedan apretadas en los últimos niveles de arriba. CLAHE evita que la lámpara se queme todavía más, pero aclara cada bloque oscuro por su cuenta, así que la pared y la mesa pierden la transición de oscuro a claro que tiene la exposición objetivo. El propio tutorial de OpenCV menciona este caso: la ecualización “won’t work good in places where there is large intensity variations”, cuando hay píxeles claros y oscuros a la vez [2].
Rómpelo
equalizeHist acepta un solo canal. Pásale la imagen a color y OpenCV se detiene con una aserción.
# equalizeHist recibe un canal de 8 bits, no una imagen a color
cv2.equalizeHist(img)Traceback (most recent call last): File "<exec>", line 2, in <module> cv2.error: OpenCV(4.11.0) /home/runner/work/pyodide-recipes/pyodide-recipes/packages/opencv-python/build/opencv-python-4.11.0.86/opencv/modules/imgproc/src/histogram.cpp:3440: error: (-215:Assertion failed) _src.type() == CV_8UC1 in function 'equalizeHist'
El mensaje indica la comprobación que falló, _src.type() == CV_8UC1: un canal (C1) de valores de 8 bits sin signo (8U).
Tu turno
Aplica el objeto CLAHE de la primera celda a la versión en gris de img y guarda el resultado en out.
out = None # CLAHE sobre la versión en gris de img
print(out)Cuándo usarlos
La ecualización y CLAHE son las herramientas correctas cuando no hay una exposición objetivo que alcanzar y la meta es ver detalle, o cuando la imagen alimenta un algoritmo que necesita contraste más que realismo. Son la herramienta equivocada para devolverle a una foto el aspecto que debería haber tenido, que es lo que midió este proyecto.
El proyecto se queda con la foto multiplicada y la termina en la lección 6.
Por qué funciona
Aplanar el histograma pasa cada valor por la distribución acumulada de la imagen, de modo que partes iguales de los píxeles caen en partes iguales del rango de salida [1]. Esa regla no sabe nada de la escena, y por eso le fue mal aquí. Por qué la exposición objetivo tiene la distribución de tonos que tiene, y por qué ninguna exposición única abarca a la vez la lámpara y la mesa oscura, está en Noise and dynamic range (en inglés) y High dynamic range (en inglés).
Errores comunes
- Una imagen a color en
equalizeHist. La celda Rómpelo. - Ecualizar B, G y R por separado. Cada canal recibe su propia reasignación, y el balance de color se desplaza. Ecualiza solo el brillo.
- Un
clipLimitgrande en una imagen con ruido. CLAHE amplifica entonces el ruido en las regiones uniformes, lo que el límite existe para evitar [2]. - Leer “más contraste” como “mejor”. Una foto ecualizada suele verse más llamativa y a menudo está más lejos de la realidad. Mide contra una referencia cuando tengas una.
Referencias
[1] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 3.3 “Histogram Processing”, Histogram Equalization, p. 148, and Local Histogram Processing, p. 173. Pearson.
[2] OpenCV. Histograms - 2: Histogram Equalization (Python tutorial, doc/py_tutorials/py_imgproc/py_histograms/py_histogram_equalization/py_histogram_equalization.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv
[3] Zuiderveld, K. (1994). Contrast Limited Adaptive Histogram Equalization. In Graphics Gems (ISBN 978-0-12-336156-1), pp. 474–485. Elsevier. doi:10.1016/B978-0-12-336156-1.50061-6
[4] Pizer, S. M., Amburn, E. P., Austin, J. D., Cromartie, R., Geselowitz, A., Greer, T., ter Haar Romeny, B., Zimmerman, J. B., & Zuiderveld, K. (1987). Adaptive histogram equalization and its variations. Computer Vision, Graphics, and Image Processing, 39(3), 355–368. doi:10.1016/S0734-189X(87)80186-X
[5] Fairchild, M. D. (2007). The HDR Photographic Survey. IS&T 15th Color Imaging Conference, pp. 233–238. doi:10.2352/CIC.2007.15.1.art00044