【原創】這次更新比較慢,譯碼過程比想象中復雜一些,更主要是譯出來的DCT系數無法確定是否正確,要想驗證就需要再進行正向壓縮編碼,再次形成jpeg圖像驗證正確,后續工作正在開展,這里就說一說譯碼的主要思路和過程。
說到譯碼過程,首先要了解jpeg圖像數據流的組成:
數據流是以MCU(最小編碼單元)為基本單位的,一個MCU又由若干個Y,Cr,Cb顏色分量單元組成,這里的顏色分量單元可以看作是一個8*8的矩陣,也就是譯碼過程中的最小單元了,可能讀者對於這些結構的關系還不是很了解,OK,我們來看看下面這個圖(word繪制,簡潔易懂~)

一個MCU中的顏色分量單元個數由jpeg文件頭中SOF0段中定義,SOF0段中有定義每個顏色分量的水平和垂直采樣因子,這里假設水平,垂直采樣因子分別為2:1:1,2:1:1。這個采樣因子是什么意思呢,我們來看看上圖。假設上圖是一個16*16(像素)的圖片,可以分成四個8*8的小矩陣,每個8*8小矩陣都是一個顏色分量單元,Y分量的水平采樣因子為2則說明在一個MCU的水平方向上采樣兩次,垂直采樣因子同理,因此在本例中一個MCU會有4個Y顏色分量單元,1個Cr顏色分量單元,1個Cb顏色分量單元。從這里我們也能得知一個MCU的大小是由水平、垂直采樣因子的最大值決定的,假設分別為V_max和H_max,則MCU的大小為(V_max*8)*(H_max*8)。這些結構的關系如下:
顏色分量單元組成MCU,MCU組成數據流
現在再來看看圖片中含有多個MCU的情況,假設有四個,允許我再盜用一下上面的圖哈,

可以看到,一幅圖像里MCU是按行排列的,在數據流中保存的順序就是MCU1、MCU2、MCU3、MCU4。。。。每個MCU中的順序為Y1、Y2、Y3、Y4、Cr、Cb(這里是按采樣因子4:1:1,其他也類似,總之就是按Y、Cr、Cb的順序)。
了解了數據流的組成,下面就要開始譯碼工作了,這也是個煩雜的過程。。。。
在JPEG壓縮過程中,不僅使用了哈夫曼編碼,還使用了RLE行程編碼和差分編碼,真是極盡壓縮啊。。。這三種編碼在JPEG中是綜合運用的,可以達到很好的壓縮效果。本人這里就從解碼的角度來進行介紹了,個人覺得從解碼角度來理解更為方便~
首先,JPEG中直流和交流系數是分開進行編碼的,也就是說譯碼的過程有些許不同,而且采用的哈夫曼樹也是不同的,其次,JPEG中的每個顏色分量也是分開進行編碼的,不同顏色分量采用的哈夫曼樹也是不同的,具體的每個顏色分量的交直流系數采用的哈夫曼樹ID可以從文件頭的SOF0段中得知,SOF0段的內容可以看我的另一篇博文
http://www.cnblogs.com/gungnir2011/p/3615273.html
在數據流中是以bit為單位存儲信息的,每個DCT系數(8*8)矩陣都有1個直流分量和63個交流分量,每個矩陣的譯碼都先譯直流分量,再譯交流分量,具體的步驟如下:
1.按bit讀取,對直流哈夫曼樹進行搜索,直到搜索到葉子節點,說明這時命中了一個編碼,哈夫曼樹的權值代表還需繼續讀取多少bit,比如權值為0x04,則說明繼續讀取4bit,這4bit的值根據譯碼表(后面會給出)獲取的值就是直流DCT系數。
2.繼續按bit讀取,這時是對交流哈夫曼樹進行搜索,直到葉子節點,這里的權值和直流不一樣,權值的高四位代表即將譯出的交流DCT系數前面有多少個0,這里是行程編碼,低四位代表還需繼續讀取多少bit,之后獲得系數和直流一樣。
3.不斷重復步驟2,直到滿足結束條件:
- 讀到的權值為0,則該矩陣中之后的所有值都為0,並結束該矩陣的譯碼;
- 63個交流分量都已全部譯完。
譯碼表:
| 編碼數值 | 實際數值 |
| 0,1 | -1,1 |
| 00,01,10,11 | -3,-2,2,3 |
| 000,001,010,011,100,101,110,111 | -7,-6,-5,-4,4,5,6,7 |
| ................. | ...................... |
之后就是譯完一個矩陣譯下一個,譯完一個顏色分量譯下一個,直到所有譯完~~
當然這只是理論,在實踐過程中會遇到各種奇葩問題,下次更新中會詳細說說我遇到的各種奇葩錯誤,現在還未完全寫完譯碼部分,為了進度現在直接在研究libjpeg開源庫,之后可能會找個時間把譯碼做完,未完待續~~
---------------------------------------------------分割線-------------------------------------------------------------
現在來說說我遇到的各種奇葩問題:
首先,關於權值問題,剛開始譯碼的時候沒有考慮到交流權值的低四位和直流權值為0的情況,運行自然就掛掉了。。。遇到這種情況,就說明繼續讀取0bit,0bit的值就是0,所以遇到這種情況得到的系數值就應該是0。
其次,有些8*8的矩陣譯碼有時候會出現譯出第65個值,按道理來說應該只有64個值的,這種情況我還沒有弄清楚是什么原因,但是在沒有出現這種情況以前譯碼的結果都是對的,當出現這種情況以后大概再過幾個矩陣之后所有的譯碼結果基本上都不對。。。具體原因我會繼續分析,因此在這里再次推薦使用庫函數進行操作。
再來說說一些注意事項和建議吧:
- 記錄當前讀取到第幾個字節的第幾位時,記錄位數一定要計算好,一位出錯,之后全錯。。。像一些系數譯出來要退出,以及其他退出情況時,退出之前要將位數往后移;
- 對於繼續讀取的位數為0的情況最好單獨進行處理,邏輯也容易清晰一些;
- 讀取出來的二進制要根據上述譯碼表進行譯碼,不能直接轉換,否則結果不對;
其他就是一些編程時需要注意的小問題了,就不一一敘說了。
