Por qué funciona: Unidad 1.2: Captura de la imagen, Unidad 1.3: La imagen como datos

Corregir una foto mal expuesta con OpenCV

El primer proyecto de OpenCV en la Práctica: abrir una foto tomada dos pasos demasiado oscura, leer su histograma, probar cinco formas de corregirla y medir cada una contra una toma más larga y bien expuesta de la misma escena (el objetivo). Multiplicar es lo que más se acerca; las correcciones automáticas, lo que más se aleja.

Luis Condados · · 7 min de lectura Principiante

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 foto tomada dos pasos oscura (mitad izquierda) y la misma foto multiplicada por 2,4 (mitad derecha).
La foto tomada dos pasos oscura (mitad izquierda) y la misma foto multiplicada por 2,4 (mitad derecha).

Una foto tomada con poca luz a menudo se puede aclarar hasta que parezca una exposición más larga de la misma escena, y este proyecto mide qué forma de aclararla se acerca más.

La animación sube un único ajuste, el multiplicador que se aplica a cada píxel, y muestra la foto corregida junto a una toma más larga y bien expuesta de la misma escena (el objetivo), con la diferencia entre ambas bajando.

Una foto oscura de una lámpara de escritorio y una carta de colores que se aclara paso a paso mientras un multiplicador sube de 1,0 a 2,4, junto a la exposición objetivo, con la distancia hasta ella bajando de 47,0 a 9,1.
El multiplicador sube de 1,0 a 2,4 y la distancia a la exposición objetivo (a la derecha) baja de 47,0 a 9,1. Fotogramas del HDR Photographic Survey [1].
1 · abrir la fotocv2.imread2 · leer su histogramacv2.calcHist3 · corregir exposiciónganancia · gamma · ecualizar4 · medir la diferenciacontra la toma objetivo5 · recortar y guardarcv2.resize · cv2.imwriteprobar otro ajuste
  1. Abrir la foto. Cargar el archivo en una rejilla de números, uno por color en cada punto (lección 1).
  2. Leer su histograma. Contar cuántos píxeles hay en cada brillo, lo que muestra qué tan oscura está la foto y qué está ya quemado (lección 2).
  3. Corregir la exposición. Cambiar todos los valores con una sola regla: sumar una constante, multiplicar por una ganancia, curvar con una curva gamma o dejar que un ecualizador automático decida (lecciones 3, 4 y 5).
  4. Medir la diferencia. Comparar la foto corregida con una toma más larga y bien expuesta de la misma escena (el objetivo), píxel a píxel, y promediar la diferencia. Cuanto más bajo, más cerca; 0 es idéntico.
  5. Recortar, redimensionar, guardar. Quedarte con la parte que quieres, reducirla sin aliasing y escribirla en un archivo (lección 6).

Dónde ya lo has visto: los controles de Exposición y Brillo en los editores de fotos del celular y en Lightroom; el botón Automático junto a ellos; el histograma en la pantalla de reproducción de una cámara.

TL;DR. Aclarar una foto oscura escalando sus valores se acerca mucho más a la exposición objetivo que sumarles algo o dejar que una herramienta automática decida, y nada recupera lo que la cámara quemó. Con dos pasos de menos (casi una cuarta parte de la luz que recibió el objetivo), una ganancia de 2,4 deja una diferencia media de 9,1 (de 255) con el objetivo, sumar brillo deja 18,0 y la ecualización de histograma 30,6. Con tres pasos de menos gana la gamma, 13,9 contra 15,1.

La escena y el objetivo

Todas las lecciones trabajan sobre una sola escena: una lámpara de escritorio, una tarjeta y una carta de colores, fotografiadas dieciocho veces con tiempos de obturación de 1/800 s a 30 s para el HDR Photographic Survey de Mark Fairchild [1]. La unidad 1.2 de la ruta de Fundamentos (en inglés) mide los mismos dieciocho archivos raw. Este módulo usa cuatro, revelados de la misma forma:

