本篇是該系列的第四篇,承接前篇的文件頭解析,主要介紹霍夫曼解碼相關內容。
承接上篇,文件頭解析完畢后,就進入了編碼數據區域,即SOS的tag后的區域,也是圖片數據量的大頭所在。
1. 待處理的數據區域
一個例子來說明,仍使用那張animal_park.jpg的圖片。
其二進制數據顯示如下(FFDA所代表的SOS之后深色標注區域):
截取到的二進制數據為:F9 96 8B FA 71 EA 5B 24 B5 ...
2. 解碼過程規則描述
a)從此顏色分量單元數據流的起點開始一位一位的讀入,直到讀入的編碼與該分量直流哈夫曼樹的某個碼字(葉子結點)一致,然后用直流哈夫曼樹
查得該碼字對應的權值。權值(共8位)表示該直流分量數值的二進制位數,也就是接下來需要讀入的位數。
b)繼續讀入位數據,直到讀入的編碼與該分量交流哈夫曼樹的某個碼字(葉子結點)一致,然后用交流哈夫曼樹查得該碼字對應的權值。權值的高4位
表示當前數值前面有多少個連續的零,低4 位表示該交流分量數值的二進制位數,也就是接下來需要讀入的位數。
c)不斷重復步驟b,直到滿足交流分量數據結束的條件。
而結束條件有兩個,只要滿足其中一個即可:
①當讀入碼字的權值為零,表示往后的交流變量全部為零;
②已經讀入63個交流分量。
3. 准備工作——霍夫曼表
在解析文件頭時,會得到四張霍夫曼表——DC0,AC0, DC1,AC1,待后面解碼時使用。
DC0——Y分量的直流部分
AC0——Y分量的交流部分(表太長,沒列全)
DC1——UV分量的直流部分
AC1——UV分量的交流部分
4. 解碼步驟
這是難點所在,解碼的過程其實就是霍夫曼樹的查找過程。mcu單元內部使用了RLE行程編碼和霍夫曼編碼來壓縮數據。
此時,正需要一個例子來實戰說明,因為理論是對實踐的抽象。
例子:F9 96 8B FA 71 EA 5B 24 B5。。。
對應的二進制位展開:1111 1001, 1001 0110, 1000 1011, 1111 1010, 0111 0001, 1110 1010, 0101 1011, 0010 0100, 1011 0101。。。
step1. 先讀入若干位與DC0表的Code進行匹配。
讀取2位的11時, 無匹配的Code,因為2位寬的Code只有0b00和0b01
3位的111 無 3 0b100,0b101和0b110。
4位的1111 無 4 0b1110。
5位的11111 無 5 0b11110。
6位的111110 有 6 0b111110,恰好匹配!其對應的CodeVal為0x7
step2. 利用上面得到的CodeVal進行拆分,並讀取后面若干位。
0x7=0x07,高四位為0,低四位為7,則再讀取后面的7位二進制,為:01, 1001 0。
后面讀取的值,這樣算:如果開頭為1則為正數,如果開頭為0,則為負數,然后對各位求反得到數值,即可。
01, 1001 0這個值,由於開頭為0,則為負數,多少呢?取反得到:10, 01101 = 0x4D = 77,最后得到最終值為:-77。
step3. 通過上面兩步驟的第一次掃描,得到的為Y分量的DC值,后面還需經過63次掃描得到剩余的AC值(一般掃描幾次就結束了)。
上面DC值標記為-77。
step4. 繼續通過類似step1和step2來取得AC值,注意要查找AC0表。
讀取5位的110, 10時,有匹配的Code:0b11010=0x1a,其對應的CodeVal=0x04;
取得后四位的值——4,表示還需讀取的二進制位數量,來表示真正的信源值——0b0010,經(step2中描述)變換后值為-13;
那么可以RLE標記為(0,-13),其中0來自於CodeVal的高4位,-13為另讀入的數據值。可也記為key-val對。
step5. 重復step4的操作,直到得到(0,0)(位置為5B那個字節的最高四位)。
后面的依次為:
Code CodeVal RLE_val RLE
11, 1111 1010(0x3FA) 0x34 0111(-8) (3, -8)
00 0x1 0 (-1) (0, -1)
1, 1110 10(0x7A) 0x71 1(1) (7, 1)
0, 0 0x1 1(1) (0, 1)
01 0x0 -- (0, 0) -> 結束於5B的高4位
為更直觀的介紹解析結果,將二進制位數據進行划分,表示如下:
未標記的hex表示: F9 96 8B FA 71 EA 5B 24 B5。。。
未標記的binary表示:1111 1001, 1001 0110, 1000 1011, 1111 1010, 0111 0001, 1110 1010, 0101 1011, 0010 0100, 1011 0101。。。
標記后的binary表示:1111 1001, 1001 0110, 1000 1011, 1111 1010, 0111 0001, 1110 1010, 0101 1011, 0010 0100, 1011 0101。。。
紅色表示為查表得到的Code,藍色表示RLE_val(其二進制位長度為CodecVal后四位值)。
step6. 通過step1-step5的掃描,得到RLE表:-77, (0, -13), (3, -8),(0, -1),(7, 1), (0, 1), (0, 0)
step7. step1到step6結束后,表示一個mcu的霍夫曼解碼結束,則可以進行8x8的矩陣展開。
RLE中的(m,n),m表示前面填充0的個數,n表示實際值。
根據RLE表,其霍夫曼解碼結果如下:
5. 一些思考總結
由此huffman decode后得到的表,可以看出其巨大優勢,僅僅使用了6Bytes+4bits就表示了8*8=64Bytes的數據量,體現了用時間換空間的
特點,即增加更多計算工作,來減少數據存儲空間。
同時,還需要注意以下幾點:
a). 接下來根據Y分量采樣因子來確定后續是Y分量的表還是UV分量的表。
如果Y/U/V采樣因子都為1x1,則接下來依次得到U和V分量的表,再后面繼續為Y分量的表;
如果Y分量采樣因子為2x2,即最常見的YUV420格式(每2x2個像素點,進行2x2個Y分量采樣,以及1x1個U和V分量采樣),則當前的已得到
的表為Y00,后續的表為Y01,Y10和Y11,然后才能是U分量的表和V分量的表。
因為采樣是按照block為原子單位展開,如下圖所示(黑色點表示一個block):
這里需要補充幾個名詞:
block——8x8的像素采樣表。
MCU——根據采樣因子確定的一個完整編碼單元。
針對YUV420的2x2的Y分量采樣率,其關系如下圖:
而如果是1x1的采樣率,則一個MCU內只包含一個Y分量的block,一個U分量的block和一個V分量的block,圖示就不畫了。
例如,該圖片因為2x2的采樣率,則一個MCU的大小位16x16,其包括四個Y分量的block,一個U分量的block和一個V分量的block。
b). 由於DPCM(diff pcm)編碼原因,第N個Y分量的DC值保存的是與前面一個的差值。取得第N個值時,掃描出的值再加上前一個值(跨MCU時仍成立)。
接下來的1011, 0010 0100, 1011 0101用於確定Y01的表,根據同樣的規則,查表分割如下:
1011, 0010 0100, 1011 0101
得到RLE序列為:9, (0, 1), (0, 1), (0, 0)
經DPCM修正為:9+(-77)=-68, (0, 1), (0, 1), (0, 0)
如下為重建該block后的表:
c). 由於編碼時某些場景會插入某些字節,因此掃描時要跳過某些字節。