5.5. Regiões e máscaras

Por padrão, toda operação no módulo image afeta cada pixel de sua imagem de origem. Esse é o comportamento mais simples de descrever e o mais adequado quando a tarefa do algoritmo realmente abrange o quadro inteiro – uma correção de cor uniforme, um histograma global, um passo de codificação para transmissão. Mas, na prática, a maioria dos algoritmos quer observar menos do que isso. Um rastreador de blobs que monitora um marcador colorido se importa com a parte da cena em que o marcador pode aparecer, não com a parede atrás dele. Um passo de limpeza morfológica só é seguro sobre os pixels que um estágio anterior marcou como candidatos. Um detector de rostos pode rodar apenas dentro da caixa delimitadora que um detector mais grosseiro já estreitou. O módulo image dá suporte a esse trabalho por meio de dois mecanismos que restringem uma operação a um subconjunto de pixels: regiões de interesse retangulares e máscaras binárias. Eles se combinam livremente, e quase todo método que toca em pixels aceita um ou outro – ou ambos – como argumento de palavra-chave.

5.5.1. Regiões de interesse

Uma região de interesse é um retângulo de pixels identificado pela quádrupla (x, y, w, h) apresentada na página de coordenadas. Cerca de trinta métodos da interface aceitam um argumento de palavra-chave roi; quando presente, a operação roda apenas nos pixels dentro daquele retângulo e deixa o restante da imagem intocado. Quando roi é None ou é omitido, a operação roda sobre a imagem inteira – como se roi=(0, 0, width, height) tivesse sido passado.

No código, a palavra-chave fica ao lado de quaisquer outros argumentos que a operação recebe:

# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))

A primeira coisa que as ROIs proporcionam é o controle de falsos positivos. Um rastreador de cor que olha apenas para a mesa nunca será acionado pela camisa de alguém passando; um detector de bordas que roda apenas dentro da área de trabalho definida nunca relatará as bordas do próprio suporte da câmera. Reduzir a área de busca à parte da cena com a qual o algoritmo realmente se importa é a melhoria mais barata que um pipeline pode fazer em sua própria confiabilidade.

A segunda coisa que elas proporcionam é o pipeline do grosseiro ao fino. Objetos de resultado de detecção – um blob, um rect, um apriltag e assim por diante – expõem suas caixas delimitadoras como a mesma quádrupla (x, y, w, h) que roi aceita. Assim, um primeiro estágio grosseiro pode retornar uma caixa delimitadora, a caixa entra diretamente no roi do próximo estágio, e o segundo estágio roda sobre a área mais estreita. Cada estreitamento progressivo tanto acelera o estágio seguinte quanto torna seus resultados mais confiáveis, porque o espaço de busca já foi filtrado.

5.5.2. Máscaras binárias

Um retângulo é a forma correta quando a área de interesse está alinhada aos eixos. Quando não está – uma região curva, uma não convexa, os pixels que algum estágio anterior classificou como “correspondências” – é preciso instruir a operação a se restringir a um padrão arbitrário de pixels. O mecanismo para isso é uma máscara binária: uma Image separada, com as mesmas dimensões da origem, usada como um interruptor liga/desliga por pixel. Um pixel diferente de zero na máscara diz “inclua o pixel de origem correspondente”; um pixel zero diz “deixe o pixel de origem em paz.”

Uma máscara geralmente é uma imagem BINARY – o formato de um bit por pixel que existe exatamente para esse propósito – mas qualquer imagem de canal único funcionará, porque o consumidor trata qualquer valor diferente de zero como ligado.

Os métodos de filtragem, limiarização e aritmética aceitam um argumento de palavra-chave mask. A forma é a mesma em cada um: uma imagem binária alocada separadamente, com as mesmas dimensões da origem, passada adiante.

ROIs e máscaras se combinam. Passe ambas, e a operação roda apenas nos pixels que estão dentro da ROI e ligados na máscara. Os dois mecanismos dão ao código da aplicação alavancas independentes – uma para a área de interesse retangular, outra para o padrão arbitrário dentro dela – sem fazer com que uma forma herde restrições da outra.

Uma pequena grade representando uma imagem. Um retângulo tracejado desenhado pela porção superior-central da grade rotula a ROI: apenas os pixels dentro deste retângulo são considerados. Dentro da ROI, um conjunto aproximadamente circular de células preenchidas rotula a máscara: apenas essas células preenchidas são de fato modificadas. As demais células são levemente sombreadas para indicar que estão intocadas.

Uma ROI confina uma operação a um retângulo alinhado aos eixos. Uma máscara a estreita ainda mais a um padrão arbitrário de pixels. As duas se combinam: apenas os pixels dentro da ROI e ligados na máscara são modificados.

5.5.3. Construindo máscaras

Três métodos de Image constroem geometrias de máscara comuns no local, zerando os pixels fora da região escolhida:

Cada um recebe (x, y, w, h) (para o retângulo e a elipse) ou (x, y, radius) (para o círculo). Chamar qualquer um deles sem argumentos centraliza a geometria e a dimensiona para preencher a imagem, que é a forma à qual uma aplicação recorre quando o objetivo é um simples oval ou círculo de imagem inteira que não esconde nada além dos cantos.

mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear()              # start from all zeros
mask.mask_ellipse()       # centred, full-size oval

As máscaras interessantes raramente vêm apenas dos métodos mask_*. Elas vêm de estágios anteriores do pipeline: um passo de limiarização produz uma imagem binária cujos pixels diferentes de zero marcam as correspondências, exatamente a forma certa para alimentar o argumento mask= do próximo estágio. Um passo de limpeza morfológica refina essa máscara sem alterar sua forma. Qualquer coisa que acabe sendo uma imagem de canal único é, ela própria, uma máscara válida.

5.5.4. Como as operações modificam a imagem

Um padrão visível em todos os trechos de código das últimas páginas – a operação retornando a mesma img para encadeamento – vale a pena ser destacado explicitamente para que não precise ser reafirmado cada vez que um novo método é apresentado. Três famílias de métodos aparecem na interface de Image, cada uma tratando a imagem de origem de forma diferente:

  • Métodos de operação modificam os pixels da origem no local e retornam a mesma imagem para encadeamento. As famílias de desenho, aritmética, limiar e filtro se comportam todas dessa maneira. img.gaussian(1) borra img e retorna a mesma img; reatribuir – img = img.gaussian(1) – é inofensivo, mas desnecessário.

  • Métodos de conversão operam no local por padrão da mesma forma que os métodos de operação, mas aceitam copy=True e copy_to_fb=True para alocar uma imagem de resultado separada quando a origem precisa ser preservada. As conversões de formato e as cópias geométricas são os principais membros desta família.

  • Métodos de inspeção leem os pixels e retornam um objeto de resultado – uma lista de características detectadas, um histograma, um conjunto de estatísticas – sem modificar a imagem de origem de forma alguma.

Essa tricotomia é consistente em toda a interface. Saber a qual família um método pertence diz à aplicação o que esperar de uma chamada: se os pixels da origem sobreviverão intactos, se uma imagem de resultado separada será alocada e se o valor de retorno é a própria origem ou outra coisa.