5.2. Coordenadas e regiões¶
O processamento de imagens atua sobre pixels e, para atuar sobre um pixel, um algoritmo precisa endereçá-lo por coordenada. Para atuar sobre um retângulo de pixels, vale o mesmo – o retângulo precisa ser descrito de uma forma com a qual o algoritmo e o código da aplicação concordem. A convenção que o módulo image usa para coordenadas e retângulos é direta, com um detalhe que confunde leitores acostumados à convenção matemática em vez da convenção da computação gráfica, e vale a pena deixá-lo explícito desde o início.
5.2.1. A grade de pixels¶
O pixel (0, 0) é o canto superior esquerdo de uma imagem. O eixo x corre para a direita, então um x maior significa mais à direita. O eixo y corre para baixo, então um y maior significa mais abaixo na imagem. Uma imagem de largura por altura contém pixels em coordenadas inteiras de (0, 0) até (width - 1, height - 1); não há pixel em (width, 0) ou (0, height) – essas posições são as bordas direita e inferior, um passo além do último pixel real em cada direção.
O eixo y voltado para baixo é o detalhe mencionado acima. Um leitor acostumado à geometria do papel quadriculado espera que um y maior signifique mais para cima; aqui essa intuição é exatamente invertida. A razão da inversão é que tanto os sensores digitais quanto os monitores digitais trabalham a partir do canto superior esquerdo e percorrem cada linha para a direita, de cima para baixo, e dispor os pixels na memória nessa mesma ordem torna a relação entre “a posição i no buffer” e “a linha r, coluna c da imagem” a aritmética mais simples possível – a posição i do pixel (x, y) é simplesmente y * width + x. Toda biblioteca de imagens concordou com esse arranjo décadas atrás pela mesma razão, e o custo é um pequeno ajuste mental ao trabalhar com imagens pela primeira vez.
O sistema de coordenadas da imagem: origem no canto superior esquerdo, x correndo para a direita, y correndo para baixo. Uma região retangular dentro da imagem é nomeada por seu canto superior esquerdo (x, y) e suas dimensões (w, h).¶
5.2.2. Retângulos¶
A maioria das operações sobre uma imagem se importa menos com um único pixel do que com um retângulo de pixels – uma área onde olhar, uma região para copiar, um quadro dentro de um quadro sobre o qual calcular estatísticas. A forma de nomear um retângulo escolhe a extensão mais simples possível da convenção de pixel único: forneça a coordenada do canto superior esquerdo, seguida das dimensões do retângulo, empacotadas em uma quádrupla (x, y, w, h). Os pixels dentro do retângulo ficam nas colunas de x até x + w - 1 e nas linhas de y até y + h - 1.
O detalhe que vale deixar explícito aqui é que w e h são tamanhos, não coordenadas do canto inferior direito. O retângulo (10, 20, 4, 3) cobre as colunas 10, 11, 12, 13 e as linhas 20, 21, 22 – doze pixels no total – e não uma região que vai de (10, 20) até (4, 3). A convenção é uniforme em todo o módulo, então, uma vez internalizada, os tropeços param, mas ela realmente confunde as pessoas na primeira vez.
A forma (x, y, w, h) aparece em três lugares que parecem distintos, mas compartilham a convenção. O primeiro é quando uma imagem descreve a própria área: o retângulo que cobre a imagem inteira é (0, 0, width, height). O segundo é quando um método de detecção retorna um resultado com uma caixa delimitadora – um blob, um rect, um apriltag – e a caixa é relatada de volta como (x, y, w, h). O terceiro é quando um método precisa ser instruído a trabalhar sobre uma sub-região da imagem em vez do quadro inteiro; o argumento nomeado roi que delimita a operação recebe a mesma quádrupla.
Pegar uma caixa delimitadora de um método e passá-la para o roi do método seguinte é um dos padrões mais comuns no processamento de imagens. A caixa delimitadora de uma primeira detecção grosseira reduz a área de busca para uma segunda detecção mais fina, e o vocabulário uniforme entre os resultados de detecção e os argumentos dos métodos é o que torna esse padrão tão direto quanto ele é – uma única forma de tupla, usada da mesma maneira em ambos os lados da transferência.
5.2.3. Endereços inteiros, centroides fracionários¶
Os próprios endereços de pixels são inteiros. Um pixel ou está ou não está em uma dada coluna e linha inteiras, e perguntar o que está na coordenada (40.5, 30.7) não é uma pergunta bem formulada – não há pixel situado exatamente nessa posição. Algumas poucas grandezas que o módulo image deriva das posições dos pixels são, no entanto, fracionárias, e vale entender por quê, para que a distinção não pegue a aplicação de surpresa mais tarde.
O caso mais comum é o centroide – o centro de massa de uma região. Para uma região conexa de pixels, o centroide em forma de ponto flutuante é a média das posições dos pixels membros, ponderada por sua densidade. Uma região cujos pixels se estendem por duas colunas terá um centroide x de, digamos, 41,6 – uma posição real que o olho descreveria como “o meio daquela região”, ainda que nenhum pixel real esteja exatamente nesse x. Os objetos de resultado de detecção carregam ambas as formas como propriedades somente leitura: um par de inteiros (cx / cy, útil ao realimentar a posição em algo que espera coordenadas inteiras de pixel) e um par de ponto flutuante (cxf / cyf, útil quando a posição vai para uma malha de controle que se beneficia de resolução de subpixel).
O outro caso é o deslocamento entre dois quadros medido no domínio da frequência. Técnicas que analisam o conteúdo espectral de uma imagem em vez de seus pixels diretamente conseguem resolver deslocamentos menores do que um pixel, e relatam esses deslocamentos como valores (dx, dy) de ponto flutuante.
A regra prática: endereços de pixels são inteiros; posições e deslocamentos que saem de um algoritmo podem ser de ponto flutuante. Os métodos de desenho aceitam qualquer uma das formas e arredondam valores de ponto flutuante para baixo, ao pixel inteiro mais próximo, quando o resultado precisa cair sobre a grade.
5.2.4. Cartesianas e polares¶
O sistema descrito até aqui é cartesiano: cada pixel é nomeado por seu deslocamento horizontal e vertical em relação à origem. É o sistema em que os bytes são armazenados – o pixel i no buffer corresponde ao pixel na coluna i % width e na linha i // width, percorrendo as linhas a partir do topo – e é o sistema em que todo método opera por padrão.
Vale conhecer uma segunda representação porque alguns algoritmos funcionam muito melhor nela. As coordenadas polares nomeiam cada pixel por sua distância a um ponto central escolhido e pelo ângulo entre ele e uma direção de referência. Os pixels da imagem não se moveram – os bytes ainda estão no mesmo buffer em ordem de linhas – mas o esquema de endereçamento mudou de “a que distância à direita e a que distância abaixo” para “a que distância do centro e em que ângulo ao redor dele”.
O mesmo ponto P, nomeado de duas formas: cartesiana (x, y) a partir da origem no canto superior esquerdo, polar (r, theta) a partir de um centro escolhido.¶
Por que se dar ao trabalho de mudar? Por causa de duas identidades que transformam buscas difíceis em buscas fáceis.
Em coordenadas polares, girar a imagem em torno do centro escolhido é a mesma operação que transladar seus pixels ao longo do eixo do ângulo – a direção x na imagem reprojetada. Uma cópia rotacionada é a original deslocada para a esquerda ou para a direita na forma polar.
Na variante log-polar – o eixo da distância usa uma escala logarítmica, o eixo do ângulo permanece linear – escalar a imagem em torno do centro escolhido é a mesma operação que transladar seus pixels ao longo do eixo da distância – a direção y. Uma cópia escalada é a original deslocada para cima ou para baixo na forma log-polar.
Assim, um algoritmo que precisa reconhecer um padrão conhecido sob rotação ou escala pode fazer sua busca no espaço polar, onde ambas as transformações viram translações comuns. Translações são muito mais baratas de buscar do que rotações e escalas, e a reprojeção polar é o que torna essa substituição possível.
As coordenadas polares não substituem as cartesianas para armazenar pixels; os bytes vivem sempre na grade cartesiana. O módulo fornece um par de métodos que reprojetam uma imagem da forma cartesiana para a polar sob demanda, o algoritmo que precisa de coordenadas polares faz seu trabalho, e ou o resultado é projetado de volta ou a medição no espaço polar é usada diretamente. Esse mecanismo é a única razão pela qual coordenadas polares aparecem em qualquer lugar da superfície do módulo.
Com coordenadas cartesianas para nomear pixels individuais, a quádrupla (x, y, w, h) para nomear retângulos deles, e coordenadas polares disponíveis quando um algoritmo se beneficia delas, uma aplicação tem um vocabulário completo para nomear onde algo está em uma imagem. O que de fato está armazenado em qualquer uma dessas posições é a próxima camada da fundação.