文章在研究JPEG压缩编码对图像数据压缩的基本原理的基础上,设计了JPEG图像压缩算法程序实现流程,利用 Python语言对程序进行了编写,并实现了对压缩质量进行控制,验证了JPEG压缩编码对图像数据压缩的可行性。
用 JPEG压缩软件将图像从原图像中输出并对它进行重建,通过直观比较,发现用 JPEG压缩软件压缩图像对原图像的显示和感官仍然非常好。通过对输出的压缩比等参数的研究,科学地论证了JPEG 压缩编码对图像数据巨大的压缩效果以及良好的压缩质量。
图像等媒体信息的记录、存储正朝着数字化的方向发展。而这些被数字化的图像的数据量之大是非常惊人的。这些大容量的数据无疑对存储器容量、计算机的速度都造成极大的压力。解决这一问题,如果单纯地扩大存储器容量,在存储和处理的时候不仅因为图像数据量大而造成大量问题,同时在图像数据的传输过程中也因为网络带宽的限制而极大的制约着网络多媒体技术的发展。通信网络的飞速发展使数据流量变得越来越大,复杂性也在不断增长,仅仅依靠加大通信干线的流量是不太现实的。但是如果能通过数据压缩手段把信息数据量压缩下来,以压缩的形式存储和传输,既节约了存储空间,又提高了通信干线的传输效率,同时也使计算机能实时处理高质量的音频、视频信息。通过压缩图像数据,其最直接的作用在于,可以降低图像传输所需的带宽,同时不再需要另外的实体设备或存储容量,可以达到更高的传输精度。由此可以看出,对静态图像进行压缩是绝对必要的。
图像压缩编码是在对数字图像进行大量统计分析,在掌握和了解图像信息的统计特性的基础上,充分利用图像本身的相关性强的特点,寻求消除或减少相关性或改变图像信源概率分布不均匀性的方法,以实现图像数据的压缩。
JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。
JPEG可以用有损压缩方式去除冗余的图像数据,用较少的磁盘空间得到较好的图像品质。而且JPEG是一种很灵活的格式,具有调节图像质量的功能,它允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在10;1到40;1,压缩比越大,图像品质就越低;相反地,压缩比越小,图像品质就越高。同一幅图像,用JPEG格式存储的文件是其他类型文件的1/10~1/20,通常只有几十KB,质量损失较小,基本无法看出。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网;它可减少图像的传输时间,支持24位真彩色;也普遍应用于需要连续色调的图像中。[1]
支持极高的压缩率,因此JPEG图像的下载速度大大加快;
能够轻松地处理16.8M颜色,可以很好地再现全彩色的图像;
在对图像的压缩处理过程中,该图像格式可以允许自由地在最小文件尺寸和最大文件尺寸之间选择;
文件尺寸相对较小,下载速度快,有利于在带宽并不“富裕”的情况下传输。
并非所有的浏览器都支持将各种JPEG图像插入网页;
压缩时,可能使图像的质量受到损失,因此不适宜用该格式来显示高清晰度的图像。[2]
JPEG算法的第一个步骤是将一张图像分割成 8X8的片段,以便后续对这些片段进行处理,这些片段通过后续的单独处理,使整个压缩过程更为方便。
所谓“颜色空间”,是指表达颜色的数学模型,比如我们常见的“RGB”模型,就是把颜色分解成红绿蓝三种分量,这样一张图片就可以分解成三张灰度图,数学表达上,每一个8X8的图案,可以表达成三个8X8的矩阵,其中的数值的范围一般在[0,255]之间,如图所示。
不同的颜色模型各有不同的应用场景,例如RGB模型适合于像显示器这样的自发光图案,而在印刷行业,使用油墨打印,图案的颜色是通过在反射光线时产生的,通常使用CMYK模型,而在JPEG压缩算法中,需要把图案转换成为YCbCr模型,这里的Y表示亮度(Luminance),Cb和Cr分别表示绿色和红色的“色差值”。
“色差”概念的产生源自电视业,早期的电视机都是黑白两种颜色,那时传送电视信号,仅需传送亮度信号也就是 Y信号。而彩色电视机出现在那以后,人们在 y信号之外又添加两条色差讯号来传输色彩信息,这样的做法就是兼容黑白电视机,黑白电视机只需要处理该讯号中的 Y讯号即可。
根据三基色原理,人们发现红绿蓝三种颜色所贡献的亮度是不同的,绿色的“亮度”最大,蓝色最暗,设红色所贡献的亮度的份额为KR,蓝色贡献的份额为KB,那么亮度为:
后续python程序实现JPEG压缩算法时,考虑到调用numpy库压缩效率将远远超过使用for循环,因此采用上式的矩阵表达形式。
为什么我们把一个看似毫不相关的色彩空间转换算法添加到压缩算法中呢?这并非画蛇添足,在文章开始时提到有损压缩的基本原理,有损压缩首先要做的事情就是“把重要的信息和不重要的信息分开”,YCbCr恰好能做到这一点。由于人眼的构造,图像中的明暗变化对于人眼而言更容易被感知。根据本学期另一门专业选修课程的知识,在视网膜上有两种感光细胞,分别是能观测光线变化的视杆细胞,与能观测色彩的视锥细胞,由于视杆细胞比视锥细胞数目要多,我们更加能够观测出明暗变化的细节。比如说下面这张图:
可以看出,亮度图的细节更加丰富。JPEG把图像转换为YCbCr图像之后,就可以针对数据得重要程度的不同做不同的处理。这就是为什么JPEG使用这种颜色空间的原因。
离散余弦变换(Discrete cosine transform),简称DCT,是JPEG算法中的核心内容。离散余弦变换属于傅里叶变换的另外一种形式,当要处理的不再是函数,而是一堆离散的数据时,并且这些数据是对称的话,那么傅里叶变化出来的函数只含有余弦项,这种变换称为离散余弦变换。举个例子,有一组一维数据[x0,x1,x2,…,xn-1],那么可以通过DCT变换得到n个变换级数Fi:
此时原始数据Xi可以通过离散余弦变换变化的逆变换(IDCT)表达出来:
也就是说,经过DCT变换,可以把一个数组分解成数个数组的和,如果我们数组视为一个一维矩阵,那么可以把结果看做是一系列矩阵的和:
在JPEG压缩过程中,经过颜色空间的转换,每一个8X8的图像块,在数据上表现为3个8X8的矩阵,紧接着我们对这三个矩阵做一个二维的DCT转换,二维的DCT转换公式为:
举个例子,比如一个所有数值都一样的矩阵,经过DCT转换后,将所有级数组合成一个新的矩阵:
可以看到,经过DCT转换,矩阵被全部集中在左上角的直流分量F(0,0)上,其他位置都变成了0。在实际的JPEG压缩过程中,由于图像本身的连贯性,一个8X8的图像中的数值一般不会出现大的跳跃,经过DCT转换会有类似的效果,左上角的直流分量保存了一个大的数值,其他分量都接近于0。我们以Lenna左上角第一块图像的Y分量为例,经过变换的矩阵为:
数据经过DCT变化后,被明显分成了直流分量和交流分量两部分,为后面的进一步压缩起到了充分的铺垫作用,可以说是整个JPEG中最重要的一步。
图像经过离散余弦变换,图像数据虽然已经面目全非,但仍然是处于可逆状态,并没有进入有损压缩中有损的那一步。接下来我们看一下数据中的细节是如何被滤去的,经过颜色空间转换和离散余弦变换,每一个8X8的图像块都变成了三个8X8的浮点数矩阵,分别表示Y,Cr,Cb数据。比如以其中某个亮度数据矩阵举例,它的数据如下:
在可以损失一部分精度的情况下,如何用更少的空间存储这些浮点数?答案是使用量子化(Quantization),简称量化。“量子”这个概念来自于物理学,意思是说连续的能量可以看做是一个个单元体的组合,比如游戏中在处理角色面朝方向时,一般并不是使用0到2π这样的浮点数,而是把方向分成16个区间,用0到16这样的整数来表示,这样只用4个bit就足够了。JPEG提供的量化算法如下:
G是需要处理的图像矩阵,Q称作量化系数矩阵(Quantization matrices),JPEG算法提供了两张标准的量化系数矩阵,分别用于处理亮度数据Y和色差数据Cr以及Cb:
以左上角的-415.38为例,对应的量子化系数是16,那么round(-415.38/16)=round(-25.96125)=-26。最终得到的量子化后的结果为:
可以看到,大部分数据变成了0,这非常有利于后面的压缩存储。有损压缩就是把数据中重要的数据和不重要的数据分开,然后分别处理。DCT系数矩阵中的不同位置的值代表了图像数据中不同频率的分量,这两张表中的数据时人们根据人眼对不不同频率的敏感程度的差别所积累下的经验制定的,一般来说人眼对于低频的分量必高频分量更加敏感,所以两张量化系数矩阵左上角的数值明显小于右下角区域。在实际的压缩过程中,还可以根据需要在这些系数的基础上再乘以一个系数,以使更多或更少的数据变成0,我们平时使用的图像处理软件在生成jpg文件时,在控制压缩质量的时候,就是控制的这个系数,后续在程序实现中也会体现。
2.5 ZigZag编排
在量化之后,8*8的矩阵仍然是二维矩阵,如何调整我们DCT的结果能更高地提升压缩率呢?观察量化后的矩阵,我们发现大量信息都集中在左上角,所以我们采用ZigZag编排:
这么做的目的只有一个,就是尽可能把0放在一起,由于0大部分集中在右下角,所以才去这种由左上角到右下角的顺序,经过这种顺序变换,最终矩阵变成一个整数数组。
2.6 RLE编码
行程编码(Run Length Encoding)又称“运行长度编码”或“游程编码”,它是一种无损压缩编码。例如:5555557777733322221111111,这个数据的一个特点是相同的内容会重复出现很多次,那么就可以用一种简化的方法来记录这一串数字,如(5,6)(7,5)(3,3)(2,4)(1,7)即为它的行程编码。行程编码的位数会远远少于原始字符串的位数。举个例子来看一下:57,45,0,0,0,0,23,0,-30,-16,0,0,1,0,0,0,0 ,0 ,0 ,0,…,0,可以表示为:(0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-16) ; (2,1) ; EOB。即每组数字的头一个表示0的个数,而且为了能更有利于后续的处理,必须是 4 bit,就是说,只能是 0~15,这是这个行程编码的一个特点。JPEG使用了1个字节的高4位来表示连续“0”的个数,而使用它的低4位来表示编码下一个非“0”系数所需要的位数,跟在它后面的是量化AC系数的数值。其中(0,0)和(15,0)比较特殊,(0,0)代表块结束,(15,0)代表已经有16个连续的0。
由于作业要求中提到可以选择RLE编码或Huffman编码进行压缩,我在后续程序实现中只选择了RLE一种编码方式。
运行时可以输入质量因子,控制压缩比(压缩比越高,图像质量越差)。
主要实现方法是在量化步骤时,对JPEG亮度量化表和色度量化表乘以一个质量因子Q,改变表内数据。质量因子Q为1时,相当于JPEG标准量化表,质量因子越大,压缩比越大,质量越差;质量因子越小,压缩比越小,质量越好。以Lenna为例:
质量因子为1,相当于JPEG标准量化表压缩结果,程序还计算了压缩比约为8.023013598016913,PSNR(即峰值信噪比)值约为29.634010562060844(PSNR值越高,与原始图像越相似。PSNR高于40dB说明图像质量极好,在30—40dB通常表示图像质量是好的,在20—30dB说明图像质量差,PSNR低于20dB图像不可接受)。
随着质量因子的增大,图像质量逐渐降低,但压缩比也逐渐提高。
本文简要分析了jpeg图像压缩算法的基本原理和实现过程,并通过python实现了算法,在保证图片PSNR值在20以上时,可以达到接近90%的压缩率(如果实现Huffman编码,压缩率应该会有进一步地提升)。
但是相较于格式工厂的压缩,我的算法得到的与原图差距仍存在很多问题:压缩速率远低于格式工厂(500KB左右图像大约需要5秒,这已经是我在优化算法中所有可以使用numpy库代替for循环运算之后的结果),部分细节不明显,图像边缘出现失真(尤其是对于图像中出现颜色突变的部分,以及一些低分辨率图像,这个问题较为严重)等问题。
尽管在过程中遇到了许许多多解决了和仍未解决的困难,但通过本次大作业,我更加深入全面地理解了JPEG图像压缩算法的原理,同时也极大地提升了自己的动手能力。相信随着技术的进步,各种压缩方法会越来越多,质量越来越好,得到进一步的发展与应用。
[1] 吴娱.数字图像处理:北京邮电大学出版社,2017:47-48.
[2] 李小平.多媒体技术:北京理工大学出版社,2015:185.
[3] JPEG压缩原理详解:https://www.cnblogs.com/Arvin-JIN/p/9133745.html.
[4] 二维离散余弦变换2D-DCT实战:https://blog.csdn.net/ahafg/article/details/48808443.
[5] ZigZag变换加速,空间换时间做法:https://my.oschina.net/tigerBin/blog/1083549.