5.6. Desenhar formas e texto

Um algoritmo que decide algo sobre uma imagem muitas vezes precisa que essa decisão seja visível. Um detetor de manchas encontra uma região que a aplicação interessa; a aplicação quer que a região seja desenhada no fotograma para que um operador – ou o programador que executa o script – possa ver o que foi encontrado. Uma transformação de coordenadas mapeia uma posição de entrada para uma de saída; depurá-la geralmente significa marcar as duas posições na mesma imagem. A pré-visualização da IDE lê o que estiver no buffer de fotograma no momento em que faz a consulta, por isso a forma mais simples de tornar a saída do algoritmo visível é escrever anotações no próprio buffer de fotograma. A família de desenho na classe Image é o conjunto de ferramentas exatamente para esse trabalho.

5.6.1. As primitivas

Cada método de desenho coloca um tipo específico de marca na imagem. O catálogo é pequeno e fica próximo das primitivas geométricas que uma anotação realmente necessita:

  • draw_line() – um segmento de linha reta entre dois pontos extremos.

  • draw_rectangle() – um retângulo alinhado com os eixos, oco ou preenchido.

  • draw_circle() – um círculo em torno de um centro, oco ou preenchido.

  • draw_ellipse() – uma elipse com rotação arbitrária.

  • draw_cross() – um sinal de adição num ponto, a marca habitual para um centróide ou um alvo de clique.

  • draw_arrow() – uma seta de um ponto inicial para um ponto final.

  • draw_edges() – os quatro lados de um quadrilátero arbitrário, dados os quatro pontos de canto; a forma natural de delinear uma etiqueta detetada ou uma região com perspetiva distorcida.

  • draw_string() – texto de uma fonte de mapa de bits embutida.

Cada um destes modifica a imagem fonte no local e devolve a mesma imagem para encadeamento, seguindo a convenção de método operacional estabelecida anteriormente.

A grid of small panels showing each of the eight drawing primitives applied once. Each panel contains a line, a rectangle, a circle, an ellipse, a cross, an arrow, a quadrilateral, or a short text string, with the name of the method that produced it labelled underneath.

As oito primitivas de desenho, uma por painel. Cada método produz um tipo de marca.

5.6.2. Cor

Cada método de desenho recebe um argumento color que decide que valor escrever em cada pixel pintado. A forma que esse argumento assume depende do formato da imagem. Para uma imagem RGB565, a forma natural é um tuplo (r, g, b) com cada canal em 0255; o módulo compacta isso na palavra RGB565 de 16 bits antes de escrever. Para uma imagem em escala de cinzentos, a forma natural é um número inteiro de brilho de 0 (preto) a 255 (branco). Os métodos também aceitam o valor armazenado bruto do formato – uma palavra compactada de 16 bits para RGB565, um inteiro de 8 bits para escala de cinzentos – que é a forma eficiente quando a cor foi calculada noutro lado e já está na forma armazenada.

Omitir o argumento color pinta a branco. Esse padrão é conveniente para trabalho em escala de cinzentos, onde o branco é o valor máximo e lê-se claramente contra a maioria dos fundos. Para sobreposições de depuração RGB565 está quase sempre errado: o verde ou o vermelho geralmente lê-se melhor contra o tipo de cena que uma câmara realmente vê, e uma cor explícita comunica intenção.

5.6.3. Espessura e preenchimento

A maioria dos métodos geométricos recebe duas flags que decidem como a marca é desenhada:

  • thickness=N define a largura da linha em pixels. O padrão é 1, que é adequado para a maioria das sobreposições; um valor maior é útil quando uma anotação tem de permanecer visível contra uma cena complexa ou após uma etapa subsequente do pipeline modificar a imagem.

  • fill=True muda a marca de um contorno para uma sólida, pintando cada pixel interior com a cor escolhida. O padrão é False.

Estas flags não se aplicam às primitivas que não têm interior para preencher – a linha, a cruz, a seta, o quadrilátero – onde apenas thickness é relevante.

5.6.4. Desenhar texto

draw_string() escreve caracteres de uma fonte de mapa de bits embutida de 8 por 10 pixels. x e y são o canto superior esquerdo da célula do primeiro caractere, text é a cadeia de caracteres a desenhar, e color segue a mesma convenção que os métodos geométricos. A fonte cobre todo o intervalo ASCII imprimível e não tem kerning – cada caractere ocupa a mesma célula de 8 pixels de largura – o que facilita o posicionamento da saída.

img.draw_string(10, 10, "blobs: 3", color=(0, 255, 0))

A cadeia de caracteres pode incluir newlines (\n); cada newline move o próximo caractere para o início de uma nova linha dez pixels abaixo da anterior. O argumento scale desenha cada caractere num tamanho maior por um fator de ponto flutuante, e x_spacing e y_spacing adicionam preenchimento em torno de cada caractere. Um pequeno conjunto de flags de rotação / espelhamento / inversão aplica-se a toda a cadeia ou a cada caractere individualmente – controlo suficiente para dispor texto ao longo de um ângulo ou contra uma aresta não horizontal quando o layout o exige.

5.6.5. Limpar a tela

Um método na família não desenha nenhuma marca específica. Simplesmente repõe todos os pixels da imagem a zero:

  • clear() – zera todos os pixels, opcionalmente restringido a um ROI ou com âmbito através de uma máscara.

clear() é a resposta certa quando uma aplicação está a compor uma anotação do zero em cada fotograma – começar com uma tela preta, desenhar as novas anotações, entregar o resultado ao ecrã – em vez de sobrepor sobre o fotograma capturado. É também a forma mais económica de preparar uma imagem de rascunho para uso como buffer de máscara.

Uma imagem recém-alocada já é zero a partir do construtor, pelo que clear() importa especificamente para buffers reutilizados entre fotogramas.