Por que funciona: Unidade 1.2: Captura da imagem, Unidade 1.3: A imagem como dado

Corrigir uma foto mal exposta com OpenCV

O primeiro projeto de OpenCV na Prática: abrir uma foto tirada dois stops escura demais, ler seu histograma, testar cinco formas de corrigi-la e medir cada uma contra uma foto mais longa e bem exposta da mesma cena (o alvo). Multiplicar chega mais perto; as correções automáticas ficam mais longe.

Luis Condados · · 7 min de leitura Iniciante

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 foto tirada dois stops escura (metade esquerda) e a mesma foto multiplicada por 2,4 (metade direita).
A foto tirada dois stops escura (metade esquerda) e a mesma foto multiplicada por 2,4 (metade direita).

Uma foto tirada com pouca luz muitas vezes pode ser clareada até parecer uma exposição mais longa da mesma cena, e este projeto mede qual forma de clarear chega mais perto.

A animação aumenta um único ajuste, o multiplicador aplicado a cada pixel, e mostra a foto corrigida ao lado de uma foto mais longa e bem exposta da mesma cena (o alvo), com a diferença entre as duas caindo.

Uma foto escura de um abajur de mesa e uma carta de cores clareando passo a passo enquanto um multiplicador sobe de 1,0 a 2,4, ao lado da exposição-alvo, com a distância até ela caindo de 47,0 para 9,1.
O multiplicador sobe de 1,0 a 2,4 e a distância até a exposição-alvo (à direita) cai de 47,0 para 9,1. Quadros do HDR Photographic Survey [1].
1 · abrir a fotocv2.imread2 · ler o histogramacv2.calcHist3 · corrigir a exposiçãoganho · gama · equalizar4 · medir a diferençacontra a foto-alvo5 · recortar e salvarcv2.resize · cv2.imwritetestar outro ajuste
  1. Abrir a foto. Carregar o arquivo numa grade de números, um por cor em cada ponto (lição 1).
  2. Ler o histograma. Contar quantos pixels há em cada brilho, o que mostra o quão escura a foto está e o que já estourou (lição 2).
  3. Corrigir a exposição. Mudar todos os valores por uma única regra: somar uma constante, multiplicar por um ganho, curvar com uma curva gama ou deixar um equalizador automático decidir (lições 3, 4 e 5).
  4. Medir a diferença. Comparar a foto corrigida com uma foto mais longa e bem exposta da mesma cena (o alvo), pixel a pixel, e tirar a média da diferença. Quanto menor, mais perto; 0 é idêntico.
  5. Recortar, redimensionar, salvar. Ficar com a parte que interessa, reduzir sem aliasing e gravar num arquivo (lição 6).

Onde você já viu isso: os controles de Exposição e Brilho nos editores de foto do celular e no Lightroom; o botão Automático ao lado deles; o histograma na tela de reprodução de uma câmera.

TL;DR. Clarear uma foto escura multiplicando seus valores chega bem mais perto da exposição-alvo do que somar a eles ou deixar uma ferramenta automática decidir, e nada traz de volta o que a câmera estourou. Com dois stops a menos (quase um quarto da luz que o alvo recebeu), um ganho de 2,4 deixa uma diferença média de 9,1 (de 255) até o alvo, somar brilho deixa 18,0 e a equalização de histograma 30,6. Com três stops a menos, a gama vence, 13,9 contra 15,1.

A cena e o alvo

Todas as lições trabalham sobre uma única cena: um abajur de mesa, um cartão e uma carta de cores, fotografados dezoito vezes com tempos de obturador de 1/800 s a 30 s para o HDR Photographic Survey de Mark Fairchild [1]. A unidade 1.2 da trilha de Fundamentos (em inglês) mede os mesmos dezoito arquivos raw. Este módulo usa quatro, revelados do mesmo jeito:

ArquivoObturadorCinza médioCinza ≥ 250Papel
reference.webp5 s104.711.7%o alvo contra o qual toda correção é avaliada
dark.webp1.3 s56.67.8%cerca de dois stops abaixo
darker.webp1/2 s40.86.3%mais de três stops abaixo
bright.webp20 s173.827.2%dois stops acima

A distância usada do começo ao fim é a diferença absoluta média entre a foto corrigida e o alvo, sobre todos os pixels e canais de cor, na escala de 0 a 255.

A correção inteira, numa célula

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))
Saída
distance to the reference: 9.1
dark | fixed | reference
dark | fixed | reference

O que cada correção consegue

Cada correção foi avaliada no seu melhor ajuste, encontrado testando o parâmetro em passos pequenos (valores entre parênteses).

CorreçãoEscura, 1.3 sMais escura, 1/2 sClara, 20 s
nada47.062.368.4
somar uma constante (valor)18.0 (50)25.7 (65)68.4 (0)
multiplicar (ganho)9.1 (2.4)15.1 (4.2)22.8 (0.5)
gama (γ)10.7 (0.55)13.9 (0.4)33.4 (2.35)
equalizeHist no brilho30.631.930.3
CLAHE no brilho35.551.358.8

