5.19. Correções tonais

As correções tonais alteram a forma como o brilho e a cor são distribuídos numa imagem capturada – as correções que uma aplicação aplica quando um fotograma está demasiado escuro, demasiado claro, demasiado plano ou inclinado 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 mudam a cor que cada pixel representa. Ambas têm equivalentes no ISP do sensor, que corrige cada fotograma à medida que chega; os métodos aqui aplicam-se a uma Image já capturada, após a captura, nos casos em que o fotograma precisa de mais correção do que a fornecida pelo ISP.

5.19.1. Equalização do histograma

A operação de expansão de contraste mais simples é a equalização do histograma. A ideia é remapear os valores dos pixels de forma a que o histograma da saída seja o mais plano possível – cada valor aparece aproximadamente com igual frequência. O efeito visual é que uma imagem de baixo contraste (cujo histograma está concentrado numa faixa estreita) se torna uma imagem de alto contraste cujos pixels cobrem toda a gama 0255.

histeq() executa a equalização:

img.histeq()

O mecanismo é direto. A função de distribuição cumulativa (CDF) do histograma da fonte é calculada; cada valor de pixel de entrada é mapeado para a sua posição na CDF, escalada para a gama de saída. Onde os pixels já estavam distribuídos uniformemente, o mapeamento é próximo da identidade; onde os pixels estavam acumulados num determinado brilho, o mapeamento expande-os estirando esse brilho por uma gama mais ampla de valores de saída.

O resultado é dramático em cenas de baixo contraste – a diferença entre uma fotografia interior escura e a mesma fotografia após histeq é frequentemente a diferença entre «ilegível» e «perfeitamente legível». A contrapartida é que a operação amplifica tudo, incluindo o ruído do sensor. Numa cena com detalhe real de baixo contraste a recuperar, histeq é a resposta certa; numa 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 do histograma é global: usa uma CDF calculada a partir da imagem inteira e aplica-a em todo o lado. Isto funciona em imagens cuja gama de brilho é aproximadamente uniforme, mas falha em cenas com regiões escuras e claras localizadas – a CDF é puxada para o lado que tem mais pixels, e o lado oposto fica sobrecorrigido.

A variante adaptativa é a Equalização de Histograma Adaptativa com Limite de Contraste, comumente designada CLAHE. Em vez de uma CDF global, a CLAHE calcula uma CDF separada para cada pequena secção da imagem, equaliza cada secção com base na sua própria CDF, e mistura os limites das secções entre si. O resultado é que os ajustes de brilho acontecem localmente – o canto sombreado recebe a sua própria equalização sem que o canto claro o desvie na direção errada.

O parâmetro 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 o «contraste limitado» no nome se refere. A equalização local pode amplificar demasiado o ruído em regiões planas onde a CDF tem muito poucos valores distintos; o limite de recorte controla a agressividade com que qualquer bin pode ser redistribuído, o que impede a amplificação do ruído ao mesmo tempo que permite a expansão de contraste onde é necessária. Um valor à volta de 10 é um bom ponto de partida; valores maiores permitem que a CLAHE trabalhe com mais intensidade ao custo de ruído mais visível, valores menores tornam-na mais suave.

A CLAHE é mais dispendiosa do que o histeq global, mas produz resultados mais limpos em cenas onde o brilho está distribuído de forma irregular – o que se verifica na maioria das cenas do mundo real.

5.19.3. Gama, contraste e brilho

A equalização do histograma é a forma orientada pelos dados de remapear o brilho. A forma independente dos dados consiste em aplicar uma curva escolhida, parametrizada por alguns ajustes fáceis de afinar. 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 superiores a 1.0 clarificam a imagem e elevam os meios-tons (a clássica correção «gama do monitor»); valores inferiores a 1.0 escurecem-na. O parâmetro é não linear – preserva os pontos de negro e branco e apenas reformula a distribuição intermédia, o que é o comportamento correto quando o objetivo é recuperar detalhes nas regiões de sombra ou realce sem esmagar os extremos existentes.

contrast passa cada pixel por uma multiplicação simples em torno do ponto de cinzento médio. Valores superiores a 1.0 aumentam o contraste (o escuro fica mais escuro, o claro fica mais claro, o cinzento médio mantém-se igual); valores inferiores a 1.0 reduzem o contraste.

brightness adiciona uma constante ao valor de cada pixel. Valores positivos clarificam, 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 recentrar o resultado.

Os três parâmetros compõem-se: uma única chamada a gamma() pode aplicar uma curva de gama, depois uma multiplicação de contraste, depois um deslocamento de brilho, tudo numa única passagem. A ordem é primeiro gama, depois contraste, depois brilho, o que corresponde à ordem que dá os resultados mais intuitivos quando os três são diferentes do 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 processo de imagem – ajustando os ganhos relativos nos canais vermelho, verde e azul para que um patch de cor cinzento médio seja lido como cinzento real – está também disponível como operação pós-captura numa Image finalizada:

img.awb()

O padrão utiliza o algoritmo gray-world: assume-se que a cor média de toda a imagem é cinzento neutro, e os ganhos por canal são ajustados para que assim seja. A forma alternativa max=True utiliza o algoritmo white-patch: assume-se que os pixels mais brilhantes 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 cinzentos (onde não há cor a equilibrar) nem em YUV (onde a representação de cor não é a que estes algoritmos utilizam).

Quando recorrer à forma pós-captura em vez do balanço de branco automático do ISP: quando a escolha do ISP foi uma má correspondência para a cena em particular, quando a aplicação está a carregar fotogramas de referência do disco capturados sob condições diferentes, ou quando o julgamento de cor é suficientemente importante para que a aplicação queira executá-lo novamente com a sua própria escolha de algoritmo.

5.19.5. A matriz de correção de cor

Quando a correção de cor que a imagem necessita não é o escalonamento por canal que o balanço de branco fornece, mas uma mistura de canais mais geral, a operação a utilizar é a matriz de correção de cor. O método ccm() aplica uma matriz 3 por 3 (ou 3 por 4 com desvio) 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 à aplicação corrigir a diafonia 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. Combinado com um desvio por canal, a forma 3 por 4 permite à aplicação também re-zerá cada canal.

O material do ISP pipeline cobre o porquê das matrizes de correção de cor. A forma pós-captura na Image é simplesmente a mesma operação, aplicada após a captura.