如06对应 Huffman 表的111000,那么
69 = (4,5) --- 1111111110011001 (69=0x45=4*16+5 )
21 = (1,5) --- 11111110110
从而得到最后的结果:
111000 111001 ; 111000 101101 ; 1111111110011001 10111 ; 11111110110 00001…
使用范式 Huffman 编码表的好处就是使得出现频率高的数字小于8位,而出现频率低的数字大于8位,这样对整体而言,就会极大地减少数据量。
需要注意的是,在 JPG 文件中,一般有两个 Huffman 表,一个是 DC 用,一个是 AC 用,它们是类似的。
对 DC 编码的部分是单独来处理的,并且是放在上面这个串的最前面。
总体来说,到目前为止,我们就得到了最后需要真正存储用的简化后,也即压缩后的数据了。
四、JPEG文件存储格式
介绍了 JPEG 的原理,我们再来结合一个具体的实例来详细讨论上面所涉及到的细节。
我们先来制作一个简单的8X8大小的像素图,然后把它存成JPEG格式。
方法是用 windows 的画图工具,定义一个8X8大小的图,用一些色块填充进去,然后另存为 JPEG 格式,如 test8x8.jpg。如下图所示:
![](http://blogimg.chinaunix.net/blog/upfile2/081113094435.jpg)
JFIF 是一个文件格式标准,JPEG 是一个压缩标准,总体来说它们不是一个概念。
JFIF 是 JPEG File Interchange Format 的缩写,也即 JPEG 文件交换格式。JFIF 是一个图片文件格式标准,它是一种使用 JPEG 图像压缩技术存储摄影图像的方法。JFIF 代表了一种"通用 语言 "文件格式,它是专门为方便用户在不同的计算机和应用程序间传输 JPEG 图像而设计的语言。
JFIF 文件格式定义了一些内容是 JPEG 压缩标准未定义的,如 resolution/aspect ratio,color space 等。
我们可以打开 JPEG 文件查看里面的内容,即可看到上面的各个标记段:
![](http://blogimg.chinaunix.net/blog/upfile2/081113094558.jpg)
从图上可以看出:
在头部有 FFD8 ,表示图像的开始;结束部分有 FFD9 ,表示图像的结束。
在中间有两个量化表 DQT 对应的标记 FFDB ;
还有图像大小信息对应的 FFC0
再后面有四个 Haffman 表对应的 FFC4 ;
一般一个 JPG 文件里会有 2 类 Haffman 表:一个用于 DC 一个用于 AC ,也即实际有 4个表,亮度的 DC,AC 两个,色度的 DC,AC 两个。
然后是图像数据段标记 FFDA;
我们再来看看各个标记的细部,具体分析一下各个部分的含义。
1、图片的识别信息
![](http://blogimg.chinaunix.net/blog/upfile2/081113094627.jpg)
2、量化表的实例
![](http://blogimg.chinaunix.net/blog/upfile2/081113094719.jpg)
也即第一个 DQT 量化表的内容表示为十进制是:
![](http://blogimg.chinaunix.net/blog/upfile2/081113094746.jpg)
第二个量化表的内容为:
![](http://blogimg.chinaunix.net/blog/upfile2/081113094809.jpg)
当你打开不同的 JPEG 文件,你会看到这两个表可能也是会有区别的。这个主要是使用了不同的量化方式的结果。
3、图像信息段
![](http://blogimg.chinaunix.net/blog/upfile2/081113094851.jpg)
![](http://blogimg.chinaunix.net/blog/upfile2/081113094954.jpg)
第一个 DHT 表,00,类型为 DC table,HT ID 号为 0;
第二个 DHT 表,10,类型为 AC table,HT ID 号也为 0;
第三个 DHT 表,01,类型为 DC table,HT ID 号为 1;
第四个 DHT 表,11,类型为 AC table,HT ID 号为 1;
即前两个表为Y亮度分量的 DC/AC 表,后两个为 UV 色度分量的 DC/AC 表。
以第一个表为例,因为长度只有 31,那么 00 后面的 16 字节,即绿色部分:
![](http://blogimg.chinaunix.net/blog/upfile2/081113095024.jpg)
组号为 2 的,代码有 1 个;
组号为 3 的代码有 5 个;
组号为 4/5/6/7/8/9 的代码各 1 个。
总共 12 个。
再看后续的数据:
00 01 02 03 04 05 06 07 08 09 0A 0B
即对应:
![](http://blogimg.chinaunix.net/blog/upfile2/081113095440.jpg)
其他未出现的组号,对应的数据未使用到。也就是说前面提到过的范式 Huffman 编码里,目前只使用部分数据即可,原因是这个 8x8 的图像数据很小。
5、图像数据段
这里 SOS 段,长度为 12,后面所含有的 component 数量为 3 个,也即 Y UV。然后后面是各 component 的编号,及对应所使用的 Huffman 表的 ID 是多少。
在这个段的后面就是所有压缩后的数据。直到结束的问题,即 FFD9,EOI(End Of Image)。
五、JPEG 压缩过程的优化
JPEG 在目前的应用范围是非常广泛的,各种嵌入式系统中也大量地使用了 JPEG 压缩,如 IPCAM 摄像头、数字相机、移动存贮等。在这些领域由于传输数据的带宽限制或者是存贮数据的容量的限制,常常需要使用图像压缩技术来将原始大量的图像数据压缩后在进行传输或存贮,以充分利用带宽与存贮空间,达到更好的利用效率。这样,在嵌入系统中,就会使用到 JPEG 压缩。而且由于嵌入系统的资源有限的特点,在很多情况下,很需要再对 JPEG 编码压缩的过程做更进一步的优化,我们这里详细讨论一下如何实现这些优化。
浮点运算的优化
我们回头查看一下 JPEG 压缩中的 DCT 变换过程,公式:
![](http://blogimg.chinaunix.net/blog/upfile2/081113101229.jpg)
上面的公式属于 DCT 的二维计算方式,经过简化,可以将其简化为两个一维的公式:
![](http://blogimg.chinaunix.net/blog/upfile2/081113101340.jpg)
这样,上面的过程就可以简化为分别计算行和列的 DCT 变换。
对于一行来说需要计算的是 (8 x 8) 次乘法和 (8 x 8) 次加法,8 行就是 8 x (8 x 8) 次乘法和 8 x (8 x 8) 次加法,然后列也是相同,那么总数就为 2 x (8 x (8 x 8))=1024 次乘法和 2 x (8 x (8 x 8))=1024 次加法, 运算量变为二维计算的1/4。
但是这样的运算数量还是太大,还需要进一步优化。
在很多嵌入系统中,很多情况下需要不使用浮点运算,这样就需要再找出一维 DCT 的一些规律,然后对其进行进一步的优化。
在对一维 DCT 的运算中,还可以分为奇数列/行和偶数列/行
![](http://blogimg.chinaunix.net/blog/upfile2/081113101739.jpg)
其中 AAN 算法只需要 29 次加法和 5 次乘法。(注意,它是指每次一维运算要 29 次加法和 5 次乘法,一共是需要 29*8*2 次加法和 5*8*2 次乘法的)。
![](http://blogimg.chinaunix.net/blog/upfile2/081113101811.jpg)
{a, b, c, d, e, f, g} = 1/2 { cos(pi/4), cos(pi/16), cos(pi/8), cos(3pi/16), cos(5pi/16), cos(3pi/8), cos(7pi/16) }
再对上面的含有 pi 的系数进行整数优化,从而避免浮点运算,就会得到:
![](http://blogimg.chinaunix.net/blog/upfile2/081113101838.jpg)
3/8=1/4+1/8
5/8=1/2+1/8
7/8=1-1/8
上面的除以 2,除以 8,都可以通过移位来实现,即右移一位和右移三位。即总数为 30 次加法,12 次移位即可。
这样就在很大程度上将原本需要使用乘法,浮点运算的过程全部转换成了简单的加法和移位处理了,这样使用数学的方法,用近似的值来完成整个转换过程,会有很好的 性能 和处理效果。
在处理上面的数据中,可以使用一些中间变量来记录中间结果,这样就可以减少反复计算中间值,而直接使用已经计算得到了的中间值。
tmp0 = x[0] + x[7];
tmp7 = x[0] - x[7];
tmp1 = x[1] + x[6];
tmp6 = x[1] - x[6];
tmp2 = x[2] + x[5];
tmp5 = x[2] - x[5];
tmp3 = x[3] + x[4];
tmp4 = x[3] - x[4];
tmp10 = tmp0 + tmp3;
tmp13 = tmp0 - tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp1 - tmp2;
/* 对偶数项进行运算 X 0,4,6,2 */
X[0]=tmp1+tmp11;
X[4]=tmp10 /2 - tmp11
X[6]=tmp12-(tmp13/4+ tmp13/8);
X[2]=tmp12/4+tmp12/8-tmp13;
其他的各个值也是类似处理的。
六、JPEG 在本嵌入式 Linux 应用中遇到的问题
在本系统中,提供给用户一些播放图片和预览图片的功能,在这个过程中就需要使用到对 JPEG 的处理。
1、JPEG 出错的处理
在对图片做预览处理的时候,有些图片原始尺寸很大,那么就需要将其转换成较小的缩略图,在转换为缩略图进行显示时,遇到了一个问题,即有时需要显示的图片,会导致系统无响应。
后来查找原因,定位到 JPEG 文件的数据不完整,才导致 jpeg decoder 出现无响应。
在前面的部分,有说到 JPEG 文件的格式中,JPEG 结束的标记 EOI (End Of Image) 为“FFD9”。
如果需要显示的图片,在传输过程中,或转换过程中,出现了没有 EOI 数据,那么应该在程序中,将其废弃,避免出现系统无响应。
2、JPEG 解码的效率优化
在解码 JPEG 时,可以使用 software decode 或 hardware decode 来处理。Hardware decode 的优点是充分利用 DSP 所提供的硬件解码功能,其解码速度会较 software decode 有数量级的提高。但有时使用 hardware decode 有一些限制,如各种 DSP 提供的 SDK 会是直接访问硬件,将 jpeg 直接输出到显示设备,从而会导致 hardware decode 与应用系统集成的麻烦。
而使用 software decode,就能在应用层完全掌握 jpeg decode 的数据缓冲结果,并可做一些图片的叠加效果或对其进行半透明混合处理,从而会有较高的灵活性。并且使用 decode buffer cache,来将已经解码的数据进行保存,在 UI 后续的使用中,就可不必反复去解码 JPEG 图片,从而也能有效提高绘图效率。
七、总结
上面的内容是本人对 JPEG 原理做的一个详细的实例分析,还介绍了 JPEG 编码过程中对浮点运算的优化处理,它特别适用于在资源有限的嵌入系统中避免大量的浮点运算。
在对 JPEG 原理做了一个详细的分析后,大家会对 JPEG 涉及到的各个细节有了一个更加明确的认识。当你自己动手结合 JPEG 编码过程来分析时,将会有一个清楚的全局观。
本文结合应用实例,对在嵌入式 Linux 应用中遇到的 JPEG 有关的问题,做了一个说明,大家在自己的设计过程中也可以作为参考。
现在,你如果再回头去看 JPEG 的原理,你现在应该能看懂它整个过程的来龙去脉了。