5.23. Perspektiivikorjaus

Varoitus

Mielivaltainen 3-kertaa-3 transform-matriisi on tuettu vain OpenMV Cam N6:lla – avainsana jätetään hiljaisesti huomiotta jokaisella muulla kortilla. Sovellusten, joiden on toimittava muualla, on käytettävä valmista rotation_corr() -metodia (sen corners=-muotoa) tai esilaskettava korjattu kuva kortin ulkopuolella.

Valmis rotation_corr() -metodi paketoi tietyn perspektiivivääristymien perheen pienen parametrijoukon taakse ja toimii jokaisella tuetulla kortilla. Jotkin sovellukset tarvitsevat vääristymän, joka ei sovi tuohon muotoon: mielivaltaisen projektiivisen uudelleenkuvauksen yhdestä nelikulmiosta toiseen, kalibroidun korjauksen tunnetulle kiinnitykselle, joka on jo selvitetty etukäteen, tai jonkin aiemman algoritmin valmiina luovuttaman vääristymämatriisin. Niitä varten draw_image() – yhdessä metodien copy(), crop() ja scale() kanssa – hyväksyy transform-avainsanan, joka ottaa käsin rakennetun 3-kertaa-3-matriisin, joka kuvaa vääristymän suoraan.

5.23.1. Affiinit ja projektiiviset muunnokset

Geometriset vääristymät ilmaistaan homogeenisissa koordinaateissa: pikselin sijainti (x, y), johon on lisätty 1, kerrottuna 3-kertaa-3-matriisilla.

Affiini muoto on hyvä paikka aloittaa. Sen alin rivi on kiinnitetty arvoon \((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}\]

Auki kirjoitettuna jokainen ulostulokoordinaatti on syötekoordinaattien lineaarinen yhdistelmä plus vakio:

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

joka kattaa skaalauksen, kierron, leikkauksen ja siirron missä tahansa yhdistelmässä – ja niissä kaikissa yhdensuuntaiset viivat pysyvät yhdensuuntaisina.

Projektiivinen (perspektiivinen) muoto vapauttaa alimman rivin:

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

Auki kirjoitettuna:

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

Jakaminen termillä \(w' = g x + h y + 1\) on se, mikä tekee muunnoksesta projektiivisen pelkän affiinin sijaan. Kun \(g\) ja \(h\) ovat molemmat nollia, \(w'\) pysyy ykkösenä eikä jako tee mitään – jälleen affiini muoto. Kun jompikumpi on nollasta poikkeava, \(w'\) vaihtelee syötteen sijainnin mukaan ja eri sijainneissa olevat pikselit lyhentyvät eri määrillä, mikä ei enää pidä yhdensuuntaisia viivoja yhdensuuntaisina – se on juuri keystone-ilmiö, joka syntyy katsottaessa tasoa vinosta kulmasta. Projektiivinen muunnos on yleisin geometrinen vääristymä, joka vie suorat viivat suoriksi viivoiksi; skaalaus, peilaus, transponointi, kierto ja neljän kulman kiertokorjaus ovat kaikki sen erikoistapauksia.

Nimetyt muunnokset johtuvat suoraan affiinista muodosta. Identiteettimuunnos on identiteettimatriisi, ja:

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

Useimpia käsin rakennettuja muunnoksia varten sovellus aloittaa yhdellä näistä pohjana ja kertoo sisään lisää matriiseja jokaista lisäoperaatiota varten päätyen yhteen 3-kertaa-3-matriisiin, joka kuvaa yhdistetyn vääristymän. Matriisit sovelletaan oikealta vasemmalle: \(M = T R S\) suorittaa ensin skaalauksen, sitten kierron ja sitten siirron. Yhdistelmä, jota kaikki lopulta tarvitsevat, on kierto kuvan keskipisteen ympäri – pelkkä kiertomatriisi pyörittää kuvaa pikseliorigon ympäri vasemmassa yläkulmassa, joten keskitetty versio siirtää keskipisteen \((c_x, c_y)\) origoon, kiertää ja siirtää sen takaisin:

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

Matriisi annetaan transform-avainsanan kautta, toimitettuna 3-kertaa-3-muotoisena ulab.numpy.ndarray-oliona. Käytettävä metodi on draw_image(), joka vääristää lähteen matriisin läpi piirtäessään sen kohteeseen – tulos päätyy puskuriin, jota sovellus hallitsee, ja vääristymä yhdistyy kaiken muun kanssa kutsussa: skaalauksen, alpha-sekoituksen ja maskauksen.

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)

Esimerkki vääristää kuvan img kuvalle canvas skaalattuna 1,2-kertaiseksi kumpaankin suuntaan ja siirrettynä vasemmalle ja ylös 20 ja 15 pikseliä – affiini vääristymä, joka on rakennettu suoraan edellä kuvatuista matriisin alkioista. Sama avainsana metodeissa copy(), crop() ja scale() soveltaa vääristymän kuvaan itseensä.