4.15. Bufory ramek

Po zainicjowaniu sensora kamery emituje on ramki w sposób ciągły z częstotliwością ramek – jedną nową ramkę w każdym okresie ramki, niezależnie od tego, czy aplikacja jest na nią gotowa. Każda ramka musi gdzieś wylądować w pamięci RAM, w przeciwnym razie zostanie utracona. Pula buforów ramek to miejsce, w którym te ramki przebywają od momentu opuszczenia DMA do przetworzenia przez kod użytkownika, a liczba buforów ramek, które kamera utrzymuje w tej puli, decyduje o tym, jak DMA i aplikacja je współdzielą. Wybór jest udostępniany przez framebuffers() i dostępne są cztery tryby, wybierane na podstawie liczby buforów.

4.15.1. Pojedynczy bufor (count = 1)

Jeden bufor ramki w pamięci RAM. DMA go wypełnia; aplikacja z niego odczytuje; kolejne wywołanie snapshot() nie może się rozpocząć, dopóki aplikacja nie zwolni bufora, ponieważ ten sam bufor jest potrzebny w obu przypadkach.

Kamera i aplikacja działają w ścisłej synchronizacji. DMA musi czekać, aż aplikacja zakończy pracę, a aplikacja musi czekać, aż DMA zakończy pracę, co oznacza, że osiągalna częstotliwość ramek wynosi w najlepszym przypadku połowę częstotliwości ramek sensora – co druga ramka emitowana przez sensor przybywa, gdy bufor jest zajęty, i zostaje utracona.

Ten tryb jest najmniejszy pod względem zużycia pamięci RAM i najwolniejszy pod względem przepustowości. Używaj go tylko wtedy, gdy pamięci RAM jest zbyt mało, aby przydzielić drugi bufor.

4.15.2. Podwójny bufor (count = 2)

Dwa bufory ramek w pamięci RAM: jeden bufor tylny, który wypełnia DMA, oraz jeden bufor przedni, z którego odczytuje aplikacja. Gdy aplikacja kończy pracę z buforem przednim, role obu się zamieniają i DMA zaczyna wypełniać świeżo zwolniony bufor, podczas gdy aplikacja odczytuje z dopiero co wypełnionego.

Dopóki aplikacja przetwarza każdą ramkę w czasie krótszym niż jeden okres ramki kamery, aplikacja widzi pełną częstotliwość ramek sensora – kolejna ramka DMA czeka już w buforze tylnym, gdy aplikacja ponownie wywołuje snapshot(). W momencie jednak, gdy czas przetwarzania przekroczy jeden okres ramki, częstotliwość spada o połowę: kamera wyprodukuje dwie ramki w czasie, jaki aplikacja poświęca na przetworzenie jednej, i tylko druga z tych dwóch zostanie dostarczona.

Poza tym punktem częstotliwość degraduje się płynnie wraz z czasem przetwarzania. Za każdym razem, gdy DMA kończy nową ramkę w buforze tylnym, podczas gdy aplikacja wciąż pracuje nad buforem przednim, nowa ramka nadpisuje poprzedni zrzut w tym samym miejscu, zamiast zostać odrzucona. Aplikacja zawsze otrzymuje najnowszą ramkę wyprodukowaną przez kamerę przy kolejnym snapshot(), a osiągalna częstotliwość aplikacji staje się odwrotnością jej czasu przetwarzania.

4.15.3. Potrójny bufor (count = 3)

Trzy bufory ramek w pamięci RAM: dwa bufory tylne, przez które DMA cyklicznie przechodzi, oraz jeden bufor przedni, nad którym aplikacja aktualnie pracuje. Jest to domyślny tryb wybierany przez OpenMV Cam, gdy jest wystarczająco dużo wolnej pamięci RAM, z automatycznym przejściem do podwójnego lub pojedynczego bufora, gdy jej nie ma.

