JPEG文件的存儲格式有很多種肋拔,但最常用的是JFIF格式,即JPEG File Interchange Format。JPEG文件大體可以分為兩個部分:
(1)標記碼鸟蜡;由兩個字節(jié)構成乘粒,其中豌注,前一個字節(jié)是固定值0XFF代表了一個標記碼的開始,后一個字節(jié)不同的值代表著不同的含義灯萍。需要提醒的是轧铁,連續(xù)的多個0XFF可以理解為一個0XFF,并表示一個標記碼的開始旦棉。另外齿风,標記碼在文件中一般是以標記代碼的形式出現的。例如绑洛,SOI的標記代碼是0XFFD8救斑,即,如果JPEG文件中出現了0XFFD8真屯,則代表此處是一個SOI標記系谐。
(2)壓縮數據;一個完整的兩字節(jié)標記碼的后面讨跟,就是該標記碼對應的壓縮數據了纪他,它記錄了關于文件的若干信息。
一些典型的標記碼晾匠,及其所代表的含義如下所示:
SOI茶袒,Start Of Image, 圖像開始凉馆,標記代碼為固定值0XFFD8薪寓,用2字節(jié)表示;
APP0澜共,Application 0, 應用程序保留標記0向叉,標記代碼為固定值0XFFE0,用2字節(jié)表示嗦董;該標記碼之后包含了9個具體的字段:
(1)數據長度:2個字節(jié)母谎,用來表示(1)--(9)的9個字段的總長度,即不包含標記代碼但包含本字段京革;
(2)標示符:5個字節(jié)奇唤,固定值0X4A6494600幸斥,表示了字符串“JFIF0”;
(3)版本號:2個字節(jié)咬扇,一般為0X0102甲葬,表示JFIF的版本號為1.2;但也可能為其它數值懈贺,從而代表了其它版本號经窖;
(4)X,Y方向的密度單位:1個字節(jié),只有三個值可選梭灿,0:無單位钠至;1:點數每英寸;2:點數每厘米胎源;
(5)X方向像素密度:2個字節(jié),取值范圍未知屿脐;
(6)Y方向像素密度:2個字節(jié)涕蚤,取值范圍未知;
(7)縮略圖水平像素數目:1個字節(jié)的诵,取值范圍未知万栅;
(8)縮略圖垂直像素數目:1個字節(jié),取值范圍未知西疤;
(9)縮略圖RGB位圖:長度可能是3的倍數烦粒,保存了一個24位的RGB位圖;如果沒有縮略位圖(這種情況更常見)代赁,則字段(7)(8)的取值均為0扰她;
APPn, Application n, 應用程序保留標記n(n=1---15),標記代碼為2個字節(jié),取值為0XFFE1--0XFFFF芭碍;包含了兩個字段:
(1)數據長度徒役,2個字節(jié),表示(1)(2)兩個字段的總長度窖壕;即忧勿,不包含標記代碼,但包含本字段瞻讽;
(2)詳細信息:數據長度-2個字節(jié)鸳吸,內容不定;
DQT速勇,Define Quantization Table, 定義量化表晌砾;標記代碼為固定值0XFFDB;包含9個具體字段:
(1)數據長度:2個字節(jié)烦磁,表示(1)和多個(2)字段的總長度贡羔;即廉白,不包含標記代碼,但包含本字段乖寒;
(2)量化表:數據長度-2個字節(jié)猴蹂,其中包括以下內容:
(a)精度及量化表ID,1個字節(jié)楣嘁,高4位表示精度磅轻,只有兩個可選值,0:8位逐虚;1:16位聋溜;低4位表示量化表ID,取值范圍為0--3叭爱;
(b)表項撮躁,64(精度取值+1)個字節(jié),例如买雾,8位精度的量化表把曼,其表項長度為64(0+1)=64字節(jié);
本標記段中漓穿,(2)可以重復出現嗤军,表示多個量化表,但最多只能出現4次晃危;
SOFO叙赚,Start Of Frame, 幀圖像開始,標記代碼為固定值0XFFC0僚饭;包含9個具體字段:
(1)數據長度:2個字節(jié)震叮,(1)--(6)共6個字段的總長度;即鳍鸵,不包含標記代碼冤荆,但包含本字段;
(2)精度:1個字節(jié)权纤,代表每個數據樣本的位數钓简;通常是8位;
(3)圖像高度:2個字節(jié)汹想,表示以像素為單位的圖像高度外邓,如果不支持DNL就必須大于0;
(4)圖像寬度:2個字節(jié)古掏,表示以像素為單位的圖像寬度损话,如果不支持DNL就必須大于0;
(5)顏色分量個數:1個字節(jié),由于JPEG采用YCrCb顏色空間丧枪,這里恒定為3光涂;
(6)顏色分量信息:顏色分量個數*3個字節(jié),這里通常為9個字節(jié)拧烦;并依此表示如下一些信息:
(a)顏色分量ID: 1個字節(jié)忘闻;
(b)水平/垂直采樣因子:1個字節(jié),高4位代表水平采樣因子恋博,低4位代表垂直采樣因子齐佳;
(c)量化表:1個字節(jié),當前分量使用的量化表ID债沮;
本標記段中炼吴,字段(6)應該重復出現3次,因為這里有3個顏色分量疫衩;
DHT硅蹦,Define Huffman Table定義Huffman表,標記碼為0XFFC4闷煤;包含2個字段:
(1)數據長度童芹,2個字節(jié),表示(1)--(2)的總長度曹傀,即,不包含標記代碼饲宛,但包含本字段皆愉;
(2)Huffman表,數據長度-2個字節(jié)艇抠,包含以下字段:
(a)表ID和表類型幕庐,1個字節(jié),高4位表示表的類型家淤,取值只有兩個异剥;0:DC直流;1:AC交流絮重;低4位冤寿,Huffman表ID;需要提醒的是青伤,DC表和AC表分開進行編碼督怜;
(b)不同位數的碼字數量,16個字節(jié)狠角;
(c)編碼內容号杠,16個不同位數的碼字數量之和(字節(jié));
本標記段中,字段(2)可以重復出現姨蟋,一般需要重復4次屉凯。
DRI,Define Restart Interval眼溶,定義差分編碼累計復位的間隔悠砚,標記碼為固定值0XFFDD;
包含2個具體字段:
(1)數據長度:2個字節(jié)偷仿,取值為固定值0X0004哩簿,表示(1)(2)兩個字段的總長度;即酝静,不包含標記代碼节榜,但包含本字段;
(2)MCU塊的單元中重新開始間隔:2個字節(jié)别智,如果取值為n宗苍,就代表每n個MCU塊就有一個RSTn標記;第一個標記是RST0薄榛,第二個是RST1,RST7之后再從RST0開始重復讳窟;如果沒有本標記段,或者間隔值為0敞恋,就表示不存在重開始間隔和標記RST丽啡;
SOS,Start Of Scan硬猫,掃描開始补箍;標記碼為0XFFDA,包含2個具體字段:
(1)數據長度:2個字節(jié)啸蜜,表示(1)--(4)字段的總長度坑雅;
(2)顏色分量數目:1個字節(jié),只有3個可選值衬横,1:灰度圖裹粤;3:YCrCb或YIQ;4:CMYK蜂林;
(3)顏色分量信息:包括以下字段遥诉,
(a)顏色分量ID:1個字節(jié);
(b)直流/交流系數表ID噪叙,1個字節(jié)突那,高4位表示直流分量的Huffman表的ID;低4位表示交流分量的Huffman表的ID构眯;
(4)壓縮圖像數據
(a)譜選擇開始:1個字節(jié)愕难,固定值0X00;
(b)譜選擇結束:1個字節(jié),固定值0X3F猫缭;
(c)譜選擇:1個字節(jié)葱弟,固定值0X00;
本標記段中猜丹,(3)應該重復出現芝加,有多少個顏色分量,就重復出現幾次射窒;本段結束之后藏杖,就是真正的圖像信息了;圖像信息直到遇到EOI標記就結束了脉顿;
EOI蝌麸,End Of Image,圖像結束艾疟;標記代碼為0XFFD9来吩;
另外,需要說明的是蔽莱,在JPEG中0XFF具有標記的意思弟疆,所以在壓縮數據流(真正的圖像信息)中,如果出現了0XFF盗冷,就需要做特別處理了怠苔。方法是,如果在圖像數據流中遇到0XFF仪糖,應該檢測其緊接著的字符柑司,如果是:
(1)0X00,表示0XFF是圖像流的組成部分乓诽;需要進行譯碼帜羊;
(2)0XD9咒程,表示與0XFF組成標記EOI鸠天,即,代表圖像流的結束帐姻,同時稠集,圖像文件結束;
(3)0XD0--0XD7饥瓷,組成RSTn標記剥纷,需要忽視整個RSTn標記,即不對當前0XFF和緊接著的0XDn兩個字節(jié)進行譯碼呢铆,并按RST標記的規(guī)則調整譯碼變量晦鞋;
(4)0XFF,忽略當前0XFF,對后一個0XFF進行判斷悠垛;
(5)其它數值线定,忽然當前0XFF,并保留緊接著此數值用于譯碼确买;
需要說明的是斤讥,JPEG文件格式中,一個字(16位)的存儲使用的是Motorola格式湾趾,而不是Intel格式芭商。也就是說,一個字的高字節(jié)(高8位)在數據流的前面搀缠,低字節(jié)(低8位)在數據流的后面铛楣,與平時習慣的Intel格式有所不同。這種字節(jié)順序問題的起因在于早期的硬件發(fā)展上胡嘿。在8位CPU的時代蛉艾,許多8位CPU都可以處理16位的數據,但它們顯然是分兩次進行處理的衷敌。這個時候就出現了先處理高位字節(jié)還是先處理低位字節(jié)的問題勿侯。以Intel為代表的廠家生產的CPU采用先低字節(jié)后高字節(jié)的方式;而以Motorola,IBM為代表的廠家生產的CPU則采用了先高字節(jié)后低字節(jié)的方式缴罗。Intel的字節(jié)順序也稱為little-endian助琐,而Motorola的字節(jié)順序就叫做big-endian。而JPEG/JFIF文件格式則采用了big-endian格式面氓。下面的函數兵钮,實現了從intel格式到motolora格式的轉換
USHORT Intel2Moto(USHORT val)
{
BYTE highBits = BYTE(val / 256);
BYTE lowBits = BYTE(val % 256);
return lowBits * 256 + highBits;
}
解碼
1)讀入JPEG/JFIF文件的相關信息
按照JFIF文件格式,將JPEG文件相關的字段信息一一讀取出來舌界,并進行相應的解析掘譬。例如,圖像的寬度呻拌、高度葱轩、量化表、Huffman表藐握、水平/垂直采樣因子等靴拱。一般而言,JFIF格式文件的讀取順序依次為:
SOI字段猾普;
APP0字段袜炕;
APPn字段;
DQT字段初家;
SOFO字段偎窘;
DHT字段乌助;
SOS字段;
壓縮數據字段陌知;
EOI字段眷茁;
讀取JPEG文件相關信息的時候,有兩點需要特別注意:
(a)由于JPEG中以0XFF來做為特殊標記符纵诞,因此上祈,如果某個像素的取值為0XFF,那么實際在保存的時候浙芙,是以0XFF00來保存的登刺,從而避免其跟特殊標記符0XFF之間產生混淆。所以嗡呼,在讀取文件信息的時候纸俭,如果遇0XFF00,就必須去除后面的00南窗;即揍很,將0XFF00當做0XFF;
(b)JPEG文件中万伤,一個字(16位)的存儲是采用了Motorola格式(big-endian)窒悔,而不是我們常用的Intel格式(little-endian)。因此敌买,如果需要的話简珠,請在處理之間進行依次高低字節(jié)的轉換。
(2) 讀取Huffman表
在標記碼DHT之后虹钮,包含了一個或者多個Huffman表(通常是4個表)聋庵。對于一個Huffman表而言,它包含了以下三部分內容:
(a)表ID和表類型芙粱;1個字節(jié)祭玉;僅有4個可選的取值,0X00,0X01,0X10,0X11春畔,分別表示DC直流0號表脱货,DC直流1號表,AC交流0號表拐迁,AC交流1號表蹭劈;
(b)不同位數的碼字數量疗绣;前面提到,JPEG中的Huffman編碼表是按照編碼長度的位數以表格的形式保存的线召,而且,Huffman編碼表的位數只能是1--16位多矮,因此缓淹,這里用16個字節(jié)來分別表示1--16位的每種位長的編碼在Huffman樹中的個數哈打。
(c)編碼內容;該字段記錄了Huffman樹中各個葉子節(jié)點的權重讯壶,上一個字段(不同位數的碼字數量)的16個數值之和料仗,就是本字段的長度,也就是Huffman樹中葉子節(jié)點的個數伏蚊。
這里立轧,我們不妨以下面一段Huffman表的數據為例來說明情況(均以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
以上數據串中第一行代表了Huffman表ID、表類型躏吊、不同位數的碼字數量信息氛改;
第一行的第一個字節(jié)0X11代表了表的ID和類型是AC交流1號表;
第一行的第2到第17字節(jié)代表了不同位數碼字的數量比伏。即胜卤,第2個字節(jié)00表示沒有位數為1的編碼;第3個和第4個字節(jié)的02表示位數為2和位數為3的編碼各有兩個赁项;第5個字節(jié)的00表示沒有位數為5的編碼葛躏。。悠菜。舰攒。此外,通過這些數據我們發(fā)現悔醋,此Huffman樹有0+2+2+0+5+1+6+1=17個葉子節(jié)點芒率。
第二行為編碼的內容,表明17個葉子節(jié)點按照從小到大的順序排列篙顺,即偶芍,權值依次為0,1,11,2,21,3,31,41...
(3) 構建Huffman樹
讀取到Huffman表的數據之后,就需要構建Huffman樹了德玫。其具體規(guī)則如下
(a)第一個編碼的數字必定為0匪蟀;如果第一個編碼的位數為1,就被編碼為0宰僧;如果第一個編碼的位數為2材彪,就被編碼為00;如果第一個編碼的位數為3琴儿,就被編碼為000段化。。造成。
(b)從第二個編碼開始显熏,如果它和它前面編碼具有相同的位數,則當前編碼是它前面的編碼加1晒屎;如果它的編碼位數比它前面的編碼位數大喘蟆,則當前編碼時它前面的編碼加1之后再在后面添加若干個0缓升,直到滿足編碼位數的長度為止。
還是以上面的數據為例:
第一行的第2個字節(jié)00表示沒有位數為1的編碼蕴轨;
第一行的第3個字節(jié)02表示位數為2的編碼有2個港谊;由于沒有位數為1的編碼,因此這里位數為2的編碼中的第一個為00橙弱,第二個為00+1=01歧寺;
第一行的第4個字節(jié)02表示位數為3的編碼有2個;因此棘脐,這里位數為3的編碼中的第一個為01+1=10成福,然后添加1個“0”,得到100荆残;位數為3的編碼中的第二個為100+1=101奴艾;
依次類推,可以得到如下的Huffman樹
特別提醒的是内斯,如果中間有某個位數的編碼缺失蕴潦,例如,沒有4位的編碼俘闯,則應該在3位的編碼后面加1潭苞,添加2個“00”補足5位,形成下一個5位編碼真朗。
(4) DC系數的Huffman解碼
JPEG編碼階段我們講到此疹,DC系數是以(A,B)的中間形式進行編碼的。其中的A代表了B的二進制編碼位數遮婶,B則利用VLI進行編碼蝗碎。另外,88的圖像塊經過DCT變換之后得到的88的系數矩陣旗扑,經過Huffman編碼及RLE編碼之后蹦骑,寫入編碼數據的時候,DC系數也是被寫在數據流最前面的臀防。因此眠菇,解碼的時候,DC系數也是最先被讀取出來袱衷,假設捎废,我們一次性讀入了若干個字節(jié)長度的數據。其中的第一個字節(jié)代表了DC系數的Huffman編碼致燥,通過查找DC系數的Huffman表(亮度表或色度表)登疗,得到該Huffman編碼所在的組編號,該編號就是DC系數中間格式(A,B)中的A篡悟,也就是B的位數谜叹。例如,A=2搬葬,就代表B采用2位二進制數進行編碼荷腊。這樣一來,讀取接下來的A位二進制數急凰,將其譯碼為十進制,就得到了DC系數的差值。將該差值與上一個DC系數值相加羽杰,就得到了真正的當前DC系數的值瞬测。
(5) AC系數的Huffman解碼
處理完DC系數之后,接下來進行AC系數的譯碼工作床三,顯然一罩,這里依然需要讀取一個Huffman編碼,通過查找AC系數的Huffman編碼表撇簿,進行解碼聂渊,我們得到(A,B)的數據對,其中的A代表了0的個數四瘫,而B則代表了后面數據的位數汉嗽。例如,(2,3)就代表了當前AC系數之前有2個0找蜜,下一個需要讀取的二進制數據是3位饼暑。需要提醒的是,(0,0)代表EOB洗做,即88塊的編碼結束弓叛。接著,讀取B位二進制數據诚纸,進行譯碼邪码,我們就得到了AC系數的值。如此反復循環(huán)咬清,直到遇到EOB闭专,或者讀取了63個AC系數,我們就完成了一個88塊的系數矩陣的譯碼工作旧烧。
(6) 反量化
在譯碼得到了88的系數矩陣之后影钉,我們需要進行反量化工作。該步驟掘剪,就是將前一個步驟得到的88系數矩陣分別乘以8*8的量化矩陣即可平委。
(7) 反Zig-zag掃描
JPEG編碼過程中,為了編碼方便夺谁,采用了Zig-zag掃描廉赔,因此肉微,這里需要進行反Zig-zag掃描,重新排列88的反量化系數矩陣蜡塌。反Zig-zag掃描的輸入時88矩陣碉纳,輸出依然是8*8矩陣,只不過馏艾,數據的排列方式有所不同而已劳曹。
(8) DCT逆變換
DCT變換,將原始圖像變換到頻域琅摩,而DCT逆變換铁孵,就是要將數據從頻域變換回時域。
DCT逆變換的計算公式為:
DCT逆變換的公式房资,可以改寫為:
其中A為矩陣:
左邊為未轉秩的數據順序蜕劝,右邊為轉秩之后的數據順序。
(9)顏色模式轉換
BMP圖片是以RGB顏色空間進行保存的轰异,因此熙宇,將JPEG解碼為BMP必須進行顏色模式的轉換。另外溉浙,由于DCT要求的定義域對稱烫止,所以,在編碼的時候將RGB的數值范圍從[0,255]統(tǒng)一減去128戳稽,將數值范圍轉換到[-128,127]的范圍內馆蠕。因此,解碼的時候惊奇,必須為每個顏色分量加上128互躬。另外需要注意的是,通過解碼變換之后得到的RGB的值有可能超過255或者小于0颂郎;如果小于0吼渡,就截斷為0,如果大于255乓序,就截取為255寺酪;