5.23. Correção de perspectiva¶
Aviso
A matriz transform 3-por-3 arbitrária é suportada apenas na OpenMV Cam N6 – o argumento nomeado é silenciosamente ignorado em todas as outras placas. Aplicações que precisam rodar em qualquer outro lugar devem usar o método pronto rotation_corr() (com sua forma corners=) ou pré-calcular a imagem corrigida fora da placa.
O método pronto rotation_corr() empacota uma família específica de deformações de perspectiva por trás de um pequeno conjunto de parâmetros, e roda em todas as placas suportadas. Algumas aplicações precisam de uma deformação que não se encaixa nessa forma: um remapeamento projetivo arbitrário de um quadrilátero para outro, uma correção calibrada para uma montagem conhecida que já foi resolvida off-line, uma matriz de deformação entregue pronta por algum algoritmo anterior. Para esses casos, draw_image() – junto com copy(), crop() e scale() – aceita um argumento nomeado transform que recebe uma matriz 3-por-3 construída manualmente descrevendo a deformação diretamente.
5.23.1. Transformações afins e projetivas¶
As deformações geométricas são expressas em coordenadas homogêneas: a posição do pixel (x, y) com um 1 anexado, multiplicada por uma matriz 3-por-3.
A forma afim é o ponto de partida. Sua linha inferior é fixada em \((0, 0, 1)\):
Escrita por extenso, cada coordenada de saída é uma combinação linear das coordenadas de entrada mais uma constante:
o que abrange escala, rotação, cisalhamento e translação em qualquer combinação – e, sob todas elas, linhas paralelas permanecem paralelas.
A forma projetiva (perspectiva) libera a linha inferior:
Escrita por extenso:
A divisão por \(w' = g x + h y + 1\) é o que torna a transformação projetiva, em vez de meramente afim. Quando \(g\) e \(h\) são ambos zero, \(w'\) permanece em um e a divisão não faz nada – a forma afim novamente. Quando qualquer um deles é diferente de zero, \(w'\) varia com a posição de entrada e pixels em posições diferentes sofrem escorço por quantidades diferentes, o que não mantém mais linhas paralelas paralelas – é exatamente o efeito trapezoidal (keystone) de olhar para um plano achatado de um ângulo oblíquo. Uma transformação projetiva é a deformação geométrica mais geral que leva linhas retas a linhas retas; escalar, espelhar, transpor, rotacionar e a correção de rotação por quatro cantos são todos casos especiais dela.
As transformações nomeadas derivam diretamente da forma afim. A transformação identidade é a matriz identidade, e:
Para a maioria das transformações construídas manualmente, uma aplicação começa com uma dessas como base e multiplica matrizes adicionais para cada operação extra, terminando com uma única matriz 3-por-3 que descreve a deformação composta. As matrizes são aplicadas da direita para a esquerda: \(M = T R S\) executa primeiro a escala, depois a rotação, depois a translação. A composição que todos acabam precisando é a rotação em torno do centro da imagem – uma matriz de rotação pura gira a imagem em torno da origem do pixel no canto superior esquerdo, então a versão centralizada move o centro \((c_x, c_y)\) para a origem, rotaciona e o move de volta:
5.23.2. O argumento nomeado transform¶
A matriz é fornecida por meio de um argumento nomeado transform, passado como um ulab.numpy.ndarray 3-por-3. O método a ser usado é draw_image(), que deforma a origem através da matriz enquanto a desenha sobre um destino – o resultado cai em um buffer que a aplicação controla, e a deformação se compõe com tudo o mais na chamada: a escala, a mesclagem alfa, o mascaramento.
import ulab.numpy as np
M = np.array([[1.2, 0.0, -20.0],
[0.0, 1.2, -15.0],
[0.0, 0.0, 1.0]])
canvas.draw_image(img, transform=M)
O exemplo deforma img sobre canvas escalado por 1,2 em cada direção e deslocado para a esquerda e para cima em 20 e 15 pixels, respectivamente – uma deformação afim construída diretamente a partir das entradas da matriz descritas acima. O mesmo argumento nomeado em copy(), crop() e scale() aplica a deformação à própria imagem.