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

Brilho e contraste no OpenCV

Clareie uma foto subexposta de dois jeitos, somando uma constante com cv2.add ou multiplicando com cv2.convertScaleAbs, e meça qual chega mais perto da exposição alvo: 18,0 contra 9,1.

Luis Condados · · 4 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 escura com todos os valores multiplicados por 2,4.
A foto escura com todos os valores multiplicados por 2,4.

Clarear uma foto é mudar o número de cada pixel, e há dois jeitos simples de fazer isso: somar a mesma quantidade a todos ou multiplicar todos pelo mesmo fator.

A foto escura do abajur ao lado de uma versão clareada, com as distâncias de cada uma até a exposição alvo, 47,0 e 9,1.
À esquerda: a foto como foi tirada, dois stops escura. À direita: todos os valores multiplicados por 2,4. A distância é a diferença média até uma foto mais longa e bem exposta da mesma cena (o alvo), por pixel e canal. Quadro do HDR Photographic Survey [3].

Onde você já viu isso: os controles de Brilho e Contraste de qualquer editor de fotos do celular; Cores › Brilho-Contraste no GIMP; o controle de Exposição do Lightroom e do darktable.

TL;DR. Multiplicar os valores dos pixels clareia uma foto escura com muito mais fidelidade do que somar a eles, porque uma exposição mais longa multiplica a luz. Nesta foto, somar 50 deixa uma diferença média de 18,0 até a exposição alvo, e multiplicar por 2,4 deixa 9,1.

O problema

A lição 2 mostrou o histograma da foto escura espremido na ponta esquerda. A correção precisa empurrá-lo para a direita. O módulo tem uma foto mais longa e bem exposta da mesma cena (o alvo), tirada em 5 segundos, então cada correção pode receber uma nota: em média, a que distância cada pixel e canal da foto corrigida fica do mesmo pixel do alvo? Quanto menor, melhor; 0 é idêntico.

Somar, depois multiplicar

O tutorial de brilho e contraste do OpenCV escreve as duas operações numa fórmula só, saída = α × entrada + β [1]. β é somado a cada valor (brilho); α multiplica cada valor (contraste, ou ganho). cv2.add faz a soma, e cv2.convertScaleAbs faz a multiplicação, arredonda e limita o resultado a 0–255.

Python · OpenCV
import cv2
import numpy as np

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

def distance(a, b):
    # diferença média por pixel e canal, na escala 0-255
    return np.abs(a.astype(int) - b.astype(int)).mean()

added = cv2.add(img, np.full_like(img, 50))
multiplied = cv2.convertScaleAbs(img, alpha=2.4)
print("as it is:  ", round(distance(img, ref), 1))
print("add 50:    ", round(distance(added, ref), 1))
print("times 2.4: ", round(distance(multiplied, ref), 1))
Saída
as it is:   47.0
add 50:     18.0
times 2.4:  9.1
FunçãoArgumentosO que faz
cv2.add(img, other)dois arrays da mesma formasoma os dois, parando em 255
cv2.convertScaleAbs(img, alpha, beta)ganho alpha, deslocamento betaalpha * img + beta, arredondado, em valor absoluto, limitado a 0–255
np.full_like(img, 50)uma forma e um valorum array como img, preenchido com 50

Somar 50 foi a melhor soma, e multiplicar por 2,4 a melhor multiplicação, quando cada uma foi testada em passos ao longo da sua faixa (a busca está no código do módulo, com link na página do projeto). Multiplicar corta a diferença restante mais ou menos pela metade.

Python · OpenCV
def half(x):
    return cv2.resize(x, None, fx=0.5, fy=0.5)

small = [half(x) for x in (added, multiplied, ref)]
cv2.imshow("add 50 | times 2.4 | reference", np.hstack(small))
Saída
add 50 | times 2.4 | reference
add 50 | times 2.4 | reference

A versão somada (à esquerda) tem um véu cinza: a mesa e a parede escuras subiram os mesmos 50 que todo o resto, então os pretos deixaram de ser pretos. A versão multiplicada mantém os escuros escuros e estica o resto, que é o que uma exposição mais longa faz. O tutorial do OpenCV descreve o mesmo véu para β e observa que α custa detalhe nas regiões que já eram claras [1], que foi o que aconteceu com o abajur.

Um ganho tão grande funciona porque a foto está só dois stops escura. Para a foto mais de três stops escura, a lição 4 mostra onde um ganho deixa de funcionar.

Quebre

cv2.add para em 255. O + do NumPy em arrays uint8 não para: ele dá a volta, e 253 + 50 vira 47.

Quebre
# O + do NumPy em uint8 dá a volta em vez de parar em 255
wrapped = img + np.full_like(img, 50)
y, x = np.unravel_index(img.max(axis=2).argmax(), img.shape[:2])
print("brightest pixel:", img[y, x])
print("numpy:", wrapped[y, x], " cv2.add:", added[y, x])
cv2.imshow("img + 50 with NumPy", wrapped)
Saída
brightest pixel: [253 251 255]
numpy: [47 45 49]  cv2.add: [255 255 255]
img + 50 with NumPy
img + 50 with NumPy

As partes mais claras da imagem viram as mais escuras. Use cv2.add, ou converta para um tipo mais largo, some e corte com clip.

Sua vez

A foto superexposta, bright.webp, precisa escurecer. Escolha um multiplicador que deixe a distância dela até o alvo abaixo de 30.

Sua vez
bright = cv2.imread("bright.webp")
fixed = cv2.convertScaleAbs(bright, alpha=1.0)  # mude alpha
print(round(distance(fixed, ref), 1))

Por que funciona

A foto escura foi exposta por 1,3 segundo e o alvo por 5, então o alvo captou quase quatro vezes mais luz. As contagens brutas de uma câmera crescem na proporção da luz, e é por isso que um ganho, e não um deslocamento, é o formato certo de correção (From photons to counts (em inglês)). O melhor ganho aqui é 2,4, e não algo perto de 4, porque a foto não é bruta: seus valores passaram por uma curva de tons que comprime os valores claros, e The response curve (em inglês) mede essa curva nesta mesma cena. Soma e multiplicação de imagens pixel a pixel são as operações aritméticas básicas do processamento de imagens [2].

Erros comuns

  • img + 50 com NumPy. Dá a volta depois de 255, como mostra a célula Quebre.
  • cv2.add(img, 50) com um número solto. No OpenCV 4.8.1 isso soma 50 só ao canal azul; no 4.11 e no 5.0 soma aos três (os dois casos verificados em 30 de setembro de 2026). Uma instalação antiga dá um resultado azulado sem nenhum erro. Passe um array, como fazem as células aqui.
  • Esperar que a multiplicação recupere altas luzes estouradas. Um valor que já está em 255 continua em 255 com qualquer ganho. O abajur desta foto se perde em qualquer ajuste.
  • Julgar a correção no olho. A versão somada pode parecer mais clara e mais bonita na tela do celular e mesmo assim estar mais longe da exposição alvo. Meça contra alguma referência.

Referências

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

[2] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 2.6 “Introduction to the Basic Mathematical Tools Used in Digital Image Processing”, Arithmetic Operations, p. 69. 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