czech english

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.


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:
  1. zvětší se navdata paket, takže pokud čtete např. pevných 2048 bajtů, vyletí výjimka (to bylo snadné opravit)
  2. 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
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ů
Integrace pohybu ze 30ti snímků
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
Malé otočení
Malé otočení
je vidět jak jsou skoro všechny posuny stejné a čtyřtulka se otáčí na místě.
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:
Překážky z pohybu
Překážky z pohybu
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.
Ještě minimálně jedna situace je třeba vzít v úvahu a to, když se Heidi naklání:
Náklon
Náklon
Zde je vidět pěkný „vír”, přestože makrobloky povolují pouze posun a nikoliv otáčení.
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 ...
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:
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!).