5.12. Limiarização binária

Muitos pipelines de processamento de imagem se resumem a uma pergunta sobre cada pixel: este brilho está dentro da faixa que significa “primeiro plano”? Esta cor está próxima o suficiente do vermelho para ser o marcador que a aplicação está rastreando? Este pixel faz parte do conjunto candidato que o próximo estágio do pipeline deve examinar? A limiarização é a operação que transforma essas perguntas em uma resposta binária em cada posição – ligado se o pixel corresponde, desligado se não – e reduz toda a imagem a uma máscara contra a qual o restante do pipeline pode trabalhar.

5.12.1. O método binary

O método binary() executa essa classificação em todos os pixels em uma única chamada. Ele recebe uma lista de faixas de limiar – as condições que um pixel pode corresponder para contar como “ligado” – e reescreve a imagem de modo que todo pixel que correspondeu a pelo menos uma das faixas seja definido com o valor máximo do formato, e todo pixel que não correspondeu seja definido como zero. O resultado é a máscara binária que o restante do pipeline pode usar diretamente.

Na forma mais simples, a lista de limiar tem uma faixa e a chamada retorna uma máscara de pixels nessa faixa:

img.binary([(120, 255)])

A forma de lista é o que torna binary poderoso. Um pipeline que deseja rastrear dois marcadores coloridos, ou uma faixa de brilho mais um pico isolado de saturação, passa ambas as faixas na mesma lista e obtém uma única máscara de saída cobrindo todas as correspondências.

Um gradiente horizontal em escala de cinza no topo rotulado "input -- valores de pixel de 0 a 255". Abaixo dele, uma faixa de limiar inclusiva de lo a hi é marcada com colchetes ao longo do gradiente, englobando a faixa de valores de brilho que contam como correspondências. Uma barra de saída binária na parte inferior mostra branco dentro da faixa de lo a hi e preto fora dela.

A limiarização transforma uma imagem de valores contínuos em uma máscara binária: cada pixel dentro da faixa de limiar torna-se o máximo do formato, cada pixel fora dela torna-se zero.

5.12.2. A tupla de escala de cinza

Para uma imagem em escala de cinza, cada entrada na lista de limiar é uma tupla de dois elementos (lo, hi) descrevendo uma faixa de brilho inclusiva. Pixels com valores entre lo e hi (inclusive) correspondem; tudo fora dessa faixa não corresponde. Os padrões naturais são diretos:

  • (0, 60) corresponde a pixels escuros – tudo do preto até o cinza profundo.

  • (180, 255) corresponde a pixels claros – tudo do cinza-claro até o branco.

  • (100, 160) corresponde a pixels cinza-médio – uma banda no meio da faixa de brilho.

A ordem dos dois valores dentro de uma tupla não importa; o método os troca internamente se lo for maior que hi, então (60, 0) funciona da mesma forma que (0, 60).

5.12.3. A tupla LAB para cor

Para uma imagem RGB565, cada entrada é uma tupla de seis elementos (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) descrevendo uma faixa inclusiva no espaço de cor LAB em vez de diretamente em vermelho, verde e azul. Os limiares são L (luminosidade), A (eixo cromático verde-vermelho) e B (eixo cromático azul-amarelo), cada um comparado com o valor do pixel naquele canal.

A razão para passar pelo LAB em vez de limiarizar RGB diretamente é a propriedade em torno da qual o espaço de cor LAB foi projetado: o LAB separa a luminosidade da cromaticidade. Dois pixels que mostram a mesma cor mas em brilhos diferentes acabam em valores L diferentes, mas em valores A e B aproximadamente iguais. Essa separação permite que as faixas de limiar descrevam uma cor por sua posição nos eixos A e B e deixem a faixa L bem aberta para aceitar essa cor em todos os brilhos, da sombra ao realce. Um limiar baseado em RGB não consegue fazer isso – qualquer mudança na iluminação move os três valores R, G, B de uma vez, e um rastreador construído sobre limiares RGB quebra na primeira vez que uma nuvem passa na frente do sol.

O padrão prático: escolha as faixas A e B que descrevem a cor que a aplicação está rastreando, e deixe a faixa L ampla – frequentemente (0, 100) para aceitar qualquer brilho – a menos que a aplicação queira especificamente limiarizar tanto pelo brilho quanto pela cor.

Para tuplas com menos de seis valores, os componentes ausentes assumem por padrão a faixa máxima (nenhuma restrição naquele eixo). Uma tupla de dois elementos (l_lo, l_hi) em uma lista de limiar RGB565, portanto, limiariza apenas pela luminosidade e corresponde a todas as cores.

Nota

Uma faixa L verdadeiramente bem aberta tem uma armadilha na extremidade inferior. À medida que a luminosidade cai em direção a zero, todas as cores convergem para o preto, com os valores A e B colapsando em direção a zero e tornando-se dominados por ruído – de modo que pixels escuros podem entrar nas faixas A e B e ser rastreados como a cor-alvo. Se regiões pretas da cena aparecerem como correspondências, aumente l_lo até que elas saiam.

5.12.4. Flags

Três argumentos de palavra-chave controlam a saída:

  • invert=True inverte o resultado. Todo pixel que teria correspondido torna-se zero, e todo pixel que teria sido zero torna-se o valor máximo. Útil quando a forma natural de descrever o primeiro plano é por aquilo que ele não é.

  • zero=True muda o modo de operação: os pixels correspondentes são zerados e os pixels não correspondentes mantêm seus valores originais. Use isso quando o objetivo é apagar os pixels correspondentes da imagem em vez de reduzir a imagem a uma máscara binária deles.

  • to_bitmap=True retorna o resultado como uma imagem BINARY em vez de sobrescrever o formato existente da origem. O resultado de um bit por pixel é o que os argumentos de máscara posteriores aceitam diretamente, e a conversão frequentemente poupa a pressão de memória de carregar uma máscara de formato completo.

A máscara e a ROI seguem a mesma convenção do restante da interface: um retângulo roi limita a operação a uma subárea, uma imagem mask a limita a um padrão arbitrário de posições.

5.12.5. No local por padrão

Como as operações aritméticas, binary é executado no local por padrão: os pixels da imagem de origem são sobrescritos com a saída binária, e os valores originais desaparecem após a chamada. A forma to_bitmap=True é a alternativa quando a origem precisa ser preservada e a saída deve ser uma imagem BINARY recém-alocada. A forma copy=True também é aceita para um resultado de mesmo formato em um novo buffer.