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.
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.
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.

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.
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))as it is: 47.0 add 50: 18.0 times 2.4: 9.1
| Função | Argumentos | O que faz |
|---|---|---|
cv2.add(img, other) | dois arrays da mesma forma | soma os dois, parando em 255 |
cv2.convertScaleAbs(img, alpha, beta) | ganho alpha, deslocamento beta | alpha * img + beta, arredondado, em valor absoluto, limitado a 0–255 |
np.full_like(img, 50) | uma forma e um valor | um 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.
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))
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.
# 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)brightest pixel: [253 251 255] numpy: [47 45 49] cv2.add: [255 255 255]
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.
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 + 50com 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