如本文有任何差错和疑问,希望网友指正和共同研讨。 Blog: http://hi.baidu.com/cat%5Fng/
JPEG文件编/解码详解 cat_ng 猫猫 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个具体字段:
本标记段可以包含图像的一个微缩版本,存为24位的RGB像素。如果没有微缩图像(这种情况更常见),则字段⑦“缩略图水平像素数目”和字段⑧“缩略图垂直像素数目”的值均为0。
l APPn,Application,应用程序保留标记n,其中n=1~15(任选) u 标记代码 2字节 固定值0xFFE1~0xFFF u 包含2个具体字段: 例如,Adobe Photoshop生成的JPEG图像中就用了APP1和APP13两个标记段分别存储了一幅图像的副本。
l DQT,Define Quantization Table,定义量化表 u 标记代码 2字节 固定值0xFFDB u 包含9个具体字段: a) 精度及量化表ID 1字节 高4位:精度,只有两个可选值 b) 表项 (64×(精度+1))字节 例如8位精度的量化表
本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。
u 标记代码 2字节 固定值0xFFC0 u 包含9个具体字段: a) 颜色分量ID 1字节 b) 水平/垂直采样因子 1字节 高4位:水平采样因子 c) 量化表 1字节 当前分量使用的量化表的ID 本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。
l DHT,Difine Huffman Table,定义哈夫曼表 u 标记代码 2字节 固定值0xFFC4 u 包含2个具体字段: a) 表ID和表类型 1字节 高4位:类型,只有两个值可选 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个具体字段:
如果没有本标记段,或间隔值为0时,就表示不存在重开始间隔和标记RST
u 标记代码 2字节 固定值0xFFDA u 包含2个具体字段: 而JFIF中使用YCrCb,故这里颜色分量数恒为3 ④ 压缩图像数据
本标记段中,字段③应该重复出现,有多少个颜色分量(字段②),就出现多少次(一般为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和表类型 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 红色部分(第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。 2)从第二个码字开始, b)举例说明 继续以上边的例子说明问题。 n 由于没有1位的码字,所以第一个码字的位数为2,即码字为00; n 由于2位的码字有两个,所以第二个码字位数仍为2,即码字为00+1=01; n 第三个码字为3位,比第二个码字长1位,所以第三个码字为:01+1=10,然后再添1个“0”,得100; n …… 如此类推,最后得到这个哈夫曼树如下:
特别注意的是,如果中间有某个位数的码字缺失,例如没有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分量的采样点。 换句话说,对于整张图片来说,数据流的数据依次是:
图 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的顺序是: 每个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个采样点。有 因此,对于整张图片来说,数据流的数据依次是:
3.颜色分量单元的内部解码 1)理论说明 “颜色分量单元”是笔者为说明问题而建立的概念,指的是MCU中某个颜色分量中的一个8*8数据块,例如上面提到的Y1 、Cr1、Cb1 都是一个颜色分量单元。 图像数据流是以位(bit)为单位存储信息的。并且内部的数据都是在编码时通过正向离散余弦变换(FDCT)进行时空域向频率域变换而得到的结果,所以对于每个颜色分量单元都应该由两部分组成:1个直流分量和63个交流分量。 解码的过程其实就是哈夫曼树的查找过程。 首先查阅标记段SOS中的颜色分量信息,可以得出各个颜色分量对应使用的直流分量和交流分量使用的哈夫曼树编号。一般来说, Cr分量:直流分量:直流1号哈夫曼树,交流分量:交流1号哈夫曼树; Cb分量:直流分量:直流1号哈夫曼树,交流分量:交流1号哈夫曼树。
颜色分量单元内部综合运用了RLE行程编码和哈夫曼编码来压缩数据。每个像素的数据流由两部分构成:编码和数值,并且两者基本以互相隔开方式出现(除非该编码的权值为零)。具体读入单个颜色分量单元的步骤如下: a)从此颜色分量单元数据流的起点开始一位一位的读入,直到读入的编码与该分量直流哈夫曼树的某个码字(叶子结点)一致,然后用直流哈夫曼树查得该码字对应的权值。权值(共8位)表示该直流分量数值的二进制位数,也就是接下来需要读入的位数。 b)继续读入位数据,直到读入的编码与该分量交流哈夫曼树的某个码字(叶子结点)一致,然后用交流哈夫曼树查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。 c)不断重复步骤b,直到满足交流分量数据结束的条件。而结束条件有两个,只要满足其中一个即可: ①当读入码字的权值为零,表示往后的交流变量全部为零; ②已经读入63个交流分量。 d)各个数值的译码是按下表进行的:
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
直流哈夫曼树 交流哈夫曼树
详细说明一下: 读入数据流并对照直流哈夫曼树,第一个哈夫曼编码为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的矩阵中应为:
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./download.asp?f=itu-1150PDF (访问日期:2007-1-1) [4] 公子御风,JFIF文件格式即JPEG文件交换格式(JPEG File Interchonge Format).http://cat1226./4574350.html (访问日期:2006-12-29) [5] 云风,JPEG 简易文档 V2.11.http://rtornados./2442419.html (访问日期:2006-12-30)
|
|