5.23. Perspektivkorrigering

Varning

Den godtyckliga 3-gånger-3-matrisen transform stöds endast på OpenMV Cam N6 – nyckelordet ignoreras tyst på alla andra kort. Applikationer som behöver kunna köras någon annanstans måste använda den färdiga metoden rotation_corr() (med dess corners=-form) eller förberäkna den korrigerade bilden utanför kortet.

Den färdiga metoden rotation_corr() paketerar en viss familj av perspektivförvrängningar bakom en liten uppsättning parametrar och körs på varje stött kort. Vissa applikationer behöver en förvrängning som inte passar den formen: en godtycklig projektiv omavbildning från en fyrhörning till en annan, en kalibrerad korrigering för ett känt fäste som redan har räknats ut offline, eller en förvrängningsmatris som lämnats över färdig av någon tidigare algoritm. För dessa accepterar draw_image() – tillsammans med copy(), crop() och scale() – ett nyckelord transform som tar en handbyggd 3-gånger-3-matris som direkt beskriver förvrängningen.

5.23.1. Affina och projektiva transformationer

Geometriska förvrängningar uttrycks i homogena koordinater: pixelpositionen (x, y) med en 1 tillagd, multiplicerad med en 3-gånger-3-matris.

Den affina formen är platsen att börja på. Dess nedersta rad är fixerad till \((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}\]

Utskrivet är varje utdatakoordinat en linjär kombination av indatakoordinaterna plus en konstant:

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

vilket täcker skalning, rotation, skjuvning och translation i valfri kombination – och under alla dessa förblir parallella linjer parallella.

Den projektiva (perspektiv-) formen frigör den nedersta raden:

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

Utskrivet:

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

Divisionen med \(w' = g x + h y + 1\) är det som gör transformationen projektiv snarare än bara affin. När \(g\) och \(h\) båda är noll förblir \(w'\) lika med ett och divisionen gör ingenting – den affina formen igen. När någondera är skild från noll varierar \(w'\) med indatapositionen och pixlar på olika positioner blir förkortade med olika mycket, vilket inte längre håller parallella linjer parallella – det är precis den stupande effekten (keystone) av att titta på ett plant plan från en sned vinkel. En projektiv transformation är den mest allmänna geometriska förvrängning som tar raka linjer till raka linjer; skalning, spegling, transponering, rotation och fyrhörnsrotationskorrigeringen är alla specialfall av en sådan.

De namngivna transformationerna faller ut direkt ur den affina formen. Identitetstransformationen är identitetsmatrisen, och:

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

För de flesta handbyggda transformer börjar en applikation med en av dessa som bas och multiplicerar in ytterligare matriser för varje extra operation, och slutar med en enda 3-gånger-3-matris som beskriver den sammansatta förvrängningen. Matriser tillämpas från höger till vänster: \(M = T R S\) kör skalningen först, sedan rotationen, sedan translationen. Den sammansättning som alla behöver så småningom är rotation kring bildens centrum – en bar rotationsmatris snurrar bilden kring pixelursprunget i det övre vänstra hörnet, så den centrerade versionen flyttar centrum \((c_x, c_y)\) till ursprunget, roterar och flyttar tillbaka det:

\[\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. Nyckelordet transform

Matrisen skickas in via ett nyckelord transform, angivet som en 3-gånger-3 ulab.numpy.ndarray. Metoden att ta till är draw_image(), som förvränger källan genom matrisen medan den ritar den på en destination – resultatet hamnar i en buffert som applikationen kontrollerar, och förvrängningen komponeras med allt annat i anropet: skalningen, alfablandningen, maskeringen.

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)

Exemplet förvränger imgcanvas skalad med 1,2 i varje riktning och förskjuten åt vänster och uppåt med 20 respektive 15 pixlar – en affin förvrängning byggd direkt från matriselementen som beskrivs ovan. Samma nyckelord på copy(), crop() och scale() tillämpar förvrängningen på själva bilden.