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.