JPEG文件格式介绍及解码算法

JPEG文件的存储格式有很多种,但最常用的是JFIF格式,即JPEG File Interchange Format。JPEG文件大体可以分为两个部分:

(1)标记码;由两个字节构成,其中,前一个字节是固定值0XFF代表了一个标记码的开始,后一个字节不同的值代表着不同的含义。需要提醒的是,连续的多个0XFF可以理解为一个0XFF,并表示一个标记码的开始。另外,标记码在文件中一般是以标记代码的形式出现的。例如,SOI的标记代码是0XFFD8,即,如果JPEG文件中出现了0XFFD8,则代表此处是一个SOI标记。

(2)压缩数据;一个完整的两字节标记码的后面,就是该标记码对应的压缩数据了,它记录了关于文件的若干信息。

一些典型的标记码,及其所代表的含义如下所示:

SOI,Start Of Image, 图像开始,标记代码为固定值0XFFD8,用2字节表示;

APP0,Application 0, 应用程序保留标记0,标记代码为固定值0XFFE0,用2字节表示;该标记码之后包含了9个具体的字段:

(1)数据长度:2个字节,用来表示(1)--(9)的9个字段的总长度,即不包含标记代码但包含本字段;

(2)标示符:5个字节,固定值0X4A6494600,表示了字符串“JFIF0”;

(3)版本号:2个字节,一般为0X0102,表示JFIF的版本号为1.2;但也可能为其它数值,从而代表了其它版本号;

(4)X,Y方向的密度单位:1个字节,只有三个值可选,0:无单位;1:点数每英寸;2:点数每厘米;

(5)X方向像素密度:2个字节,取值范围未知;

(6)Y方向像素密度:2个字节,取值范围未知;

(7)缩略图水平像素数目:1个字节,取值范围未知;

(8)缩略图垂直像素数目:1个字节,取值范围未知;

(9)缩略图RGB位图:长度可能是3的倍数,保存了一个24位的RGB位图;如果没有缩略位图(这种情况更常见),则字段(7)(8)的取值均为0;

APPn, Application n, 应用程序保留标记n(n=1---15),标记代码为2个字节,取值为0XFFE1--0XFFFF;包含了两个字段:

(1)数据长度,2个字节,表示(1)(2)两个字段的总长度;即,不包含标记代码,但包含本字段;

(2)详细信息:数据长度-2个字节,内容不定;

DQT,Define Quantization Table, 定义量化表;标记代码为固定值0XFFDB;包含9个具体字段:

(1)数据长度:2个字节,表示(1)和多个(2)字段的总长度;即,不包含标记代码,但包含本字段;

(2)量化表:数据长度-2个字节,其中包括以下内容:

(a)精度及量化表ID,1个字节,高4位表示精度,只有两个可选值,0:8位;1:16位;低4位表示量化表ID,取值范围为0--3;

(b)表项,64(精度取值+1)个字节,例如,8位精度的量化表,其表项长度为64(0+1)=64字节;

本标记段中,(2)可以重复出现,表示多个量化表,但最多只能出现4次;

SOFO,Start Of Frame, 帧图像开始,标记代码为固定值0XFFC0;包含9个具体字段:

(1)数据长度:2个字节,(1)--(6)共6个字段的总长度;即,不包含标记代码,但包含本字段;

(2)精度:1个字节,代表每个数据样本的位数;通常是8位;

(3)图像高度:2个字节,表示以像素为单位的图像高度,如果不支持DNL就必须大于0;

(4)图像宽度:2个字节,表示以像素为单位的图像宽度,如果不支持DNL就必须大于0;

(5)颜色分量个数:1个字节,由于JPEG采用YCrCb颜色空间,这里恒定为3;

(6)颜色分量信息:颜色分量个数*3个字节,这里通常为9个字节;并依此表示如下一些信息:

(a)颜色分量ID: 1个字节;

(b)水平/垂直采样因子:1个字节,高4位代表水平采样因子,低4位代表垂直采样因子;

(c)量化表:1个字节,当前分量使用的量化表ID;

本标记段中,字段(6)应该重复出现3次,因为这里有3个颜色分量;

DHT,Define Huffman Table定义Huffman表,标记码为0XFFC4;包含2个字段:

(1)数据长度,2个字节,表示(1)--(2)的总长度,即,不包含标记代码,但包含本字段;

(2)Huffman表,数据长度-2个字节,包含以下字段:

(a)表ID和表类型,1个字节,高4位表示表的类型,取值只有两个;0:DC直流;1:AC交流;低4位,Huffman表ID;需要提醒的是,DC表和AC表分开进行编码;

(b)不同位数的码字数量,16个字节;

(c)编码内容,16个不同位数的码字数量之和(字节);

本标记段中,字段(2)可以重复出现,一般需要重复4次。

DRI,Define Restart Interval,定义差分编码累计复位的间隔,标记码为固定值0XFFDD;

包含2个具体字段:

(1)数据长度:2个字节,取值为固定值0X0004,表示(1)(2)两个字段的总长度;即,不包含标记代码,但包含本字段;

(2)MCU块的单元中重新开始间隔:2个字节,如果取值为n,就代表每n个MCU块就有一个RSTn标记;第一个标记是RST0,第二个是RST1,RST7之后再从RST0开始重复;如果没有本标记段,或者间隔值为0,就表示不存在重开始间隔和标记RST;

SOS,Start Of Scan,扫描开始;标记码为0XFFDA,包含2个具体字段:

(1)数据长度:2个字节,表示(1)--(4)字段的总长度;

(2)颜色分量数目:1个字节,只有3个可选值,1:灰度图;3:YCrCb或YIQ;4:CMYK;

(3)颜色分量信息:包括以下字段,

(a)颜色分量ID:1个字节;

(b)直流/交流系数表ID,1个字节,高4位表示直流分量的Huffman表的ID;低4位表示交流分量的Huffman表的ID;

(4)压缩图像数据

(a)谱选择开始:1个字节,固定值0X00;

(b)谱选择结束:1个字节,固定值0X3F;

(c)谱选择:1个字节,固定值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两个字节进行译码,并按RST标记的规则调整译码变量;

(4)0XFF,忽略当前0XFF,对后一个0XFF进行判断;

(5)其它数值,忽然当前0XFF,并保留紧接着此数值用于译码;

需要说明的是,JPEG文件格式中,一个字(16位)的存储使用的是Motorola格式,而不是Intel格式。也就是说,一个字的高字节(高8位)在数据流的前面,低字节(低8位)在数据流的后面,与平时习惯的Intel格式有所不同。这种字节顺序问题的起因在于早期的硬件发展上。在8位CPU的时代,许多8位CPU都可以处理16位的数据,但它们显然是分两次进行处理的。这个时候就出现了先处理高位字节还是先处理低位字节的问题。以Intel为代表的厂家生产的CPU采用先低字节后高字节的方式;而以Motorola,IBM为代表的厂家生产的CPU则采用了先高字节后低字节的方式。Intel的字节顺序也称为little-endian,而Motorola的字节顺序就叫做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)。因此,如果需要的话,请在处理之间进行依次高低字节的转换。

(2) 读取Huffman表

在标记码DHT之后,包含了一个或者多个Huffman表(通常是4个表)。对于一个Huffman表而言,它包含了以下三部分内容:

(a)表ID和表类型;1个字节;仅有4个可选的取值,0X00,0X01,0X10,0X11,分别表示DC直流0号表,DC直流1号表,AC交流0号表,AC交流1号表;

(b)不同位数的码字数量;前面提到,JPEG中的Huffman编码表是按照编码长度的位数以表格的形式保存的,而且,Huffman编码表的位数只能是1--16位,因此,这里用16个字节来分别表示1--16位的每种位长的编码在Huffman树中的个数。

(c)编码内容;该字段记录了Huffman树中各个叶子节点的权重,上一个字段(不同位数的码字数量)的16个数值之和,就是本字段的长度,也就是Huffman树中叶子节点的个数。

这里,我们不妨以下面一段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、表类型、不同位数的码字数量信息;

第一行的第一个字节0X11代表了表的ID和类型是AC交流1号表;

第一行的第2到第17字节代表了不同位数码字的数量。即,第2个字节00表示没有位数为1的编码;第3个和第4个字节的02表示位数为2和位数为3的编码各有两个;第5个字节的00表示没有位数为5的编码。。。。此外,通过这些数据我们发现,此Huffman树有0+2+2+0+5+1+6+1=17个叶子节点。

第二行为编码的内容,表明17个叶子节点按照从小到大的顺序排列,即,权值依次为0,1,11,2,21,3,31,41...

(3) 构建Huffman树

读取到Huffman表的数据之后,就需要构建Huffman树了。其具体规则如下

(a)第一个编码的数字必定为0;如果第一个编码的位数为1,就被编码为0;如果第一个编码的位数为2,就被编码为00;如果第一个编码的位数为3,就被编码为000。。。

(b)从第二个编码开始,如果它和它前面编码具有相同的位数,则当前编码是它前面的编码加1;如果它的编码位数比它前面的编码位数大,则当前编码时它前面的编码加1之后再在后面添加若干个0,直到满足编码位数的长度为止。

还是以上面的数据为例:

第一行的第2个字节00表示没有位数为1的编码;

第一行的第3个字节02表示位数为2的编码有2个;由于没有位数为1的编码,因此这里位数为2的编码中的第一个为00,第二个为00+1=01;

第一行的第4个字节02表示位数为3的编码有2个;因此,这里位数为3的编码中的第一个为01+1=10,然后添加1个“0”,得到100;位数为3的编码中的第二个为100+1=101;

依次类推,可以得到如下的Huffman树

Snip20161124_1.png

特别提醒的是,如果中间有某个位数的编码缺失,例如,没有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系数也是最先被读取出来,假设,我们一次性读入了若干个字节长度的数据。其中的第一个字节代表了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系数的值。如此反复循环,直到遇到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逆变换的计算公式为:

![Uploading 1345131501_6624_203673.png . . .]

DCT逆变换的公式,可以改写为:

1345131501_6624.png

其中A为矩阵:

1345131617_8476.png
1345131629_6848.png
1345131650_4581.png

左边为未转秩的数据顺序,右边为转秩之后的数据顺序。

(9)颜色模式转换

BMP图片是以RGB颜色空间进行保存的,因此,将JPEG解码为BMP必须进行颜色模式的转换。另外,由于DCT要求的定义域对称,所以,在编码的时候将RGB的数值范围从[0,255]统一减去128,将数值范围转换到[-128,127]的范围内。因此,解码的时候,必须为每个颜色分量加上128。另外需要注意的是,通过解码变换之后得到的RGB的值有可能超过255或者小于0;如果小于0,就截断为0,如果大于255,就截取为255;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容