5.6. Desenhando formas e texto#

Um algoritmo que decide algo sobre uma imagem frequentemente precisa que essa decisão seja visível. Um detector de blobs encontra uma região de interesse da aplicação; a aplicação quer a região desenhada no quadro para que um operador – ou o desenvolvedor 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 do IDE lê o que estiver no frame buffer no momento em que faz a consulta, então a maneira mais simples de tornar a saída do algoritmo visível é escrever anotações no próprio frame buffer. A família de métodos de desenho da 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 se mantém próximo das primitivas geométricas que uma anotação realmente precisa:

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

  • draw_rectangle() – um retângulo alinhado aos eixos, vazado ou preenchido.

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

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

  • draw_cross() – um sinal de mais em um ponto, a marca usual para um centroide ou um alvo de clique.

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

  • draw_edges() – os quatro lados de um quadrilátero arbitrário, dados os quatro pontos dos cantos; a maneira natural de contornar uma tag detectada ou uma região distorcida em perspectiva.

  • draw_string() – texto a partir de uma fonte bitmap embutida.

Cada um desses métodos modifica a imagem de origem in-place e retorna a mesma imagem para encadeamento, seguindo a convenção dos métodos operadores 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 faz um tipo de marca.#

5.6.2. Cor#

Cada método de desenho recebe um argumento color que decide qual valor escrever em cada pixel pintado. A forma desse argumento depende do formato da imagem. Para uma imagem RGB565, a forma natural é uma tupla (r, g, b) com cada canal de 0 a 255; o módulo compacta isso na palavra RGB565 de 16 bits antes de escrevê-la. Para uma imagem em escala de cinza, a forma natural é um único inteiro de brilho, de 0 (preto) a 255 (branco). Os métodos também aceitam o valor bruto armazenado do formato – uma palavra empacotada de 16 bits para RGB565, um inteiro de 8 bits para escala de cinza – que é a forma eficiente quando a cor foi calculada em outro lugar e já está na forma armazenada.

Omitir o argumento color pinta de branco. Esse padrão é conveniente para trabalho em escala de cinza, onde o branco é o valor máximo e se destaca claramente contra a maioria dos fundos. Para sobreposições de depuração em RGB565, ele quase sempre está errado: verde ou vermelho geralmente se destacam melhor contra o tipo de cena que uma câmera realmente vê, e uma cor explícita comunica a 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 serve para a maioria das sobreposições; um valor maior é útil quando uma anotação precisa permanecer visível contra uma cena carregada ou após uma etapa posterior do pipeline modificar ainda mais a imagem.

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

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

5.6.4. Desenhando texto#

draw_string() escreve caracteres usando uma fonte bitmap embutida de 8 por 10 pixels. x e y são o canto superior esquerdo da célula do primeiro caractere, text é a string a desenhar, e color segue a mesma convenção dos métodos geométricos. A fonte abrange toda a faixa ASCII imprimível e não tem kerning – cada caractere ocupa a mesma célula de 8 pixels de largura – o que torna a saída fácil de posicionar.

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

A string pode incluir quebras de linha (\n); cada quebra de linha move o próximo caractere para o início de uma nova linha dez pixels abaixo da anterior. O argumento scale desenha cada caractere em um tamanho maior por um fator de ponto flutuante, e x_spacing e y_spacing adicionam espaçamento em torno de cada caractere. Um pequeno conjunto de flags de rotacionar / espelhar / inverter se aplica tanto à string inteira quanto a cada caractere individualmente – controle suficiente para dispor o texto ao longo de um ângulo ou contra uma borda não horizontal quando o layout exigir.

5.6.5. Limpando a tela#

Um método da família não desenha nenhuma marca específica. Ele apenas redefine cada pixel da imagem para zero:

  • clear() – zera cada pixel, opcionalmente restrito a uma ROI ou delimitado por uma máscara.

clear() é a resposta certa quando uma aplicação está compondo uma anotação do zero a cada quadro – começar com uma tela preta, desenhar as novas anotações, entregar o resultado ao display – em vez de sobrepor sobre o quadro capturado. É também a maneira mais barata de preparar uma imagem temporária para uso como buffer de máscara.

Uma imagem recém-alocada já vem zerada do construtor, então clear() importa especificamente para buffers reutilizados entre quadros.