JPEG文件的存儲(chǔ)格式有很多種,但最常用的是JFIF格式,即JPEG File Interchange Format。JPEG文件大體可以分為兩個(gè)部分:
(1)標(biāo)記碼;由兩個(gè)字節(jié)構(gòu)成,其中,前一個(gè)字節(jié)是固定值0XFF代表了一個(gè)標(biāo)記碼的開(kāi)始,后一個(gè)字節(jié)不同的值代表著不同的含義。需要提醒的是,連續(xù)的多個(gè)0XFF可以理解為一個(gè)0XFF,并表示一個(gè)標(biāo)記碼的開(kāi)始。另外,標(biāo)記碼在文件中一般是以標(biāo)記代碼的形式出現(xiàn)的。例如,SOI的標(biāo)記代碼是0XFFD8,即,如果JPEG文件中出現(xiàn)了0XFFD8,則代表此處是一個(gè)SOI標(biāo)記。
(2)壓縮數(shù)據(jù);一個(gè)完整的兩字節(jié)標(biāo)記碼的后面,就是該標(biāo)記碼對(duì)應(yīng)的壓縮數(shù)據(jù)了,它記錄了關(guān)于文件的若干信息。
一些典型的標(biāo)記碼,及其所代表的含義如下所示:
SOI,Start Of Image, 圖像開(kāi)始,標(biāo)記代碼為固定值0XFFD8,用2字節(jié)表示;
APP0,Application 0, 應(yīng)用程序保留標(biāo)記0,標(biāo)記代碼為固定值0XFFE0,用2字節(jié)表示;該標(biāo)記碼之后包含了9個(gè)具體的字段:
(1)數(shù)據(jù)長(zhǎng)度:2個(gè)字節(jié),用來(lái)表示(1)--(9)的9個(gè)字段的總長(zhǎng)度,即不包含標(biāo)記代碼但包含本字段;
(2)標(biāo)示符:5個(gè)字節(jié),固定值0X4A6494600,表示了字符串“JFIF0”;
(3)版本號(hào):2個(gè)字節(jié),一般為0X0102,表示JFIF的版本號(hào)為1.2;但也可能為其它數(shù)值,從而代表了其它版本號(hào);
(4)X,Y方向的密度單位:1個(gè)字節(jié),只有三個(gè)值可選,0:無(wú)單位;1:點(diǎn)數(shù)每英寸;2:點(diǎn)數(shù)每厘米;
(5)X方向像素密度:2個(gè)字節(jié),取值范圍未知;
(6)Y方向像素密度:2個(gè)字節(jié),取值范圍未知;
(7)縮略圖水平像素?cái)?shù)目:1個(gè)字節(jié),取值范圍未知;
(8)縮略圖垂直像素?cái)?shù)目:1個(gè)字節(jié),取值范圍未知;
(9)縮略圖RGB位圖:長(zhǎng)度可能是3的倍數(shù),保存了一個(gè)24位的RGB位圖;如果沒(méi)有縮略位圖(這種情況更常見(jiàn)),則字段(7)(8)的取值均為0;
APPn, Application n, 應(yīng)用程序保留標(biāo)記n(n=1---15),標(biāo)記代碼為2個(gè)字節(jié),取值為0XFFE1--0XFFFF;包含了兩個(gè)字段:
(1)數(shù)據(jù)長(zhǎng)度,2個(gè)字節(jié),表示(1)(2)兩個(gè)字段的總長(zhǎng)度;即,不包含標(biāo)記代碼,但包含本字段;
(2)詳細(xì)信息:數(shù)據(jù)長(zhǎng)度-2個(gè)字節(jié),內(nèi)容不定;
DQT,Define Quantization Table, 定義量化表;標(biāo)記代碼為固定值0XFFDB;包含9個(gè)具體字段:
(1)數(shù)據(jù)長(zhǎng)度:2個(gè)字節(jié),表示(1)和多個(gè)(2)字段的總長(zhǎng)度;即,不包含標(biāo)記代碼,但包含本字段;
(2)量化表:數(shù)據(jù)長(zhǎng)度-2個(gè)字節(jié),其中包括以下內(nèi)容:
(a)精度及量化表ID,1個(gè)字節(jié),高4位表示精度,只有兩個(gè)可選值,0:8位;1:16位;低4位表示量化表ID,取值范圍為0--3;
(b)表項(xiàng),64(精度取值+1)個(gè)字節(jié),例如,8位精度的量化表,其表項(xiàng)長(zhǎng)度為64(0+1)=64字節(jié);
本標(biāo)記段中,(2)可以重復(fù)出現(xiàn),表示多個(gè)量化表,但最多只能出現(xiàn)4次;
SOFO,Start Of Frame, 幀圖像開(kāi)始,標(biāo)記代碼為固定值0XFFC0;包含9個(gè)具體字段:
(1)數(shù)據(jù)長(zhǎng)度:2個(gè)字節(jié),(1)--(6)共6個(gè)字段的總長(zhǎng)度;即,不包含標(biāo)記代碼,但包含本字段;
(2)精度:1個(gè)字節(jié),代表每個(gè)數(shù)據(jù)樣本的位數(shù);通常是8位;
(3)圖像高度:2個(gè)字節(jié),表示以像素為單位的圖像高度,如果不支持DNL就必須大于0;
(4)圖像寬度:2個(gè)字節(jié),表示以像素為單位的圖像寬度,如果不支持DNL就必須大于0;
(5)顏色分量個(gè)數(shù):1個(gè)字節(jié),由于JPEG采用YCrCb顏色空間,這里恒定為3;
(6)顏色分量信息:顏色分量個(gè)數(shù)*3個(gè)字節(jié),這里通常為9個(gè)字節(jié);并依此表示如下一些信息:
(a)顏色分量ID: 1個(gè)字節(jié);
(b)水平/垂直采樣因子:1個(gè)字節(jié),高4位代表水平采樣因子,低4位代表垂直采樣因子;
(c)量化表:1個(gè)字節(jié),當(dāng)前分量使用的量化表ID;
本標(biāo)記段中,字段(6)應(yīng)該重復(fù)出現(xiàn)3次,因?yàn)檫@里有3個(gè)顏色分量;
DHT,Define Huffman Table定義Huffman表,標(biāo)記碼為0XFFC4;包含2個(gè)字段:
(1)數(shù)據(jù)長(zhǎng)度,2個(gè)字節(jié),表示(1)--(2)的總長(zhǎng)度,即,不包含標(biāo)記代碼,但包含本字段;
(2)Huffman表,數(shù)據(jù)長(zhǎng)度-2個(gè)字節(jié),包含以下字段:
(a)表ID和表類型,1個(gè)字節(jié),高4位表示表的類型,取值只有兩個(gè);0:DC直流;1:AC交流;低4位,Huffman表ID;需要提醒的是,DC表和AC表分開(kāi)進(jìn)行編碼;
(b)不同位數(shù)的碼字?jǐn)?shù)量,16個(gè)字節(jié);
(c)編碼內(nèi)容,16個(gè)不同位數(shù)的碼字?jǐn)?shù)量之和(字節(jié));
本標(biāo)記段中,字段(2)可以重復(fù)出現(xiàn),一般需要重復(fù)4次。
DRI,Define Restart Interval,定義差分編碼累計(jì)復(fù)位的間隔,標(biāo)記碼為固定值0XFFDD;
包含2個(gè)具體字段:
(1)數(shù)據(jù)長(zhǎng)度:2個(gè)字節(jié),取值為固定值0X0004,表示(1)(2)兩個(gè)字段的總長(zhǎng)度;即,不包含標(biāo)記代碼,但包含本字段;
(2)MCU塊的單元中重新開(kāi)始間隔:2個(gè)字節(jié),如果取值為n,就代表每n個(gè)MCU塊就有一個(gè)RSTn標(biāo)記;第一個(gè)標(biāo)記是RST0,第二個(gè)是RST1,RST7之后再?gòu)腞ST0開(kāi)始重復(fù);如果沒(méi)有本標(biāo)記段,或者間隔值為0,就表示不存在重開(kāi)始間隔和標(biāo)記RST;
SOS,Start Of Scan,掃描開(kāi)始;標(biāo)記碼為0XFFDA,包含2個(gè)具體字段:
(1)數(shù)據(jù)長(zhǎng)度:2個(gè)字節(jié),表示(1)--(4)字段的總長(zhǎng)度;
(2)顏色分量數(shù)目:1個(gè)字節(jié),只有3個(gè)可選值,1:灰度圖;3:YCrCb或YIQ;4:CMYK;
(3)顏色分量信息:包括以下字段,
(a)顏色分量ID:1個(gè)字節(jié);
(b)直流/交流系數(shù)表ID,1個(gè)字節(jié),高4位表示直流分量的Huffman表的ID;低4位表示交流分量的Huffman表的ID;
(4)壓縮圖像數(shù)據(jù)
(a)譜選擇開(kāi)始:1個(gè)字節(jié),固定值0X00;
(b)譜選擇結(jié)束:1個(gè)字節(jié),固定值0X3F;
(c)譜選擇:1個(gè)字節(jié),固定值0X00;
本標(biāo)記段中,(3)應(yīng)該重復(fù)出現(xiàn),有多少個(gè)顏色分量,就重復(fù)出現(xiàn)幾次;本段結(jié)束之后,就是真正的圖像信息了;圖像信息直到遇到EOI標(biāo)記就結(jié)束了;
EOI,End Of Image,圖像結(jié)束;標(biāo)記代碼為0XFFD9;
另外,需要說(shuō)明的是,在JPEG中0XFF具有標(biāo)記的意思,所以在壓縮數(shù)據(jù)流(真正的圖像信息)中,如果出現(xiàn)了0XFF,就需要做特別處理了。方法是,如果在圖像數(shù)據(jù)流中遇到0XFF,應(yīng)該檢測(cè)其緊接著的字符,如果是:
(1)0X00,表示0XFF是圖像流的組成部分;需要進(jìn)行譯碼;
(2)0XD9,表示與0XFF組成標(biāo)記EOI,即,代表圖像流的結(jié)束,同時(shí),圖像文件結(jié)束;
(3)0XD0--0XD7,組成RSTn標(biāo)記,需要忽視整個(gè)RSTn標(biāo)記,即不對(duì)當(dāng)前0XFF和緊接著的0XDn兩個(gè)字節(jié)進(jìn)行譯碼,并按RST標(biāo)記的規(guī)則調(diào)整譯碼變量;
(4)0XFF,忽略當(dāng)前0XFF,對(duì)后一個(gè)0XFF進(jìn)行判斷;
(5)其它數(shù)值,忽然當(dāng)前0XFF,并保留緊接著此數(shù)值用于譯碼;
需要說(shuō)明的是,JPEG文件格式中,一個(gè)字(16位)的存儲(chǔ)使用的是Motorola格式,而不是Intel格式。也就是說(shuō),一個(gè)字的高字節(jié)(高8位)在數(shù)據(jù)流的前面,低字節(jié)(低8位)在數(shù)據(jù)流的后面,與平時(shí)習(xí)慣的Intel格式有所不同。這種字節(jié)順序問(wèn)題的起因在于早期的硬件發(fā)展上。在8位CPU的時(shí)代,許多8位CPU都可以處理16位的數(shù)據(jù),但它們顯然是分兩次進(jìn)行處理的。這個(gè)時(shí)候就出現(xiàn)了先處理高位字節(jié)還是先處理低位字節(jié)的問(wèn)題。以Intel為代表的廠家生產(chǎn)的CPU采用先低字節(jié)后高字節(jié)的方式;而以Motorola,IBM為代表的廠家生產(chǎn)的CPU則采用了先高字節(jié)后低字節(jié)的方式。Intel的字節(jié)順序也稱為little-endian,而Motorola的字節(jié)順序就叫做big-endian。而JPEG/JFIF文件格式則采用了big-endian格式。下面的函數(shù),實(shí)現(xiàn)了從intel格式到motolora格式的轉(zhuǎn)換
USHORT Intel2Moto(USHORT val)
{
BYTE highBits = BYTE(val / 256);
BYTE lowBits = BYTE(val % 256);
return lowBits * 256 + highBits;
}
解碼
1)讀入JPEG/JFIF文件的相關(guān)信息
按照J(rèn)FIF文件格式,將JPEG文件相關(guān)的字段信息一一讀取出來(lái),并進(jìn)行相應(yīng)的解析。例如,圖像的寬度、高度、量化表、Huffman表、水平/垂直采樣因子等。一般而言,JFIF格式文件的讀取順序依次為:
SOI字段;
APP0字段;
APPn字段;
DQT字段;
SOFO字段;
DHT字段;
SOS字段;
壓縮數(shù)據(jù)字段;
EOI字段;
讀取JPEG文件相關(guān)信息的時(shí)候,有兩點(diǎn)需要特別注意:
(a)由于JPEG中以0XFF來(lái)做為特殊標(biāo)記符,因此,如果某個(gè)像素的取值為0XFF,那么實(shí)際在保存的時(shí)候,是以0XFF00來(lái)保存的,從而避免其跟特殊標(biāo)記符0XFF之間產(chǎn)生混淆。所以,在讀取文件信息的時(shí)候,如果遇0XFF00,就必須去除后面的00;即,將0XFF00當(dāng)做0XFF;
(b)JPEG文件中,一個(gè)字(16位)的存儲(chǔ)是采用了Motorola格式(big-endian),而不是我們常用的Intel格式(little-endian)。因此,如果需要的話,請(qǐng)?jiān)谔幚碇g進(jìn)行依次高低字節(jié)的轉(zhuǎn)換。
(2) 讀取Huffman表
在標(biāo)記碼DHT之后,包含了一個(gè)或者多個(gè)Huffman表(通常是4個(gè)表)。對(duì)于一個(gè)Huffman表而言,它包含了以下三部分內(nèi)容:
(a)表ID和表類型;1個(gè)字節(jié);僅有4個(gè)可選的取值,0X00,0X01,0X10,0X11,分別表示DC直流0號(hào)表,DC直流1號(hào)表,AC交流0號(hào)表,AC交流1號(hào)表;
(b)不同位數(shù)的碼字?jǐn)?shù)量;前面提到,JPEG中的Huffman編碼表是按照編碼長(zhǎng)度的位數(shù)以表格的形式保存的,而且,Huffman編碼表的位數(shù)只能是1--16位,因此,這里用16個(gè)字節(jié)來(lái)分別表示1--16位的每種位長(zhǎng)的編碼在Huffman樹(shù)中的個(gè)數(shù)。
(c)編碼內(nèi)容;該字段記錄了Huffman樹(shù)中各個(gè)葉子節(jié)點(diǎn)的權(quán)重,上一個(gè)字段(不同位數(shù)的碼字?jǐn)?shù)量)的16個(gè)數(shù)值之和,就是本字段的長(zhǎng)度,也就是Huffman樹(shù)中葉子節(jié)點(diǎn)的個(gè)數(shù)。
這里,我們不妨以下面一段Huffman表的數(shù)據(jù)為例來(lái)說(shuō)明情況(均以16進(jìn)制表示):
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
以上數(shù)據(jù)串中第一行代表了Huffman表ID、表類型、不同位數(shù)的碼字?jǐn)?shù)量信息;
第一行的第一個(gè)字節(jié)0X11代表了表的ID和類型是AC交流1號(hào)表;
第一行的第2到第17字節(jié)代表了不同位數(shù)碼字的數(shù)量。即,第2個(gè)字節(jié)00表示沒(méi)有位數(shù)為1的編碼;第3個(gè)和第4個(gè)字節(jié)的02表示位數(shù)為2和位數(shù)為3的編碼各有兩個(gè);第5個(gè)字節(jié)的00表示沒(méi)有位數(shù)為5的編碼。。。。此外,通過(guò)這些數(shù)據(jù)我們發(fā)現(xiàn),此Huffman樹(shù)有0+2+2+0+5+1+6+1=17個(gè)葉子節(jié)點(diǎn)。
第二行為編碼的內(nèi)容,表明17個(gè)葉子節(jié)點(diǎn)按照從小到大的順序排列,即,權(quán)值依次為0,1,11,2,21,3,31,41...
(3) 構(gòu)建Huffman樹(shù)
讀取到Huffman表的數(shù)據(jù)之后,就需要構(gòu)建Huffman樹(shù)了。其具體規(guī)則如下
(a)第一個(gè)編碼的數(shù)字必定為0;如果第一個(gè)編碼的位數(shù)為1,就被編碼為0;如果第一個(gè)編碼的位數(shù)為2,就被編碼為00;如果第一個(gè)編碼的位數(shù)為3,就被編碼為000。。。
(b)從第二個(gè)編碼開(kāi)始,如果它和它前面編碼具有相同的位數(shù),則當(dāng)前編碼是它前面的編碼加1;如果它的編碼位數(shù)比它前面的編碼位數(shù)大,則當(dāng)前編碼時(shí)它前面的編碼加1之后再在后面添加若干個(gè)0,直到滿足編碼位數(shù)的長(zhǎng)度為止。
還是以上面的數(shù)據(jù)為例:
第一行的第2個(gè)字節(jié)00表示沒(méi)有位數(shù)為1的編碼;
第一行的第3個(gè)字節(jié)02表示位數(shù)為2的編碼有2個(gè);由于沒(méi)有位數(shù)為1的編碼,因此這里位數(shù)為2的編碼中的第一個(gè)為00,第二個(gè)為00+1=01;
第一行的第4個(gè)字節(jié)02表示位數(shù)為3的編碼有2個(gè);因此,這里位數(shù)為3的編碼中的第一個(gè)為01+1=10,然后添加1個(gè)“0”,得到100;位數(shù)為3的編碼中的第二個(gè)為100+1=101;
依次類推,可以得到如下的Huffman樹(shù)
特別提醒的是,如果中間有某個(gè)位數(shù)的編碼缺失,例如,沒(méi)有4位的編碼,則應(yīng)該在3位的編碼后面加1,添加2個(gè)“00”補(bǔ)足5位,形成下一個(gè)5位編碼。
(4) DC系數(shù)的Huffman解碼
JPEG編碼階段我們講到,DC系數(shù)是以(A,B)的中間形式進(jìn)行編碼的。其中的A代表了B的二進(jìn)制編碼位數(shù),B則利用VLI進(jìn)行編碼。另外,88的圖像塊經(jīng)過(guò)DCT變換之后得到的88的系數(shù)矩陣,經(jīng)過(guò)Huffman編碼及RLE編碼之后,寫入編碼數(shù)據(jù)的時(shí)候,DC系數(shù)也是被寫在數(shù)據(jù)流最前面的。因此,解碼的時(shí)候,DC系數(shù)也是最先被讀取出來(lái),假設(shè),我們一次性讀入了若干個(gè)字節(jié)長(zhǎng)度的數(shù)據(jù)。其中的第一個(gè)字節(jié)代表了DC系數(shù)的Huffman編碼,通過(guò)查找DC系數(shù)的Huffman表(亮度表或色度表),得到該Huffman編碼所在的組編號(hào),該編號(hào)就是DC系數(shù)中間格式(A,B)中的A,也就是B的位數(shù)。例如,A=2,就代表B采用2位二進(jìn)制數(shù)進(jìn)行編碼。這樣一來(lái),讀取接下來(lái)的A位二進(jìn)制數(shù),將其譯碼為十進(jìn)制,就得到了DC系數(shù)的差值。將該差值與上一個(gè)DC系數(shù)值相加,就得到了真正的當(dāng)前DC系數(shù)的值。
(5) AC系數(shù)的Huffman解碼
處理完DC系數(shù)之后,接下來(lái)進(jìn)行AC系數(shù)的譯碼工作,顯然,這里依然需要讀取一個(gè)Huffman編碼,通過(guò)查找AC系數(shù)的Huffman編碼表,進(jìn)行解碼,我們得到(A,B)的數(shù)據(jù)對(duì),其中的A代表了0的個(gè)數(shù),而B(niǎo)則代表了后面數(shù)據(jù)的位數(shù)。例如,(2,3)就代表了當(dāng)前AC系數(shù)之前有2個(gè)0,下一個(gè)需要讀取的二進(jìn)制數(shù)據(jù)是3位。需要提醒的是,(0,0)代表EOB,即88塊的編碼結(jié)束。接著,讀取B位二進(jìn)制數(shù)據(jù),進(jìn)行譯碼,我們就得到了AC系數(shù)的值。如此反復(fù)循環(huán),直到遇到EOB,或者讀取了63個(gè)AC系數(shù),我們就完成了一個(gè)88塊的系數(shù)矩陣的譯碼工作。
(6) 反量化
在譯碼得到了88的系數(shù)矩陣之后,我們需要進(jìn)行反量化工作。該步驟,就是將前一個(gè)步驟得到的88系數(shù)矩陣分別乘以8*8的量化矩陣即可。
(7) 反Zig-zag掃描
JPEG編碼過(guò)程中,為了編碼方便,采用了Zig-zag掃描,因此,這里需要進(jìn)行反Zig-zag掃描,重新排列88的反量化系數(shù)矩陣。反Zig-zag掃描的輸入時(shí)88矩陣,輸出依然是8*8矩陣,只不過(guò),數(shù)據(jù)的排列方式有所不同而已。
(8) DCT逆變換
DCT變換,將原始圖像變換到頻域,而DCT逆變換,就是要將數(shù)據(jù)從頻域變換回時(shí)域。
DCT逆變換的計(jì)算公式為:
DCT逆變換的公式,可以改寫為:
其中A為矩陣:
左邊為未轉(zhuǎn)秩的數(shù)據(jù)順序,右邊為轉(zhuǎn)秩之后的數(shù)據(jù)順序。
(9)顏色模式轉(zhuǎn)換
BMP圖片是以RGB顏色空間進(jìn)行保存的,因此,將JPEG解碼為BMP必須進(jìn)行顏色模式的轉(zhuǎn)換。另外,由于DCT要求的定義域?qū)ΨQ,所以,在編碼的時(shí)候?qū)GB的數(shù)值范圍從[0,255]統(tǒng)一減去128,將數(shù)值范圍轉(zhuǎn)換到[-128,127]的范圍內(nèi)。因此,解碼的時(shí)候,必須為每個(gè)顏色分量加上128。另外需要注意的是,通過(guò)解碼變換之后得到的RGB的值有可能超過(guò)255或者小于0;如果小于0,就截?cái)酁?,如果大于255,就截取為255;