5.19. Correcciones tonales

Las correcciones tonales modifican cómo se distribuyen el brillo y el color en una imagen capturada: son los ajustes que aplica una aplicación cuando un fotograma queda demasiado oscuro, demasiado brillante, demasiado plano o sesgado hacia un color equivocado.

Las correcciones pertenecen a dos familias: los ajustes de brillo y contraste, que redistribuyen el brillo, y los ajustes de color, que cambian el color con el que se lee cada píxel. Ambas tienen sus análogos en el ISP del sensor, que corrige cada fotograma a medida que entra; los métodos descritos aquí se aplican a una Image ya capturada, a posteriori, para los casos en que el fotograma necesita más corrección de la que el ISP proporcionó.

5.19.1. Ecualización de histograma

La operación de estiramiento de contraste más sencilla es la ecualización de histograma. La idea es reasignar los valores de los píxeles de modo que el histograma de la salida sea lo más plano posible: que cada valor aparezca con una frecuencia aproximadamente igual. El efecto visual es que una imagen de bajo contraste (cuyo histograma se concentra en una banda estrecha) se convierte en una de alto contraste cuyos píxeles cubren todo el rango de 0 a 255.

histeq() ejecuta la ecualización:

img.histeq()

El mecanismo es directo. Se calcula la función de distribución acumulativa (CDF) del histograma del origen; cada valor de píxel de entrada se asigna a su posición en la CDF, escalada al rango de salida. Donde los píxeles ya estaban distribuidos de forma uniforme, la asignación se aproxima a la identidad; donde los píxeles se acumulaban en un único nivel de brillo, la asignación los dispersa estirando ese brillo a través de un rango más amplio de valores de salida.

El resultado es espectacular en escenas de bajo contraste: la diferencia entre una fotografía tenue de interior y la misma fotografía tras aplicar histeq es a menudo la diferencia entre «ilegible» y «perfectamente legible». La contrapartida es que la operación amplifica todo, incluido el ruido del sensor. En una escena con detalle real de bajo contraste que recuperar, histeq es la respuesta correcta; en una escena limpia y bien expuesta que simplemente no lo necesita, histeq introduce ruido visible.

5.19.2. CLAHE: ecualización adaptativa

La ecualización de histograma es global: utiliza una única CDF calculada a partir de toda la imagen y la aplica en todas partes. Eso funciona en imágenes cuyo rango de brillo es aproximadamente uniforme, pero falla en escenas con regiones oscuras y brillantes localizadas: la CDF se ve arrastrada hacia el lado que tiene más píxeles, y el lado opuesto queda sobrecorregido.

La variante adaptativa es la Ecualización Adaptativa de Histograma con Contraste Limitado, comúnmente denominada CLAHE. En lugar de una única CDF global, CLAHE calcula una CDF independiente para cada pequeña baldosa de la imagen, ecualiza cada baldosa con su propia CDF y mezcla los bordes de las baldosas entre sí. El resultado es que los ajustes de brillo se producen localmente: la esquina en sombra obtiene su propia ecualización sin que la esquina brillante la arrastre en la dirección equivocada.

El indicador adaptive=True cambia histeq() al modo CLAHE:

img.histeq(adaptive=True, clip_limit=10)

El parámetro clip_limit es la parte de CLAHE a la que se refiere el «contraste limitado» del nombre. La ecualización local puede sobreamplificar el ruido en regiones planas donde la CDF tiene muy pocos valores distintos; el límite de recorte restringe con qué intensidad puede redistribuirse cualquier intervalo individual, lo que evita la amplificación del ruido sin dejar de permitir el estiramiento del contraste donde importa. Un valor en torno a 10 es un punto de partida razonable; los valores mayores hacen que CLAHE trabaje con más intensidad a costa de un ruido más visible, y los menores lo hacen más suave.

CLAHE es más costoso que el histeq global, pero produce resultados más limpios en escenas donde el brillo está distribuido de forma desigual, que son la mayoría de las escenas del mundo real.

5.19.3. Gamma, contraste y brillo

La ecualización de histograma es la forma basada en datos de reasignar el brillo. La forma independiente de los datos consiste en aplicar una curva elegida, parametrizada mediante unos pocos ajustes fáciles de afinar. gamma() proporciona tres:

