4.16. Podgląd obrazu#
Pula buforów ramek to miejsce, z którego aplikacja odczytuje swoje ramki. Podczas gdy aplikacja pracuje nad tymi ramkami, cokolwiek jest podłączone do kamery w celu ich podglądu, również potrzebuje kopii każdej ramki. Kamera ma do tego celu drugi, dedykowany bufor oraz jedną regułę dotyczącą jego wypełniania: za każdym razem, gdy aplikacja wywołuje snapshot(), poprzednia przechwycona ramka jest kopiowana do bufora podglądu przed zwróceniem nowej ramki.
Aplikacja i podglądarka nigdy nie rywalizują o tę samą pamięć. Aplikacja odczytuje swoją ramkę z puli; podglądarka odczytuje swoją ramkę z bufora podglądu. Oba procesy zachodzą równolegle.
4.16.1. Bufor ramki strumienia#
Bufor podglądu – bufor ramki strumienia – to pojedynczy obszar pamięci RAM o stałym rozmiarze, oddzielony od puli buforów ramek. Jego rozmiar jest ustalany podczas kompilacji oprogramowania układowego i nie zmienia się wraz z framesize() ani pixformat(). Na nowszych OpenMV Cam typowo jest to około megabajta – wystarczająco dużo, aby pomieścić podgląd o umiarkowanej rozdzielczości, znacznie mniej niż ramka w pełnej rozdzielczości przy największych rozmiarach sensora.
Kod aplikacji nie odczytuje ani nie zapisuje tego bufora bezpośrednio; sterownik kamery wypełnia go jako efekt uboczny snapshot().
4.16.2. Co snapshot robi dla podglądu#
Przy każdym wywołaniu snapshot(), zanim sterownik zwolni poprzedni bufor ramki aplikacji z powrotem do puli i przekaże nowy, kopiuje poprzednią ramkę do bufora podglądu – wraz ze wszystkim, co aplikacja narysowała na niej podczas przetwarzania, wciąż obecnym na obrazie. Możliwe są dwie ścieżki. To, która z nich zostanie wykonana, jest wybierane przez podglądarkę, a nie przez kamerę: konsument, który otworzył podgląd, informuje sterownik, czy chce surowy obraz, czy JPEG, oraz jaki rozmiar surowego okna może zaakceptować.
Surowa kopia pomniejszona. Gdy podglądarka poprosiła o surowe ramki, sterownik kopiuje poprzednią ramkę w jej natywnym formacie pikseli (RGB565, skala szarości itp.). Jeśli ramka jest większa niż surowe okno, o które poprosiła podglądarka, sterownik pomniejsza ją z filtrowaniem dwuliniowym, aż się zmieści; w przeciwnym razie piksele przechodzą bez zmian. Brak artefaktów kompresji; podglądarka widzi te same piksele, nad którymi pracowała aplikacja.
Kompresja JPEG. Gdy podglądarka poprosiła o JPEG – lub gdy surowa kopia w ogóle nie zmieściłaby się w buforze strumienia – sterownik kompresuje poprzednią ramkę do formatu JPEG w jej pełnej rozdzielczości do bufora strumienia. Jakość jest dostosowywana adaptacyjnie dla każdej ramki, tak aby skompresowane dane wyjściowe mieściły się w pojemności bufora strumienia. Gdy ramka się mieści, sterownik stopniowo podnosi jakość o jeden krok w kierunku górnego pułapu zależnego od rozmiaru pikseli przechwyconej ramki (mniejsze ramki mogą mieć wyższą jakość; większe ramki są ograniczane niżej, aby nie mogły się przepełnić przy niewielkiej zmianie treści). Gdy ramka nie mieści się, sterownik zmniejsza bieżącą jakość o połowę, utrzymuje ją na obniżonym poziomie przez kolejne kilkadziesiąt ramek, aby nowe ustawienie miało czas się ustabilizować, i usuwa przepełnioną ramkę z podglądu. Pętla aplikacji działa dalej bez zakłóceń; tylko podglądarka traci odrzuconą ramkę.
Ramki, które kamera produkuje w formacie już skompresowanym (format pikseli JPEG na sensorach emitujących JPEG bezpośrednio), pomijają obie ścieżki: zakodowany strumień bitów jest kopiowany bezpośrednio do bufora podglądu bez zmian.
Podglądarka odpytuje według własnego harmonogramu, zwykle znacznie wolniej niż kamera przechwytuje, więc próbkuje podrzędnie surową częstotliwość przechwytywania: pokazywane są tylko te zrzuty obrazu, które zdąży odczytać w odpowiednim czasie. Jeśli świeży snapshot() dotrze do bufora podglądu, zanim podglądarka odczyta poprzednią ramkę, bufor jest wciąż zablokowany przez podglądarkę i nowa aktualizacja podglądu zostaje pominięta – ten zrzut zostaje utracony ze strumienia podglądu. Własna pula buforów ramek aplikacji pozostaje nienaruszona; przechwycona ramka nadal trafia do aplikacji normalnie.
4.16.3. Ręczne wypchnięcie ostatniej ramki#
Ponieważ podgląd jest aktualizowany jako efekt uboczny snapshot(), skrypt, który kończy działanie bez ponownego wywołania snapshot, pozostawia to, co ostatnio wysłał do podglądu, na podglądarce w nieskończoność – co w przypadku skryptu wykonującego swoją pracę przed pierwszym zrzutem obrazu, a następnie kończącego działanie, oznacza pusty podgląd. image.Image.flush() (lub równoważne flush() na obiekcie CSI) kopiuje bieżącą zawartość bufora ramki aplikacji do bufora strumienia na żądanie, bez przechwytywania nowej ramki:
img = csi0.snapshot()
# process the image and draw on it
img.flush() # previewer sees the annotated frame
To samo wywołanie jest również przydatne, gdy pomiędzy zrzutami obrazu wykonywana jest długotrwała operacja, a podglądarka w przeciwnym razie pokazywałaby przez cały ten czas nieaktualny podgląd.
Informacja
Aplikacja podglądu musi odczytać ramkę z bufora strumienia, zanim skrypt zakończy działanie. Wywołanie flush na końcu krótkiego skryptu jedynie przygotowuje ramkę; jeśli skrypt następnie zwróci kontrolę kamerze, zanim podglądarka zdąży odpytać, bufor zostanie ponownie użyty przy następnym uruchomieniu, a ta ostatnia ramka zostanie utracona. W przypadku podglądów na końcu skryptu daj podglądarce chwilę na pobranie ramki (krótki sleep po flush lub po prostu nie kończ działania natychmiast) przed zakończeniem skryptu.