Trzeci bufor całkowicie oddziela częstotliwość ramek kamery od częstotliwości ramek aplikacji. DMA zawsze ma bufor, do którego może zapisywać; aplikacja zawsze ma bufor, z którego może odczytywać; przy każdym snapshot() najnowszy gotowy bufor tylny staje się nowym buforem przednim, a poprzedni bufor przedni zostaje zwolniony dla DMA. Częstotliwość ramek aplikacji odpowiada czasowi, jaki faktycznie zajmuje przetworzenie każdej ramki – bez skoku o połowę, w który wpada podwójny bufor, gdy czas przetwarzania nieznacznie przekroczy jeden okres ramki.

4.15.4. Wideo FIFO (count = 4 lub więcej)

Cztery lub więcej buforów ramek w pamięci RAM, ułożonych jako pierścień ramek przechwytywanych jedna po drugiej. Każda ramka dostarczana przez kamerę jest kolejkowana do FIFO, a snapshot() zwraca najstarszą zakolejkowaną ramkę zamiast najnowszej. Aplikacja przechodzi przez przechwycone ramki w kolejności ich przechwytywania, w czasie, jaki faktycznie ma na każdą z nich.

Ten tryb jest właściwym wyborem, gdy liczy się każda ramka i spodziewane są krótkie przestoje w przetwarzaniu: zapis wideo na kartę SD, której stos pamięci masowej może blokować się na dziesiątki milisekund podczas kasowania, strumieniowanie przez USB do hosta, który na chwilę przestaje odczytywać, lub buforowanie krótkiej serii szybkiego zdarzenia w celu jego analizy w kodzie.

Dwie polityki obsługują przypadek, w którym FIFO zapełnia się, zanim aplikacja zdąży je opróżnić.

  • Odrzucanie starych ramek (domyślnie). Gdy FIFO się zapełnia, wszystkie zakolejkowane ramki z wyjątkiem aktywnej są odrzucane, dzięki czemu kolejne snapshot() zwraca świeżą ramkę zamiast nieaktualnej. DMA przez cały czas kontynuuje przechwytywanie, więc po przestoju aplikacja zawsze widzi świeże dane. Jest to właściwa polityka, gdy celem jest utrzymanie aktualności przechwytywanego strumienia – nagrywanie wideo, transmisja na żywo.

  • Zatrzymanie przechwytywania przy przepełnieniu. Przekaż fflush=False do konstruktora CSI, a DMA przestanie wypełniać FIFO, gdy będzie ono pełne, pozostawiając zakolejkowane ramki nienaruszone. snapshot() nadal zwraca ramki w kolejności przechwytywania, dopóki aplikacja ich nie opróżni, po czym DMA wznawia pracę. Jest to właściwa polityka, gdy celem jest zachowanie każdej ramki krótkiej serii – przechwytywanie szybkiego ruchu w celu późniejszej analizy klatka po klatce w kodzie.

Pełną dokumentację API znajdziesz w csi.CSI.framebuffers().

4.15.5. Tryb wyzwalany

Alternatywą dla powyższych trybów stale działających jest przechwytywanie wyzwalane, w którym sensor emituje ramkę tylko wtedy, gdy snapshot() o nią poprosi. Kamera pozostaje bezczynna pomiędzy zrzutami obrazu i za każdym razem rozpoczyna nową ekspozycję, gdy aplikacja się zgłasza.

Kosztem jest przepustowość: przechwytywanie wyzwalane nie może nakładać się na poprzednie, więc maksymalna osiągalna częstotliwość ramek wynosi połowę normalnej częstotliwości sensora. Korzyścią jest synchronizacja ekspozycji. Zrzut obrazu kontroluje dokładnie moment rozpoczęcia ekspozycji, czego aplikacja potrzebuje, gdy ekspozycja musi pokrywać się z zewnętrznym zdarzeniem – błyskiem stroboskopu, sensorem pozycji przenośnika, impulsem na linii GPIO – zamiast wypadać tam, gdzie akurat znajduje się przesuwająca się ramka swobodnie działającego sensora w momencie, gdy aplikacja jest gotowa do jej odczytu.

Tryb wyzwalany jest specyficzny dla sensora. Na obsługiwanych sensorach włącza się go przez wywołanie csi0.ioctl(csi.IOCTL_SET_TRIGGERED_MODE, True), a wyłącza przez przekazanie False.