JPEG檔案編/解碼詳解 .
JPEG(Joint Photographic Experts Group)是聯合影像專家小組的英文縮寫。它由國際電話與電報諮詢委員會CCITT(The International Telegraph and Telephone Consultative Committee)與國際標準化組織ISO於1986年聯合成立的一個小組,負責制定靜態數字影像的編碼標準。
小組一直致力於標準化工作,開發研製出連續色調、多級灰度、靜止影像的數字影像壓縮編碼方法,即JPEG演算法。JPEG演算法被確定為國際通用標準,其適用範圍廣泛,除用於靜態影像編碼外,還推廣到電檢視像序列的幀內影像壓縮。而用JPEG演算法壓縮出來的靜態圖片檔案稱為JPEG檔案,副檔名通常為*.jpg、*.jpe*.jpeg。
JPEG專家組開發了兩種基本的壓縮演算法、兩種資料編碼方法、四種編碼模式。具體如下:
壓縮演算法:
l 有損的離散餘弦變換(Discrete Cosine Transform,DCT);
l 無損的預測技術壓縮。
資料編碼方法:
l 哈夫曼編碼;
l 算術編碼;
編碼模式:
l 基於DCT順序模式:編/解碼通過一次掃描完成;
l 基於DCT遞進模式:編/解碼需要多次掃描完成,掃描效果從粗糙到精細,逐級遞進;
l 無損模式:基於DPCM,保證解碼後完全精確恢復到原影像取樣值;
l 層次模式:影像在多個空間多種解析度進行編碼,可以根據需要只對低解析度資料作解碼,放棄高解析度資訊。
在實際應用中,JPEG影像使用的是離散餘弦變換、哈夫曼編碼、順序模式。
JPEG壓縮編碼演算法的主要計算步驟如下:
(0) 8*8分塊。
(1) 正向離散餘弦變換(FDCT)。
(2) 量化(quantization)。
(3) Z字形編碼(zigzag scan)。
(4) 使用差分脈衝編碼調製(DPCM)對直流係數(DC)進行編碼。
(5) 使用行程長度編碼(RLE)對交流係數(AC)進行編碼。
(6) 熵編碼。
筆者在實踐過程中查閱了大量的資料,發現大多數書籍資料和網上資料都是從編碼角度分析JPEG的編/解碼方式,並且都只是介紹編碼過程中的主要方法。所以,本文從解碼角度詳細分析JPEG的編/解碼過程,並且加入許多筆者實踐過程中遇到的問題和解決方法,希望從另一個角度說明問題,以更好幫助讀者結合其他資料解決問題。
不過,介紹解碼過程之前,首先要了解JPEG檔案中資料的儲存格式。
一、JPEG檔案格式介紹
JPEG檔案使用的資料儲存方式有多種。最常用的格式稱為JPEG檔案交換格式(JPEG File Interchange Format,JFIF)。而JPEG檔案大體上可以分成兩個部分:標記碼(Tag)和壓縮資料。
標記碼由兩個位元組構成,其前一個位元組是固定值0xFF,後一個位元組則根據不同意義有不同數值。在每個標記碼之前還可以新增數目不限的無意義的0xFF填充,也就說連續的多個0xFF可以被理解為一個0xFF,並表示一個標記碼的開始。而在一個完整的兩位元組的標記碼後,就是該標記碼對應的壓縮資料流,記錄了關於檔案的諸種資訊。
常用的標記有SOI、APP0、DQT、SOF0、DHT、DRI、SOS、EOI。
注意,SOI等都是標記的名稱。在檔案中,標記碼是以標記程式碼形式出現。例如SOI的標記程式碼為0xFFD8,即在JPEG檔案中的如果出現資料0xFFD8,則表示此處為一個SOI標記。
本文附錄列出一張完整的JPEG定義的標記表,供讀者查閱。這裡僅列出幾個常用標記的標記程式碼、佔用位元組長度和表示的意義。
u 標記程式碼 2位元組 固定值0xFFD8
u 標記程式碼 2位元組 固定值0xFFE0
u
包含9個具體欄位:
①
資料長度
2位元組
①~⑨9個欄位的總長度
即不包括標記程式碼,但包括本欄位
②
識別符號
5位元組
固定值0x4A46494600,即字串“JFIF0”
③
版本號
2位元組
一般是0x0102,表示JFIF的版本號1.2
可能會有其他數值代表其他版本
④ X和Y的密度單位
1位元組
只有三個值可選
0:無單位;1:點數/英寸;2:點數/釐米
⑤ X方向畫素密度
2位元組
取值範圍未知
⑥ Y方向畫素密度
2位元組
取值範圍未知
⑦
縮圖水平畫素數目
1位元組
取值範圍未知
⑧
縮圖垂直畫素數目
1位元組
取值範圍未知
⑨
縮圖RGB點陣圖
長度可能是3的倍數
縮圖RGB點陣圖資料
本標記段可以包含影像的一個微縮版本,存為24位的RGB畫素。如果沒有微縮影像(這種情況更常見),則欄位⑦“縮圖水平畫素數目”和欄位⑧“縮圖垂直畫素數目”的值均為0。
l APPn,Application,應用程式保留標記n,其中n=1~15(任選)
u 標記程式碼 2位元組 固定值0xFFE1~0xFFF
u
包含2個具體欄位:
①
資料長度
2位元組
①~②2個欄位的總長度
即不包括標記程式碼,但包括本欄位
② 詳細資訊
資料長度-2位元組
內容不定
例如,Adobe Photoshop生成的JPEG影像中就用了APP1和APP13兩個標記段分別儲存了一幅影像的副本。
l DQT,Define Quantization Table,定義量化表
u 標記程式碼 2位元組 固定值0xFFDB
u
包含9個具體欄位:
①
資料長度
2位元組
欄位①和多個欄位②的總長度
即不包括標記程式碼,但包括本欄位
② 量化表
資料長度-2位元組
a)
精度及量化表ID
1位元組
高4位:精度,只有兩個可選值
0:8位;1:16位
低4位:量化表ID,取值範圍為0~3
b)
表項
(64×(精度+1))位元組
例如8位精度的量化表
其表項長度為64×(0+1)=64位元組
本標記段中,欄位②可以重複出現,表示多個量化表,但最多隻能出現4次。
u 標記程式碼 2位元組 固定值0xFFC0
u
包含9個具體欄位:
①
資料長度
2位元組
①~⑥六個欄位的總長度
即不包括標記程式碼,但包括本欄位
②
精度
1位元組
每個資料樣本的位數
通常是8位,一般軟體都不支援
12位和16位
③
影像高度
2位元組
影像高度(單位:畫素),如果不支援 DNL
就必須 >0
④
影像寬度
2位元組
影像寬度(單位:畫素),如果不支援 DNL
就必須 >0
⑤
顏色分量數
1位元組
只有3個數值可選
1:灰度圖;3:YCrCb或YIQ;4:CMYK
而JFIF中使用YCrCb,故這裡顏色分量數恆為3
⑥顏色分量資訊
顏色分量數×3位元組(通常為9位元組)
a) 顏色分量ID 1位元組
b)
水平/垂直取樣因子
1位元組
高4位:水平取樣因子
低4位:垂直取樣因子
(曾經看到某資料把這兩者調轉了)
c)
量化表
1位元組
當前分量使用的量化表的ID
本標記段中,欄位⑥應該重複出現,有多少個顏色分量(欄位⑤),就出現多少次(一般為3次)。
l DHT,Difine Huffman Table,定義哈夫曼表
u 標記程式碼 2位元組 固定值0xFFC4
u
包含2個具體欄位:
①資料長度
2位元組
欄位①和多個欄位②的總長度
即不包括標記程式碼,但包括本欄位
② 哈夫曼表
資料長度-2位元組
a)
表ID和表型別
1位元組
高4位:型別,只有兩個值可選
0:DC直流;1:AC交流
低4位:哈夫曼表ID,
注意,DC表和AC表分開編碼
b) 不同位數的碼字數量 16位元組
c)
編碼內容
16個不同位數的碼字數量之和(位元組)
本標記段中,欄位②可以重複出現(一般4次),也可以致出現1次。例如,Adobe Photoshop 生成的JPEG圖片檔案中只有1個DHT標記段,裡邊包含了4個哈夫曼表;而Macromedia Fireworks生成的JPEG圖片檔案則有4個DHT標記段,每個DHT標記段只有一個哈夫曼表。
l DRI,Define Restart Interval,定義差分編碼累計復位的間隔
u 標記程式碼 2位元組 固定值0xFFDD
u
包含2個具體欄位:
①資料長度
2位元組
固定值0x0004,①~②兩個欄位的總長度
即不包括標記程式碼,但包括本欄位
②MCU塊的單元中的重新開始間隔
2位元組
設其值為n,則表示每n個MCU塊就有一個
RSTn標記。第一個標記是RST0,第二個是
RST1等,RST7後再從RST0重複。
如果沒有本標記段,或間隔值為0時,就表示不存在重開始間隔和標記RST
l SOS,Start of Scan,掃描開始 12位元組
u 標記程式碼 2位元組 固定值0xFFDA
u
包含2個具體欄位:
①資料長度
2位元組
①~④兩個欄位的總長度
即不包括標記程式碼,但包括本欄位
②顏色分量數
1位元組
應該和SOF中的欄位⑤的值相同,即:
1:灰度圖是;3:
YCrCb或YIQ;4:CMYK。
而JFIF中使用YCrCb,故這裡顏色分量數恆為3
③顏色分量資訊
a) 顏色分量ID
1位元組
b) 直流/交流係數表號
1位元組
高4位:直流分量使用的哈夫曼樹編號
低4位:交流分量使用的哈夫曼樹編號
④
壓縮影像資料
a)譜選擇開始
1位元組
固定值0x00
b)譜選擇結束
1位元組
固定值0x3F
c)譜選擇
1位元組
在基本JPEG中總為00
本標記段中,欄位③應該重複出現,有多少個顏色分量(欄位②),就出現多少次(一般為3次)。本段結束後,緊接著就是真正的影像資訊了。影像資訊直至遇到一個標記程式碼就自動結束,一般就是以EOI標記表示結束。
u 標記程式碼 2位元組 固定值0xFFD9
這裡補充說明一下,由於在JPEG檔案中0xFF具有標誌性的意思,所以在壓縮資料流(真正的影像資訊)中出現0xFF,就需要作特別處理。具體方法是,在資料0xFF後新增一個沒有意義的0x00。換句話說,如果在影像資料流中遇到0xFF,應該檢測其緊接著的字元,如果是
1)0x00,則表示0xFF是影像流的組成部分,需要進行譯碼;
2)0xD9,則與0xFF組成標記EOI,則影像流結束,同時影像檔案結束;
3)0xD0~0xD7,則組成RSTn標記,則要忽視整個RSTn標記,即不對當前0xFF和緊接的0xDn兩個位元組進行譯碼,並按RST標記的規則調整譯碼變數;
3)0xFF,則忽視當前0xFF,對後一個0xFF再作判斷;
4)其他數值,則忽視當前0xFF,並保留緊接的此數值用於譯碼。
二、 JPEG解碼過程詳解
下面來詳細講述JPEG檔案的解碼過程。
1.讀入檔案的相關資訊
按照上述的JPEG檔案資料儲存方式,把要解碼的檔案的相關資訊一一讀出,為接下來的解碼工作做好準備。參考方法是,設計一系列的結構體對應各個標記,並儲存標記內表示的資訊。其中影像長寬、多個量化表和哈夫曼表、水平/垂直取樣因子等多項資訊比較重要。以下給出讀取過程中的兩個問題。
1)整個檔案的大體結構
JFIF格式的JPEG檔案(*.jpg)的一般順序為:
SOI(0xFFD8)
APP0(0xFFE0)
[APPn(0xFFEn)]可選
DQT(0xFFDB)
SOF0(0xFFC0)
DHT(0xFFC4)
SOS(0xFFDA)
壓縮資料
EOI(0xFFD9)
2)字的高低位問題
JPEG檔案格式中,一個字(16位)的儲存使用的是 Motorola 格式, 而不是 Intel 格式。也就是說, 一個字的高位元組(高8位)在資料流的前面, 低位元組(低8位)在資料流的後面,與平時習慣的Intel格式不一樣。.
3)讀出哈夫曼表資料
a)理論說明
在標記段DHT內,包含了一個或者多個的哈夫曼表。對於單一個哈夫曼表,應該包括了三部分:
l
哈夫曼表ID和表型別
這個位元組的值為一般只有四個0x00、0x01、0x10、0x11。
0x00表示DC直流0號表;
0x01表示DC直流1號表;
0x10表示AC交流0號表;
0x11表示AC交流1號表。
l 不同位數的碼字數量
JPEG檔案的哈夫曼編碼只能是1~16位。這個欄位的16個位元組分別表示1~16位的編碼碼字在哈夫曼樹中的個數。
l 編碼內容
這個欄位記錄了哈夫曼樹中各個葉子結點的權。所以,上一欄位(不同位數的碼字數量)的16個數值之和就應該是本欄位的長度,也就是哈夫曼樹中葉子結點個數。
b)舉例說明
以下面一段哈夫曼表資料舉例說明(資料全部以16進製表示):
11
00
02 02 00
05 01 06 01
00 00 00
00 00 00 00
00
00
01 11 02
21 03 31 41
12 51 61 71
81 91 22
13 32
紅色部分(第1位元組)為哈夫曼表ID和表型別,其值0x11表示此部分資料描述的是AC交流1號表。
藍色部分(2~17位元組)為不同位數的碼字的數量。這16個數值實際意義為:沒有1位和4位的哈夫曼碼字;2位和3位的碼字各有2個;5位碼字有5個;6位和8位碼字各有1個;7位碼字各有6個;沒有9位或以上的碼字。
綠色部分(18~34位元組)為編碼內容。由藍色部分資料知道,此哈夫曼樹有0+2+2+0+5+1+6+1=17個葉子結點,即本欄位應該有17個位元組。這段資料表示17個葉子結點按從小到大排列,其權值依次為0、1、11、2、21、3、31、41……
4)建立哈夫曼樹
a)理論說明
在讀出哈夫曼表的資料後,就要建立哈夫曼樹。具體方法為:
1)第一個碼字必定為0。
如果第一個碼字位數為1,則碼字為0;
如果第一個碼字位數為2,則碼字為00;
如此類推。
2)從第二個碼字開始,
如果它和它前面的碼字位數相同,則當前碼字為它前面的碼字加1;
如果它的位數比它前面的碼字位數大,則當前碼字是前面的碼字加1後再在後邊添若干個0,直至滿足位數長度為止。
b)舉例說明
繼續以上邊的例子說明問題。
n 由於沒有1位的碼字,所以第一個碼字的位數為2,即碼字為00;
n 由於2位的碼字有兩個,所以第二個碼字位數仍為2,即碼字為00+1=01;
n 第三個碼字為3位,比第二個碼字長1位,所以第三個碼字為:01+1=10,然後再添1個“0”,得100;
n ……
如此類推,最後得到這個哈夫曼樹如下:
序號 |
碼字長度 |
碼字 |
權值 |
1 |
2 |
00 |
0x00 |
2 |
2 |
01 |
0x01 |
3 |
3 |
100 |
0x11 |
4 |
3 |
101 |
0x02 |
5 |
5 |
11000 |
0x21 |
6 |
5 |
11001 |
0x03 |
7 |
5 |
11010 |
0x31 |
8 |
5 |
11011 |
0x41 |
9 |
5 |
11100 |
0x12 |
10 |
6 |
111010 |
0x51 |
11 |
7 |
1110110 |
0x61 |
12 |
7 |
1110111 |
0x71 |
13 |
7 |
1111000 |
0x81 |
14 |
7 |
1111001 |
0x91 |
15 |
7 |
1111010 |
0x22 |
16 |
7 |
1111011 |
0x13 |
17 |
8 |
11111000 |
0x32 |
特別注意的是,如果中間有某個位數的碼字缺失,例如沒有4位碼字,則應該在3位碼字加1後,新增“00”補足5位,形成下一個5位碼字。
在準備好所有的圖片資訊後,就可以對圖片資料進行解碼了。
1)理論說明
分析影像資料流的結構,筆者準備以一個從巨集觀到微觀的順序為讀者詳細剖析,即:
資料流à最小編碼單元à資料單元與顏色分量。
a) 在圖片畫素資料流中,資訊可以被分為一段接一段的最小編碼單元(Minimum Coded Unit,MCU)資料流。所謂MCU,是影像中一個正方矩陣畫素的資料。
矩陣的大小是這樣確定的:
查閱標記SOF0,可以得到影像不同顏色分量的取樣因子,即Y、Cr、Cb三個分量各自的水平取樣因子和垂直取樣因子。大多圖片的取樣因子為4:1:1或1:1:1。其中,4:1:1即(2*2):(1*1):(1*1));1:1:1即(1*1):(1*1):(1*1)。記三個分量中水平取樣因子最大值為Hmax,垂直取樣因子最大值為Vmax,那麼單個MCU矩陣的寬就是Hmax*8畫素,高就是Vmax*8畫素。
如果,整幅影像的寬度和高度不是MCU寬度和高度的整數倍,那麼編碼時會用某些數值填充進去,保證解碼過程中MCU的完整性(解碼完成後,可直接忽檢視像寬度和高度外的資料)。
在資料流中,MCU的排列方法是從左到右,從上到下。
b) 每個MCU又分為若干個資料單元。資料單元的大小必定為8*8,所以每個MCU的資料單元個數為Hmax*Vmax。
另外JPEG的壓縮方法與BMP檔案有所不同,它不是把每個畫素的顏色分量連續儲存在一起的,而是把圖片分成Y,Cr,Cb三張子圖,然後分別壓縮。而三個顏色分量的取樣密度(即取樣因子)可能一樣(例如1:1:1)也可能不一樣(例如4:1:1)。
每個MCU內部,資料的順序是Y、Cr、Cb。如果一個顏色分量有多個資料單元,則順序是從左到右,從上到下。
2)舉例說明
下面通過一幅32*35的影像,對上面兩個問題列出兩種取樣因子的具體說明。
圖 1 整張完整的影像(4:1:1) 圖 2 將影像的MCU1放大
圖1及圖3中灰色部分為實際影像大小(32px*35px);粗虛線表示各個MCU的分界;細虛線表示MCU內部資料單元的分界。
a) 取樣因子為4:1:1
此時,Hmax=max(2,1,1)=2,Vmax=max(2,1,1)=2。所以,MCU的寬為Hmax*8=16畫素,高為Vmax*8=16畫素。影像實際的寬剛好是2個MCU,但高則稍稍大於2個MCU的高度,所以要補足3行MCU。
在資料流中,MCU的順序是MCU1àMCU2àMCU3àMCU4àMCU5àMCU6。
每個MCU又分為Hmax*Vmax=2*2=4個資料單元。由於取樣因子是4:1:1,即(2*2):(1*1):(1*1),所以Y分量的水平和垂直方向都是每2個畫素取樣2次;Cr分量和Cb分量的水平和垂直方向都是每2個畫素取樣1次。因此,在一個MCU來裡邊,Y分量有256個取樣點,即4個完整的資料單元;Cr分量和Cb分量各自只有64個取樣點。
如果以MCU1說明MCU資料的次序,則依次為Y1 、Y2 、Y5 、Y6 、Cr1256 、Cb1256 。圖2中全部256個點均是Y的取樣點,紅色部分為Cr分量和Cr分量的取樣點。
換句話說,對於整張圖片來說,資料流的資料依次是:
[Y1
、Y2
、Y5
、Y6
、Cr1256
、Cb1256]
、[Y3
、Y4
、Y7
、Y8
、Cr3478
、Cb3478]
、[Y9
、Y10
、Y13
、Y14
、Cr9101314
、Cb9101314
]、……
圖 3 整張完整的影像(1:1:1)
b) 取樣因子為1:1:1
如圖3。Hmax=max(1,1,1)=1,Vmax=max(1,1,1)=2。所以,MCU的寬為Hmax*8=8畫素,高為Vmax*8=8畫素。影像實際的寬剛好是4個MCU,但高則稍稍大於4個MCU的高度,所以要補足5行MCU。
在資料流中,MCU的順序是:
MCU1àMCU2àMCU3àMCU4à
…………
àMCU18àMCU19àMCU20。
每個MCU又分為Hmax*Vmax=1*1=1個資料單元,也就是一個資料單元就是一個MCU。由於取樣因子是1:1:1,即(1*1):(1*1):(1*1),所以Y分量、Cr分量和Cb分量的水平和垂直方向都是每1個畫素取樣1次,也就是圖象的每一個畫素都是取樣點。因此,在一個MCU來裡邊,Y分量、Cr分量和Cb分量各自有64個取樣點。有
因此,對於整張圖片來說,資料流的資料依次是:
[Y1
、Cr1、Cb1]
、[Y2
、Cr2
、Cb2]
、[Y3
、Cr3、Cb3]
、………… [Y19
、Cr19、Cb19]、[Y20
、Cr20、Cb20]。
3.顏色分量單元的內部解碼
1)理論說明
“顏色分量單元”是筆者為說明問題而建立的概念,指的是MCU中某個顏色分量中的一個8*8資料塊,例如上面提到的Y1 、Cr1、Cb1 都是一個顏色分量單元。
影像資料流是以位(bit)為單位儲存資訊的。並且內部的資料都是在編碼時通過正向離散餘弦變換(FDCT)進行時空域向頻率域變換而得到的結果,所以對於每個顏色分量單元都應該由兩部分組成:1個直流分量和63個交流分量。
解碼的過程其實就是哈夫曼樹的查詢過程。
首先查閱標記段SOS中的顏色分量資訊,可以得出各個顏色分量對應使用的直流分量和交流分量使用的哈夫曼樹編號。一般來說,
Y分量:直流分量:直流0號哈夫曼樹,交流分量:交流0號哈夫曼樹;
Cr分量:直流分量:直流1號哈夫曼樹,交流分量:交流1號哈夫曼樹;
Cb分量:直流分量:直流1號哈夫曼樹,交流分量:交流1號哈夫曼樹。
顏色分量單元內部綜合運用了RLE行程編碼和哈夫曼編碼來壓縮資料。每個畫素的資料流由兩部分構成:編碼和數值,並且兩者基本以互相隔開方式出現(除非該編碼的權值為零)。具體讀入單個顏色分量單元的步驟如下:
a)從此顏色分量單後設資料流的起點開始一位一位的讀入,直到讀入的編碼與該分量直流哈夫曼樹的某個碼字(葉子結點)一致,然後用直流哈夫曼樹查得該碼字對應的權值。權值(共8位)表示該直流分量數值的二進位制位數,也就是接下來需要讀入的位數。
b)繼續讀入位資料,直到讀入的編碼與該分量交流哈夫曼樹的某個碼字(葉子結點)一致,然後用交流哈夫曼樹查得該碼字對應的權值。權值的高4位表示當前數值前面有多少個連續的零,低4位表示該交流分量數值的二進位制位數,也就是接下來需要讀入的位數。
c)不斷重複步驟b,直到滿足交流分量資料結束的條件。而結束條件有兩個,只要滿足其中一個即可:
①當讀入碼字的權值為零,表示往後的交流變數全部為零;
②已經讀入63個交流分量。
d)各個數值的譯碼是按下表進行的:
實際數值 |
編碼長度 |
編碼 |
0 |
0 |
- |
-1,1 |
1 |
0,1 |
-3,-2,2,3 |
2 |
00,01,10,11 |
-7,-6,-5,-4,4,5,6,7 |
3 |
000,001,010,011,100,101,110,111 |
-15,……,-8,8,……,15 |
4 |
0000,……,0111,1000,……,1111 |
-31,……,-16,16,……,31 |
5 |
00000,……,01111,10000,……,11111 |
-63,……,-32,32,……,63 |
6 |
…… |
-127,……,-64,64,……,127 |
7 |
…… |
-255,……,-128,128,……,255 |
8 |
…… |
-511,……,-256,256,……,511 |
9 |
…… |
-1023,……,-512,512,……,1023 |
10 |
…… |
-2047,……,-1024,1024,……,2047 |
11 |
…… |
-4095,……,-2048,2048,……,4095 |
12 |
…… |
-8191,……,-4096,4096,……,8191 |
13 |
…… |
-16383,……,-8192,8192,……,16383 |
14 |
…… |
-32767,……,-16384,16384,……,32767 |
15 |
…… |
2)舉例說明
下面舉例說明以上幾點。某個顏色分量單後設資料如下:
D3 5E 6E 4D 35 f5 8A
若以二進位制表示,則為:
1101 0011 0101 1110 0110 1110 0100 1101 0011 0101 1111 0101 1000 1010
假設該顏色分量單元對應以下直流哈夫曼樹和交流哈夫曼樹,則可將各個以位為單位的資料流拆分如下:
110 1001101 01 1 11001 101 11001 001 101 00 11010 1 1111010 11 00 01010
直流哈夫曼樹 交流哈夫曼樹
序號 |
碼字長度 |
碼字 |
權值 |
1 |
2 |
00 |
0x00 |
2 |
2 |
01 |
0x01 |
3 |
2 |
10 |
0x02 |
4 |
3 |
110 |
0x07 |
5 |
4 |
1110 |
0x1e |
6 |
5 |
11110 |
0x2e |
序號 |
碼字長度 |
碼字 |
權值 |
1 |
2 |
00 |
0x00 |
2 |
2 |
01 |
0x01 |
3 |
3 |
100 |
0x11 |
4 |
3 |
101 |
0x02 |
5 |
5 |
11000 |
0x21 |
6 |
5 |
11001 |
0x03 |
7 |
5 |
11010 |
0x31 |
8 |
5 |
11011 |
0x41 |
9 |
5 |
11100 |
0x12 |
10 |
6 |
111010 |
0x51 |
11 |
7 |
1110110 |
0x61 |
12 |
7 |
1110111 |
0x71 |
13 |
7 |
1111000 |
0x81 |
14 |
7 |
1111001 |
0x91 |
15 |
7 |
1111010 |
0x22 |
16 |
7 |
1111011 |
0x13 |
17 |
8 |
11111000 |
0x32 |
詳細說明一下:
讀入資料流並對照直流哈夫曼樹,第一個哈夫曼編碼為110,其權值為6,所以往後讀入6位資料“1001101”,譯碼成數值為77。因為每個顏色分量單元只有一個直流分量,所以下一個就是第一個交流分量了。
繼續讀入資料流並對照交流哈夫曼樹,得哈夫曼編碼為01,其權值為1,所以它的前面沒有零,並往後讀如1位資料“1”,譯碼成數值為1。如此往復,最後讀到哈夫曼編碼“00”,其權值為0,所以滿足交流變數結束條件(最後剩餘的“01010”對本顏色分量單元來說是冗餘的,它可能屬於下一個顏色分量單元)。
實際上,這段資料譯碼為:
77,(0,1),(0,5),(0,-6),(0,-3),(5,1),(2,3)
因此,把它置於1個8*8的矩陣中應為:
77 |
1 |
5 |
-6 |
-3 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
4.直流係數的差分編碼
把所有的顏色分量單元按顏色分量(Y、Cr、Cb)分類。每一種顏色分量內,相鄰的兩個顏色分量單元的直流變數是以差分來編碼的。也就是說,通過步驟3解碼出來的直流變數數值只是當前顏色分量單元的實際直流變數減去前一個顏色分量單元的實際直流變數。也就是說,當前直流變數要通過前一個顏色分量單元的實際(非解碼)直流分量來校正:
DCn=DCn-1+Diff
其中Diff為差分校正變數,也就是直接解碼出來的直流係數。但如果當前顏色分量單元是第一個單元,則解碼出來的直流數值就是真正的直流變數。
再次提醒的是,3個顏色分量的直流變數是分開進行差分編碼的。也就是說,為1張圖片解碼時應設定3個獨立的直流校正變數。另一個問題是,當資料流中出現標記RSTn,則3個顏色分量的直流差分校正變數Diff都需要重新復位到0。
5.反量化
不同的顏色分量使用不同的量化表,這個可以從標記段SOF中的顏色分量資訊欄位查得。一般是Y分量使用量化表0,而Cr、Cb兩個分量共同使用量化表1。
反量化的過程比較簡單。只需要對8*8的顏色分量單元的64個值逐一乘以對應的量化表內位置相同的值則可。影像內全部的顏色分量單元都要進行反量化。
6.反Zig-zag編碼
如果將反量化後的每個8*8顏色分量單元的每個元素編號,如下圖4,那麼各反Zig-zag編碼的過程就是把矩陣元素按圖5重新排列。
圖 4 將顏色分量單元元素編碼 圖 5 反Zig-zag編碼
關於量化和反Zig-zag編碼的先後順序,筆者查閱的幾份資料有不同的見解。經過實踐試驗,解碼的過程中,是應該直接用檔案提供的量化表反量化矩陣資料,再將其反Zig-zag編碼才能正確解碼。
7.隔行的正負糾正
這個問題比較特別,因為在筆者認真閱讀的幾份資料中都沒有提及此問題。而是筆者通過對已知影像進行JPEG編碼壓縮,然後和該圖的JPEG檔案資料對比發現的問題。具體原因不明。
實際上,就是必須對每個顏色分量單元的奇數行(每個顏色分量單元有8行,假設把它按0、1、……、6、7編出行號),即1、3、5、7行,進行取相反數操作(正的變負,負的變正)。
8.反離散餘弦變換
之前提到,檔案中的資料是在編碼時通過正向離散餘弦變換(FDCT)進行時空域向頻率域變換而得到的結果,所以現在解碼就必須將其反向離散餘弦變換(IDCT),就是把顏色分量單元矩陣中的頻率域數值向時空域轉換。並且,原來的頻率域的矩陣大小為8*8,則經過反向離散餘弦變換後,時空域的矩陣仍然是8*8。
設正負糾正後的頻率域矩陣為F[u][v],而反向離散餘弦變換後的矩陣為f[i][j],其中0≤u,v,i,j≤7。具體使用的公式如下:
,其中
C(u)= (當u=0),C(u)=1(當u≠0);
C(v)= (當v=0),C(u)=1(當v≠0);
另外補充一下正向離散餘弦變換的公式,用於編碼:
9.YCrCb向RGB轉換
要在螢幕上顯示影像,就必須以RGB模式表示影像的顏色。所以,解碼時需要把YCrCb模式向RGB模式轉換。
正如前面提到,並不是每種顏色分量的取樣因子都一樣,所以轉換時需要注意。如果取樣因子是1:1:1,則每一個畫素點的3個顏色分量都被取樣,所以沒有問題。但4:1:1的取樣因子就不一樣了。由“初步瞭解影像資料流的結構”一節中對4:1:1的取樣因子的分析,可以知道一個MCU裡有4個Y分量單元,而Cr分量和Cb分量各自只有1個分量單元。以圖2為例,僅有的一個Cr分量單元(紅色的64個取樣點)應該平鋪用於4個Y分量單元,即左上角16個值用於Y1,右上角16個值用於Y2,左下角16個值用於Y5,右下角16個值用於Y6。換句話說,一個Cr取樣點服務於4個Y取樣點。對於Cb分量,道理一樣。
另外,由於離散餘弦變化要求定義域的對稱,所以在編碼時把RGB的數值範圍從[0,255]統一減去128偏移成[-128,127]。因此解碼時必須為每個分量加上128。具體公式如下:
R=Y +1.402*Cb +128;
G=Y-0.34414*Cr -0.71414*Cb +128;
B=Y +1.772*Cb +128;
還有一個問題,通過變換得出的R、G、B值可能超出了其定義域,所以要作出檢查。如果大於255,則截斷為255;如果小於0,則截斷為0。
下面補充RGB模式向YCrCb模式的公式:
Y =0.299*R +0.587*G +0.114*B ;
Cr= -0.1687*R - 0.3313*G +0.5*B +128;
Cb=0.5 *R - 0.4187*G - 0.0813*B+128;
至此,每個MCU的解碼已經完成。而每一個MCU如何組成一幅完整的影像,請參考“初步瞭解影像資料流的結構”分析。
參考文獻
[1] 李才偉,中山大學計算機系多媒體課程教學課件.
[2] 張益貞,Visual C++實現MPEG/JPEG編解碼技術.北京:人民郵電出版社
[3] CCIT,Information Technology-digital Compression and Conding of Continuous-ton Still Images-requirements and Guidelines.http://www.wotsit.org/download.asp?f=itu-1150PDF (訪問日期:2007-1-1)
[4] 公子御風,JFIF檔案格式即JPEG檔案交換格式(JPEG File Interchonge Format).http://cat1226.bokee.com/4574350.html (訪問日期:2006-12-29)
[5] 雲風,JPEG 簡易文件 V2.11.http://rtornados.bokee.com/2442419.html (訪問日期:2006-12-30)
標記名 |
標記程式碼 |
說明 |
幀開始標記,Start of Frame,非層次哈夫曼編碼 |
||
SOF0 |
0xFFC0 |
基線離散餘弦變換 |
SOF1 |
0xFFC1 |
擴充套件順序離散餘弦變換 |
SOF2 |
0xFFC2 |
遞進離散餘弦變換 |
SOF3 |
0xFFC3 |
空間順序無損 |
幀開始標記,Start of Frame,層次哈夫曼編碼 |
||
SOF5 |
0xFFC5 |
差分離散餘弦變換 |
SOF6 |
0xFFC6 |
差分層次離散餘弦變換 |
SOF7 |
0xFFC7 |
差分空間無損 |
幀開始標記,Start of Frame,非層次算術編碼 |
||
JPEG |
0xFFC8 |
為JPEG擴充套件保留 |
SOF9 |
0xFFC9 |
擴充套件順序離散餘弦變換 |
SOF10 |
0xFFCA |
遞進離散餘弦變換 |
SOF11 |
0xFFCB |
空間順序無損 |
幀開始標記,Start of Frame,層次算術編碼 |
||
SOF13 |
0xFFCD |
差分離散餘弦變換 |
SOF14 |
0xFFCE |
差分層次離散餘弦變換 |
SOF15 |
0xFFCF |
差分空間無損 |
其他標記 |
||
DHT |
0xFFC4 |
定義哈夫曼樹表 |
DAC |
0xFFCC |
定義算術編碼表 |
RST0 |
OxFFD0 |
差分編碼累計復位,共8個 |
…… |
…… |
|
RST7 |
OxFFD7 |
|
SOI |
OxFFD8 |
影像開始 |
EOI |
OxFFD9 |
影像結束 |
SOS |
0xFFDA |
開始掃描,影像資料開始 |
DQT |
0xFFDB |
定義量化表 |
DNL |
0xFFDC |
定義線數 |
DRI |
0xFFDD |
定義差分編碼累計復位的間隔 |
DHP |
0xFFDE |
定義層次級數 |
EXP |
0xFFDF |
展開參考影像 |
APP0 |
0xFFE0 |
為應用程式保留,共15個 |
…… |
…… |
|
APP15 |
0xFFEE |
|
JPG0 |
0xFFF0 |
為JPEG擴充套件保留,共14個 |
…… |
…… |
|
JPG13 |
0xFFFD |
|
COM |
0xFFFE |
註釋 |
TEM |
0xFF01 |
算術編碼中作臨時之用 |
RES |
0xFF02 |
保留,共189個 |
…… |
…… |
|
RES |
0xFFBF |
相關文章
- Python檔案讀寫詳解及設定檔案的字元編碼Python字元
- JPEG格式研究——(2)JPEG檔案格式
- 位元組碼檔案結構詳解
- Dockerfile檔案詳解Docker
- mtl檔案詳解
- cmake檔案詳解
- BMP檔案詳解
- LD檔案詳解
- Jpeg的檔案資訊
- 前端編輯器配置檔案EditorConfig詳解前端
- java class檔案詳解Java
- JavaScript 檔案物件詳解JavaScript物件
- redis 配置檔案詳解Redis
- Class 檔案格式詳解
- haproxy配置檔案詳解
- Scala檔案操作詳解
- redis配置檔案詳解Redis
- Dockerfile檔案全面詳解Docker
- SSH配置檔案詳解
- zookeeper配置檔案詳解
- nginx配置檔案詳解Nginx
- WCF配置檔案詳解
- Java Class檔案詳解Java
- 控制檔案詳解(轉)
- /etc/fstab檔案詳解
- URL與URL編碼詳解
- Python字元編碼詳解Python字元
- Java Class 位元組碼檔案結構詳解Java
- Nginx的配置檔案詳解Nginx
- MachO 檔案結構詳解Mac
- managed-schema 檔案詳解
- vim的配置檔案詳解
- C++ 檔案操作詳解C++
- Hibernate配置檔案詳解
- BIND配置檔案詳解(三)
- Spring 配置檔案詳解Spring
- vsftpd配置檔案詳解FTP
- struts檔案上傳詳解