5.19. Correções tonais¶
As correções tonais alteram como o brilho e a cor são distribuídos em uma imagem capturada – os ajustes que uma aplicação aplica quando um quadro está escuro demais, claro demais, sem contraste ou enviesado para a cor errada.
As correções pertencem a duas famílias: ajustes de brilho e contraste, que redistribuem o brilho, e ajustes de cor, que alteram qual cor cada pixel passa a representar. Ambas têm correspondentes no ISP do sensor, que corrige cada quadro na entrada; os métodos aqui se aplicam a uma Image já capturada, depois do fato, para os casos em que o quadro precisa de mais correção do que o ISP forneceu.
5.19.1. Equalização de histograma¶
A operação de estiramento de contraste mais simples é a equalização de histograma. A ideia é remapear os valores dos pixels de modo que o histograma da saída seja o mais plano possível – cada valor aparece aproximadamente com a mesma frequência. O efeito visual é que uma imagem de baixo contraste (cujo histograma está concentrado em uma faixa estreita) se torna uma imagem de alto contraste cujos pixels cobrem toda a faixa de 0 a 255.
histeq() executa a equalização:
img.histeq()
O mecanismo é direto. A função de distribuição acumulada (CDF) do histograma da fonte é calculada; cada valor de pixel de entrada é mapeado para sua posição na CDF, escalonada para a faixa de saída. Onde os pixels já estavam distribuídos de forma uniforme, o mapeamento fica próximo da identidade; onde os pixels estavam acumulados em um único nível de brilho, o mapeamento os espalha estirando esse brilho por uma faixa mais ampla de valores de saída.
O resultado é dramático em cenas de baixo contraste – a diferença entre uma fotografia interna escura e a mesma fotografia após histeq costuma ser a diferença entre “ilegível” e “perfeitamente legível”. O compromisso é que a operação amplifica tudo, inclusive o ruído do sensor. Em uma cena com detalhes reais de baixo contraste a recuperar, histeq é a resposta certa; em uma cena limpa e bem exposta que simplesmente não precisa disso, histeq introduz ruído visível.
5.19.2. CLAHE: equalização adaptativa¶
A equalização de histograma é global: ela usa uma única CDF calculada a partir da imagem inteira e a aplica em todos os lugares. Isso funciona em imagens cuja faixa de brilho é aproximadamente uniforme, mas falha em cenas com regiões escuras e claras localizadas – a CDF é puxada na direção do lado que tem mais pixels, e o lado oposto fica supercorrigido.
A variante adaptativa é a Contrast Limited Adaptive Histogram Equalisation, comumente chamada de CLAHE. Em vez de uma única CDF global, a CLAHE calcula uma CDF separada para cada pequeno bloco da imagem, equaliza cada bloco em relação à sua própria CDF e mescla as fronteiras entre os blocos. O resultado é que os ajustes de brilho acontecem localmente – o canto sombreado recebe sua própria equalização sem que o canto claro o puxe na direção errada.
A flag adaptive=True coloca histeq() em modo CLAHE:
img.histeq(adaptive=True, clip_limit=10)
O parâmetro clip_limit é a parte da CLAHE a que se refere o “contrast limited” do nome. A equalização local pode superamplificar o ruído em regiões planas, onde a CDF tem pouquíssimos valores distintos; o limite de recorte limita o quão agressivamente qualquer compartimento isolado pode ser redistribuído, o que evita a amplificação do ruído sem deixar de permitir o estiramento de contraste onde ele importa. Um valor em torno de 10 é um ponto de partida razoável; valores maiores fazem a CLAHE trabalhar mais ao custo de mais ruído visível, valores menores a tornam mais suave.
A CLAHE é mais cara que o histeq global, mas produz resultados mais limpos em cenas onde o brilho está distribuído de forma desigual – o que descreve a maioria das cenas do mundo real.
5.19.3. Gama, contraste e brilho¶
A equalização de histograma é a maneira orientada a dados de remapear o brilho. A maneira independente dos dados é aplicar uma curva escolhida, parametrizada por alguns botões fáceis de ajustar. gamma() fornece três:
img.gamma(gamma=1.0, contrast=1.0, brightness=0.0)
Cada parâmetro aplica uma transformação específica a cada pixel:
gamma passa o valor de cada pixel pela função de potência output = input ** (1 / gamma). O significado padrão: valores maiores que 1.0 clareiam a imagem e elevam os tons médios (a clássica correção de “gama do monitor”); valores menores que 1.0 a escurecem. O parâmetro é não linear – ele preserva os pontos de preto e de branco e apenas remodela a distribuição entre eles, que é o comportamento certo quando o objetivo é recuperar detalhes em regiões de sombra ou de realce sem esmagar os extremos existentes.
contrast passa cada pixel por uma multiplicação direta em torno do ponto de cinza médio. Valores maiores que 1.0 aumentam o contraste (o escuro fica mais escuro, o claro fica mais claro, o cinza médio permanece o mesmo); valores menores que 1.0 reduzem o contraste.
brightness adiciona uma constante ao valor de cada pixel. Valores positivos clareiam, valores negativos escurecem. O deslocamento é uniforme – nada é preservado – o que raramente é o que uma aplicação quer por si só, mas combina bem com uma passagem de contraste para recentralizar o resultado.
Os três parâmetros se compõem: uma única chamada a gamma() pode aplicar uma curva de gama, depois uma multiplicação de contraste e depois um deslocamento de brilho, tudo em uma única passagem. A ordem é gama primeiro, depois contraste e depois brilho, que corresponde à ordem que dá os resultados mais intuitivos quando os três estão com valores não padrão.
5.19.4. Balanço de branco automático¶
A família de cor das correções tonais começa com o balanço de branco automático. O mesmo mecanismo que o ISP do sensor executa como parte do pipeline de imagem – ajustar os ganhos relativos dos canais vermelho, verde e azul de modo que uma área de cor cinza média seja lida como cinza de verdade – também está disponível como uma operação pós-captura sobre uma Image finalizada:
img.awb()
O padrão usa o algoritmo gray-world: assume-se que a cor média da imagem inteira é cinza neutro, e os ganhos por canal são ajustados para que isso se verifique. A forma alternativa max=True usa o algoritmo white-patch: assume-se que os pixels mais claros são branco neutro, e os ganhos são ajustados para que assim seja. Ambos funcionam em RGB565 e em Bayer bruto; nenhum funciona em escala de cinza (onde não há cor a balancear) nem em YUV (onde a representação de cor não é aquela sobre a qual esses algoritmos operam).
Quando recorrer à forma pós-captura em vez do balanço de branco automático do ISP: quando a escolha do ISP foi inadequada para a cena específica, quando a aplicação está carregando quadros de referência do disco que foram capturados sob condições diferentes, ou quando a avaliação de cor importa o suficiente para que a aplicação queira reexecutá-la com sua própria escolha de algoritmo.
5.19.5. A matriz de correção de cor¶
Quando a correção de cor de que a imagem precisa não é o escalonamento por canal que o balanço de branco oferece, mas sim uma mistura de canais mais geral, a operação a que recorrer é a matriz de correção de cor. O método ccm() aplica uma matriz 3 por 3 (ou 3 por 4 com offset) que multiplica o vetor (r, g, b) de cada pixel para produzir um novo vetor (r, g, b):
img.ccm([[1.1, -0.05, -0.05],
[-0.05, 1.1, -0.05],
[-0.05, -0.05, 1.1]])
A matriz permite que a aplicação corrija o crosstalk entre os canais de cor – onde a resposta do sensor vermelho inclui alguma luz verde, por exemplo, a matriz pode subtrair uma fração do canal verde da saída vermelha para compensar. Combinada com um offset por canal, a forma 3 por 4 permite que a aplicação também rezere cada canal.
O material sobre o pipeline do ISP cobre o porquê das matrizes de correção de cor. A forma pós-captura na Image é apenas a mesma operação, aplicada depois do fato.