ArchivoObturaciónGris medioGris ≥ 250Papel
reference.webp5 s104.711.7%el objetivo contra el que se evalúa cada corrección
dark.webp1.3 s56.67.8%unos dos pasos por debajo
darker.webp1/2 s40.86.3%más de tres pasos por debajo
bright.webp20 s173.827.2%dos pasos por encima

La distancia que se usa en todo el módulo es la diferencia absoluta media entre la foto corregida y el objetivo, sobre todos los píxeles y canales de color, en la escala de 0 a 255.

La corrección completa, en una celda

Python · OpenCV
import cv2
import numpy as np

img = cv2.imread("dark.webp")
ref = cv2.imread("reference.webp")

fixed = cv2.convertScaleAbs(img, alpha=2.4)
gap = np.abs(fixed.astype(int) - ref.astype(int)).mean()
print("distance to the reference:", round(gap, 1))

cv2.imwrite("fixed.jpg", fixed, [cv2.IMWRITE_JPEG_QUALITY, 90])
side = [cv2.resize(x, None, fx=0.5, fy=0.5) for x in (img, fixed, ref)]
cv2.imshow("dark | fixed | reference", np.hstack(side))
Salida
distance to the reference: 9.1
dark | fixed | reference
dark | fixed | reference

Qué logra cada corrección

Cada corrección se evaluó en su mejor ajuste, encontrado probando su parámetro en pasos pequeños (valores entre paréntesis).

CorrecciónOscura, 1.3 sMás oscura, 1/2 sClara, 20 s
nada47.062.368.4
sumar una constante (valor)18.0 (50)25.7 (65)68.4 (0)
multiplicar (ganancia)9.1 (2.4)15.1 (4.2)22.8 (0.5)
gamma (γ)10.7 (0.55)13.9 (0.4)33.4 (2.35)
equalizeHist sobre el brillo30.631.930.3
CLAHE sobre el brillo35.551.358.8

De la tabla salen cuatro cosas:

  • Una ganancia le gana a un desplazamiento. Una exposición más larga multiplica la luz, así que un multiplicador es la forma correcta de corrección, y reduce a la mitad la diferencia que deja la suma (lección 3). El propio tutorial de OpenCV advierte que el desplazamiento deja un velo gris [3].
  • La gamma supera a la ganancia a medida que la foto se oscurece. Una ganancia lo bastante grande para levantar tres pasos de sombra lleva a blanco todo lo que está por encima de 60; la gamma lleva 0 a 0 y 255 a 255 con cualquier ajuste, así que nunca satura [2] (lección 4).
  • Las correcciones automáticas quedan más lejos. La ecualización reparte el brillo según lo frecuente que es cada nivel [2], y esta foto es casi toda pared y mesa oscuras (lección 5).
  • La sobreexposición es la dirección difícil. Nada acerca la foto clara a menos de 22,8, porque el 15,6% de sus píxeles está en 250 o más donde el objetivo todavía tiene detalle. Esos valores se perdieron.

Qué sacar de esto

  • Una foto uno o dos pasos oscura: multiplica por una sola ganancia y compárala con algo en lo que confíes (una tarjeta gris, una segunda toma). Aquí eso llevó 47,0 a 9,1.
  • Mucho más oscura que eso: usa gamma, o una ganancia y una gamma juntas, y acepta sombras más planas. Aquí 62,3 pasó a 13,9.
  • Sin referencia, y solo necesitas ver detalle: ecualización o CLAHE, sabiendo que el resultado no se parecerá a la escena.
  • Sobreexpuesta: vuelve a disparar o haz bracketing. El software solo llevó la foto clara hasta 22,8, y el 15,6% de ella no se pudo recuperar con ningún ajuste. High dynamic range (en inglés) muestra cómo se combinan varias exposiciones de esta escena para conservarlo todo.
  • Cuándo vale la pena un modelo: este módulo no probó un realzador aprendido. La cifra a superar es 9,1 con dos pasos de menos y 13,9 con tres.

