Szybka, bezstratna kompresja strumienia wideo

14

Mam wideo pochodzące ze stacjonarnej kamery. Zarówno rozdzielczość, jak i liczba klatek na sekundę są dość wysokie. Dane, które otrzymuję, są w formacie Bayera i używają 10 bitów na piksel. Ponieważ na mojej platformie nie ma 10-bitowego typu danych, oryginalne dane są przechowywane w pamięci za pomocą 16-bitowych słów. Chcę zaimplementować pewnego rodzaju bezstratną kompresję danych przed przesłaniem ich przez sieć.

  • Aparat się nie porusza, więc duże części kolejnych klatek są prawie identyczne - ale wciąż nie do końca, z powodu nieuniknionego hałasu (odszumianie nie jest opcją, ponieważ powinno być bezstratne i nie powinno „tracić” nawet szumu ).
  • Z powodu wysokiej liczby klatek na sekundę nawet zmieniające się części nie zmieniają się znacznie między dwoma kolejnymi klatkami.
  • Wygląda jednak na to, że aparat trochę się trzęsie. Bardzo mało, ale nawet nieruchome obiekty nie są całkowicie takie w przestrzeni obrazu.
  • Kompresję należy wykonać w locie, więc nie mogę zebrać wielu klatek i skompresować ich wszystkich razem, ale mogę spojrzeć 1 klatkę wstecz i użyć jej jako odniesienia.

W oparciu o powyższe, moją pierwszą myślą było spakowanie bitów danych, aby te 6 nadmiarowych bitów nie było marnowanych na każde słowo. Pomyślałem jednak, że jeśli użyję kodowania entropijnego (np. Huffman itp.), Nadmiarowość zostanie automatycznie uwzględniona, więc nie jest konieczne dodatkowe pakowanie. Więc zrobiłem następujące:

  • Wziął binarną różnicę między dwiema kolejnymi ramkami. Pierwotny zakres danych wynosił 0 ~ 1023 (np. 10 bitów bez znaku). Dane różnicowe zostają podpisane, a zakres wzrasta do -1023 ~ 1023, ale zmienność danych (lub prawidłowy termin matematyczny) staje się znacznie mniejsza niż w danych oryginalnych, w rzeczywistości większość wartości, co nie dziwi, jest bliska zeru .
  • Zastosowano kodowanie ryżu do różnicy. Z tego, co rozumiem, wygląda to na dobry wybór dla zestawów danych składających się głównie z małych wartości liczbowych.

To daje mi około 60% zmniejszenie rozmiaru ramek 1280x720, a mój system testowy (Linux w VirtualBox na jednym rdzeniu) może wykonać ~ 40 takich kompresji na sekundę (bez dużej optymalizacji). Nie wspaniale, ale rozsądnie, jak sądzę (a może tak?).

Czy są lepsze sposoby? Jakieś typowe błędy, które popełniłem? Jakieś ogólne kroki, które przeoczyłem? Ramki o wyższej rozdzielczości mogą być później używane - czy powinienem oczekiwać lepszych współczynników kompresji dla większych rozmiarów ramek?

UPD .:

  • Użyłem tej biblioteki do kodowania Rice'a. Biblioteka jest bardzo wolna (sam autor opisuje ją jako coś do nauki, a nie do prawdziwego użytku), na przykład odczytuje i zapisuje w pętlach fragmenty jeden po drugim, co zabija wydajność. Początkowo dawało mi to tylko ~ 20 FPS, po bardzo podstawowej optymalizacji stało się 40 FPS (jak opisano powyżej), później zoptymalizowałem je trochę więcej, osiągnęło 80. To znaczy na jednym rdzeniu i7 bez wektoryzacji.
  • Jeśli chodzi o wektoryzację, niestety nie mogłem wymyślić sposobu na wektoryzację kodu Rice'a (nawet nie wiem czy to w ogóle możliwe - nie mogłem znaleźć żadnych danych na temat kodu Rice'a, co mogłem znaleźć na temat kodu Huffmana sugeruje, że jest sekwencyjny i nie może być skutecznie wektoryzowany, co może dotyczyć zarówno kodu Rice, jak i innych kodów o zmiennej długości).
  • Próbowałem też zupełnie innego podejścia: podziel dane na małe części (np. 64-pikselowe) i zastosuj proste tłumienie zera. Znajdujemy największą liczbę w bloku, zapisujemy liczbę bitów wymaganą do jej przedstawienia na początku bloku (w tym przypadku wymagane były 4 dodatkowe bity), a następnie zmniejsz wszystkie liczby w bloku do tej samej liczby bitów Spodziewałem się, że stopień kompresji będzie zły, ale jeśli kawałki są małe, wiele z nich nie będzie miało skoków hałasu, dlatego ich różnicę binarną można zmniejszyć do około 4 ~ 6 bitów na wartość, a tak naprawdę to tylko około 5% gorszy niż kod Rice'a, przy czym jest około dwa razy szybszy (np. 160 FPS w moim przypadku). Próbowałem wektoryzować, ale trochę wkurzam wektoryzację, więc może z tego powodu mogłem osiągnąć tylko około x1,8 dalszego przyspieszenia.

