5.23. Corecția perspectivei

Atenționare

Matricea arbitrară transform de 3 pe 3 este acceptată doar pe OpenMV Cam N6 – pe orice altă placă cuvântul-cheie este ignorat în mod silențios. Aplicațiile care trebuie să ruleze oriunde altundeva trebuie să folosească metoda predefinită rotation_corr() (cu forma sa corners=) sau să precalculeze imaginea corectată în afara plăcii.

Metoda predefinită rotation_corr() împachetează o familie anume de deformări de perspectivă în spatele unui set mic de parametri și rulează pe orice placă acceptată. Unele aplicații au nevoie de o deformare care nu se încadrează în acea formă: o remapare proiectivă arbitrară de la un patrulater la altul, o corecție calibrată pentru o montare cunoscută care a fost deja calculată în prealabil, o matrice de deformare predată gata făcută de către un algoritm din amonte. Pentru acestea, draw_image() – împreună cu copy(), crop() și scale() – acceptă un cuvânt-cheie transform care primește o matrice de 3 pe 3 construită manual, descriind direct deformarea.

5.23.1. Transformări afine și proiective

Deformările geometrice sunt exprimate în coordonate omogene: poziția pixelului (x, y) cu un 1 adăugat, înmulțită cu o matrice de 3 pe 3.

Forma afină este punctul de plecare. Rândul ei inferior este fixat la \((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}\]

Scrisă explicit, fiecare coordonată de ieșire este o combinație liniară a coordonatelor de intrare plus o constantă:

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

ceea ce acoperă scalarea, rotația, forfecarea și translația în orice combinație – iar sub toate acestea, liniile paralele rămân paralele.

Forma proiectivă (de perspectivă) eliberează rândul inferior:

\[\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}\]

Scrisă explicit:

\[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}\]

Împărțirea la \(w' = g x + h y + 1\) este ceea ce face transformarea proiectivă, nu doar afină. Când \(g\) și \(h\) sunt ambele zero, \(w'\) rămâne la unu, iar împărțirea nu face nimic – din nou forma afină. Când oricare este nenul, \(w'\) variază în funcție de poziția de intrare, iar pixelii de la poziții diferite sunt scurtați diferit, ceea ce nu mai menține liniile paralele paralele – este exact efectul de trapez al privirii unui plan plat dintr-un unghi oblic. O transformare proiectivă este cea mai generală deformare geometrică ce duce liniile drepte în linii drepte; scalarea, oglindirea, transpunerea, rotirea și corecția rotației cu patru colțuri sunt toate cazuri particulare ale ei.

Transformările denumite decurg direct din forma afină. Transformarea identică este matricea identitate, iar:

\[\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}\]

Pentru majoritatea transformărilor construite manual, o aplicație pornește de la una dintre acestea ca bază și înmulțește cu matrice suplimentare pentru fiecare operație în plus, ajungând la o singură matrice de 3 pe 3 care descrie deformarea compusă. Matricele se aplică de la dreapta la stânga: \(M = T R S\) execută mai întâi scalarea, apoi rotația, apoi translația. Compunerea de care are nevoie toată lumea mai devreme sau mai târziu este rotația în jurul centrului imaginii – o matrice de rotație simplă rotește imaginea în jurul originii pixelilor din colțul din stânga sus, așa că versiunea centrată mută centrul \((c_x, c_y)\) în origine, rotește și îl mută înapoi:

\[\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. Cuvântul-cheie transform

Matricea se transmite printr-un cuvânt-cheie transform, furnizat ca un ulab.numpy.ndarray de 3 pe 3. Metoda la care se apelează este draw_image(), care deformează sursa prin matrice pe măsură ce o desenează pe o destinație – rezultatul ajunge într-un tampon (buffer) controlat de aplicație, iar deformarea se compune cu tot restul din apel: scalarea, amestecarea alfa, mascarea.

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)

Exemplul deformează img pe canvas scalat cu 1.2 în fiecare direcție și deplasat la stânga și în sus cu 20, respectiv 15 pixeli – o deformare afină construită direct din elementele matricei descrise mai sus. Același cuvânt-cheie pe copy(), crop() și scale() aplică deformarea imaginii înseși.