Limitaciones

  • El objetivo es una elección, no la verdad. La toma de 5 segundos ya tiene el 11,7% de sus píxeles en 250 o más. La foto oscura conservó detalle en la lámpara que el objetivo perdió, así que una corrección que lo preserva se evalúa como peor.
  • Los mejores ajustes se eligieron mirando la respuesta. Cada ganancia y cada gamma de arriba se eligió para minimizar la distancia al objetivo. Sin un objetivo tendrías que elegir a ojo o con una regla, y te iría peor.
  • Una escena, una cámara. Una lámpara en un cuarto oscuro es un rango de luz extremo. Una escena más plana favorecería más a la ecualización, y el orden de las correcciones podría cambiar.
  • La distancia pesa todos los píxeles por igual. Casi todo este fotograma es pared y mesa oscuras, así que la puntuación refleja sobre todo las sombras. Una métrica que pesara más la lámpara y la carta ordenaría las correcciones de otra forma.
  • Las entradas son WebP con calidad 90. Todos los números se calcularon a partir de los archivos publicados, así que el Ejecutar del lector los reproduce, pero no son los valores en bruto del sensor.

Las lecciones

  1. Abrir una imagen y leer sus píxeles en OpenCV
  2. Histogramas de imagen en OpenCV
  3. Brillo y contraste en OpenCV
  4. Corrección gamma en OpenCV con una tabla de consulta
  5. Ecualización de histograma y CLAHE en OpenCV
  6. Recortar, redimensionar y guardar una imagen en OpenCV

Todas las celdas del módulo están también en un notebook, para ejecutarlo en tu computadora con el OpenCV que instala pip: ábrelo en Colab. Sus salidas coinciden con las de la página, celda por celda.

Cómo se obtuvieron estos números
DatosHDR Photographic Survey, “Luxo Double Checker” [1]: archivos _MDF0010.NEF (1/2 s), _MDF0011.NEF (1.3 s), _MDF0013.NEF (5 s), _MDF0015.NEF (20 s). Términos de investigación y uso no comercial; descargados, no redistribuidos en raw
Reveladorawpy 0.27.0 (LibRaw 0.22.1): balance de blancos de la cámara, sin aclarado automático, medio tamaño, 8 bits; redimensionados a 960 px de ancho con INTER_AREA; guardados como WebP calidad 90
PuntuacionesDiferencia absoluta media con reference.webp sobre todos los píxeles y canales. Rejillas: suma de 0 a 150 en pasos de 5; ganancia de 0.2 a 6.0 en pasos de 0.1; gamma de 0.20 a 3.00 en pasos de 0.05; CLAHE clipLimit=2.0, tileGridSize=(8, 8)
En la páginaPyodide 314.0.7, OpenCV 4.11.0, NumPy 2.4.6 (la salida que muestra cada celda la genera npm run pycells con este build)
En localOpenCV 5.0.0, NumPy 2.5.2, Python 3.12; todos los números de la tabla de arriba coinciden con los de la página hasta un decimal
MáquinaIntel Core i7-12700H, Ubuntu 22.04.5 LTS. No se cronometra nada; los números son deterministas
Comandosuv run --extra sensing sensing-download, luego uv run --extra sensing practice-m1 --assets --media en fundamentals-lab; npm run pycells en el repositorio del sitio
Artefactooutput/practice_m1_numbers.json

Para seguir leyendo

  • Gonzalez & Woods, Digital Image Processing, capítulo 3 [2], para las transformaciones de intensidad y el procesamiento de histogramas con la teoría detrás de cada una.
  • El tutorial de OpenCV Changing the contrast and brightness of an image! [3], que corrige otra foto subexpuesta con α, β y γ.
  • Image sensing (en inglés), la unidad de Fundamentos sobre esta misma escena, para ver por qué la cámara saturó donde lo hizo y cómo las dieciocho exposiciones se combinan en una.

Referencias

[1] 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

[2] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), chapter 3 “Intensity Transformations and Spatial Filtering”: section 3.2 “Some Basic Intensity Transformation Functions”, p. 136, and section 3.3 “Histogram Processing”, p. 147. Pearson.

[3] OpenCV. Changing the contrast and brightness of an image! (tutorial, doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv