Por qué funciona: Unidad 1.2: Captura de la imagen, Unidad 1.3: La imagen como datos
Corrección gamma en OpenCV con una tabla de consulta
Construye una tabla de consulta de 256 entradas y aplícala con cv2.LUT para aclarar más los tonos oscuros que los claros. Con dos pasos de menos, multiplicar gana (9,1 contra 10,7); con tres, gana la gamma (13,9 contra 15,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 corrección gamma aclara más las partes oscuras de una foto que las claras, así que las sombras se abren sin que las luces se quemen.

Dónde ya lo has visto: el ajuste Gamma de los videojuegos y de los menús del monitor; el control del medio de la herramienta Niveles en GIMP y Photoshop, que es un control de gamma.
TL;DR. Una curva gamma levanta las sombras más que las luces y nunca empuja un valor más allá del blanco, lo que la convierte en la mejor corrección cuando una foto está muy oscura. Con dos pasos de menos, multiplicar todavía gana (9,1 contra 10,7); con tres pasos, gana la gamma (13,9 contra 15,1).
El problema
La lección 3 corrigió la foto dos pasos oscura multiplicando todos los valores por 2,4. El proyecto tiene también una más oscura, tomada a 1/2 segundo, más de tres pasos por debajo del objetivo de 5 segundos. Multiplicarla por 4,2 es lo mejor que puede hacer una ganancia, y todo lo que empezó por encima de 60 termina en 255: la ganancia que rescata las sombras destruye todo lo demás. La celda de más abajo lo cuenta.
Una tabla de consulta
La corrección gamma pasa cada valor por una curva de potencia: divide entre 255 para que vaya de 0 a 1, eleva a γ y multiplica otra vez por 255 [1]. Un γ menor que 1 aclara, mayor que 1 oscurece, y 0 sigue siendo 0 mientras 255 sigue siendo 255 con cualquier γ [2].
Como solo hay 256 valores de entrada posibles, la curva se calcula una vez en una tabla de 256 respuestas, y cv2.LUT reemplaza cada píxel por su entrada. Así se aplica rápido cualquier mapeo fijo por valor (gamma, curvas de contraste, gradación de color).
import cv2
import numpy as np
ref = cv2.imread("reference.webp")
def distance(a, b):
return np.abs(a.astype(int) - b.astype(int)).mean()
def gamma(img, g):
table = (np.arange(256) / 255) ** g * 255
table = table.round().astype(np.uint8)
return cv2.LUT(img, table)
for name, g, a in [("dark.webp", 0.55, 2.4),
("darker.webp", 0.40, 4.2)]:
img = cv2.imread(name)
by_gamma = distance(gamma(img, g), ref)
by_gain = distance(cv2.convertScaleAbs(img, alpha=a), ref)
print(name, "gamma", g, round(by_gamma, 1),
"| times", a, round(by_gain, 1))dark.webp gamma 0.55 10.7 | times 2.4 9.1 darker.webp gamma 0.4 13.9 | times 4.2 15.1
| Función | Argumentos | Qué hace |
|---|---|---|
np.arange(256) | — | las 256 entradas posibles, de 0 a 255 |
cv2.LUT(img, table) | una imagen de 8 bits, una tabla de 256 entradas | reemplaza cada valor v por table[v], canal por canal |
En la foto dos pasos oscura, el multiplicador todavía va un poco por delante. En la que está tres pasos oscura, va por delante la gamma. Cuanto más lejos está la foto del objetivo, más importa que la gamma nunca recorte.
darker = cv2.imread("darker.webp")
times = cv2.convertScaleAbs(darker, alpha=4.2)
for name, x in [("as shot", darker), ("times 4.2", times),
("gamma 0.40", gamma(darker, 0.40))]:
share = (x.max(axis=2) == 255).mean()
print(name, "- at 255:", round(100 * share, 1), "%")
def half(x):
return cv2.resize(x, None, fx=0.5, fy=0.5)
small = [half(x) for x in (times, gamma(darker, 0.40), ref)]
cv2.imshow("times 4.2 | gamma 0.40 | reference", np.hstack(small))as shot - at 255: 6.2 % times 4.2 - at 255: 14.4 % gamma 0.40 - at 255: 6.4 %
Multiplicar por 4,2 lleva la proporción de píxeles en 255 de 6,2% a 14,4%, mientras que la gamma solo la lleva a 6,4%: la versión multiplicada (izquierda) perdió la lámpara, la carta y el charco de luz en un blanco liso. La versión con gamma conserva los bordes de la carta y el aro de la lámpara, y sus sombras son más claras y más planas que las del objetivo, que es de donde viene la distancia que le queda.
La lección 5 prueba la alternativa automática, que elige la curva a partir del histograma en lugar de a partir de un objetivo.
Rómpelo
La fórmula solo funciona con valores entre 0 y 1. Eleva valores de 0–255 a la potencia directamente y el píxel más claro, 255, se convierte en 2550.40, cerca de 9.
# Elevar valores de 0-255 a una potencia sin escalar antes a 0-1
wrong = darker.astype(float) ** 0.40
wrong = np.clip(wrong, 0, 255).astype(np.uint8)
print("largest value after the wrong gamma:", wrong.max())
cv2.imshow("gamma on 0-255 instead of 0-1", wrong)largest value after the wrong gamma: 9
La imagen entera se hunde en un casi negro.
Tu turno
Un γ mayor que 1 oscurece. Elige un γ que deje la sobreexpuesta bright.webp a menos de 36 del objetivo.
# elige una gamma que oscurezca bright.webp hacia el objetivo
g = 1.0
print(round(distance(gamma(cv2.imread("bright.webp"), g), ref), 1))Por qué funciona
La imagen revelada de una cámara ya es la luz en bruto pasada por una curva con esta forma, y The response curve (en inglés) mide esa curva en esta misma escena de la lámpara. Aplicar otra curva de potencia dobla los tonos todavía más en el mismo sentido, y por eso un único γ puede deshacer buena parte de un error de exposición que un multiplicador simple no puede. El costo se ve en Quantization and bit depth (en inglés): estirar los pocos niveles más oscuros de una foto de 8 bits sobre un rango más amplio reparte los 256 niveles muy finos, y los degradados suaves pueden formar bandas.
Errores comunes
- Olvidar el
/ 255. Es la celda Rómpelo. - Construir la tabla en float.
cv2.LUTquiere una tabla del mismo tipo que la imagen; constrúyela comouint8, como hace la celda. - Confundir hacia dónde va γ. En esta fórmula, γ menor que 1 aclara. Algunas herramientas definen la gamma al revés (como 1/γ), así que revisa qué significa un control antes de copiar su número.
- Esperar que la gamma recupere luces quemadas. 255 va a 255 con cualquier γ.
Referencias
[1] OpenCV. Changing the contrast and brightness of an image!, section “Gamma correction” (tutorial, doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv
[2] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 3.2 “Some Basic Intensity Transformation Functions”, Power-Law (Gamma) Transformations, p. 139. Pearson.
[3] 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