5.20. Regresja i podobieństwo

Dwa kolejne pomiary w klasie Image podsumowują obraz jako coś innego niż rozkład wartości pikseli. Regresja liniowa progowanych pikseli daje aplikacji linię, na której może działać – klasyczne wejście dla robota podążającego za linią. Pomiar podobieństwa daje aplikacji pojedynczą liczbę opisującą, jak bardzo dwa obrazy są do siebie podobne – naturalne wejście dla testu regresji obrazu wzorcowego lub detektora dużych zmian.

5.20.1. Regresja liniowa

Gdy piksele pierwszego planu obrazu układają się w linię przebiegającą przez ramkę – taśma na torze, za którą podąża robot, linia horyzontu, krawędź drogi lub korytarza – aplikacja zwykle nie chce każdego pojedynczego piksela pierwszego planu. Chce linii najlepiej dopasowanej przez nie wszystkie, sparametryzowanej tak, aby mogła zdecydować, jak linia jest zorientowana i gdzie przecina ramkę.

get_regression() wykonuje to dopasowanie. Przyjmuje tę samą formę krotki progowej, której używają binary() i find_blobs(), identyfikuje każdy piksel pasujący do progu i zwraca pojedynczy wynik line opisujący linię najlepiej dopasowaną przez te piksele:

line = img.get_regression([(0, 60)])
if line:
    img.draw_line((line.x1(), line.y1(),
                   line.x2(), line.y2()),
                  color=(255, 0, 0))

Dopasowanie wykorzystuje regresję liniową Theila-Sena – odporną metodę, która lepiej toleruje wartości odstające niż bardziej znane dopasowanie metodą najmniejszych kwadratów. Niewielka garstka pikseli oddalonych od prawdziwej linii nie zniekształca wyniku tak, jak miałoby to miejsce w przypadku najmniejszych kwadratów, co odpowiada zaszumionej rzeczywistości pierwszego planu prawdziwego wyniku progowania.

Wynik line przenosi punkty końcowe przycięte do prostokąta obrazu (x1, y1, x2, y2), długość i magnitudę linii (length, magnitude) oraz geometryczny opis linii w postaci biegunowej (theta, rho) – kąt linii względem poziomu i jej prostopadłą odległość od początku układu. Postać biegunowa to zwykle to, czego potrzebuje pętla sterowania: theta mówi robotowi, w którą stronę nachyla się linia, rho mówi mu, gdzie linia przecina obraz, a pętla sprzężenia zwrotnego na obu z nich utrzymuje robota wyśrodkowanego na linii.

Garstka argumentów kluczowych dostraja odporność i koszt. x_stride i y_stride pomijają piksele podczas dopasowania – większe kroki czynią regresję tańszą kosztem dopasowania mniejszej liczby pikseli. area_threshold i pixels_threshold odrzucają linie, za którymi nie stoi wystarczająca liczba pasujących pikseli. target_size przeskalowuje wejście do mniejszego rozmiaru przed dopasowaniem – regresja działa szybciej na zastępczym obrazie 80 na 60 bez większej utraty dokładności kierunku linii.

Jeśli nie udało się dopasować akceptowalnej linii – jeśli próg nie dopasował żadnych pikseli albo dopasował wzorzec, który nie wygląda jak linia – metoda zwraca None. Prawdziwy kod podążania za linią zabezpiecza każde wywołanie get_regression() sprawdzeniem None przed sięgnięciem po atrybuty linii.

5.20.2. Podobieństwo obrazów

Inny rodzaj pomiaru: zamiast pytać „co zawiera obraz?”, pytamy „jak bardzo te dwa obrazy są do siebie podobne?”. Operacją, po którą należy sięgnąć, jest get_similarity(), która oblicza indeks podobieństwa strukturalnego (SSIM) między obrazem źródłowym a obrazem referencyjnym.

s = img.get_similarity(reference)
print(s.mean, s.stdev)

SSIM to standardowa metryka podobieństwa obrazów stosowana w całym przetwarzaniu obrazów, ponieważ zachowuje się tak, jak zachowuje się ludzka intuicja podobieństwa – niewielkie przesunięcie lub niewielka zmiana jasności obniża wynik nieznacznie, podczas gdy duża zmiana strukturalna (brakujący obiekt, inna scena) obniża go drastycznie. Wynik mieści się w zakresie od -1 do +1: +1 oznacza, że oba obrazy są identyczne, 0 oznacza, że są niepowiązane, a -1 oznacza, że są strukturalnie przeciwne. Zwracany obiekt similarity udostępnia średnie SSIM dla całego obrazu, a także odchylenie standardowe, min i max wyników dla poszczególnych kafelków.

Dla rodzaju porównania, w którym mała liczba jest lepsza niż duża – testu regresji, który powinien raportować zero przy „nic się nie zmieniło” i rosnąć w miarę narastania zmian – flaga dssim=True zwraca niepodobieństwo strukturalne: średnie SSIM odjęte od 1, tak że wartość zwracana wynosi 0.0 dla obrazów identycznych i rośnie w miarę ich różnicowania się.

5.20.3. Zastosowania SSIM

Dwa typowe zastosowania:

Testowanie regresji z obrazem wzorcowym. Środowisko testowe przechwytuje ramkę referencyjną w znanych, prawidłowych warunkach i zapisuje ją jako obraz wzorcowy. Kolejne przebiegi testów przechwytują obraz w tych samych warunkach i porównują go z obrazem wzorcowym za pomocą SSIM. Wynik powyżej pewnego progu (0.95 lub 0.98 w zależności od tolerancji) oznacza zaliczenie; poniżej – niezaliczenie. Środowisko testowe nie musi wiedzieć, co się zmieniło – to wynik SSIM jest sygnałem.

Wykrywanie dużych zmian. Aplikacja, która chce zgrubniejszej wersji różnicowania ramek – takiej, która ignoruje małe zmiany jasności, ale reaguje na duże zmiany strukturalne – może użyć SSIM względem ramki referencyjnej zamiast operacji difference() na pojedynczych pikselach z następującym po niej progowaniem. SSIM jest mniej wrażliwe na dryf oświetlenia niż różnicowanie na poziomie pikseli, co czyni je lepszym wyborem, gdy celem jest wykrycie, że „scena wygląda istotnie inaczej”, a nie że „zmienił się jakikolwiek pojedynczy piksel”.

Oba zastosowania korzystają z tego samego wywołania – img.get_similarity(reference) – i wyzwalają się przy progu zwróconego wyniku. Różnica polega jedynie na tym, czy próg jest wysoki (test regresji, szukający niemal identycznego dopasowania), czy niski (wykrywanie zmian, szukające dowolnej dużej zmiany strukturalnej).

5.20.4. Forma przekształć-i-porównaj

Przydatny niuans: get_similarity() akceptuje te same parametry x, y, x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint i transform co draw_image(). Obraz referencyjny jest pozycjonowany, skalowany i przekształcany przez te parametry przed uruchomieniem porównania SSIM.

Oznacza to, że aplikacja może zapytać „jak podobna jest ta scena do ramki referencyjnej po znanym przesunięciu / obrocie / skalowaniu”, bez przygotowywania wstępnie przekształconego obrazu referencyjnego. To tani sposób na zbudowanie modułu śledzącego, który przeszukuje przestrzeń parametrów i raportuje, które przekształcenie obrazu referencyjnego najlepiej pasuje do bieżącej ramki.