H.264 pro vidění dravce
fandorama průzkum a III. létající blog
H.264 je video codec, který používá AR.Drone 2.0 pro streamování videa. Co bych rád prozkoumal je, zda už přímo z těchto dat nelze detekovat překážky, aniž by se obrázek rozbaloval. Cílem by bylo proletět 100m vzrostlým lesem bez kolize. Blog update: 12/3 — ARDrone3 - první řada
Motivace
Když jsem před více jak deseti lety kráce pracoval pro firmu IXOS, tak jejich
úspěch byl, mimo jiné, postaven na možnosti zpracovávat rovnou zabalené
obrázky, aniž by je bylo nutné nejprve rozbalovat. Pokud má video výstup z
drony rozlišení 1280x720 v RGB, tak to máte 2.7MB na jeden obrázek. Pokud ale
použijete přímo pakovanou verzi, tak jeden „rozdílový” snímek (P-frame) má
běžně pod 10kB. P-frame obsahuje informaci o pohybu jednotlivých makro-bloků,
ze které by se možná dalo zjistit, zda v daném směru je objekt blízko či
daleko…
Fandorama
Toto je první „průzkumný” fandorama projekt. Jedná se
možná o slepou uličku, ale možná také ne. Pokud by vás výsledky zajímaly, s
tím, že průběžné pozorování budou popisovány již ve třetím „létajícím
blogu”, tak projekt podpořte. V případě úspěchu získáte slevu ve výši vašeho
příspěvku na nákup Parrot AR.Drone 2.0 či náhradních dílů u firmy
Profitec, českého
distributora firmy Parrot.
- Detaily tohoto projektu: https://fandorama.cz/projekty/921778164/h264-drone-vision/
Blog
Toto je třetí „létající blog” o autonomní navigaci Heidi.
Už nevím na kterou soutěž jet (a 450EUR se mi za
IMAV 2013 dávat nechtělo), tak toto bude malé
„samo-studium”.
Předešlé díly blogu případně naleznete zde:
Stejně jako dříve i tady předem se omlouvám za texty bez korekcí.
22. července 2013 — BEGIN
Teď ráno toho už asi moc nestihnu, tak alespoň začnu. Nejprve díky za první
„anonymní” podporu na
fandorama.cz (do
zprávy pro příjemce prosím dávejte kontaktní mail, ať vám pak mohu napsat).
AR.Drone 2.0 používá pro kódování videa
H.264 codec, který je relativně
nový (hmm, 2003 == deset let). Vedle online videa se běžně používá třeba Blue-ray
discích.
Pro čtení videa z drony stačí otevřít TCP spojení na portu 5555, přeskakovat
PaVE hlavičky a co vám zbude jsou obrázky kódované H.264 codecem. Někteří
(např. CVDrone) se s PaVE hlavičkami
vůbec netrápí a rovnou hledají začátky takzvaných „NAL bloků” (zájemcům bych
asi doporučil PDFko Video
coding with H.264/AVC: Tools, Performance, and Complexity — je to celkem
čitelné a zároveň tam naleznete dostatek užitečných detailů pro první vhled).
NAL bloky
NAL je zkratka za Network Adaptation Layer. Jednotlivý obrázek (frame) je
popsán několika díly (slice) a každý začíná kódem 00 00 00 01. Následuje
bajt určující typ — když jsem jednoduchý prográmek pustil na jedno z videí,
tak jsem dostal 0x67, 0x68, 0x65, 0x61 (mnohokrát), 0x3D (omyl?), … Nás budou
zajímat 0x61, což jsou P-slices (predicted) a měly by obsahovat i informace o
pohybu jednotlivých makrobloků (oblastí velikosti 16x16 pixelů).
Timeout … musím do práce. Pokračování příště.
23-24. července 2013 — PaVE
Dnes jsem začal úklidem a odstraňováním PaVE (Parrot Video Encapsulation)
hlaviček. Zatímco v zabaleném videu je kód 00 00 00 01 velice nepravděpodobný,
v PaVE hlavičce je skoro jistý. 0x3D ze včerejška tedy zmizí a dostanete něco
jako:
h264.py logs\video_rec_130714_140929.bin | uniq -c 1 0x67 1 0x68 1 0x65 29 0x61 1 0x67 1 0x68 1 0x65 29 0x61 . . . 1 0x67 1 0x68 1 0x65 29 0x61 1 0x67 1 0x68 1 0x65 29 0x61 1 0x67 1 0x68 1 0x65 18 0x61
Videa jsou 30fps, takže každou sekundu je to vždy jeden celý snímek a 29
predikovaných (P-slice).
ffmpeg
Stáhl jsem si ffmpeg, abych měl
dokumentaci i z druhého extrému. Zdrojový kód i s git repository má 126MB.
Musím přiznat, že používání gitu pro Open Source projekty je opravdu velmi
pohodlné (jak už jednou máte git nainstalovaný):
git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg
Asi vhodné místo pro začátek je ffmpeg/libavcodec/h264_parser.c. Pro určení
typu části (slice) je důležitých spodních 5 bitů, tedy z 0x67, 0x68, 0x65 a
0x61 dostaneme 7=NAL_SPS, 8=NAL_PPS, 5=NAL_IDR_SLICE a 1=NAL_SLICE. Žádný další
typ AR.Drone 2.0 neposílá.
Nás zajímá 1=NAL_SLICE … ale nejsem si jistý, zda je čitelnější zdrojový kód
nebo
dokumentace
(sekce 7.3.3).
Exp-Golomb codes
Po jednobajtové hlavičce, flag a level bajtu jsou v NAL_SLICE čísla už převážně
kódovaná proměnným počtem bitů. Používají se „Exp-Golomb” kódy, které se
jmenují podle Solomon W.
Golomba, jenž je v roce 1960 vymyslel. Pro popis čísel stačí zhruba
dvojnásobek logaritmu bitů. 1 => 0; 01x => 1,2; 001xx => 3,4,5,6; atd. Pěkné
tabulky najdete např. na
wikipedii. Podobným
způsobem se kódují i čísla se znaménkem.
Trošku váhám, zda má smysl takto, málem po větách, to dávat na web. Ještě hodně
tápu, ale když tam nic nedám, tak se nic nedozvíte … snad to brzy začne
mít hlavu a patu.
p.s. díky za další podporu — vypadá to, že minimálně tři další lidi toto téma
zajímá
26. července 2013 — firmware 2.4.1
Dnes malá odbočka, flashnul jsem na Heidi nový firmware 2.4.1
s podporou GPS modulu. Jak jsem psal už
dříve, získal jsem ho od kamaráda, co
má iPhone, a pak i našel na
webu
vývojářů. Flashoval jsem to z PC Windows, tj. byla to kombinace informaci
jak flashnout starou dronu,
tak postup pro
downgrade firmware nové drony.
Postup ve zkratce:
- zapnout dronu (raději s plně nabitými bateriemi)
- ve Windows Exloreru otevřít ftp://192.168.1.1:5551/ a Copy & Paste soubor ardrone2_update.plf (zkoušel jsem ještě dva jiné způsoby, ale buď jsem byl málo trpělivý, nebo prostě nefungovaly)
- připojit se přes telnet a oeditovat verzi update:
# echo "2.4.1" > /update/version.txt
- vypnout a zapnout napájení
- počkat 2-3 minuty než se rozsvítí zelené LEDky
Vše snad proběhlo OK, zvedla se verze firmware a zmizel updateovací soubor:
BusyBox v1.14.0 () built-in shell (ash) Enter 'help' for a list of built-in commands. # cat /firmware/version.txt 2.4.1 # ls /update/ version.txt #
A teď důsledky:
- zvětší se navdata paket, takže pokud čtete např. pevných 2048 bajtů, vyletí výjimka (to bylo snadné opravit)
- přestanou fungovat staré drivery, u mne konkrétně sonar:
insmod /data/video/driver2/cdc-acm.ko insmod: can't insert '/data/video/driver2/cdc-acm.ko': invalid module format
… změnila se totiž i verze kernelu a je tedy třeba vše znova zkompilovat.
Původní 2.6.32.9-gbb4d210 se změnila na 2.6.32.9-g1dd1a2a (tady jsem se teď
zasekl, protože zjišťuji, že prostředí, co jsem měl v práci, už není k
dispozici, takže to bude náročnější krok, než jsem čekal).
Hmm, tak ono se toho změnilo více, ne jenom nová datová struktura … dříve:
CTRL STATE 2 1 8 2 52 3 46 4 16 5 12 6 88 7 16 8 24 9 76 10 56 11 16 12 44 13 92 14 108 15 364 16 328 17 8 18 40 19 65 20 12 21 18 22 83 23 56 24 72 25 32 26 8 27 12 65535 8
nyní:
CTRL STATE 2 1 8 2 52 3 46 4 16 5 12 6 88 7 16 8 24 9 92 10 56 11 16 12 44 13 92 14 108 15 364 16 328 17 8 18 40 19 65 20 12 21 18 22 83 23 64 24 72 25 32 26 8 27 480 28 6 29 32 65535 8
Struktura 9 (72 na 92), 23 (56 na 64), 27 (12 na 480) a nové 28 a 29.
Nepoužívám žádnou z nich a chápu, že některé programy se změnou velikosti
struktury mohou mít celkem problémy. NAVDATA_PWM_TAG, NAVDATA_WIND_TAG,
NAVDATA_ZIMMU_3000_TAG(?? možná to použili pro něco jiného). No to jsem zvědav,
zda to vůbec poletí … pokračování po víkendu.
1. srpna 2013 — Sequence Parameter Set (SPS)
Tak konečně mikro-krůček ve zpracování H264 kódovaného videa …
Jako vždy jsem chtěl nejprve použít zkratku a parsovat pouze P-slice, které
snad obsahují ty zajímavé informace. V proudu bitů se ale střídají jak dříve
zmiňovaná čísla v Golomb formátu, tak i čísla s pevným počtem bitů. A chyba
lávky. Jak velký tento fixní počet je, je zase kódované jinde, konkrétně v
SPS-slice(Sequence Parameter Set). Na čem jsem klopýtnul byl frame_num, kde
počet bitů je v log2_max_frame_num_minus4.
Dokumentaci
používám z linku přes
stack
overflow a jako velmi užitečnou jsem shledal i C-implementaci, co mi link
poslal jeden fandorama přispěvovatel: http://www.w6rz.net/h264_parse.zip.
Je tam jak EXE, tak zdrojáky, takže si můžu po kouskách snadno ověřovat, že parsuji to samé
co ostatní …
A nějaká pozorování?
- log2_max_frame_num_minus4 je zatím všude rovno 10, tedy na uložení pořadového čísla se používá 14bitů.
- pic_order_cnt_type je 2, takže jednotlive makro-bloky snad jdou postupně po řádcích
- bloků je 80x45, což odpovídá rozlišení 1280x720
Ostatnímu zatím moc nerozumím. Asi bych si přepsal/napsal BitStreamReader, aby
se jak čísla tak „golomby” jednoduššeji četly.
19. srpna 2013 — Picture Parameter Set (PPS)
Byl jsem teď 14 dní na dovolené, tak všechno trošku zahálelo. Na druhou stranu
suma se ještě nepřehoupla, tak tento „výzkum” zatím není závazný …
Tak co nového, resp. starého, protože informace jsou z před-dovolené? Dostal
jsem se snad už konečně až k dekódování makro-bloků, ale nemám žádnou zpětnou
kontrolu, tak asi bude nutné dekódovat celý P-slice. Bylo pro změnu nutné
použít i několik bitů z PPS (Picture Parameter Set). Skoro by bylo na čase to
udělat pořádně, ale to bych nebyl já :-(. SPS a PPS byly stejné ve všech video
záznamech co mám, takže nejrychlejší parsování je to přeskakovat (resp.
assertovat, že jsou stále stejné).
PPS je bajtově [0xce, 0x1, 0xa8, 0x77, 0x20], což odpovídá výpisu z minule zmiňovaného parseru:
Nal length 10 start code 4 bytes ref 3 type 8 Picture parameter set pic_parameter_set_id: 0 seq_parameter_set_id: 0 entropy_coding_mode_flag: 0 pic_order_present_flag: 0 num_slice_groups_minus1: 0 num_ref_idx_l0_active_minus1: 0 num_ref_idx_l1_active_minus1: 0 weighted_pred_flag: 0 weighted_bipred_idc: 0 pic_init_qp_minus26: -26 pic_init_qs_minus26: -14 chroma_qp_index_offset: 0 deblocking_filter_control_present_flag: 1 constrained_intra_pred_flag: 0 redundant_pic_cnt_present_flag: 0
Ještě doplním SPS, které je také stále stejné:
Nal length 14 start code 4 bytes ref 3 type 7 Sequence parameter set profile: 66 constaint_set0_flag: 1 constaint_set1_flag: 0 constaint_set2_flag: 0 constaint_set3_flag: 0 level_idc: 31 seq parameter set id: 0 log2_max_frame_num_minus4: 10 pic_order_cnt_type: 2 num_ref_frames: 1 gaps_in_frame_num_value_allowed_flag: 0 pic_width_in_mbs_minus1: 79 (1280) pic_height_in_map_minus1: 44 frame_mbs_only_flag: 1 derived height: 720 direct_8x8_inference_flag: 0 frame_cropping_flag: 0 vui_parameters_present_flag: 0
Tak teď už hurá na makro bloky! … pokračování snad brzy.
22. srpna 2013 — CAVLC vs. CABAC
To jsem si naběhl — teď už nemám žádnou výmluvu . Na druhou stranu mám
radost, že existuje několik lidí, které výsledky opravdu zajímají . Zpátky
ni krok …
Zatím stále tápu, teď nad souborem m:\git\ffmpeg\libavcodec\h264_cavlc.c.
Až včera mi došlo, že jsem možná na špatné koleji a bych měl možná studovat
raději m:\git\ffmpeg\libavcodec\h264_cabac.c?! Co to tedy ten CAVLC a CABAC
je?
Jedná se od dva různé způsoby kódování, kde CAVLC je zkratka za
Context-adaptive
variable-length coding a CABAC je
Context-adaptive
binary arithmetic coding. Celkem pěkný popis je v IEEE článku
Video coding with
H.264/AVC: Tools, Performance, and Complexity, ze kterého pochází následující
obrázek:
CAVLC vs. CABAC |
Matice se linearizuje podle vzoru do 1D pole. U CAVLC se nejprve zjistí počet
nenulových prvků (po transformaci obrazu je většina prvků nulová) a
ukončujících +/- jedniček (často se vyskytují a označují se T1). Ten se vyšle
v kombinovaném slově (použité jsou kódovací tabulky). Pak jsou jednotlivé
koeficienty procházeny od konce a ukládá se znaménko a hodnota, s použitím
předešlé hodnoty, pomocí šesti kódovacích tabulek. Konečně nulové elementy jsou
indikovány celkovým počtem nul před posledním nenulovým prvkem. Hm, to jsem to
pěkně odpapouškoval, ale moc tomu tak po ránu nerozumím :-(.
Tak ještě jednou podle obrázku. Nenulových prvků je 5, OK. „Trailing” budou
pouze ty koncové jedničky, tj. ta 1,1,-1 a počet 3, OK. Znaménka jedniček od
konce bude to -1,1,1 a pak následují 2 a 1 (pozpátku), OK. Počet nul/mezer jsou
2, OK. A mezi poslední a předposlední žádná nula není, tj. 0, pak jsou dvě
mezery, tj. 1, 1 a zbytek už je nezajímavý (0), protože vím, že nuly měly být
2, OK. Jako hypotéza pochopení CAVLC to teď asi stačí.
Mám se s CABAC vůbec zabývat? Standard H264 podporuje oba způsoby, ale který
používá AR Drone 2.0? (timeout, musím do práce…)
p.s. Picture Parameter Set — int cabac; ///< entropy_coding_mode_flag, který
pro AR Drone 2.0 je vždy 0, tj. CAVLC byla správná volba.
23. srpna 2013 — ./ffprobe -report s TRACE
Když jsem pročítal zdrojový kód ffmpeg na
dekódovaní
CAVLC, tak na mnoha místech byl tprintf, který by vypisoval přesně to
co mne zajímalo. Implementace této funkce ale je na #ifdef TRACE, který je
třeba odkomentovat v m:\git\ffmpeg\libavcodec\get_bits.h:588 a nově
zkompilovat. Přešel jsem tedy na Linux, kde příprava a kompilace byla otázkou
pár minut.
Bylo třeba udělat následující kroky:
- git clone git://source.ffmpeg.org/ffmpeg.git
- ./configure
- editace ffmpeg/libavcodec/get_bits.h
- make
- ./ffprobe -report <moje video>
Výsledek zcela předčil mé očekávání. V automaticky vygenerovaném logu (je nutné
použít parametr -report) je vedle mnoha tprintf výpisů i bitový vypis s
odkazem do kódu, kde přesně byl dekódován. Ještě varování: ten log je
obrovský, protože pár bitů nahrazujete jednou nebo více řádkami textu a není
možné parsovat pouze váš P-slice bez úvodních SPS, PPS a I-slice …
Malý výsek pro představu — odpovídající začátku P-slice:
1 1 1 0 ue @ 2 in libavcodec/h264.c decode_slice_header:3363 00000000000001 1 14 1 bit @ 3 in libavcodec/h264.c decode_slice_header:3500 1 1 1 1 bit @ 17 in libavcodec/h264.c decode_slice_header:3779 1 1 1 0 ue @ 18 in libavcodec/h264.c decode_slice_header:3782 [h264 @ 0x2a02580] List0: ST fn:0 0x0x7f64e315a250 0 0 1 0 bit @ 19 in libavcodec/h264_refs.c ff_h264_decode_ref_pic_list_reordering:215 0 0 1 0 bit @ 20 in libavcodec/h264_refs.c ff_h264_decode_ref_pic_marking:759 00000111110 62 11 31 se @ 21 in libavcodec/h264.c decode_slice_header:3870 1 1 1 0 se @ 33 in libavcodec/h264.c decode_slice_header:3900 1 1 1 0 se @ 34 in libavcodec/h264.c decode_slice_header:3901 [h264 @ 0x2a02580] pic:1 mb:0/0 1 1 1 0 ue @ 36 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:733 [h264 @ 0x2a02580] topright MV not available [h264 @ 0x2a02580] pred_motion match_count=0 [h264 @ 0x2a02580] pred_motion (-2 0 0) (-2 0 0) (-2 0 0) -> ( 0 0 0) at 0 0 0 list 0 1 1 1 0 se @ 37 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:966 1 1 1 0 se @ 38 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:967 [h264 @ 0x2a02580] final mv:0 0 0001011 11 7 10 ue @ 39 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:1054 1 1 1 0 se @ 46 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:1101 [h264 @ 0x2a02580] pred_nnz L40 T0 n8 s28 P0 1 1 1 0 vlc @ 47 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T0 n9 s29 P0 01 1 2 5 vlc @ 48 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] trailing:1, total:1 011 3 3 1 vlc @ 51 in libavcodec/h264_cavlc.c decode_residual:581 [h264 @ 0x2a02580] pred_nnz L40 T0 n10 s36 P0 1 1 1 0 vlc @ 54 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T1 n11 s37 P1 1 1 1 0 vlc @ 55 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L1 T0 n12 s30 P1 01 1 2 5 vlc @ 56 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] trailing:1, total:1 00011 3 5 5 vlc @ 59 in libavcodec/h264_cavlc.c decode_residual:581 [h264 @ 0x2a02580] pred_nnz L1 T0 n13 s31 P1 1 1 1 0 vlc @ 64 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T1 n14 s38 P1 1 1 1 0 vlc @ 65 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T0 n15 s39 P0 1 1 1 0 vlc @ 66 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pic:1 mb:1/0 1 1 1 0 ue @ 68 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:733 [h264 @ 0x2a02580] topright MV not available [h264 @ 0x2a02580] pred_motion match_count=1 [h264 @ 0x2a02580] pred_motion (-2 0 0) (-2 0 0) ( 0 0 0) -> ( 0 0 0) at 1 0 0 list 0 1 1 1 0 se @ 69 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:966 1 1 1 0 se @ 70 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:967 [h264 @ 0x2a02580] final mv:0 0 000010100 20 9 19 ue @ 71 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:1054 00101 5 5 -2 se @ 80 in libavcodec/h264_cavlc.c ff_h264_decode_mb_cavlc:1101 [h264 @ 0x2a02580] pred_nnz L0 T40 n0 s12 P0 01 1 2 5 vlc @ 85 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] trailing:1, total:1 011 3 3 1 vlc @ 88 in libavcodec/h264_cavlc.c decode_residual:581 [h264 @ 0x2a02580] pred_nnz L1 T40 n1 s13 P1 1 1 1 0 vlc @ 91 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T1 n2 s20 P1 1 1 1 0 vlc @ 92 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T0 n3 s21 P0 01 1 2 5 vlc @ 93 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] trailing:1, total:1 0010 2 4 4 vlc @ 96 in libavcodec/h264_cavlc.c decode_residual:581 [h264 @ 0x2a02580] pred_nnz L0 T40 n4 s14 P0 1 1 1 0 vlc @ 100 in libavcodec/h264_cavlc.c decode_residual:465 [h264 @ 0x2a02580] pred_nnz L0 T40 n5 s15 P0 …
27. srpna 2013 — není všechno zlato …
… co se třpytí aneb snažím se podle výstupu z ffprobe upravit svůj
pythonovský skriptík a ono v tom 82MB textovém dumpu ze dvou snímků není
všechno :-(. Konkrétně:
00000111110 62 11 31 se @ 21 in libavcodec/h264.c decode_slice_header:3876 1 1 1 0 se @ 33 in libavcodec/h264.c decode_slice_header:3906 1 1 1 0 se @ 34 in libavcodec/h264.c decode_slice_header:3907
Tj. když se přečte slice_qp_delta, která má 11 bitů, s tím, že se začína na 21
bitu ve video streamu (vnitřek slice), tak se bitový čítač posune o 12 a
nikoliv o 11 na 33. Důvod je, že tam chybí čtení
tmp = get_ue_golomb_31(&h->gb);
na libavcodec/h264.c decode_slice_header:3895.
Je tedy možné, že některá čtení z podmínky TRACE vypadla, nebo TRACE musí být
definované někde trošku jinde (???). Postup je pomalý, ale snad to alespoň lépe
pochopím …
27. srpna 2013 — ce(v): context-adaptive variable-length entropy-coded syntax element
No není to krásný název? Doufám, že to je už poslední vychytávka pro parsování
CAVLC. Jedná se o sadu tabulek s proměnnou délkou klíče, která např. určuje
počet nenulových prvků a jedniček na konci sekvence. O jakou se přesně jedná
tabulku určují vstupní parametry. Je to vlastně klasický bitový slovník, tak
nevím, proč se tak čertím . Asi se mi nechce přepisovat ty tabulky, ale ani
to není až tak strašné, jenom čas už není …
6. září 2013 — zkratka
Kdysi mi bylo vyčteno, že používám nedovolené zkratky … a používám. Stále
jsem to celé dekódování CAVLC do konce nedotáhl, a přitom to není „to
hlavní”. Na druhou stranu mé chápání H.264 se už trošku posunulo a tak
přečtení seminární práce
Implementation of a basic
H.264/AVC Decoder od Martina Fiedlera byla už radost a většině jsem i rozuměl
.
Hledal jsem alternativní implementaci k FFMPEG, pokud možno jednoduchou (i
kdyby pomalou) a pro Windows. Ve zmiňované práci byl mezi odkazy link
[5] H.264/AVC Reference
Software (opravený původní link). Stáhl jsem si
jm18.5.zip, rozbalil
a našel projekty pro všechna Visual Studia, včetně toho, co používám (stále
vc9). Chvíli jsem si krokoval v debugeru, ale chybějící bity pro vlastní
implementaci nevykoukal. Tak jsem použil „dočasnou zkratku” a do souboru
macroblock.c:405 připsal:
fprintf( stderr, "%d %d %d %d\n", currMB->block_x/4, currMB->block_y/4, curr_mv.mv_x, curr_mv.mv_y );
a dál si hrál už jenom s parsováním stderr výstupu v Pythonu. Dobrá zpráva je,
že minimálně první dva snímky měly identické posuny jako TRACE z FFMPEG (včetně
podivného (-130, 60), ale většina byla +/-2). Výsledkem jsou 80x45 pixelů
veliké „snímky”, kde pro vizualizaci jsem použil součet absolutních hodnot
přes 30 snímků a pak přeškáloval, aby maximum bylo 255. Výsledek jsem pak
uložil v PGM formátu. Při startu
to vypadá např. takto:
Integrace pohybu ze 30ti snímků |
nebo chcete-li původní textový soubor v PGM.
p.s. kdyby jste šli v mých stopách, tak si nezapomeňte změnit konfiguraci tak,
aby se negeneroval výstupní soubor (je jinak obrovský). Upravené řádky konfigurace
vypadájí takto:
InputFile = "m:\hg\md\heidi\frames\mix2.bin" # H.264/AVC coded bitstream OutputFile = "" # Output file, YUV/RGB
10. září 2013 — combinePgm ver0
Jelikož procházet jednotlivé pidi-snímky je dost nepraktické, tak jsem jsi
napsal jednoduchý skript (spíše 10 řádek) na kombinování několika snímků do
jednoho. Výsledek 10x10 se teď počítá …
… hmm, čekal jsem něco více, asi tomu budu muset podhodit akčnější video.
Vypadá to spíše na nějakou chybu, protože výstupní PGM jsou od třetího snímku
všechna stejná :-(.
p.s. pro zpestření příkládám video link od Jirky:
čtyřtulka a osmitulka jako servírky .
12. září 2013 — programování ve sprše
Přiznám se, že hrozně nerad hledám chyby v programu civěním do monitoru.
Daleko přijatelnější je „nasadit si brouka do hlavy” a on se ten „bug” pak
sám najde . Stejné to bylo i s předešlým snímkem, proč se opakují stále
stejné obrázky?
A rozuzlení? Makrobloky je možné zcela přeskočit, takže ve výpisu se vůbec
neobjeví. Místo čekání na makroblok s indexem (79,44), teď čekám na
„singularitu”, tj. když x+y*80 náhle klesne, tak to už je další snímek (máte
pravdu, dalo by se to udělat pořádně v generátoru toho vstupního textového
souboru, ale …).
Další chyba byla, že ten videosoubor opravdu obsahoval jenom 60 snímků a tak
vždy dojel na konec. Přestal jsem tedy integrovat po 30ti snímcích, ale změnil
to na 7. Proč? No protože video krátkého letu u nás v parku má 707 snímků a já
chtěl udělat 10x10 mozaiku . Tady je:
Referenční video na youtube naleznete zde: http://youtu.be/9NOvif6gtiU
p.s. aby nedošlo k omylu — toto je video z 12. června 2013, kdy jsem se
připravoval na RoboOrienteering, tj.
sice autonomní let, ale pouze se sonarem.
26. září 2013 — sudé posuny
Práce okolo Robotour 2013 se už pomalu
uklidňují (je ještě potřeba vymyslet vhodné místo pro rok 2014), tak se můžu
vrátit k hraní s dronou a videem. Krátkých zpráv by za tu dobu bylo hned
několik, jako polemika na téma „figura a pozadí”, kdy strom je jednou
bílý a jednou černý, nebo že už jsou k dispozici
pravidla pro IARC 7th Mission, kde
létající roboti budou nahánět uklízecí hordu Roomba iCreate robotů (do vánoc
bych o tom asi něco napsal zase podrobnji) a pod. Zpět k H264…
Z PGM obrázků je sice vidět, že „tam něco je”, ale informaci, zda mám odbočit
vpravo/vlevo či nahoru/dolů tam zatím nevidím. Chtěl jsem si tedy udělat 2D
histogram všech posunů v obrázku a první překvapení — všechna použitá čísla
jsou sudá! Ano, testoval jsem to jenom přes jedno krátké video, takže mohu
tvrdit že všech 1977841 posunů je sudých, ale nechal bych tam assert a myslím,
že bude platit.
Co to znamená? Zase jen hypotéza: ve specifikaci se píše o možnosti
čtvrt-pixelového kroku, takže si myslím, že posun 2 je ve skutečnosti posun o
půlpixel a jemnějším krokem se video procesor netrápí.
Další pozorování je, že většina posunů je ve čtverci +/- 10 pixelů, ale najde
se i pár „ustřelených” jako posun (0,48). Ty pak ale určují šedotónovou škálu
a tak bych je buď vyhodil nebo oříznul.
Platí to i pro druhý extrém, kdy je celý obraz v pohybu, a stále zde najdu
nějaké statické, tedy s posunem (0,0).
Příklad 1: defaultdict(<type 'int'>, {(-2, 0): 20, (6, 4): 1, (8, 0): 2, (-2, 4):
1, (-2, 8): 1, (4, -6): 1, (2, 6): 1, (14, 0): 1, (-2, 2): 7, (-4, 30): 1, (-4,
6): 1, (6, 2): 1, (4, 0): 14, (0, 6): 3, (-6, 0): 1, (0, -8): 2, (4, 4): 2, (2,
-4): 1, (6, -8): 1, (-2, -2): 2, (-10, -4): 1, (-2, -26): 1, (0, -4): 6, (4,
10): 1, (-2, -6): 1, (0, 0): 3395, (2, -2): 2, (6, 0): 2, (0, 12): 1, (4, -8):
1, (-4, 0): 5, (0, 4): 9, (0, 8): 8, (8, -28): 1, (4, -4): 1, (12, 0): 7, (-4,
16): 1, (2, 2): 7, (2, 0): 46, (0, 48): 1, (0, -2): 8, (-8, 0): 2, (0, 2): 29})
… uznávám, že toto moc čitelné není. Je tam ale vidět naprostá většina (0,0),
desítky posunů (0,2) a (2,0) a zbytek jsou jen drobky(?). Toto byl příklad,
když je drona v klidu.
Příklad 2: defaultdict(<type 'int'>, {(12, 12): 1, (2, -4): 2, (0, 14): 2, (-4,
0): 6, (-2, 4): 1, (12, -8): 1, (14, 0): 1, (-2, 2): 23, (2, 4): 3, (12, -4):
1, (4, -2): 1, (-2, 0): 5, (0, 6): 2, (-6, 0): 1, (0, -8): 5, (4, 4): 1, (2,
2): 326, (0, 4): 15, (4, 0): 57, (0, -4): 8, (0, 0): 1652, (2, -2): 183, (6,
0): 1, (0, 12): 1, (0, -2): 51, (8, 0): 2, (4, 2): 20, (0, 8): 2, (-4, 4): 1,
(12, 0): 1, (0, -12): 1, (0, -20): 1, (2, 0): 759, (-12, 50): 1, (0, 2): 461,
(0, 10): 1})
… vzlet. Stále dominuje (0,0), ale srovnatelné hodnoty mají i posuny (2,0) a
(0,2), nebo (2,2). Toto je z rozdílového snímku na 30Hz, takže je potřeba to
posčítat přes více. Asi čitelnější je to rozdělené přes desítkový logaritmus:
- 1000 [((0, 0), 1652)]
- 100 [((2, -2), 183), ((0, 2), 461), ((2, 0), 759), ((2, 2), 326)]
- 10 [((0, -2), 51), ((-2, 2), 23), ((0, 4), 15), ((4, 2), 20), ((4, 0), 57)]
Další plán je tedy posčítat jednotlivé vektory přes více snímků (znaménkově).
Dosud jsem jen dělal obrázky ze součtu absolutních hodnot a třeba ty světlé
oblasti jsou jen místa častých zákmitů …
1. říjen 2013 — pygame art
V pátek jsem si trošku hrál s vizualizací posunů makrobloků. Jako nástroj
používám už několik let PyGame. Teď jsem do kódu
ještě přidal řádku
pygame.image.save( foreground, filename )
ať z toho máte i něco vy ostatní. Jako koncovku souboru doporučuji PNG, protože
na podobné trojbarevné obrázky dostanete asi 7x menší soubor než pro JPG a
navíc bezztrátově (no 100% si s tou bezztrátovostí jistý nejsem).
Dnes jenom „chybné” obrázky, které mi připomněly robotovi kreace z roku 1992.
Tehdy se snažil o naplánování cesty s omezenou křivostí a výsledné křivky byly
někdy zajímavější než celý algoritmus:
Ještě proč jsou dané obrázky chybné? Jednak vektor je 16x delší než má být
(násobil jsem to velikosti makrobloku), pak má být možná na druhou stranu a
konečně ty červené tečky by asi měly být spíše na druhé straně čáry, protože
teď označují cílové místo a ne zdrojové. Vše je to integrované přes tři
rozdílové snímky.
A ještě špatná zpráva na konec. Zatím co dříve jsem tam tušil „duch stromu”
teď už v tom nevidím vůbec nic … ale to se třeba časem zlepší.
4. říjen 2013 — Augmented Reality
Včera jsem zkoušel „rozšířenou realitu”, tedy do původního videa namíchat
vektory posunů makrobloků z H264 kódování. Výsledek se mi zprvu celkem líbil
i když teď už zase moc nevím - zde najdete AR
video na YouTube a zde je
původní video. Kvalita je dost mizerná, protože původní video se rozloží na
JPG snímky, do nich se přimíchají čáry, znova se uloží jako JPG a pak se to
ještě celé postupně zabalí do nového videa. Ale nějakou představu si podle toho
snad uděláte.
Ještě malé vysvětlivky. Pokud se makroblok vůbec nehnul, tak nekreslím nic.
Pokud ano, tak na jeho místě je červené kolečko a žlutá čára je vektor, kde
byla nejlepší korelace v předešlém (?) snímku. Vše je to integrované přes tři
snímky, aby bylo vůbec něco vidět.
Některé snímky jsou pěkné/použitelné. Např. na tomto
je vidět jak jsou skoro všechny posuny stejné a čtyřtulka se otáčí na místě.
Malé otočení |
Na dalším snímků je zase idealistická ukázka, jak blízké předměty (strom a zem)
se pohybují nejvíce a ostatní vzdálenější objekty (tráva na pozadí) téměř
vůbec:
Je nutné vzít v úvahu, do jakého bodu se drona pohybuje, protože v tom místě
také k žádnému velkému posunu nedochází. Ale je to alespoň nějaký příklad, kdy
bych měl spustit malý úhybný manévr vlevo.
Překážky z pohybu |
Ještě minimálně jedna situace je třeba vzít v úvahu a to, když se Heidi naklání:
Zde je vidět pěkný „vír”, přestože makrobloky povolují pouze posun a nikoliv
otáčení.
Náklon |
A co dál? Po pravdě si stále nejsem úplně jist, jestli vektory nemají být na
druhou stranu. Dále, jak jsem psal „korelace v předešlém (?) snímku” … on to
může být ještě daleko starší snímek, H264 to podporuje, ale jestli to používá
čip na Parrot AR Drone 2 nevím. Konečně bych si asi znova přečetl, jak je to s
těma sub-makroblokama. Jestli mohou mít také nezávislé posuny nebo zda se liší
jen způsobem kódování a jestli to drona používá.
Máte-li nápady, jak z uvedených snímků už usuzovat na příkazy leť
vlevo|vpravo|nahoru|dolu, tak napište .
8. říjen 2013 — Bakalářka
Přemýšlel jsem, jak detekovat a kompenzovat „víry” na posledním obrázku z
minulého příspěvku a pak jsem si vzpomněl na
Bakalářskou práci
Karla Doležala, kterou jsem už zmiňoval ve
druhém létajícím blogu. Jde konkrétně o
kapitolu 5.4, „Potlačení pohybů kvadrokoptéry”.
Základní postřeh je, že y-ová složka vektorů se mění s absolutní X-ovou
souřadnicí a nabývá nuly ve středu otáčení. To samé platí pro x-ovou složku
vektorů a absolutní Y-ovou souřadnici. Vedle znalosti středu ale ještě
potřebujeme všechny vektory kompenzovat a na to prý je dostatečná lineární
interpolace. Nějak začít musím, tak proč nevyzkoušet už jednou prošlapávanou
cestu?
Nejprve jsem byl zvědav, jak moc je ta lineární aproximace nepřesná.
Následující dva grafy odpovídají datům z obrázku „Náklon”:
Pás okolo předpokládané proložené přímky není úplně úzký, ale třeba to bude
stačit. Zatímco s X grafem jsem celkem spokojený (střed vychází někam k 65/80),
tak u Y už moc ne (tam přímka protíná osu někde okolo 15/45, ale střed
otáčení je téměř ve spodní části obrázku). Hmm, že by bug?! A taky jo!!
Zatracený obrázkový souřadnice. Y-ová jde shora dolů, to samé u indexů
makrobloků, ale já jsem zvyklý na „normální” souřadnice a tak si automaticky
převracím (0,0) do spodního levého rohu.
Hmm, hmm, … po převracení Y-ové vzhůru nohama to hned začne dávat větší smysl
a „svatojánské mušky” na začátku videa začnou poletovat na kmeni stromu.
Všechno vylejt!
p.s. Excel, který rád na rychlé grafíky používám, také nezklamal …
Celý obrázek do Excel XY grafu přímo nedáte ... |
9. říjen 2013 — Kompenzace natočení
Začal bych s linkem na opravené video, tedy
prohozenou Y-ovou souřadnicí, kdy už to vypadá trošku věrohodněji .
Dále bych pokračoval s aplikaci nejmenších čtverců na odhad středu a průběhu
„víru”. Z bakalářky převzatá rovnice pro výpočet koeficientů v dY=k1*X+k0:
je správně, jen si je třeba dát pozor, že je třeba jí použít 2x a jednou je
y=dY a podruhé je x=dX.
Po kompenzaci vypadá výsledek následovně:
Jisté zlepšení (hlavně v levém dolním rohu) vidět je, ale jestli už je to
dostatečné zatím neumím posoudit.
11. říjen 2013 — Příprava na ver0
Tak nejprve další video. Přiznám se, že koukat
stále na to samé mne už pomalu unavuje, ale snad už to brzy skončí . Zde
vidíte nejprve kompenzace posunu a natočení. Tj. „žluté vektory” už jsou po
kompenzaci a „víry” a jednotné posuny by tam už moc neměly být vidět.
Dále jsem tam přidal různé velikosti „červených koleček”. Pokud je pohyb
menší jak 10, tj. 2.5 pixelu, tak se neukazuje nic, pro 100 malé kolečko a více
velké (překážka). Je to stále integrované přes tři snímky. Výsledek je, že
občas je vidět něco rozumného a občas je celá obrazovka rudá, jak z nějaké
střílečky.
Plán pro verzi 0 je teď následující:
- pokud je počet překážek (míněno velkých koleček odpovídajících makroblokům s posunem více jak 100) větší než 1000 (cca 1/3 makrobloků), tak nedělej nic … krvavá řež
- pokud je na levé straně o 100 více překážek než vpravo, vyhýbej se vpravo
- pokud je dole o 100 více překážek než nahoře, leť nahoru
a symetrické IFy.
Teď jsem zvědavý, co to udělá ve skutečnosti. Už jsem si vyhlídnul malý
lesík/parčík, kde bych to vyzkoušel „rozflákat”. Pokud nic, tak snad bude
alespoň jiné, více relevantní, video k ladění. Čeká mne ale ještě netriviální
integrace, aby to vše běželo v reálném čase ze streamovaného videa. Momentálně
vůbec neodhadnu, na jak je to dlouho …
16. říjen 2013 — GitHub
Zbyněk mne minulý týden přesvědčil, ať pro robotika.cz založíme
GitHub. Používám sice na zdrojáky private
repository, ale jo … asi má pravdu. Rozhodně je to 100x lepší než zdrojáky
přilepovat k článku, jak jsme dělávali před lety.
Pokud se tedy chcete podívat, nebo ještě lépe přímo zapojit, tak git najdete na
https://github.com/robotika/h264-drone-vision.
Popisky bych asi měl dát rovnou tam, ale zase v kontextu tohoto blogu bude
jasnější o co jde:
- video.py je jednoduchá utilitka na ukládání videa za letu a zároveň na konverzi zdrojového souboru na oddělené frames
- h264.py jsou pak (zatím neúspěšné) pokusy jednotlivé frames dekódovat
- mv2pgm.py generuje PGM obrázky na základě textového souboru pohybů (po řádcích x,y, dx, dy)
- mv2pygame.py pak zároveň vektory kreslí v pygame (vím, že tam mám teď natvrdo adresář, kde se mají brát podkladové JPG, ale nějak začít musím a třeba to vůbec nikoho nebude zajímat …
18. říjen 2013 — HEX
Kamarádi se mi smějí, že se nejraději koukám na videa v hexa-editoru… a už
je to tady zase . Po drobném vyhecování Jirkou bych nejprve rozchodil
h264.py,
než se pokusím o C implementaci. A možná to ani nebude nutné.
Dříve (6/9/2013) zmiňovaný parser má také možnosti zapnutí TRACE:
# define TRACE 2 //!< 0:Trace off 1:Trace on 2:detailed CABAC context information
Trošku matoucí je, že tento define je ve dvou různých souborech pojmenovaných
defines.h (jeden pro decoder a jeden pro encoder) a nepřišly mi úplně
nezávislé. Tak jsem nakonec změnil oba na 2, pak to nešlo zkompilovat
(nedefinovany p_trace jsem přepsal u encoderu na stderr) a pak jsem chvíli
hledal, kam že ten výstup zmizel (je to v souboru trace_dec.txt).
Pustil jsem to na naše oblíbené video a během dvou snímků dostal 50MB textový
soubor. Trošku trvalo, než jsem v něm našel sekci, která mne zajímala:
Annex B NALU w/ long startcode, len 25412, forbidden_bit 0, nal_reference_idc 3, nal_unit_type 1 @1075083 SH: first_mb_in_slice 1 ( 0) @1075084 SH: slice_type 1 ( 0) @1075085 SH: pic_parameter_set_id 1 ( 0) @1075086 SH: frame_num 00000000000010 ( 2) @1075100 SH: num_ref_idx_override_flag 1 ( 1) @1075101 SH: num_ref_idx_l0_active_minus1 1 ( 0) @1075102 SH: ref_pic_list_reordering_flag_l0 0 ( 0) @1075103 SH: adaptive_ref_pic_buffering_flag 0 ( 0) @1075104 SH: slice_qp_delta 0000001000000 ( 32) @1075117 SH: disable_deblocking_filter_idc 1 ( 0) @1075118 SH: slice_alpha_c0_offset_div2 1 ( 0) @1075119 SH: slice_beta_offset_div2 1 ( 0) *** POC: 2 (I/P) MB: 0 Slice: 0 Type 0 ** @1075120 mb_skip_run 1 ( 0) @1075121 mb_type 1 ( 0) @1075122 mvd0_l0 1 ( 0) @1075123 mvd1_l0 1 ( 0) @1075124 coded_block_pattern 011 ( 1) @1075127 mb_qp_delta 1 ( 0) @1075128 Luma # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075129 Luma # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075130 Luma # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075131 Luma # c & tr.1s vlc=0 #c=2 #t1=2 001 ( 1) @1075134 Luma trailing ones sign (1,1) 10 ( 2) @1075136 Luma totalrun (1,1) vlc=1 0011 ( 3) @1075140 Luma run (1,1) k=1 vlc=6 111 ( 7)
ale … s mým oblíbeným hexa E0 00 E0 10 3F BF 31 mi to nesedělo, ať jsem
se to snažil sekvenci napasovat i na dost divná místa. Slice, který mne první
zajímá, je až čtvrtý (decoder má jinak bez SPS, PPS a I slice problém), tj. je
potřeba se prokousat 134398 bajty zdrojového videa.
Cestou z práce mi došlo, že bych mohl najít přesně místo, když @1075083 vydělím
8 (tj. předpokládal jsem adresu v bit-streamu). A to místo zase nevycházelo.
Vypadalo to, že se nezapočítává úvodní 00 00 00 01 plus bajt na rozlišení
typu slice, ale stejně. A pak mi to došlo! To co takhle krásně navazuje za
sebou (vyseklé řádky jsou někde u 32MB) ve skutečnosti vůbec za sebou není!!!
Včera bych za to věšel, dnes už jsem klidnější. Ten první odstaveček
(@1075083-@1075119) je hlavička pátého (!!!) slice a to co následuje
(@1075120-@1075140) jsou data čtvrtého … sigh.
No nic, další zámek snad už tedy povolil. V Pythonu jsem si dopsal třídu
VerboseWrapper, tak teď už snad bude jasnější co mi tam chybí nebo dělám
špatně …
21. říjen 2013 — coded_block_pattern
Tak snad jsem našel další dva zakopané psy a jejich společný jmenovatel je
coded_block_pattern. První co jsem nechápal bylo, jak tabulka, která určuje
dekódování počtu prvků může být adresovaná počtem prvků?! Druhý pak kolikrát je
třeba volat funkci „residual block”, která vyčte zabalené zbytkové hodnoty
rozdílu dvou snímků/makrobloků.
coded_block_pattern určuje 48 různých rozložení makrobloku do
sub-makrobloků. Podle popisu se původní 16x16 buď zachová, nebo rozloží na dva
16x8, nebo na dva 8x16 nebo na čtyři 8x8. Ty je pak dále možno zase rozkládat
na 8x4, 4x8 nebo 4x4. Z kódu jsem moc nepochopil, kde přesně se tato informace
vyskytuje, ale rozhodující jsou 4x4 sub-bloky, kterých se do 16x16 vejde celkem
16. Převodní tabulka pak bitově určuje, který 4x4 blok je uložený.
V mém předešlém příspěvku se vyskytuje pouze 4x Luma. coded_block_pattern má
hodnotu 2 a v tabulce mu odpovídá hodnota 1. Je to tedy pouze kódování jednoho
4x4 bloku (vše berte s rezervou, protože si s tím nejsem jistý … na druhou
stranu mne až tak nezajímá, zda to náhodou neodpovídá jednomu 16x16 bloku, jako
spíše kolikrát musím zavolat funkci „residual block”, abych se dostal k datům
dalšího makrobloku).
A teď ten druhý pes, tedy vlastně první. Jakou mám použít tabulku pro načtení
počtu nenulových prvků, když toto číslo ještě neznám? Důležité je chybějící
slovo odhad. Mám-li zmíněný makroblok se čtyřmi Luma popisy, tak u prvního
nevím nic a tak můj odhad bude 0. U druhého, který je vpravo od prvního, si
můžu tipnout, že prvku bude jako u prvního. Třetí, dole pod prvním, také může
použít tuto informaci. A konečně čtvrtý prvek pro odhad použije výsledky
druhého a třetího. Po převedení indexace od 0 to vypadá takto:
if cbp in [2,3]: nC = [0]*4 nC[0] = residual( bs, nC=0 ) # Luma only 4x nC[1] = residual( bs, nC[0] ) # left nC[2] = residual( bs, nC[0] ) # up nC[3] = residual( bs, (nC[1]+nC[2]+1)/2 ) # (left+up)/2
Teď mi ještě není jasné, zda se na tuto sousednost hraje i mezi jednotlivými
4x4 bloky. Podle složitosti indexování bych tipoval že ano.
Vedle Luma se v makroblocích vyskytuje ještě ChrDC a ChrAC:
*** POC: 2 (I/P) MB: 1 Slice: 0 Type 0 ** @1075143 mb_skip_run 1 ( 0) @1075144 mb_type 1 ( 0) @1075145 mvd0_l0 1 ( 0) @@1075146 mvd1_l0 1 ( 0) @1075147 coded_block_pattern 00111 ( 32) @1075152 mb_qp_delta 011 ( -1) @1075155 ChrDC # c & tr.1s #c=0 #t1=0 01 ( 1) @1075157 ChrDC # c & tr.1s #c=0 #t1=0 01 ( 1) @1075159 ChrAC # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075160 ChrAC # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075161 ChrAC # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075162 ChrAC # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1) @1075163 ChrAC # c & tr.1s vlc=0 #c=3 #t1=3 00011 ( 3) @1075168 ChrAC trailing ones sign (2,4) 101 ( 5) @1075171 ChrAC totalrun (2,4) vlc=2 000001 ( 1) @1075177 ChrAC run (2,4) k=2 vlc=6 011 ( 3) @1075180 ChrAC run (1,4) k=1 vlc=6 001 ( 1) @1075183 ChrAC # c & tr.1s vlc=1 #c=0 #t1=0 11 ( 3) @1075185 ChrAC # c & tr.1s vlc=1 #c=0 #t1=0 11 ( 3) @1075187 ChrAC # c & tr.1s vlc=0 #c=0 #t1=0 1 ( 1)
Podle dokumentace je:
- DC transform coefficient — A transform coefficient for which the frequency index is zero in all dimensions
- AC transform coefficient — Any transform coefficient for which the frequency index in one or both dimensions is non-zero.
Zase tomu nerozumím, ale pozorování je, že makrobloky buď mají jenom Luma
(po převedení tabulkou čísla menší než 16), nebo mají navíc ChrDC (čísla menší
než 32) nebo mají ještě ChrAC (od 32 výše), která automaticky obsahují i ChrDC
a podle bitového vzoru počet Luma.
Jsem líný přepisovat všechny ty tabulky (ono na to stejně dojde), tak to dělám
postupně a zatím jsem se dohrabal k makrobloku číslo 10 (z referenčního
videa).
TO BE CONTINUED
24. říjen 2013 — test first (again)
Je to peklo! Skoro bych řekl hotový očistec. Teď si ani nedovedu představit,
jak bych to býval implementoval bez referenčního testovacího vzorku. A těch
chyb!! Byl jsem hodně naivní, když jsem si myslel, že tento prodloužený víkend
bych už to uzavřel s testovacím letem. Ostatně v detailu to můžete
sledovat
(nebo mi možná i pomoci? ) na
githubu. Progres je vidět na
řádce 340, ke kolikátému makrobloku jsem se dostal …
Chcete si to sami zkusit? Nainstaluje te si Python, naklonujte git repository a
pusťte si
h264.py test\frame0001.bin
a pokud výstup bude alespoň trošku podobný jako
frame0001.txt
(oříznutý na pouhých 262 makrobloků, tj. cca 600MB textový soubor).
A co je nového? Jednak hodnoty nC pro odhad počtu nenulových prvků se
počítají i s již dekódovaných sousedních makrobloků … ale to se dalo tak
trošku tušit. Dále jsem doplnil X binárních tabulek a pár bitů opravil. Zatím
jsem se dostal k makrobloku číslo 23, tj. ještě ani celou řádku z prvního
obrázku nemám … trošku depresivní.
29. říjen 2013 — Foxit
Dnes jsem konečně doplnil coefTokenMapping tabulky v dekódování residual dat.
Měl jsem problém, jak dostat použitelný text z PDFka … vtipně tam totiž mají
po blocích mezery a mezera je stejně tak na oddělení sloupců. Vyřešit se to ale
dá např. jiným PDF prohlížečem (díky za tip Ondrovi). Konkrétně
Foxit umožňuje přepojení do
textového režimu (View/Text Viewer) a pak dostanete textové tabulky, kde
sloupečky jsou oddělené více mezerami.
Aby jste získali co opravdu potřebujete, tak už stačí něco jako:
a = ["".join(x.split()) for x in line.split(" ") if len(x) > 0 ]
a už je možné tabulky poskládat .
p.s. dnes se omlouvám za ještě více překlepů než normálně. Nemám ještě
doinstalované některé fonty (?) a tak většinu českých znaků vidím jako
čtvereček …
31. říjen 2013 — První řádek
Před časem jsem si říkal, že až dekóduji první řádek, tak to náležitě
„oslavím”. Když k tomu došlo (cca předevčírem?), tak jsem žádný pocit
dosaženého milníku neměl a pokračoval jsem dál. Včera jsem se dostal na konec
referenčního
textového souboru, tak teď asi přejdu na větší, ještě vygenerovaný na starém
počítači, co má 80MB.
Ze změn, které jinak můžete detailně sledovat na
githubu, asi stojí za zmínku
dekódování nenulových prvků (level) a pak už konečně ty posuny (mvd). Level je
v dokumentaci popsán dvěma stránkami a moc pochopitelné to pro změnu není :-(.
Tabulka je tam jenom jedna, ale evidentně se jich používá více (konkrétně 6). V
tomto smyslu mi byl více nápomocný
článek
o paralelním dekódování, kde jsou tabulky vypsané. I ten princip je tam
srozumitelnější — ve zkratce když jdete od konce, tak tam jsou většinou 0 a 1
a prvky postupně s vysokou pravděpodobností rostou (v absolutní hodnotě).
Přecházíte tedy od jedné tabulky k další. Existuje ještě „threshold
table” popisující limity pro přechod na další tabulku. Toto jsem ale zatím
ještě neimplementoval a čekám až narazím na reálný příklad.
O nefunkčních příkladech bych se možná také trošku zmínil. Je to jedna ze zásad
eXtrémního programování, kdy do
unit-testu přidáváte „co se rozbilo” nebo „co nefungovalo”. Vedle
referenčního výstupu se mi to osvědčilo i tady, takže
h264_test.py
postupně narůstá (konkrétně testResidual()).
A co mvd, posuny makrobloků? Je to další hádanka. Jak se dostat z (x,y,dx,dy)
0 0 -6 0 1 0 6 0 2 0 0 0 3 0 0 0 4 0 0 0 5 0 0 0 6 0 -4 0 7 0 12 0 8 0 -8 8 9 0 0 -8 10 0 0 0
na
0 0 -6 0 1 0 0 0 2 0 0 0 3 0 0 0 4 0 0 0 5 0 0 0 6 0 -4 0 7 0 8 0 8 0 0 8 9 0 0 0 10 0 0 0
asi vymyslí každý, ale jak od druhého řádku dál zatím s jistotou nevím. Zase
jeden příklad
26 0 0 0 27 0 2 0 28 0 0 0 29 0 -2 0 30 0 0 0 … 26 1 0 0 27 1 -2 0 28 1 0 0 29 1 0 0 30 1 0 0
na
26 0 0 0 27 0 2 0 28 0 2 0 29 0 0 0 30 0 0 0 … 26 1 0 0 27 1 0 0 28 1 0 0 29 1 0 0 30 1 0 0
Vedle relativní změny k sousedovi je zde zase odkaz na předešlý řádek, ale
nevypadá to na průměr (?). Stejně tak ještě nemám úplně jasno co se má dít
s proměnnými při skipMacroblock > 0. Zda se mají nulovat nebo nastavit na
nedefinované. Ale to snad také ukáže příklad z těch 80MB.
2. listopad 2013 — mvd median
Tak jsem konečně rozlousknul vektory posunů v celém prvním snímku. Největší
hádanka byla, co se bere jako predikce vektoru u druhého a dalších řádků.
Ostatně jsem to zmínil už minule. A řešení? V naprosté většině případů se bere
prostřední hodnota (medián) ze tří prvků: makrobklok vlevo, makroblok nahoře a
makroblok nahoře vpravo. Zvlášť ten poslední prvek byl pro mne novinkou. Ale to
není všechno. Na konci řádku už není žádný „nahoře vpravo” a tak se použije
„nahoře vlevo”.
Popsaný algoritmus funguje na první snímek, ale selže u jednoho vektoru ve
druhém snímku. Další zakopaný pes bude ještě u přeskakování makrobloků. Zatím
používám nulové vektory posunutí, ze kterých se pak medián počítá, ale není to
100%. Takže další TODO.
3. listopad 2013 — skip_macroblock
Druhý offline záznam … potvrzuji, že další pes byl spojen s přeskakováním
makrobloků, tedy ve funkci skip_macroblock(). Při přeskakování jsem doplňoval
chybějící vektory pohybu hodnotou (0,0) a většinou to fungovalo. Kde byla
zrada jsem pochopil až při krokování a breakpointu v počítání mediánu, kam se
mi program dostal při přeskakování makrobloků! Prostě je tam další podmínka:
pokud makroblok vlevo i nahoře má nenulový vektor posunutí, tak použij medián
jako při zpracování běžného makrobloku.
Teď už vektory posunutí sedí 100% a zpracování se kousne až u desátého
referenčního snímku na nějaké chybě parsování …
p.s. chyba u desátého snímku byla v
@2434573 ChrDC # c & tr.1s #c=4 #t1=3 0000000 ( 0)
tj. všechny prvky (pro ChrDC to jsou 4) byly nenulové a neměl jsem už číst
počet nulových prvků.
6. listopad 2013 — mb_type
Znáte ten pocit, kdy se domníváte, že jste skoro u cíle, ale pak jemně zafouká a
realita vám rozboří váš domeček z karet? Tak přesně to jsem prožíval včera ráno. V
neděli večer jsem už bezchybně dekódoval prvních 25 snímků, včetně správné
interpretace vektorů pohybu. To je cca 90000 makrobloků a přestože byla nějaká
chybka ve 26. snímku, tak „cíl přece nemůže být daleko”. Karta se však
obrátila, když jsem ten problém lépe analyzoval a zjistil, že typ makrobloku je
nenulový. Zalepil to pro jeden případ a bum, další nenulový, ale jiné číslo.
OK, další záplata a další a další, to vše jenom v tom 26. snímků. Znova jsem
konzultoval dokumentaci (strana 88,tabulka 7-8), abych zjistil, že existuje 25
typů makrobloků, které je třeba speciálně pořešit. Tj. v extrémním/dalším
falešném náhledu, jsem zatím ušel jenom 1/25tinu cesty, cha cha …
Po tomto zjištění jsem si chtěl vzít měsíc dovolené (jak je to doslova
„dovolená na zotavenou”). Přes den se to trošku rozleželo a asi se na to za
chvíli zase podívám . Prioritu by teď měl dostat chodící
robot, ale asi to nikoho moc nezajímá, tak to ještě chvíli bude „úloha na
pozadí”.
S 25 typy, ono jich je vlastně 26, protože se číslují od nuly, … že by nějaké
prokletí 26tky?? Další klíčové slovo je asi Intra16x16PredMode, který je
buď DC, Horizontal, Vertical nebo Plane. Někde chybí vektory posunu (vlastně
všechny ty protipříklady byly typu DC) … no nevím. Čeho se teď nejvíce děsím
je, že bude třeba pracovat s celými 16x16 bloky (případně 16x8, 8x16, 8x8) a
nějak odhadovat, kde jsou nenulové prvky v jejich 4x4 podblocích. Predikce
počtu nenulových prvků je „centrální dogma”, které dobře funguje pro
makroblok typu 0, takže i těch dalších 25 typů na to bude třeba určitě nějak
napojit. Prostě radost. Možná si tu dovolenou přeci jenom vezmu a budu raději
zjišťovat, jak se dělají střechy a okapy, než začne mrznout …
7. listopad 2013 — frame0026.bin pokořen
Přidal jsem do gitu pro testování 26. snímek:
test/frame0026.bin
a
test/mv0026.txt
a klasicky chvíli na to byla oprava na světě . Teď mluvím o opravě vektorů
pohybu, které mi dnes dosud u tohoto snímku neseděly. Důvodem byla nutnost
ignorovat predikci a natvrdo nastavit (0,0) u těch 25 „nových” mb_type
zmiňovaných včera.
Tak trošku tiše přecházím fakt, že už jsem 26. snímek vůbec dekódoval.
Matematicky jsem to převedl „na předešlý případ” a programátorsky zase
koukám, kdy to bouchne .
Dekodér
se dohrabal k 94. snímku s tím, že jsou stále drobné chyby na začátku řádků ve
vektorech pohybu. Takže příběh stále nekončí …
13. listopad 2013 — levels
Ještě vás to baví číst? Mne už ta 80x45 sudoku občas unavuje. To však po chvíli
poleví a jsem zvědavý, na čem se to zase kouslo. Včera jsem revidoval
„levels”, tedy nenulové prvky větší než 1. Jedná se o „zbytkové prvky” po
transformaci, které v absolutní hodnotě klesají a navíc jsou často prokládány
nulami. Kolik je nul mezi jednotlivými prvky se kóduje zvlášť stejně tak pokud
je +/-1 na konci méně jak 3.
Kódování dalších prvků je vlastně takový hybrid. Úplně první číslo (jede se od
konce) různé od jedničky je kódováno exponenciálně, tedy velikost čísla je daná
počtem nul a sekvenci ukončuje jednička. Navíc je třeba sbírat data znaménkově,
takže 1=1, 01=-1, 001=2, 0001=-2, … K tomu je hned několik výjimek. Jedna je,
že pokud celkový počet prvků je větší než 3 a počet jedniček je různý od tří,
tak k načtenému číslu v absolutní hodnotě přičti jedna. To smysl dává, protože
když už tam nebyla jednička, nuly se kódují speciálně, tak další nejmenší možné
číslo je v absolutní hodnotě dvojka.
Hybrid začíná už od druhého prvku — podle absolutní velikosti se přidává
počet bitů pro „suffix”. Ve zkratce přečtete exponenciální prefix, řada nul
ukončená jedničkou, a pak přečtete pevný počet bitů a zkombinujete. Počet bitů
pro suffix se, zase až na nějaké výjimky a hodně velkými prvky, může zvětšit
pro každý prvek jenom o jedničku, pokud absolutní hodnota překročí definované
meze:
static const int incVlc[] = {0, 3, 6, 12, 24, 48, 32768}; // maximum vlc = 6
Současná implementace tam má assert na velikost 6. Dostal jsem se ke stému
snímku, ale u frame0113.bin se zase něco pokazilo. Z celého referenčního videa
jsme cca v 1/6.
p.s. jedna z výjimek je, že z prvního prvku na druhý se může suffix zvětšit na
dva bity, pokud první prvek je v absolutní hodnotě větší než 3. Viz
H264Test.testLevelTabs2
čtení 1557531 bitu.
15. listopad 2013 — varianta MVPRED_UR
Teď jsem se trošku zasekl na jedné chybě u 113 snímku, 16 řádek, pozice 0.
Referenční program se zastavil na breakpointu, který jsem dal na různá místa,
která „přeci nemohou nikdy nastat”. To byl případ i MVPRED_UR, konkrétně na
mv_prediction.c:216. O co jde? Když se počítá medián pohybu při
přeskakování makrobloků, tak, se bere v úvahu levý, horní a horní pravý/levý
(podle dostupnosti) blok. Může se stát, že ne všechny informace jsou k
dispozici (v Python kódu na to používám None), kde dobrým příkladem je první
řádek — tam není ani žádný horní prvek a tak „medián” se bere jenom z
levého prvku.
Jak ale na 80x45 velkém snímku docílit toho, že není k dispozici levý a horní
prvek, ale pouze horní pravý?! Vypadá to zase na nějakou kličku při
přeskakování makrobloků, že (0,16) je úplně vlevo a nemá tedy k dispozici
levého předchůdce, informace o pohybu není na (0,15) z důvodu přeskakování (?)
k dispozici a tak nastává případ MVPRED_UR a pro odhad se použije pouze horní
pravý prvek (1,15).
Rutinu pro medián jsem
upravil,
ale pokud nahradím nulové prvky při přeskakování nedefinovanými, tak se mi
zbytek celý rozsype :-(.
Moje momentální noční můra je, že se jedná o odkaz na starší snímek 112 (no snad to
číslo nebudu muset volat) a tam se třeba také přeskakovalo, takže reference
není k dispozici???
p.s. dostal jsem ho! Už dlouho jsem ve Visualu nepouštěl breakpoint na
změnu hodnoty na daném paměťovém místě … v popisu výše jsem pomíchal (0,15) a
(1,15) … první k dispozici byla a druhá ne, tj. celé to byl MVPRED_U, ale
princip je stejný. A důvod? Vzpomínáte na „nedávno objevených” dalších 25
typů makrobloků? (1,15) byl typu 22, tj. „intra_chroma_pred_mode”, pro který
není vektor pohybu definovaný. Oprava byla triviální — viz
diff
19. listopad 2013 — kříženci
Aneb co se stane, když zkombinujete „intra blok” se „skip blokem”? No
nedopadne to dobře. Intra blok nemá predikci pohybu. To ale neznamená, že není
k dispozici! Zní to zmateně? Ani se nedivím. Prostě jak jsem minule psal, že
pokud neznám vektor pohybu pro daný prvek (vlevo nebo nahoře) a používám
(None,None), tak to nestačí a je třeba dodat důvod, proč je to None.
Medián se použije skoro vždy jen s výjimkou, že jste na kraji (tj. None z
důvodu první řádky nebo prvního sloupce) nebo levý či horní prvek je k
dispozici a má nulový vektor. Vyřešena další „záhada”.
A jak jsme na tom po čtyřech měsících „trápení”? Jsme zhruba v 1/5
referenčního videa. Teď se to pokazí u snímku 00147.
21. listopad 2013 — Luma lev 17
Dnes ráno jsem měl krásný úlovek:
@1365333 Luma # c & tr.1s vlc=1 #c=1 #t1=0 001011 ( 11) @1365339 Luma lev (0,0) k=0 vlc=0 0000000000000010001 ( 17) @1365358 Luma totalrun (0,0) vlc=0 1 ( 1)
No není to nádhera? Nemáte trošku pocit, že už mi to leze na mozek?
Dodělal jsem už snad pořádně přechodové tabulky pro Luma Level a Luma lev
17 je příklad tak velkého čísla, že se z 0té tabulky skáče rovnou na čtvrtou
a výsledné číslo je zde 17 (frame0187.bin MB: 24). Jo, spadlo mi to tam.
Proč? Extra mezera v tabulce kódů. Pro zájemce viz
github.
22. listopad 2013 — levelSuffixSize
Tentokrát se to rozbilo „u 18 lvů” (frame0228.bin MB:30) — prostě ta
výjimka, co jsem jí popisoval včera, je pouze jednorázová. Nepřeskakuje se tedy
na čtrtou tabulku, ale pouze jednorázově se přečtou čtyři bity a dál se
pokračuje „jako by se nechumelilo”.
Po
opravě
se dostávám k frame0305.bin. Naivně bych si teď myslel, že mám tak jednu chybu
ve sto snímcích (???) a mohl bych si už dovolit přejít „do výroby”. Je tam
ale nutné dodělat rozpoznávání, že byl snímek špatně dekódovan (aktuální
představa je, že pokud parsování skončí na posledním bajtu načteného paketu,
tak je vše OK) a pak možnost to celé úplně zastavit, pokud je to hodně špatně
(např. se opravdu odkazuje na více snímků zpět). Pro to bych asi použil trik,
který jsem slyšel tento týden na prezentaci
Universal Robots: pokud se ve výrobě
objeví dva zmetky za sebou, tak se linka zastaví. To zní jako jednoduchý
algoritmus, co myslíte?
23. listopad 2013 — Functional testing
Funkcionální testování je
další důležitý prvek eXtrémního programování. Na rozdíl od unit testů, které
musí vždy všechny projít, úspěšnost fukcionálních testů je proměnlivá a měla
by vystihovat aktuální funkcionalitu programu.
Mým funkcionálním testem vlastně bylo parsování referenčního videa. Číslo
snímku, kam až se dostanu, ale není moc vypovídající. Jak jsem psal včera, já
vlastně nepotřebuji úspěšně parsovat všechny snímky a např. 99% úspěšnost by
mi stačila. Postupné dekódování jsem nahradil statistikou přes všechny snímky.
A jak to dopadlo? Ze 707 snímků jich 10 selhává. Všechno samozřejmě za
předpokladu, že ten test je správný.
p.s. pamatujete na 17 a 18 lvů? Tak snímek 305 to pokořil:
0000000000000001000000000001 (4097) … teď už prošlo 705 snímků z 707, tj.
99.7% to by snad šlo.
25. listopad 2013 — ESCAPE
Když jsem v sobotu dosáhl stavu, že už jenom dva snímky selhávají
(frame0380.bin a frame0706.bin), nechal jsem to trošku uležet. Udělal jsem si
unit test na makroblok, kde se výpis rozcházel s referencí, ale ten prošel bez
problémů!? Nechápal jsem. Pak mne napadlo jediné vysvětlení a to, že co se
píše v trace logu není totožné s tím, co je v binárních datech. Zase chvíle
na HEX editor …
A co jsem ošidil tentokrát? Neřešil jsem ESCAPE znaky, které mají zabránit
nechtěnému start bloku uprostřed dat snímku. Oprava je taková, že pokud je v
sekvenci bajtově 00 00 03 XX, tak jí je třeba nahradit 00 00 XX. Případně viz
opravu na
githubu.
A ta druhá chyba? Žádná není — jak Heidi havarovala, tak je
video přerušené a data posledního snímku tedy nejsou kompletní … . Je to
zvláštní pocit, když všech 24MB referenčních pohybu 100% sedí … ale nějakou
extra radost z toho nemám. Prostě jak mne kdysi nazval kolega v práci, že jsem
„nepotěšitelný člověk” . Podle eXtrémního programování jsem s dekodérem
hotov, dokud se neobjeví nějaká další chyba. Je čas na optimalizace a začištění
kódu … a hlavně integraci do navigace drony!!! … no trošku škoda, že
venku už mrzne.
6. prosinec 2013 — bittables
Minulý týden jsme se s Ondrou a Zbyňkem bavili o aktuální implementaci
pythonovského dekódování H264 video codecu. Pustil jsem to tak jak to je v
profileru na referenční video a vychází to cca 2 zpracované snímky za sekundu,
což není moc. Mimochodem, pokud to neznáte, v Pythonu je rovnou integrovaný i
jednoduchý profiler, takže co jsem přesně dostal bylo toto:
265325033 function calls in 373.808 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 15742034 142.370 0.000 238.575 0.000 h264.py:184(residual) 91092961 94.282 0.000 98.475 0.000 h264.py:27(bit) 23691275 40.811 0.000 87.999 0.000 h264.py:63(tab) 2094625 39.971 0.000 345.131 0.000 h264.py:348(macroblockLayer) 11923219 20.845 0.000 64.202 0.000 h264.py:41(golomb) 683 12.099 0.018 373.607 0.547 h264.py:461(parsePSlice) 4483734 7.459 0.000 10.474 0.000 h264.py:445(median) 14606420 5.612 0.000 5.612 0.000 h264.py:337(mix) 91096499 4.193 0.000 4.193 0.000 {ord} 4313872 3.015 0.000 3.015 0.000 {sorted} 3955682 2.446 0.000 28.555 0.000 h264.py:50(signedGolomb) 334970 0.291 0.000 0.688 0.000 h264.py:35(bits) 1977840 0.212 0.000 0.212 0.000 {method 'append' of 'list' objects} 1 0.077 0.077 373.808 373.808 h264.py:642(functionalTest) 707 0.058 0.000 0.058 0.000 {open} 707 0.029 0.000 0.029 0.000 {method 'read' of 'file' objects} 707 0.020 0.000 373.641 0.528 h264.py:557(parseFrameInner) 707 0.007 0.000 0.007 0.000 {method 'replace' of 'str' objects} 3535 0.005 0.000 0.005 0.000 h264.py:57(alignedByte) 1 0.001 0.001 0.001 0.001 {nt.listdir} 707 0.001 0.000 373.642 0.528 h264.py:579(parseFrame) 722 0.001 0.000 0.001 0.000 {method 'startswith' of 'str' objects} 707 0.001 0.000 0.008 0.000 h264.py:131(removeEscape) 707 0.000 0.000 0.000 0.000 h264.py:23(__init__) 2006 0.000 0.000 0.000 0.000 {len} 1 0.000 0.000 373.808 373.808 h264.py:6(<module>) 1 0.000 0.000 0.000 0.000 h264.py:22(BitStream) 1 0.000 0.000 0.000 0.000 h264.py:77(VerboseWrapper) 1 0.000 0.000 0.000 0.000 h264.py:11(setVerbose) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Druhý pokus, který byl skoro zadarmo, byl nainstalovat
pypy, což je alternativní implementace Pythonu s
„Just-in-Time” kompilerem, a pustit to v tom. Výsledek?
261010454 function calls in 165.356 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 23691275 1563207619897.772 65982.418 -3153386446098.892 -133103.282 h264.py:63(tab) 91092961 536353730042.098 5887.982 536353741505.578 5887.982 h264.py:27(bit) 334970 321684266772.059 960337.543 -296438412397.484 -884970.034 h264.py:35(bits) 11923219 307659624818.975 25803.403 -243097552805.106 -20388.584 h264.py:41(golomb) 683 163616892732.185 239556211.907 165.020 0.242 h264.py:461(parsePSlice) 3955682 87309675664.466 22071.965 -244719216.961 -61.865 h264.py:50(signedGolomb) 2094625 28846.299 0.014 -244579229.288 -116.765 h264.py:348(macroblockLayer) 91096499 11463.483 0.000 11463.483 0.000 {ord} 14606420 2244.535 0.000 2244.535 0.000 h264.py:337(mix) 1 0.218 0.218 165.356 165.356 h264.py:642(functionalTest) 707 0.036 0.000 0.036 0.000 {method 'read' of 'file' objects} 707 0.025 0.000 165.095 0.234 h264.py:557(parseFrameInner) 707 0.021 0.000 0.021 0.000 {method 'replace' of 'str' objects} 3535 0.020 0.000 0.023 0.000 h264.py:57(alignedByte) 707 0.003 0.000 0.003 0.000 h264.py:23(__init__) 2006 0.003 0.000 0.003 0.000 {len} 722 0.002 0.000 0.002 0.000 {method 'startswith' of 'str' objects} 707 0.002 0.000 0.023 0.000 h264.py:131(removeEscape) 707 0.002 0.000 165.097 0.234 h264.py:579(parseFrame) 1 0.001 0.001 0.001 0.001 {nt.listdir} 1 0.000 0.000 165.357 165.357 h264.py:2(<module>) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.000 0.000 0.000 0.000 h264.py:22(BitStream) 1 0.000 0.000 0.000 0.000 h264.py:77(VerboseWrapper) 1 0.000 0.000 0.000 0.000 h264.py:11(setVerbose) 4483734 -3669274014.929 -818.352 -3669274014.929 -818.352 h264.py:445(median) 1977840 -4159866550.303 -2103.237 -4159866550.303 -2103.237 {method 'append' of 'list' objects} 15742034 -2972002711751.617 -188794.073 94047.814 0.006 h264.py:184(residual)
Ta záporná čísla vypadají trošku podezřele, ale v zásadě 707 snímků bylo
zpracováno v čase 165.3s a s klasickým Pythonem to trvalo 373.8s. To by už byly
zhruba 4 snímky za sekundu (bez práce), ale to je stále málo … možná.
Tak co s tím?
- načtení jednotlivých bitů je drahé (ano, není divu — z pohodlnosti si hraji s "0"/"1" stringy místo přímo bity)
- tabulky jsou lokální a s každým voláním je tedy třeba je znova vytvořit
- dekódování bitového streamu do tabulek dělám tak, že postupně načítám bity a ptám se, jestli už v tabulce nejsou
Třetí bod jsme právě minulý týden diskutovali a Ondra to pak nevydržel a
rovnou ten kousek napsal jako příklad, viz
bittables.py
nebo můj mini pokus o test
bittables_test.py.
Už jsme tedy na githubu 2 contributors .
O co jde? Jedná se o stavový automat s jednoduchou přechodovou tabulkou.
Začínáte ve stavu 0. Když načtete bit 0, tak si další stav přečtete z tab[0] a
když 1, tak z tab[1]. Spodním bitem je kódovaná informace, že už jste výsledek
našli a zbytek čísla udává index do výsledkové tabulky. Pokud je spodní bit
nula, tak pokračujete v tab[stav+0] nebo tab[stav+1]. Je to jasné? Pokud ne,
tak možná je to lépe vidět přímo v tom zdroji …
p.s. ještě spíše pro sebe si sem schovám referenční volání:
m:\git\h264-drone-vision>c:\python27\python.exe -m cProfile -s time h264.py -f m:\git\data0\frames\ > tmp.txt
p.p.s.s.
globalizace
pomohla … je to sice celkově jen o třetinu rychlejší (v klasickém Pythonu), ale např. nejčastěji
volaná funkce residual() je 3x rychlejší:
15742034 32.250 0.000 123.116 0.000 h264.py:187(residual)
14. prosinec 2013 — generátor a automaty
Dnes jsem snad plně implementoval generátor bitů (k mému překvapení viditelné
zrychlení) a automaty místo hledání v tabulkách (viz úvodní část v
h264.py.
Je to lepší, ale ještě to stále není dobré. Když jsem to spustil v PYPY, tak
jsem dostal nesmyslný čas (344990319 function calls in -19819172.282 seconds),
ale volání hlavní funkce je zřejmé:
707 0.001 0.000 105.716 0.150 h264.py:555(parseFrame)
tj. 707 snímků za 105s … a tedy cca 6.7 snímků za sekundu. Pokud bych to
pustil ve čtyřech procesech, tak se možná dostanu na 25 FPS, ale to je moc na
hranici …
25. prosinec 2013 — Heidi zdrojáky
Dnes jsem dal na github zdrojáky Heidi.
Bylo by pěkné je nejprve trošku uklidit, okomentovat a vyříznout jen „zdravé
jádro”, ale toho bychom se nemuseli dočkat. Takže tam najdete mix pokusů z
AirRace,
Robotem rovně i
RoboOrienteeringu. Na druhou stranu,
pokud jste dostali pod stromeček AR Drone 2.0, tak si rovnou můžete v
Pythonu hrát …
A nějaké další motivace? No chtěl jsem začít s tou integraci pro první testy s
létáním a současným pársováním H264 kodeku a to se nutně bude odkazovat do už
existujících zdrojáků na létání. Pokud toto bude číst někdo, kdo by to opravdu
použil/používal, tak mi prosím dejte vědět — věřím, že ten kód půjde
přetvořit v něco lépe stravitelného .
27. prosinec 2013 — multiprocessing
Včera večer jsem se v tom nějak zamotal. Nahrávání, a teď nově i zpracování,
videa běží v odděleném procesu, aby nijak nezdržovalo hlavní řídící smyčku. Za
tímto účelem používám modul multiprocessing a z něj pak Process a
Queue. Modul nabízí podobnou funkcionalitu jako threading, ale oddělený
Process může běžet na jiném procesoru.
Má to ale drobný zádrhel a to v komunikaci mezi jednotlivými procesy. Python se
snaží, ale občas nemá šanci. Pokud si dobře vybavuji, jak mi to Zbyněk kdysi
vysvětloval, tak vlastně pustí paralelně další interpreter a pokud se nějaký
datový typ používá u obou, tak se serializuje tam a zpátky. A to někdy nejde.
Kdy? Například pokud si uděláte třídu PacketProcessor, která slepuje
jednotlivé video pakety (jsou celkem dost rozkouskované, typická velikost byla
1460 bajtů), a přidáte si do toho vlákno, které by v mezičase zpracovávalo
nejnovější kompletní frame. Pokud hloupě tuto instanci třídy používáte i v
hlavním programu (koukal na výsledky analýzy), tak to skončí chybou jako:
pickle.PicklingError: Can't pickle <type 'thread.lock'>: it's not found as thread.lock
Pro účely meziprocesové komunikace existují vhodnější nástroje: Queue, Pipe,
Lock … vše z modulu multiprocessing. A kde, že jsem se to zamotal? Chtěl
jsem do ARDrone2 přidat, stejně jako je to u robota Eduro,
registerDataSource() a addExtension(). První přidá zdroj dat a hlavní
smyčka robota se ho pak ptá, zda není něco nového. Druhá přidá konzumenta,
který čte „novinky” a podle toho něco dělá nebo nastavuje vnitřní stav
robota. Tak do podobného (možná stejného??) rámce se snažím zpracování videa
napasovat a první pokus se úplně nepovedl (takže jsem ho zatím ještě ani
nekomitoval).
A co jinak? Z výpisů
insmod /data/video/driver2/cdc-acm.ko insmod: can't insert '/data/video/driver2/cdc-acm.ko': invalid module format
soudím, že po flashnutí nové verze firmware (cca před půl rokem??) jsem ještě
nezkompiloval „nové” moduly pro čtení dat ze sonaru po USB (liší se build
verze kernelu). Také přišel mail z ARDrone API konference, že nový
AR.FreeFlight
2.4.10 aplikace podporuje „GPS flight recorder” a kdy to konečně přidají do
SDK? No některé jednohvězdičkové komentáře mne moc neuklidňují: Shame on
Parrot Buggy at best. Using the GPS and the return to home button caused my
drone to dive into the street from 30' destroying the nose. I am done with
Parrot's lies, faulty app, and extremely poor customer service. Tak snad to
přes víkend nerozbiji s novou oficiální aplikací .
28. prosinec 2013 — příprava na let H-264-00
Včera večer jsem to nějak
upatlal.
Zakomentoval jsem pouštění sonaru (včetně přehrávání z logu), přidal logování
src_h264_*, obešel asserty … no prostě hrůza.
Plán je
odkomentovat
řádku 135 (opravdu jde na githubu generovat URL pro vybranou řádku )
#drone.takeoff( enabledCorrections = False )
a pak možná změnit limit na
řádce
137:
for i in xrange(100):
resp. to předělat na reálný čas. Heidi by se tedy měla vznést, chvíli logovat
data a zase přistát.
V dalším kroku bych změnil
příkaz
pohybu:
drone.moveXYZA( 0.0, 0.0, 0.0, 0.0 )
na
drone.moveXYZA( 1.0, 0.0, 0.0, 0.0 )
tj. 1m/s pohyb vpřed a pak to zkoumal doma z logů … tak to by nedopadlo
dobře. Už jsem to vše zapomněl. Podle
homologace
z RoboOrienteeringu 2013 (a ještě nedodělané) to jen
nastaví
úhel pro let v X-ové ose a musím si hlídat rychlost vx.
K logování bych možná měl také poznámku: teď se loguje počet větších vektorů
(definovaných proměnnou THRESHOLD) v jednotlivých kvadrantech, přesněji
polovinách, tj. vždy součty dvou kvadrantu. Další TODO. Pro přehrávání tedy
není nutné znova zpracovávat video.
Na druhou stranu by se určitě hodila i možnost revize tohoto zdroje —
podobně to bylo na robotu Eduro, kdy např. při
sbírání puků detektor posílal pozice puků
v obraze. Jelikož se nezpracovává každý snímek (zatím to nestíhám), ale
ukládají se všechny, tak budu potřebovat nějakou značku, který snímek to byl.
Pokud si dobře vzpomínám, tak PaVE video hlavička obsahovala i časovou
známku, tak tu tam budu muset do výsledku analýzy snímku přidat. Log momentálně
vypadá takto (src_h264_131227_214401.log):
0 (0, 0, 0, 0) 1 (56, 54, 62, 48) 1 (310, 427, 365, 372) 1 (130, 159, 116, 173) 6 (425, 470, 427, 468) 33 (194, 165, 150, 209) 29 (172, 169, 140, 201) 29
Na začátku jsou jedničky, protože zpracování obrazu naběhne o trošku dříve, než
samotná řídící smyčka (to by mimochodem řešili ty dataSource, které jsem
tam přidal a zatím zase vyhodil). Jakému reálnému času odpovídá číslo 33 nevím,
chyba, ale přehráním logu a výpisem drone.time se to dozvím:
… AT*CONFIG=%i,"video:video_codec","130" QUEUE 1218.419403 (0, 0, 0, 0) QUEUE 1218.429443 (56, 54, 62, 48) QUEUE 1218.439361 (310, 427, 365, 372) QUEUE 1218.449401 (130, 159, 116, 173) QUEUE 1218.51004 (425, 470, 427, 468) QUEUE 1218.841949 (194, 165, 150, 209) QUEUE 1219.133605 (172, 169, 140, 201) Landing … (0.0028891020142788417, -0.019957041168367, 0.0) …
vypadá to na cca 0.33s (1218.84 - 1218.51), tj. 3 snímky za sekundu. Toto zatím
běželo v „normálním” Pythonu. Udělal jsem ještě
drobné
přesuny, abych to mohl celé pustit pod PYPY, tak uvidíme.
V současnosti vím o následujících problémech:
- jak už se počítá, tak spojení chvílema zadrhává a PaVE značka nemusí být na začátku paketu (zmiňovaný zakomentovaný assert).
- pracuji s nahrávaným videem, které bude mít oproti realitě nějaké zpoždění. Bude třeba přejít na jiný kanál a nahrávání vypnout (v H264 kodeku se posílá pouze jeden kanál, video procesor toho více nestíhá).
29. prosinec 2013 — Let H-264-00
Pokud si myslíte, že něco bude fungovat hned na poprvé, tak jste ještě hodně
naivní . Že půjde vše hladce jsem nečekal, ale přeci jenom jsem byl klasický
programátor optimista. Tak zase pěkně chronologicky …
Včera jsem poprvé létal s novou Android aplikaci
AR.Free
Flight 2.4.10 s Flight
Recorder modulem. Moc jsem toho nenalétal, ale také jsem s Heidi
nehavaroval . GPS funkcionalitu jsem moc nepoužil — je pěkně skrytá v
pravém horním rohu. Nahrávání na USB disk fungovalo pěkně. Drona se občas
propadala i několik metrů, tak jsem se bál, že narazím do skály, ale konec
dobrý, všechno dobré.
Dnes jsem Android aplikaci zkoušel znova a narazil na tlačítko „store map”.
Dostanete se tak na Google maps a můžete si stáhnout některé mapové čtverce.
Nevím zda stažením dalšího okolí nutně ztrácíte původně uložené mapy, ale
minimálně to vypadá lépe něž klikání do šedivé plochy. Po zapnutí čtyřtulky se
ukáže GPS poloha včetně kruhu s očekávanou chybou. Lehce foukalo a tak byla
drona hned po startu trošku unášená. Podle GPS stopy „o tom věděla”, ale nic s
tím nedělala. Při druhém pokusu začal tablet zuřivě pípat, že dochází baterky,
a tak jsem vybalil notebook a konečně začal dělat to co jsem měl: test kódu s
H264.
Zkoušel jsem rovnou PYPY, ale ještě jsem raději neodkomentoval
drone.takeoff(). A to bylo dobře. Při prvním startu vyskočilo okno
firewallu. Při druhém startu neustále timeoutoval video kanál. Před tím jsem
ukládal video na USB disk, takže to asi dělal i dál. Vypojil jsem GPS modul a
znova firewall, ale pak už video data začala chodit.
Odkomentoval jsem start, drona vzlétla, ale na místě se moc nedržela. Větřík
udělal své, takže příkaz pro let vpřed ani nebyl potřeba. Druhý test vypadal
dost podobně. Třetí test jsem zkoušel ruční řízení a
ManualControlException Traceback (most recent call last): File "app_main.py", line 72, in run_toplevel File "h264drone.py", line 192, in <module> h264drone( replayLog=replayLog, metaLog=metaLog ) File "h264drone.py", line 148, in h264drone manualControl( drone ) NameError: global name 'manualControl' is not defined
a došlo na převracení čryřtulky ve vzduchu …
Logy zatím nic moc:
0 (0, 0, 0, 0) 1 (1, 0, 1, 0) 1 (7, 31, 0, 38) 1 (0, 1, 0, 1) 1 (135, 129, 3, 261) 1 (0, 0, 0, 0) 1 (1, 0, 1, 0) 1 (0, 0, 0, 0)
a vypadá to, že na rozdíl od normálního Pythonu PYPY skončí nějak divně …
30. prosinec 2013 — Let H-264-01
Dnes jen krátce. Asi bych začal video
záznamem letu H-264-01. Letěl jsem s
tímto
kódem (jen jsem ještě odmazal
zbytečný
update). V reálu jsem si pouze všiml několika výpadků spojení a následné
kolize. To, že se čtyřtulka otáčí kolem své osy jsem viděl až z
videozáznamu.
Přemýšlím, co si asi případný čtenář pomyslí, ale toto je ten důvod:
drone.moveXYZA( drone.speed, 0.0, 0.0, vz )
Místo aby se drona zvedla rychlostí vz, tak se tou rychlostí otáčela.
Poslední parametr totiž není Z, ale A (angle).
Motivací pro řízení výšky byl jediný nenulový snímek z letu H-264-00:
left, right, up, down … mimochodem, tento kód jsem nemohl při pygame
přehrávání najít a chvilku mi trvalo si uvědomit, že při prvních testech jsem
integroval vektory přes 3 snímky, ty ale teď nemám, takže používám jenom jeden snímek.
Let H-264-01 měl tedy vyzkoušet regulaci pohybu nahoru a dolu a zároveň lépe
logovat číslo snímku a timestamp.
… 52 (203L, 329399L, (0, 0, 0, 0)) 179 (217L, 329855L, (289, 290, 182, 397)) 75 (246L, 330801L, (0, 0, 0, 0)) …
… no moc pěkně to nevypadá :-(.
9. leden 2014 — Parrot MiniDrone
Byl jsem celkem zvědavý, s čím přijde Parrot na letošní CES, a je to
MiniDrone
a skákací robot (Kamile, díky za link ).
Podle fotky z MobilMania to vypadá, že navigace pomocí spodní kamery a sonaru
zůstává, ale zbytek je zatím záhadou. Místo WiFi se pro řízení používá
Bluetooth. Na hraní po místnosti to vypadá jako vhodnější hračka než velká
drona … no necháme se překvapit.
13. leden 2014 — Pohyblivý strom
O víkendu jsem zase udělal malinký krůček vpřed. Skoro mám pocit, jestli to s
tím logováním nepřeháním … na
githubu je vidět,
kdy jsem co kódoval (teď už se tam píše jen „2 days ago”, ale lze to zjistit
přesně) a z logů (meta_140112_144903.log .. meta_140112_151852.log), kdy jsem
s létáním začal a kdy skončil …
Vznikla nová třída
PaVE (samozřejmě
současně s
PaVETest). Je to
Parrotem používaná zkratka za „Parrot Video Encapsulation” a třída skládá
přišlá data po TCP do „obrázkových paketů” (začínajících právě značkou
„PaVE”). Byla to problémová část u předešlých letů, tak teď už je to snad
udělané lépe.
Byla to radost programovat — asi to není učebnicový postup, ale mne celkem
vyhovuje:
- dummy implementace, která skoro nic nedělá, ale definuje interface
- první verze, co řeší triviální příklady
- malý refactoring
- pořádný test na různých video souborech s různou délkou paketu
- integrace a nahrazení starého kódu
Vypnul jsem nahrávání videa a přešel na základní nebufferovaný video kanál.
Zdržení by tedy mělo být minimální, ale různé snímky vypadávají a kvalita
obrazu je o řád horší. Množství přenášených dat lze regulovat, ale s tím jsem
zatím neexperimentoval.
A co to v „letové sadě” H-264-02 dělalo? Úplně první pokus drona vzlétla
snad do osmi metrů a manuálně jsem ji musel donutit přistát. Sice se mi nechtělo
to moc opakovat, ale „jedno měření, žádné měření” a podruhé a potřetí
letěla podle očekávání. Heidi měla pouze regulovat výšku, na základě informací z
kamery, tak jsem na analýzu toho prvního letu celkem zvědav.
V další sérii mi skončila na stromě. Už nevím přesně co se dělo (mírně foukalo)
a pod dronou byl strom, takže přistát nebyla správá volba. No procvičil jsem si
v dalším letu „manuální řízení” a pak už se to neopakovalo.
A co ty „pohyblivé stromy”?? Ale … to byl jen takový nápad, jak simulovat
„les” jedním stromem. A jelikož jsem nechtěl bourat do těch mála co na
zahradě máme, tak jsem si vzal ulomenou větev (4m dlouhou?) a strom simuloval.
Asi bych potřeboval asistenta …
5. únor 2014 — Chodba
Jelikož počasí na testování venku není úplně ideální, tak jsem chtěl vyzkoušet
let chodbou na ČZU a nasbírat nějaká další testovací data. V idealistickém
případě by fungovala „navigace včelky”, tj. která strana by se pohybovala
více, ta je blíž a drona by se držela pryč od ní. Celkově tedy ve středu
koridoru. Na test nám zbylo posledních 5min a ani v jednom ze dvou pokusu
Isabele neodstartovala dostatečně bezpečně, abych jí nechal dál letět rovně.
Tak snad zase za týden …
TO BE CONTINUED
26. duben 2014 — Paralelní slalom
Skoro bych mohl začít slovy „bylo nebylo ...”. Je to hrozné, jak to utíká. V
pátek mne vyhecoval PetrS k dalším experimentům s H264 kodekem. Je víkend a
uvidíme se až v pondělí v práci, takže to je vlastně paralelní slalom, kdo
to posune dál .
A přesnější zadání úkolu? Mozaika . Když jsme připravovali Isabelle na
Robot Challenge 2014 (anglická verze),
tak jsme k navigaci používali obrázky ze spodní kamery. Nakonec jsme létali s
nahrávaným video kanálem, který používá H264 kodek (ano, jedna ze „skrytých
motivací”). Detaily si najděte v angličtině, ale ve zkratce jsme používali
jenom I-frame obrázky, které chodily jednou za sekundu, a 14 P-frame snímků
zahazovali. K věci … ty snímky lze pak poskládat do mozaiky jako:
Tuto jsem dorovnával ručně (základní posun a natočení byl podle interní pozice
čtyřtulky). V OpenCV2 se mi nepodařilo najít dostatečně spolehlivé funkce
(možná bude mít někdo nějaké doporučení?) pro korelaci snímků úpravu posunu o
pár pixelů. Po pravdě i to otočení by se trošku hodilo, ale kompas byl v tomto
případě skoro dostatečný.
A teď to H264. To dělá korelaci makrobloků, není-liž pravda? A ty obsahují
pixelové posuny. Nestačilo by tedy jenom vybrat nějaký globální/převažující
posun celého snímku a bylo by to hotové? Tu 1/15 bych „už skočil” na
základě extrapolace z 14/15. Co myslíte, má to smysl zkusit?
Pokud chcete skriptík, co skládá snímky do jednoho na základě CSV souboru, tak
ten naleznete zde
(ano, další repository různých OpenCV drobností).
Už jsem za ty čtyři měcíce (alespoň to říkal github o posledním commitu) dost
věcí zapomněl. Mám video. To si můžu přehrát a uložit jednotlivé snímky pomocí
airrace.py.
Zároveň ho můžu rozložit na jednotlivé frame pomocí utilitky
video.py.
A pak konečně
h264.py
vrací posuny jednotlivých makrobloků.
První problém je, že video video_rec_140318_200101.bin má rozlišení
640X360. Když to podělíte 16, tak dostanete 22.5. Hmm. Ono tato informace by
měla jít číst z SPS, ale ta je přibalovaná pouze k I-frame, který pro test
jednotlivého snímku nemáme. Tj. první hack v h264.py:
WIDTH = 40 #80 # for the first experiments hard-coded (otherwise available in SPS) HEIGHT = 23 #45
Kupodivu pak už to nepadá a vrací pole čtveřic (souřadnice makrobloku a jeho
posun).
Tak to PetrSovi moc nezávidím. Sám se v tom „trošku” ztrácím. Nejprve jsem
řešil, že funkce h264.parseFrame() nemá jako parametr jméno souboru, ale už
přímo binární data (a dekódovala název jako by to byla video data). Pak
skutečnost, že místo pole občas vrátí None … hmm … už si vzpomínám. Ono
to nebylo 100% funkční, tj. někdy můj dekodér může selhat. Zde je seznam
vadných snímků:
26943 26946 2 frame0003.bin 16374 16376 6 frame0124.bin 17644 17646 7 frame0143.bin 15616 15618 3 frame0241.bin 15355 15357 3 frame0657.bin 16311 16313 6 frame0726.bin 15439 15441 3 frame0760.bin 16149 16151 5 frame0766.bin 18554 18556 4 frame0893.bin 23682 23684 1 frame1486.bin
tj. 10 chyb v 1566 snímcích … chyba 0.6% a další příklady k revizi.
2. červen 2014 — h264show.py
Jak dopadl „paralelní slalom” více jak před měsícem? Bídně. Jeden závodník
zakopl hned o první branku a druhý nevyjel vůbec …
… ale to ještě neznamená konec . V rámci příprav na
FRE14 jsem si rozchodil dekódování i
P-frame a pak už nic nebránilo tomu, abych si mohl přehrávat video a do něj
rovnou dokreslovat vektory posunů jednotlivých makrobloků. Zase je to spíše
alfa verze, protože parseFrame by asi měl mít na vstupu velikost snímku a
pod. Jinak mi
h264show.py
přijde poměrně jednoduché.
A výsledky? Zkoušel jsem to na datech z
AirRace a opravdu tam jsou nějaké divné
vektory:
27. říjen 2014 — first-person-drone-racing
V rámci hledání informací pro minidrone Jessica jsem
narazil na tento blog a video:
first-person-drone-racing
… tak nějak bych si představoval záverečnou prezentaci
5. březen 2015 — Katarina a parsingSPS
Do čeho jsem se to probůh před lety pustil?! Mám teď zase motivaci to
posunout o kousek dál, protože Katarina má kompenzaci
náklonu a zároveň posílá video data v H.264 kodeku. Už mne přestalo bavit stále
přepisovat „konstanty” počtu makrobloků na začátku
h264.py
a … ono to nebylo zas tak jednoduché (viz
diff).
Ano, první překážka byla moje děravá paměť a pak chvíle uhranutí, že to přeci
nemohlo být tak strašně složité, když chci přečíst jenom rozměr obrázku?? Pro
inspiraci viz
nápověda
na Stack Overflow … no není to tak triviální. Za zmínku možná stojí i to,
že ten popis je jenom začátek — Katarina z nějakého důvodu posíla i časování,
ktere je schované v vui_parameters a má tedy bit vui_parameters_present_flag
nastavený. Jinak jsem to stále psal „cestou nejmenšího odporu”, tj. pokud se
podle daného bitu větvilo další čtení, tak jsem přidal assert, jestli ten kus
musím opravdu rozepisovat … a občas jsem musel.
A proč jsem tohoto „kostlivce” zase vytáhl ze skříně? No vedle toho, že mi ho
bylo líto bych znova rád zkusil průlet mezi překážkami: jednak školní chodbou a
dále v sadu na školním pozemku … a něco o tom sepsat.
8. březen 2015 — ARDrone3 vs. ARDrone2
Že bude trošku rozdíl mezi tím, co posílá Katarina a co
posíla Heidi jsem tušil. Ale zároveň jsem si naivně myslel,
že možná bude stačit jenom upravit velikost vstupního obrázku. Nestačí.
První „bota” byla celkem jednoduchá:
bs.bits( 14, info="frame_num" ) # HACK! should use parameter from SPS
tj. měl jsem tam natvrdo, že počet bitů na kódování pořadí snímku je 14. Na
Katarině jich je 16. To bych nezjistil, kdybych nepoužil už osvědčené nástroje,
s TRACE define na 2, hledání výstupního souboru (naštěstí toto jsem si
dříve zapsal), pak vzpomínka, že
jednotlivé snímky jsou proházené (nejprve se hledá konec snímku a teprve pak se
parsuje vnitřek) … a bylo to.
Oprava byla triviální, protože jsem přestal parsovat snímky jednotlivě a SPS
jsem řešil před pár dny. Tj. další nastavitelná „globální konstanta”.
Pak ale začalo přituhovat. Narazil jsem na mb_type 2, pak 3, pak 1 …
jenom 4 jsem zatím neviděl, resp. nově přidaný assert zatím nevyletěl.
Korunu tomu nasadila pětka! O čem mluvím? ARDrone3 už nepoužívá pouze 16x16
makrobloky, ale i jemnější dělení:
@453524 mb_type 010 ( 1) @453527 mvd_l0 011 ( -1) @453530 mvd_l0 1 ( 0) @453531 mvd_l0 010 ( 1) @453534 mvd_l0 1 ( 0) @452700 mb_type 011 ( 2) @452703 mvd_l0 1 ( 0) @452704 mvd_l0 1 ( 0) @452705 mvd_l0 1 ( 0) @452706 mvd_l0 011 ( -1) @451752 mb_type 00100 ( 3) @451757 sub_mb_type 1 ( 0) @451758 sub_mb_type 1 ( 0) @451759 sub_mb_type 1 ( 0) @451760 sub_mb_type 1 ( 0) @451761 mvd_l0 1 ( 0) @451762 mvd_l0 1 ( 0) @451763 mvd_l0 010 ( 1) @451766 mvd_l0 1 ( 0) @451767 mvd_l0 1 ( 0) @451768 mvd_l0 1 ( 0) @451769 mvd_l0 1 ( 0) @451770 mvd_l0 1 ( 0) @455469 mb_type 00100 ( 3) @455474 sub_mb_type 1 ( 0) @455475 sub_mb_type 1 ( 0) @455476 sub_mb_type 1 ( 0) @455477 sub_mb_type 010 ( 1) @455480 mvd_l0 011 ( -1) @455483 mvd_l0 010 ( 1) @455486 mvd_l0 010 ( 1) @455489 mvd_l0 011 ( -1) @455492 mvd_l0 1 ( 0) @455493 mvd_l0 1 ( 0) @455494 mvd_l0 010 ( 1) @455497 mvd_l0 011 ( -1) @455500 mvd_l0 1 ( 0) @455501 mvd_l0 1 ( 0)
Jsou to bloky 16x8, 8x16 a i 8x8. Navíc počet posunů mvd_l0 závisí na
hodnotě sub_mb_type. Jak se počítají v tomto případě „globální 16x16”
offsety zatím netuším, ale to je až druhá fáze.
Konečně k té pětce — na to budete potřebovat odkaz do dokumentace,
strana 156, tabulka 9-4. Je tam speciální převodní tabulka pro
coded_block_pattern, ale zajímat vás bude tentokrát sloupec
Intra_4x4.
Závěr: ARDrone3 má výkonnější procesory a tak si asi může dovolit lepší
kompresi videa. Trošku jsem ohackoval i
h264show.py,
protože ARDrone3 PaVE bloky nepoužívá. Výstupny jsou zatím „divné”, ale
doufám, že je to mojí současnou ignorací posunů nových sub-makrobloků. Pro
zájemce viz
diff.
11. březen 2015 — mikro-pozorování
Ještě jsem ty menší makrobloky nevyřešil, ale jedno malinké pozorování bych měl
… a dává to smysl. Ty TRACE výpisy jsou totiž zavádějící —
mvd_l0 asi ve skutečnosti popisuje jak x, tak y posun. Pak dává
smysl, že při rozdělení makrobloku 16x16 na 2x 16x8 budu mít čtyři mvd_l0
posuny (dva vektory) a pro rozdělení 4x 8x8 těch vektorů bude osm (čtyři
vektory posunu). Stejně tak to zapadá do bitového patternu, který se dělá po
4x4 blocích … jen tam musím nějak ty „lokální” posuny integrovat. Mimo jiné
to znamená, že posun makrobloku není jedno číslo, ale obecně hned několik. A
konečně ignoruji detail, že Luma" a Chroma"" si jdou každá svoji cestou a
je třeba se tedy o každou starat zvlášť.
Program je teď ohackovaný pro ladění zmiňovaných nových makrobloků
(diff).
Opraveny jsou alespoň ty znaménkové Golomby. Možná jsem si měl udělat
„pracovní větev” než to bude fungovat alespoň jako předešlý kód … příště.
12. březen 2015 — ARDrone3 - první řada
Teď si naivně myslím, že už zase umím parsovat alespoň první řadu P-slice,
který může obsahovat vedle 16x16 makrobloků (mb_type=0) i 16x8, 8x16 a 8x8 (u
rozdělení 8x8 na menší 8x4, 4x8 a 4x4 si stále nejsem jistý, resp. tam jsem si
ještě stále o řád nejistější ). Narazil jsem v náhodně vyhledaném článku
Direct
macroblock coding for predictive (P) pictures in the H.264 standard na tento
obrázek:
Ono já mám i skoro pocit, že když tady odstránkuji výše, tak narazím na něco
podobného (no, nevidím to). Že je aktuální posun makrobloku kombinovaný z
levého, horního a pravo-horního posunu vím (tj. A, B, C — D se snad používá
pouze pro B-slice(?)). Co mne ale zaujalo byly pozice v rámci 16x16 bloků.
Pro A tedy vpravo nahoře a pro B a C vlevo dole. Asi bych si měl ten článek
pořádně přečíst a dozvěděl bych se více, ale … já to zatím jenom zkusil s tou
levou predikcí a ta na mém prvním příkladu funguje (tedy funguje i na dalších
pár prvních řadách co jsem prošel).
Co mi ale zatím nefunguje je druhá a další řady (mimo jiné už tam se nachází
jemnější dělení 8x8 bloku). Do gitu to zatím dávat nebudu. Pokud nějaký čtenář
tuší o čem mluvím a zná řešení hádanky, tak si klidně nechám napovědět . Je
toho teď nějak hodně a nechci tento projekt zanechat na „roky” v nefunkčním
stavu (uvědomil jsem si, že dříve jsem důsledně psal unit-testy na špatně
rozkódované makrobloky — je na čase se k tomu zase vrátit!).