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.