5.23. Correzione della prospettiva

Avvertimento

La matrice transform 3 per 3 arbitraria è supportata solo sulla OpenMV Cam N6 – la parola chiave viene ignorata silenziosamente su ogni altra scheda. Le applicazioni che devono funzionare altrove devono usare il metodo predefinito rotation_corr() (con la sua forma corners=) oppure pre-calcolare l’immagine corretta fuori dalla scheda.

Il metodo predefinito rotation_corr() racchiude una particolare famiglia di deformazioni prospettiche dietro un piccolo insieme di parametri, e funziona su ogni scheda supportata. Alcune applicazioni necessitano di una deformazione che non rientra in quella forma: una rimappatura proiettiva arbitraria da un quadrilatero a un altro, una correzione calibrata per un montaggio noto già elaborata off-line, una matrice di deformazione fornita già pronta da qualche algoritmo a monte. Per questi casi, draw_image() – insieme a copy(), crop() e scale() – accetta una parola chiave transform che prende una matrice 3 per 3 costruita a mano che descrive direttamente la deformazione.

5.23.1. Trasformazioni affini e proiettive

Le deformazioni geometriche sono espresse in coordinate omogenee: la posizione del pixel (x, y) con un 1 aggiunto, moltiplicata per una matrice 3 per 3.

La forma affine è il punto di partenza. La sua riga inferiore è fissata a \((0, 0, 1)\):

\[\begin{split}\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}\end{split}\]

Scritta per esteso, ogni coordinata di output è una combinazione lineare delle coordinate di input più una costante:

\[x' = a x + b y + c, \qquad y' = d x + e y + f\]

che copre il ridimensionamento, la rotazione, lo shearing e la traslazione in qualsiasi combinazione – e sotto tutte queste, le linee parallele restano parallele.

La forma proiettiva (prospettica) libera la riga inferiore:

\[\begin{split}\begin{bmatrix} x'' \\ y'' \\ w' \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ g & h & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}, \qquad (x', y') = \left( \frac{x''}{w'}, \; \frac{y''}{w'} \right)\end{split}\]

Scritta per esteso:

\[x' = \frac{a x + b y + c}{g x + h y + 1}, \qquad y' = \frac{d x + e y + f}{g x + h y + 1}\]

La divisione per \(w' = g x + h y + 1\) è ciò che rende la trasformazione proiettiva anziché meramente affine. Quando \(g\) e \(h\) sono entrambi zero, \(w'\) resta a uno e la divisione non fa nulla – di nuovo la forma affine. Quando uno dei due è non nullo, \(w'\) varia con la posizione di input e i pixel in posizioni diverse vengono scorciati di quantità diverse, il che non mantiene più le linee parallele parallele – è esattamente l’effetto keystone di guardare un piano piatto da un angolo obliquo. Una trasformazione proiettiva è la deformazione geometrica più generale che porta le linee rette in linee rette; il ridimensionamento, il ribaltamento, la trasposizione, la rotazione e la correzione della rotazione a quattro angoli sono tutti casi speciali di una sola.

Le trasformazioni con nome derivano direttamente dalla forma affine. La trasformazione identità è la matrice identità, e:

\[\begin{split}\underbrace{\begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{bmatrix}}_{\text{translate by } (t_x, \; t_y)} \qquad \underbrace{\begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix}}_{\text{scale by } (s_x, \; s_y)} \qquad \underbrace{\begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}}_{\text{rotate by } \theta}\end{split}\]

Per la maggior parte delle trasformazioni costruite a mano un’applicazione parte da una di queste come base e moltiplica ulteriori matrici per ogni operazione aggiuntiva, terminando con una singola matrice 3 per 3 che descrive la deformazione composita. Le matrici si applicano da destra a sinistra: \(M = T R S\) esegue prima il ridimensionamento, poi la rotazione, poi la traslazione. La composizione di cui tutti hanno bisogno prima o poi è la rotazione attorno al centro dell’immagine – una matrice di rotazione nuda fa ruotare l’immagine attorno all’origine dei pixel nell’angolo in alto a sinistra, quindi la versione centrata sposta il centro \((c_x, c_y)\) all’origine, ruota e lo riporta indietro:

\[\begin{split}M = \underbrace{\begin{bmatrix} 1 & 0 & c_x \\ 0 & 1 & c_y \\ 0 & 0 & 1 \end{bmatrix}}_{\text{move centre back}} \underbrace{\begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}}_{\text{rotate}} \underbrace{\begin{bmatrix} 1 & 0 & -c_x \\ 0 & 1 & -c_y \\ 0 & 0 & 1 \end{bmatrix}}_{\text{move centre to origin}}\end{split}\]

5.23.2. La parola chiave transform

La matrice viene fornita tramite una parola chiave transform, passata come ulab.numpy.ndarray 3 per 3. Il metodo a cui ricorrere è draw_image(), che deforma la sorgente attraverso la matrice mentre la disegna su una destinazione – il risultato finisce in un buffer controllato dall’applicazione, e la deformazione si compone con tutto il resto nella chiamata: il ridimensionamento, la fusione alpha, il mascheramento.

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)

L’esempio deforma img su canvas ridimensionata di 1,2 in ciascuna direzione e spostata a sinistra e verso l’alto rispettivamente di 20 e 15 pixel – una deformazione affine costruita direttamente dagli elementi della matrice descritti sopra. La stessa parola chiave su copy(), crop() e scale() applica la deformazione all’immagine stessa.