A tabela mostra quatro coisas:

  • Um ganho vence um deslocamento. Uma exposição mais longa multiplica a luz, então um multiplicador é a forma certa de correção, e ele corta pela metade a diferença que a soma deixa (lição 3). O próprio tutorial do OpenCV avisa que o deslocamento deixa um véu cinza [3].
  • A gama passa o ganho à medida que a foto fica mais escura. Um ganho grande o bastante para levantar três stops de sombra joga tudo acima de 60 para o branco; a gama leva 0 em 0 e 255 em 255 em qualquer ajuste, então nunca satura [2] (lição 4).
  • As correções automáticas ficam mais longe. A equalização distribui o brilho conforme a frequência de cada nível [2], e esta foto é quase toda parede e mesa escuras (lição 5).
  • A superexposição é a direção difícil. Nada deixa a foto clara mais perto do que 22,8, porque 15,6% dos seus pixels estão em 250 ou acima onde o alvo ainda tem detalhe. Esses valores se perderam.

O que levar daqui

  • Uma foto um ou dois stops escura: multiplique por um único ganho e confira contra algo em que você confia (um cartão cinza, uma segunda foto). Aqui isso levou 47,0 a 9,1.
  • Bem mais escura que isso: use gama, ou um ganho e uma gama juntos, e aceite sombras mais chapadas. Aqui 62,3 foi para 13,9.
  • Sem referência, e você só precisa enxergar detalhe: equalização ou CLAHE, sabendo que o resultado não vai se parecer com a cena.
  • Superexposta: fotografe de novo ou faça bracketing. O software só levou a foto clara até 22,8, e 15,6% dela não pôde ser recuperada com nenhum ajuste. High dynamic range (em inglês) mostra como várias exposições desta cena são combinadas para manter tudo.
  • Quando vale um modelo: este módulo não testou um realçador aprendido. O número a bater é 9,1 com dois stops a menos e 13,9 com três.

Limitações

  • O alvo é uma escolha, não a verdade. A foto de 5 segundos já tem 11,7% dos pixels em 250 ou acima. A foto escura manteve detalhe no abajur que o alvo perdeu, então uma correção que o preserva é avaliada como pior.
  • Os melhores ajustes foram escolhidos olhando a resposta. Cada ganho e cada gama acima foi escolhido para minimizar a distância até o alvo. Sem um alvo, você teria de escolher no olho ou por uma regra, e se sairia pior.
  • Uma cena, uma câmera. Um abajur num quarto escuro é uma faixa de luz extrema. Uma cena mais plana favoreceria mais a equalização, e a ordem das correções poderia mudar.
  • A distância pesa todos os pixels igualmente. A maior parte deste quadro é parede e mesa escuras, então a nota reflete principalmente as sombras. Uma métrica que pesasse mais o abajur e a carta classificaria as correções de outro jeito.
  • As entradas são WebP com qualidade 90. Todos os números foram calculados a partir dos arquivos publicados, então o Rodar do leitor os reproduz, mas eles não são os valores brutos do sensor.

As lições

  1. Abrir uma imagem e ler seus pixels no OpenCV
  2. Histogramas de imagem no OpenCV
  3. Brilho e contraste no OpenCV
  4. Correção gama no OpenCV com uma tabela de consulta
  5. Equalização de histograma e CLAHE no OpenCV
  6. Recortar, redimensionar e salvar uma imagem no OpenCV

Todas as células do módulo também estão num notebook, para rodar na sua máquina com o OpenCV que o pip instala: abra no Colab. As saídas dele batem com as da página, célula por célula.

Como estes números foram feitos
DadosHDR Photographic Survey, “Luxo Double Checker” [1]: arquivos _MDF0010.NEF (1/2 s), _MDF0011.NEF (1.3 s), _MDF0013.NEF (5 s), _MDF0015.NEF (20 s). Termos de pesquisa e uso não comercial; baixados, não redistribuídos como raw
Revelaçãorawpy 0.27.0 (LibRaw 0.22.1): balanço de branco da câmera, sem clareamento automático, meio tamanho, 8 bits; redimensionados para 960 px de largura com INTER_AREA; gravados como WebP qualidade 90
NotasDiferença absoluta média até reference.webp sobre todos os pixels e canais. Grades: soma de 0 a 150 em passos de 5; ganho de 0.2 a 6.0 em passos de 0.1; gama de 0.20 a 3.00 em passos de 0.05; CLAHE clipLimit=2.0, tileGridSize=(8, 8)
Na páginaPyodide 314.0.7, OpenCV 4.11.0, NumPy 2.4.6 (a saída exibida de cada célula é gerada por npm run pycells com este build)
LocalmenteOpenCV 5.0.0, NumPy 2.5.2, Python 3.12; todos os números da tabela acima batem com os da página até uma casa decimal
MáquinaIntel Core i7-12700H, Ubuntu 22.04.5 LTS. Nada é cronometrado; os números são determinísticos
Comandosuv run --extra sensing sensing-download, depois uv run --extra sensing practice-m1 --assets --media em fundamentals-lab; npm run pycells no repositório do site
Artefatooutput/practice_m1_numbers.json

Leitura complementar

  • Gonzalez & Woods, Digital Image Processing, capítulo 3 [2], para transformações de intensidade e processamento de histograma com a teoria por trás de cada uma.
  • O tutorial do OpenCV Changing the contrast and brightness of an image! [3], que corrige outra foto subexposta com α, β e γ.
  • Image sensing (em inglês), a unidade de Fundamentos sobre esta mesma cena, para entender por que a câmera saturou onde saturou e como as dezoito exposições se juntam numa só.

Referências

[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