Por que funciona: Unidade 1.2: Captura da imagem, Unidade 1.3: A imagem como dado
Histogramas de imagem no OpenCV
Conte quantos pixels há em cada brilho com cv2.calcHist, desenhe o histograma e leia nele que a foto escura tem 79,3% dos pixels no quarto inferior e 7,8% estourados.
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.
Um histograma conta quantos pixels têm cada brilho, o que mostra num relance se uma foto está escura demais, clara demais ou as duas coisas.

Onde você já viu isso: o histograma na tela de reprodução de uma câmera e no modo pro da câmera do celular; o painel de histograma do Lightroom, do darktable e a janela Níveis do GIMP.
TL;DR. Um histograma mostra onde está o brilho de uma foto, então você enxerga uma exposição ruim antes de corrigi-la. Na foto escura, 79,3% dos pixels ficam no quarto inferior da faixa e 7,8% estão em 250 ou acima, o que quer dizer que ela está escura demais e estourada ao mesmo tempo.
O problema
A lição 1 resumiu a foto num único número, um brilho médio de 56,6. Uma média esconde a forma: uma foto metade preta e metade branca também tem média perto de 128. Para decidir como corrigir a exposição, você precisa da distribuição inteira.
Conte os pixels em cada brilho
cv2.calcHist recebe uma lista de imagens, o canal a contar, uma máscara opcional, o número de bins e o intervalo de valores [1]. Com 256 bins sobre [0, 256], cada bin é um nível de cinza.
import cv2
import numpy as np
img = cv2.imread("dark.webp")
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([grey], [0], None, [256], [0, 256])
low = 100 * hist[:64].sum() / grey.size
high = 100 * hist[250:].sum() / grey.size
print("darkest quarter (0-63):", round(low, 1), "%")
print("250 or above:", round(high, 1), "%")darkest quarter (0-63): 79.3 % 250 or above: 7.8 %
| Argumento | Aqui | O que significa |
|---|---|---|
[grey] | uma imagem | uma lista, porque ele pode contar em várias imagens |
[0] | canal 0 | o único canal de uma imagem em cinza |
None | sem máscara | passe uma máscara para contar só parte da imagem |
[256] | 256 bins | um por nível de cinza |
[0, 256] | o intervalo | a ponta superior não entra |
Quase quatro em cada cinco pixels estão no quarto mais escuro. Essa é a subexposição. Os 7,8% em 250 ou acima são o abajur e a mancha de luz embaixo dele, que já estavam claros demais nesta exposição: nenhum clareamento vai trazer detalhe de volta ali.
Desenhe
Um histograma é mais fácil de ler como imagem. A função abaixo desenha uma barra por nível de cinza com cv2.line e depois coloca o histograma da foto escura ao lado do histograma da exposição-alvo.
def draw_histogram(grey, height=200):
hist = cv2.calcHist([grey], [0], None, [256], [0, 256]).ravel()
canvas = np.full((height, 256, 3), 255, np.uint8)
for value, count in enumerate(hist / hist.max()):
top = int(height * (1 - count))
cv2.line(canvas, (value, height), (value, top), (80, 80, 80))
return canvas
ref = cv2.cvtColor(cv2.imread("reference.webp"), cv2.COLOR_BGR2GRAY)
cv2.imshow("dark (left) and reference (right)",
np.hstack([draw_histogram(grey), draw_histogram(ref)]))
O histograma do alvo se espalha pela faixa inteira. O da foto escura fica espremido na ponta esquerda. As correções das lições 3, 4 e 5 são três formas diferentes de esticar o histograma da esquerda em direção ao da direita, e esta imagem é o que todas elas tentam alcançar.
Quebre
O intervalo é semiaberto: [0, 256] conta de 0 a 255. Escrever [0, 255], a versão que parece natural, descarta sem aviso todos os pixels em 255.
# O intervalo exclui a ponta superior: [0, 255] descarta 255
wrong = cv2.calcHist([grey], [0], None, [256], [0, 255]).ravel()
right = cv2.calcHist([grey], [0], None, [256], [0, 256]).ravel()
print("pixels at 255, range [0, 255]:", int(wrong[255]))
print("pixels at 255, range [0, 256]:", int(right[255]))pixels at 255, range [0, 255]: 0 pixels at 255, range [0, 256]: 41503
41.503 pixels, o abajur estourado, somem da contagem, e nada te avisa. É um erro fácil de pegar do próprio tutorial do OpenCV, cujo texto dá o intervalo como [0,255] [1] enquanto o código de exemplo passa (0, 256) com o comentário “the upper boundary is exclusive” [4].
Sua vez
A foto superexposta, bright.webp, foi tirada com 20 segundos. Que porcentagem dos seus pixels em cinza está em 250 ou acima? Guarde a resposta em clipped.
# porcentagem de pixels em cinza com 250 ou mais em bright.webp
clipped = 0.0
print(clipped)Por que funciona
Um histograma é a distribuição dos valores de intensidade [2]. Ele não diz nada sobre onde os pixels estão no quadro, só quantos há de cada valor, e por isso duas fotos muito diferentes podem ter o mesmo histograma. O motivo de o abajur estourar numa ponta enquanto a mesa some no escuro na outra é uma propriedade do sensor: Noise and dynamic range (em inglês) mede a largura da faixa que uma exposição consegue guardar, e High dynamic range (em inglês) mostra como várias exposições desta mesma cena são combinadas para guardar tudo.
Erros comuns
[0, 255]como intervalo. Descarta o valor 255, como a célula Quebre mostra.- Passar a imagem em vez de uma lista.
calcHist(grey, …)falha; tem de sercalcHist([grey], …). - Uma imagem colorida com o canal
[0]. Isso conta só o canal azul. Converta para cinza antes, ou conte cada canal de propósito. - Ler a altura das barras como porcentagem.
calcHistdevolve contagens. Divida porgrey.sizepara ter frações. - Indexar o resultado como
hist[v, 0]. O OpenCV 4.11 devolve uma coluna de forma (256, 1), o OpenCV 5.0 um array plano de forma (256,), então esse índice funciona num e falha no outro (os dois conferidos em 30 de setembro de 2026). Chame.ravel()antes, como fazem as células aqui.
Referências
[1] OpenCV. Histogram Calculation (tutorial, doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.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.3 “Histogram Processing”, p. 147. 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
[4] OpenCV. samples/python/tutorial_code/Histograms_Matching/histogram_calculation/calcHist_Demo.py, line 27. OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv