Por que funciona: Unidade 1.2: Captura da imagem, Unidade 1.3: A imagem como dado
Correção gama no OpenCV com uma tabela de consulta
Monte uma tabela de consulta de 256 entradas e aplique com cv2.LUT para clarear mais os tons escuros que os claros. Com dois stops de falta, multiplicar vence (9,1 contra 10,7); com três, a gama vence (13,9 contra 15,1).
Tradução assistida por máquina do original em inglês. Números, código e referências foram conferidos com ele; o texto não foi revisado por um falante nativo. Ler o original. Os labs interativos e o código estão em inglês.
A correção gama clareia mais as partes escuras de uma foto do que as claras, então as sombras se abrem sem que as altas luzes estourem.

Onde você já viu isso: a opção Gama dos jogos e dos menus de monitor; o controle do meio da ferramenta Níveis no GIMP e no Photoshop, que é um controle de gama.
TL;DR. Uma curva gama levanta as sombras mais que as altas luzes e nunca empurra um valor além do branco, o que faz dela a melhor correção quando a foto está muito escura. Com dois stops de falta, multiplicar ainda vence (9,1 contra 10,7); com três stops, a gama vence (13,9 contra 15,1).
O problema
A lição 3 corrigiu a foto dois stops escura multiplicando todos os valores por 2,4. O projeto tem também uma mais escura, tirada em 1/2 segundo, mais de três stops abaixo do alvo de 5 segundos. Multiplicar essa foto por 4,2 é o melhor que um ganho consegue, e tudo o que começou acima de 60 termina em 255: o ganho que salva as sombras destrói todo o resto. A célula mais abaixo faz essa conta.
Uma tabela de consulta
A correção gama passa cada valor por uma curva de potência: divide por 255 para que vá de 0 a 1, eleva a γ e multiplica por 255 de novo [1]. Um γ abaixo de 1 clareia, acima de 1 escurece, e 0 continua 0 enquanto 255 continua 255 para qualquer γ [2].
Como só existem 256 valores de entrada possíveis, a curva é calculada uma vez numa tabela de 256 respostas, e cv2.LUT troca cada pixel pela sua entrada. É assim que qualquer mapeamento fixo por valor (gama, curvas de contraste, gradação de cor) é aplicado rápido.
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
| Função | Argumentos | O que faz |
|---|---|---|
np.arange(256) | — | as 256 entradas possíveis, de 0 a 255 |
cv2.LUT(img, table) | uma imagem de 8 bits, uma tabela de 256 entradas | troca cada valor v por table[v], canal por canal |
Na foto dois stops escura, o multiplicador ainda fica um pouco à frente. Na que está três stops escura, a gama fica à frente. Quanto mais longe a foto está do alvo, mais pesa o fato de a gama nunca estourar.
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 leva a fatia de pixels em 255 de 6,2% para 14,4%, enquanto a gama a leva só a 6,4%: a versão multiplicada (à esquerda) perdeu o abajur, a carta e a poça de luz para um branco chapado. A versão com gama mantém as bordas da carta e o aro do abajur, e suas sombras são mais claras e mais planas que as do alvo, que é de onde vem a distância que sobra.
A lição 5 testa a alternativa automática, que escolhe a curva a partir do histograma em vez de a partir de um alvo.
Quebre
A fórmula só funciona com valores entre 0 e 1. Eleve valores de 0–255 à potência direto e o pixel mais claro, 255, vira 2550.40, cerca de 9.
# Elevando valores de 0-255 a uma potência sem escalar para 0-1 antes
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
A imagem inteira desaba para quase preto.
Sua vez
Um γ acima de 1 escurece. Escolha um γ que deixe a superexposta bright.webp a menos de 36 do alvo.
# escolha uma gama que escureça bright.webp em direção ao alvo
g = 1.0
print(round(distance(gamma(cv2.imread("bright.webp"), g), ref), 1))Por que funciona
A imagem revelada por uma câmera já é a luz bruta passada por uma curva com esse formato, e The response curve (em inglês) mede essa curva nesta mesma cena do abajur. Aplicar outra curva de potência dobra os tons ainda mais no mesmo sentido, e é por isso que um único γ consegue desfazer boa parte de um erro de exposição que um multiplicador simples não desfaz. O custo aparece em Quantization and bit depth (em inglês): esticar os poucos níveis mais escuros de uma foto de 8 bits sobre uma faixa maior espalha os 256 níveis, e degradês suaves podem formar faixas.
Erros comuns
- Esquecer o
/ 255. É a célula Quebre. - Montar a tabela em float.
cv2.LUTquer uma tabela do mesmo tipo da imagem; monte emuint8, como faz a célula. - Confundir o sentido de γ. Nesta fórmula, γ abaixo de 1 clareia. Algumas ferramentas definem a gama ao contrário (como 1/γ), então confira o que um controle significa antes de copiar o número dele.
- Esperar que a gama recupere altas luzes estouradas. 255 vira 255 para qualquer γ.
Referências
[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