5.23. Korekcja perspektywy¶
Ostrzeżenie
Dowolna macierz transform o wymiarach 3 na 3 jest obsługiwana tylko na OpenMV Cam N6 – na każdej innej płytce to słowo kluczowe jest po cichu ignorowane. Aplikacje, które muszą działać gdziekolwiek indziej, muszą używać gotowej metody rotation_corr() (z jej postacią corners=) lub wstępnie obliczyć skorygowany obraz poza płytką.
Gotowa metoda rotation_corr() pakuje pewną rodzinę zniekształceń perspektywicznych za niewielkim zestawem parametrów i działa na każdej obsługiwanej płytce. Niektóre aplikacje potrzebują zniekształcenia, które nie pasuje do tej postaci: dowolne rzutowe odwzorowanie z jednego czworokąta na inny, skalibrowana korekcja dla znanego montażu, która została już wypracowana offline, gotowa macierz zniekształcenia przekazana przez jakiś wcześniejszy algorytm. Dla nich draw_image() – wraz z copy(), crop() oraz scale() – przyjmuje słowo kluczowe transform, które przyjmuje ręcznie zbudowaną macierz 3 na 3 opisującą zniekształcenie bezpośrednio.
5.23.1. Przekształcenia afiniczne i rzutowe¶
Zniekształcenia geometryczne wyraża się we współrzędnych jednorodnych: pozycja piksela (x, y) z dołączoną 1, pomnożona przez macierz 3 na 3.
Postać afiniczna to miejsce, od którego należy zacząć. Jej dolny wiersz jest ustalony na \((0, 0, 1)\):
Rozpisana, każda współrzędna wyjściowa jest kombinacją liniową współrzędnych wejściowych plus stała:
co obejmuje skalowanie, obrót, ścinanie i translację w dowolnej kombinacji – i przy wszystkich z nich linie równoległe pozostają równoległe.
Postać rzutowa (perspektywiczna) uwalnia dolny wiersz:
Rozpisana:
Dzielenie przez \(w' = g x + h y + 1\) jest tym, co czyni przekształcenie rzutowym, a nie jedynie afinicznym. Gdy \(g\) i \(h\) są oba zerowe, \(w'\) pozostaje równe jeden, a dzielenie nic nie zmienia – znów postać afiniczna. Gdy któreś z nich jest niezerowe, \(w'\) zmienia się wraz z pozycją wejściową, a piksele w różnych pozycjach ulegają skróceniu perspektywicznemu o różne wartości, co przestaje utrzymywać linie równoległe równoległymi – jest to dokładnie efekt trapezu wynikający z patrzenia na płaską płaszczyznę pod skośnym kątem. Przekształcenie rzutowe jest najbardziej ogólnym zniekształceniem geometrycznym, które przekształca proste linie w proste linie; skalowanie, odbijanie, transponowanie, obracanie oraz korekcja obrotu z czterema narożnikami są szczególnymi przypadkami tego jednego.
Nazwane przekształcenia wynikają bezpośrednio z postaci afinicznej. Przekształcenie tożsamościowe to macierz jednostkowa, oraz:
Dla większości ręcznie budowanych przekształceń aplikacja zaczyna od jednej z tych macierzy jako podstawy i domnaża kolejne macierze dla każdej dodatkowej operacji, kończąc na pojedynczej macierzy 3 na 3 opisującej złożone zniekształcenie. Macierze stosuje się od prawej do lewej: \(M = T R S\) wykonuje najpierw skalowanie, potem obrót, a następnie translację. Złożeniem, którego każdy w końcu potrzebuje, jest obrót wokół środka obrazu – sama macierz obrotu obraca obraz wokół początku pikseli w lewym górnym narożniku, więc wyśrodkowana wersja przenosi środek \((c_x, c_y)\) do początku, obraca i przenosi go z powrotem:
5.23.2. Słowo kluczowe transform¶
Macierz przekazuje się przez słowo kluczowe transform, podane jako ulab.numpy.ndarray o wymiarach 3 na 3. Metodą, po którą warto sięgnąć, jest draw_image(), która zniekształca źródło przez macierz, gdy rysuje je na obrazie docelowym – wynik trafia do bufora kontrolowanego przez aplikację, a zniekształcenie składa się ze wszystkim innym w tym wywołaniu: skalowaniem, mieszaniem alfa, maskowaniem.
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)
Przykład zniekształca img na canvas przeskalowany o 1,2 w każdym kierunku i przesunięty w lewo i w górę odpowiednio o 20 i 15 pikseli – zniekształcenie afiniczne zbudowane bezpośrednio z opisanych powyżej wpisów macierzy. To samo słowo kluczowe w copy(), crop() oraz scale() stosuje zniekształcenie do samego obrazu.