img.gamma(gamma=1.0, contrast=1.0, brightness=0.0)

Cada parámetro aplica una transformación específica a cada píxel:

gamma pasa el valor de cada píxel a través de la función de potencia output = input ** (1 / gamma). El significado estándar: los valores mayores que 1.0 aclaran la imagen y elevan los tonos medios (la clásica corrección de «gamma de monitor»); los valores menores que 1.0 la oscurecen. El parámetro es no lineal: conserva los puntos de negro y de blanco y solo reconfigura la distribución intermedia, que es el comportamiento adecuado cuando el objetivo es recuperar detalle en las regiones de sombra o de luces altas sin aplastar los extremos existentes.

contrast pasa cada píxel a través de una multiplicación directa en torno al punto de gris medio. Los valores mayores que 1.0 aumentan el contraste (lo oscuro se vuelve más oscuro, lo brillante más brillante y el gris medio permanece igual); los valores menores que 1.0 reducen el contraste.

brightness suma una constante al valor de cada píxel. Los valores positivos aclaran y los negativos oscurecen. El desplazamiento es uniforme (no se conserva nada), lo que rara vez es lo que una aplicación quiere por sí solo, pero combina bien con una pasada de contraste para recentrar el resultado.

Los tres parámetros se componen: una única llamada a gamma() puede aplicar una curva gamma, luego una multiplicación de contraste y luego un desplazamiento de brillo, todo en una sola pasada. El orden es gamma primero, después contraste y después brillo, lo que coincide con el orden que da los resultados más intuitivos cuando los tres tienen valores distintos a los predeterminados.

5.19.4. Balance de blancos automático

La familia de color de las correcciones tonales comienza con el balance de blancos automático. El mismo mecanismo que el ISP del sensor ejecuta como parte de la cadena de imagen -ajustar las ganancias relativas de los canales rojo, verde y azul para que un parche de color gris promedio se lea como gris real- también está disponible como operación posterior a la captura sobre una Image terminada:

img.awb()

El valor predeterminado utiliza el algoritmo gray-world: se asume que el color promedio de toda la imagen es gris neutro, y las ganancias por canal se ajustan para que así sea. La forma alternativa max=True utiliza el algoritmo white-patch: se asume que los píxeles más brillantes son blanco neutro, y las ganancias se ajustan para que así sea. Ambos funcionan en RGB565 y en Bayer en bruto; ninguno funciona en escala de grises (donde no hay color que equilibrar) ni en YUV (donde la representación del color no es la que utilizan estos algoritmos).

Cuándo recurrir a la forma posterior a la captura en lugar del balance de blancos automático del ISP: cuando la elección del ISP fue poco adecuada para la escena concreta, cuando la aplicación está cargando fotogramas de referencia desde el disco que se capturaron en condiciones distintas, o cuando el juicio del color importa lo suficiente como para que la aplicación quiera volver a ejecutarlo con su propia elección de algoritmo.

5.19.5. La matriz de corrección de color

Cuando la corrección de color que necesita la imagen no es el escalado por canal que ofrece el balance de blancos, sino una mezcla de canales más general, la operación a la que recurrir es la matriz de corrección de color. El método ccm() aplica una matriz de 3 por 3 (o de 3 por 4 con desplazamiento) que multiplica el vector (r, g, b) de cada píxel para producir un nuevo vector (r, g, b):

img.ccm([[1.1, -0.05, -0.05],
        [-0.05, 1.1, -0.05],
        [-0.05, -0.05, 1.1]])

La matriz permite que la aplicación corrija la diafonía entre los canales de color: cuando la respuesta del sensor de rojo incluye algo de luz verde, por ejemplo, la matriz puede restar una fracción del canal verde de la salida del rojo para compensarlo. Combinada con un desplazamiento por canal, la forma de 3 por 4 permite además que la aplicación vuelva a poner a cero cada canal.

El material sobre la cadena del ISP cubre el porqué de las matrices de corrección de color. La forma posterior a la captura sobre la Image es simplemente la misma operación, aplicada a posteriori.