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

Equalização de histograma e CLAHE no OpenCV

Teste as correções automáticas de contraste do OpenCV, cv2.equalizeHist e CLAHE, numa foto escura com um abajur aceso, e meça por que elas ficam mais longe da exposição alvo (30,6 e 35,5) do que um simples multiplicador (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 depois da equalização de histograma do seu brilho.
A foto escura depois da equalização de histograma do seu brilho.

A equalização de histograma é o botão de contraste automático: ela espalha o brilho de uma foto por toda a faixa de valores, sem precisar saber o quão escura a foto estava.

A foto escura do abajur ao lado de uma versão equalizada, com o abajur estourado e claro demais e a parede cinza, com as distâncias 47,0 e 30,6.
À esquerda: a foto dois stops escura. À direita: depois de cv2.equalizeHist no seu brilho. Quadro do HDR Photographic Survey [5].

Onde você já viu isso: os botões Contraste automático e Níveis automáticos dos editores de foto; Cores › Automático › Equalizar no GIMP.

TL;DR. A equalização automática faz qualquer foto usar a faixa inteira de brilho, o que é diferente de fazê-la parecer uma exposição melhor da cena. Na foto escura ela deixa uma distância de 30,6 até a exposição alvo, e sua variante local, o CLAHE, 35,5, contra 9,1 de um simples multiplicador.

O problema

As lições 3 e 4 do projeto corrigiram a foto com um único número, escolhido comparando com o alvo. Na maior parte das vezes não existe alvo. Uma correção automática precisa decidir só a partir da foto, e a regra óbvia é deixar o histograma plano: usar cada nível de brilho mais ou menos na mesma quantidade.

Equalize o brilho

cv2.equalizeHist recebe um canal de 8 bits e remapeia seus valores para que o histograma fique o mais plano que os valores permitem [1, 2]. Para preservar as cores, ela é aplicada só ao brilho: converta para YCrCb, cujo canal Y é o brilho e Cr, Cb carregam a cor, equalize Y e converta de volta.

O CLAHE (contrast-limited adaptive histogram equalization, equalização adaptativa de histograma com contraste limitado) faz o mesmo em pequenos blocos da imagem (8 × 8 por padrão) e limita quanto cada bloco pode ser esticado, para que o ruído em áreas uniformes não seja amplificado [2]. A versão por blocos foi descrita por Pizer e colegas em 1987 [4], e a de contraste limitado é de Zuiderveld, publicada em 1994 [3].

Python · OpenCV
import cv2
import numpy as np

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

def distance(a, b):
    return np.abs(a.astype(int) - b.astype(int)).mean()

def on_brightness(img, op):
    # equaliza só o brilho: Y do YCrCb, cor intacta
    ycc = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
    ycc[..., 0] = op(ycc[..., 0])
    return cv2.cvtColor(ycc, cv2.COLOR_YCrCb2BGR)

equalized = on_brightness(img, cv2.equalizeHist)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
local = on_brightness(img, clahe.apply)
print("equalizeHist:", round(distance(equalized, ref), 1))
print("CLAHE:       ", round(distance(local, ref), 1))
Saída
equalizeHist: 30.6
CLAHE:        35.5
FunçãoArgumentosO que faz
cv2.equalizeHist(channel)um canal uint8deixa o histograma plano
cv2.createCLAHE(clipLimit, tileGridSize)quanto um bloco pode esticar; a grade de blocosum objeto CLAHE
clahe.apply(channel)um canal uint8CLAHE nesse canal
cv2.COLOR_BGR2YCrCb—brilho (Y) separado da cor (Cr, Cb)

Os dois ganham de não fazer nada (47,0) e os dois perdem para um único multiplicador (9,1).

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

small = [half(x) for x in (equalized, local, ref)]
cv2.imshow("equalizeHist | CLAHE | reference", np.hstack(small))
Saída
equalizeHist | CLAHE | reference
equalizeHist | CLAHE | reference

A equalização gasta a faixa de brilho no que é mais comum, e esta foto é quase toda parede e mesa escuras. Elas são esticadas por quase toda a faixa e ficam cinza-médio, enquanto o abajur e a área iluminada, uma pequena parte dos pixels, são espremidos nos últimos níveis do topo. O CLAHE evita que o abajur estoure ainda mais, mas clareia cada bloco escuro por conta própria, e assim a parede e a mesa perdem a transição do escuro para o claro que a exposição alvo tem. O próprio tutorial do OpenCV cita esse caso: a equalização “won’t work good in places where there is large intensity variations”, quando há pixels claros e escuros ao mesmo tempo [2].

Quebre

equalizeHist aceita um único canal. Passe a imagem colorida e o OpenCV para com uma asserção.

Quebre
# equalizeHist recebe um canal de 8 bits, não uma imagem colorida
cv2.equalizeHist(img)
Saída
Traceback (most recent call last):
  File "<exec>", line 2, in <module>
cv2.error: OpenCV(4.11.0) /home/runner/work/pyodide-recipes/pyodide-recipes/packages/opencv-python/build/opencv-python-4.11.0.86/opencv/modules/imgproc/src/histogram.cpp:3440: error: (-215:Assertion failed) _src.type() == CV_8UC1 in function 'equalizeHist'

A mensagem mostra a verificação que falhou, _src.type() == CV_8UC1: um canal (C1) de valores de 8 bits sem sinal (8U).

Sua vez

Aplique o objeto CLAHE da primeira célula à versão em cinza de img e guarde o resultado em out.

Sua vez
out = None  # CLAHE na versão em cinza de img
print(out)

Quando usar

A equalização e o CLAHE são as ferramentas certas quando não há exposição alvo para alcançar e o objetivo é enxergar detalhes, ou quando a imagem alimenta um algoritmo que precisa mais de contraste do que de realismo. São a ferramenta errada para devolver a uma foto a aparência que ela deveria ter, e foi isso que este projeto mediu.

O projeto fica com a foto multiplicada e a finaliza na lição 6.

Por que funciona

Deixar o histograma plano passa cada valor pela distribuição acumulada da imagem, de modo que fatias iguais dos pixels caem em fatias iguais da faixa de saída [1]. Essa regra não sabe nada sobre a cena, e por isso se saiu mal aqui. Por que a exposição alvo tem a distribuição de tons que tem, e por que nenhuma exposição única segura o abajur e a mesa escura ao mesmo tempo, está em Noise and dynamic range (em inglês) e High dynamic range (em inglês).

Erros comuns

  • Uma imagem colorida em equalizeHist. A célula Quebre.
  • Equalizar B, G e R separadamente. Cada canal ganha seu próprio remapeamento, e o equilíbrio de cor muda. Equalize só o brilho.
  • Um clipLimit grande numa imagem com ruído. O CLAHE então amplifica o ruído nas regiões uniformes, o problema que o limite existe para evitar [2].
  • Ler “mais contraste” como “melhor”. Uma foto equalizada costuma parecer mais viva e muitas vezes está mais longe da realidade. Meça contra uma referência quando tiver uma.

Referências

[1] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 3.3 “Histogram Processing”, Histogram Equalization, p. 148, and Local Histogram Processing, p. 173. Pearson.

[2] OpenCV. Histograms - 2: Histogram Equalization (Python tutorial, doc/py_tutorials/py_imgproc/py_histograms/py_histogram_equalization/py_histogram_equalization.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv

[3] Zuiderveld, K. (1994). Contrast Limited Adaptive Histogram Equalization. In Graphics Gems (ISBN 978-0-12-336156-1), pp. 474–485. Elsevier. doi:10.1016/B978-0-12-336156-1.50061-6

[4] Pizer, S. M., Amburn, E. P., Austin, J. D., Cromartie, R., Geselowitz, A., Greer, T., ter Haar Romeny, B., Zimmerman, J. B., & Zuiderveld, K. (1987). Adaptive histogram equalization and its variations. Computer Vision, Graphics, and Image Processing, 39(3), 355–368. doi:10.1016/S0734-189X(87)80186-X

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