5.31. Dopasowywanie przesunięcia

Dopasowywanie szablonów odpowiada na pytanie gdzie w ramce znajduje się ten fragment; ocena podobieństwa odpowiada na pytanie jak bardzo te dwa obrazy są do siebie podobne jako całość. Pomiędzy nimi znajduje się inne pytanie: obie ramki przedstawiają tę samą scenę, ale kamera (lub scena) przesunęła się między nimi – o ile? To jest problem przesunięcia, a moduł image rozwiązuje go za pomocą jednej metody korelacji fazowej.

5.31.1. Przesunięcie metodą korelacji fazowej

find_displacement() szacuje sztywne wyrównanie pomiędzy dwoma obrazami o tym samym rozmiarze, korzystając z korelacji fazowej – metody dziedziny częstotliwości, która wykonuje szybką transformatę Fouriera (FFT) na każdym obrazie, koreluje krzyżowo ich fazy i lokalizuje szczyt w wyniku. Pozycja szczytu to translacja, która wyrównuje oba obrazy:

d = img.find_displacement(template)

print("shift:", d.x_translation, d.y_translation,
      " response:", d.response)

Zwracany obiekt Displacement przenosi x_translation oraz y_translation – przesunięcie w pikselach na każdej osi – a także response, wynik pewności w zakresie od 0.0 do 1.0, gdzie 1.0 oznacza idealny szczyt. Odfiltrowanie wykryć poniżej response > 0.3 odrzuca błędne wyniki, w których korelacja fazowa nigdy nie znalazła czystego szczytu.

Zarówno rotation, jak i scale mają w trybie domyślnym wartości odpowiednio 0.0 oraz 1.0; przyjmują rzeczywiste wartości tylko wtedy, gdy logpolar=True (patrz poniżej).

Metoda niesie ze sobą dwa praktyczne ograniczenia. Pierwszym są wymiary będące potęgą dwójki: FFT leżące u podstaw korelacji fazowej jest najszybsze – a w kamerze w pełni obsługiwane tylko – przy rozmiarach takich jak 32 na 32, 64 na 64 oraz 128 na 128. Najczystszym rozwiązaniem jest przechwytywanie bezpośrednio w jednym z tych rozmiarów, przekazując rozdzielczość do framesize() jako krotkę:

csi0.framesize((64, 64))

Aplikacja, która potrzebuje przesunięcia z większej ramki, zamiast tego wycina z interesującego ją obszaru fragment o rozmiarze będącym potęgą dwójki i uruchamia na nim mechanizm dopasowywania.

Drugim są wejścia o tym samym rozmiarze: roi oraz template_roi muszą wybierać identyczne szerokości i wysokości, w przeciwnym razie mechanizm dopasowywania odrzuca wywołanie. Dwa przechwycenia z tej samej kamery przy tej samej konfiguracji spełniają ten warunek automatycznie; przechwycona ramka porównywana z wczytanym odniesieniem wymaga najpierw wykadrowania obu do pasujących fragmentów o rozmiarze będącym potęgą dwójki.

5.31.2. Obrót i skala za pomocą log-polar

Tryb domyślny znajduje wyłącznie translację. Gdy dwie ramki różnią się także obrotem wokół wybranego środka lub skalą względem tego samego środka, uruchomienie korelacji fazowej na log-polarnym przekształceniu każdego obrazu zamienia te parametry na translację w układzie współrzędnych log-polar – którą ten sam mechanizm korelacji fazowej potrafi odzyskać:

d = img.find_displacement(template, logpolar=True)

print("rotation rad:", d.rotation,
      " scale:", d.scale,
      " response:", d.response)

Z logpolar=True metoda uruchamia ten sam potok dopasowywania względem obrazów przekształconych do postaci log-polar zamiast oryginałów. Pola rotation oraz scale wyniku zostają wypełnione: rotation to kąt w radianach pomiędzy dwiema ramkami, scale to współczynnik skali między nimi. x_translation oraz y_translation stają się w tym trybie bez znaczenia (translacja wzdłuż osi log-polar nie odpowiada liniowej translacji w źródle).

Słowo kluczowe fix_rotation_scale=True obejmuje przypadek pośredni: dwa obrazy różnią się zarówno translacją, jak i obrotem/skalą, a aplikacja potrzebuje wyłącznie translacji po skorygowaniu obrotu i skali. Mechanizm dopasowywania najpierw uruchamia przebieg log-polar, aby odzyskać obrót i skalę, stosuje odwrotność do jednego z obrazów, a następnie uruchamia przebieg translacji, aby odzyskać pozostałe przesunięcie. Flaga ma znaczenie tylko wtedy, gdy logpolar=False – prosi mechanizm dopasowywania w trybie translacji, aby najpierw usunął obrót/skalę.

Wzorzec z transformat biegunowych – kartezjański → biegunowy → dopasowanie – to właśnie to, co find_displacement() z logpolar=True wykonuje w jednym wywołaniu. Aplikacja zapisuje przy starcie referencyjny fragment log-polar, przechwytuje i przekształca log-polarnie każdą ramkę na żywo, a metoda odzyskuje różnicę obrotu i skali między nimi. Dla aplikacji, które potrzebują mechanizmu śledzącego niezmienniczego względem obrotu i skali – robota dokującego, którego kamera pochyla się i przybliża w miarę zbliżania się do celu, stabilizowanego gimbala, który musi wiedzieć, jak obraz obraca się względem odniesienia – jest to standardowa konstrukcja.

5.31.3. Klasyczne zastosowanie

Najczęstszym zastosowaniem find_displacement() jest szacowanie ruchu z ramki na ramkę w potoku, który przetwarza poruszającą się kamerę. Kamera przechwytuje mały fragment o rozmiarze będącym potęgą dwójki w ramce N, przechwytuje fragment o tym samym rozmiarze w ramce N+1, uruchamia find_displacement() na obu i odczytuje przesunięcie w pikselach między nimi. Przesunięcie to szacowany ruch kamery (lub sceny, w zależności od tego, czyj układ odniesienia ma znaczenie) między dwoma przechwyceniami, użyteczny do:

  • Wykrywania w stylu przepływu optycznego – dron unoszący się z kamerą skierowaną w dół wykorzystuje przesunięcie na ramkę do oszacowania swojego ruchu bocznego i przekazania go z powrotem do kontrolera lotu.

  • Stabilizacji obrazu – przesunięcie między kolejnymi ramkami jest odejmowane od przechwyconego obrazu, zanim zostanie on nagrany lub przesłany, co daje płynniejszy strumień wideo.

  • Wyrównywania na potrzeby inspekcji – skanująca kamera poruszająca się wzdłuż taśmociągu wykorzystuje przesunięcie na ramkę do dopasowania każdej ramki względem następnej i zbudowania zszytego widoku całej części.

Każda z tych aplikacji przyjmuje tę samą formę: przechwyć, wyznacz przesunięcie, kumuluj w bieżącym oszacowaniu, przechwyć ponownie.