Ponieważ liczby ujemne nie mają zer wiodących, zastosowałem kodowanie zygzakowe po różnicy binarnej i przed tłumieniem Rice / zero.

Headcrab
źródło
Możesz użyć standardowego kodeka, takiego jak h264, który obsługuje tryb 10-bitowy. „Ustawienie -crf lub -qp na 0 wymusza x264 w trybie bezstratnym, ustawienia -preset wpływają tylko na stosunek prędkości do wielkości.” (Ale nie wiem, czy poradzi sobie z wydajnością w czasie rzeczywistym)
CodesInChaos
@CodesInChaos, czy zrobiłby wiele dla dwóch klatek?
Headcrab
Być może, co ważniejsze - czy standardowe kodeki mogą nawet kodować obrazy Bayera? Jeśli się nie mylę, konwersja Bayera na RGB wymaga interpolacji, a zatem jest nieodwracalna.
Headcrab

Odpowiedzi:

4

Masz przewidywanie czasowe, ale nie ma przestrzennego. Aby uzyskać lepszą kompresję kosztem szybkości, powinieneś być w stanie wykorzystać piksele powyżej i na lewo od bieżącego piksela w bieżącej klatce jako predyktory, a także piksel w tym samym miejscu w poprzedniej klatce. Powód tylko patrzenia w górę i w lewo jest taki sam jak powód patrzenia tylko na poprzednią klatkę; chcesz polegać tylko na danych, które już zdekodowałeś i ograniczać ich ilość.

Kody ryżowe są prawdopodobnie dobrym kompromisem między wydajnością a szybkością, ale statyczny kod Huffmana (wstępnie obliczony na próbce danych wideo) może być bardziej wydajny i równie szybki.

Jeśli chodzi o szybkość, upewnij się, że twój kod jest wektoryzowany - albo używając odpowiednich flag kompilatora i wzorców kodu, aby umożliwić kompilatorowi automatyczną wektoryzację, lub ręcznie pisząc kod, aby użyć wewnętrznych wektorów lub asemblera.

Wreszcie, czy jest możliwe obniżenie do 8 bitów na piksel? Oczywiście pozostawia to obszar „bezstratny”, ale nie tylko zmniejszyłby rozmiar skompresowanych danych wyjściowych, ale także, z wektoryzowanym kodem, prawdopodobnie zwiększyłby przepustowość do 2x.

Hobbs
źródło
Wydaje mi się, że obniżenie 10pbpp do 8 nie jest możliwe, ale można by zapisać delty w mniejszej liczbie bitów, podobnie jak UTF-8 używa 1 lub czasami 2 bajtów do przechowywania znaku. Jeśli delty są prawie równe 0, to rzadko zdarza się zmiana wszystkich 10 bitów, dlatego warto starać się o zapisanie 1 lub 2 bajtów.
gbjbaanb
@gbjbaanb właśnie to osiąga kodowanie Rice. Większość delt będzie małych, a zatem wykorzysta tylko kilka bitów.
hobbs
@ Hobbs, przez „przewidywanie przestrzenne” masz na myśli zastąpienie x5różnicy wartością piksela (x5 - x4)?
Headcrab
@Headcrab - podejście, które widziałem wcześniej, polega na użyciu wartości środkowej poprzedniego piksela oraz pikseli powyżej i pozostawionych w bieżącej ramce.
Jules
@Jules, jeśli piksel zostanie zastąpiony jakąś medianą otaczających pikseli, czy można przywrócić jego pierwotną wartość?
Headcrab
0

Prawdopodobnie najlepiej jest skorzystać z istniejących implementacji kompresji i dekompresji. Twoja istniejąca implementacja wydaje się podobna do kodeka HuffYUV , więc warto spróbować, aby sprawdzić, czy działa ona wystarczająco dobrze dla Ciebie.

Jules
źródło
libx264 „preset ultraszybki” całkiem dobrze służył mi historycznie FWIW ...
rogerdpack,
@rogerdpack - Warto zauważyć, że ustawienie libx264 dla bezstratnego kodowania powoduje, że wyjście nie jest zgodne z H.264 i nie działa na niektórych odtwarzaczach. Ale może to być przydatne przynajmniej dla aplikacji PO.
Jules
ciekawe, czy masz jakieś linki do tego? Zgłoszenie błędu? Zauważ też, że wideo zakodowane w HuffyYUV prawdopodobnie nie jest „przyjazne dla gracza”, wyobrażam sobie :)
rogerdpack