1、任务说明
将LENA图像用JPEG方式压缩。
2、算法原理
JPEG(Joint Photographic Experts Group)是一个由ISO和IEC两个组织机构联合组成的一个专家组,负责制定静态
的数字图像数据压缩编码标准,这个专家组开发的算法称为JPEG算法,并且成为国际上通用的标准,因此又称为JPEG标准。
JPEG是一个适用范围很广的静态图像数据压缩标准,既可用于灰度图像又可用于彩色图像。
JPEG专家组开发了两种基本的压缩算法,一种是采用以离散余弦变换(Discrete Cosine Transform,DCT)为基础的
有损压缩算法,另一种是采用以预测技术为基础的无损压缩算法。JPEG压缩是有损压缩,它利用了人的视角系统的特性,使用
量化和无损压缩编码相结合来去掉视角的冗余信息和数据本身的冗余信息。JPEG算法框图如图:
其中,压缩算法的主要步骤如下:
1) 正向离散余弦变换(FDCT)
通过离散余弦变换,空间域表示的图可以变换成频率域表示的图。离散余弦变换的公式如图所示:
然而,这种变换方式时间复杂度很高,会占用程序的运行时间。因此,在这里我们使用DCT的矩阵变换公式替代之。
余弦变换矩阵C满足:
此时,设C的转置矩阵为 ,则离散余弦变换公式可以表示为:
其中,P为像素矩阵。使用余弦变换矩阵可以大大的减少离散余弦变换的运算时间。值得注意的是,在离散余弦变换矩阵
变换公式中,我们添加了sqrt(1/N)这个成分。该成分的作用是,将是变换矩阵C成为一个正交矩阵。因此,我们有:
那么,在解压缩过程中,我们同样可以使用离散余弦变换矩阵实现逆向的离散余弦变换。
2) 量化
量化是对经过FDCT变换后的频率系数进行量化。量化的目的是减小非“0”系数的幅度以及增加“0”值系数的数目。量化是
图像质量下降的最主要原因。量化过程的公式可以表示为:
量化后的值(i,j) = ROUND(DCT(i,j)/量子(i,j));
逆量子化公式则为:
DCT(i,j) = 量化后的值(i,j)*量子(i,j);
其中,量子由对应的量子表得到。一种常用的亮度和色度量子表是:
这种表对于CCIR 601标准的电视图像是最佳的。我们还可以定义自己的量子表,或者借鉴量子表公式:
对应不同的quality值,图像也会产生不同的压缩效果,具体将在实验中进行展示。
3) Z字形编排
经过后FDCT和量子化变化之后的系数矩阵,会呈现左上角系数较大,而右下方0居多的趋势。为了达到压缩的效果,我们对系数
矩阵进行Z字形编排。编排后的系数数组,连续的0系数增多,成为一个1*64位的矢量,频率低的系数位于矢量的顶部。
则系数矩阵对应矢量数组的顺序为:
0 |
1 |
5 |
6 |
14 |
15 |
27 |
25 |
2 |
4 |
7 |
13 |
16 |
26 |
29 |
42 |
3 |
8 |
12 |
17 |
25 |
30 |
41 |
43 |
9 |
11 |
18 |
24 |
31 |
40 |
44 |
53 |
10 |
19 |
23 |
32 |
39 |
45 |
52 |
54 |
20 |
22 |
33 |
38 |
46 |
51 |
55 |
60 |
21 |
34 |
37 |
47 |
50 |
56 |
59 |
61 |
35 |
36 |
48 |
49 |
57 |
58 |
62 |
63 |
4) 直流与交流系数的编码
8×8图像块经过DCT变换之后得到的DC直流系数有两个特点,一是系数的数值比较大,二是相邻8×8图像块的DC系数值变化不大。根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值(Delta)进行编码。
量化AC系数的特点是1×64矢量中包含有许多“0”系数,并且许多“0”是连续的,因此使用非常简单和直观的游程长度编码(RLE)对它们进行编码。
JPEG使用了1个字节的高4位来表示连续“0”的个数,而使用它的低4位来表示编码下一个非“0”系数所需要的位数,跟在它后面的是量化AC系数的数值。
5) 熵编码
为了进一步的压缩图像,我们还可以使用熵编码,一般使用霍夫曼编码劳减少熵。
以上为图像压缩过程的主要步骤。相对应的,解压缩过程为解码,Z字形编排回复,逆量子化,逆向离散余弦变换。
3、主要算法实现
1) 离散余弦变换
public class FDCT { int max = 512; double[][] C = new double[max][max]; double[][] Ct = new double[max][max]; double[][] temp1 = new double[max][max]; double[][] temp2 = new double[max][max]; SquareMul sm = new SquareMul(); //正向离散余弦变换 double[][] calculateF(double[][] pixel,int size) { int i,j; //计算余弦变换矩阵 for(i=0;i<size;i++) { for(j=0;j<size;j++) { double valueC; if(i==0) valueC = (double)1.0/(Math.sqrt(size)); else { valueC = Math.sqrt(2.0/size)*Math.cos((2*j+1)*i*Math.PI/(2*size)); } C[i][j]=Ct[j][i]=valueC; } } sm.mul(C, pixel,temp1, size); sm.mul(temp1, Ct,temp2, size); return temp2; } //使用已有的离散余弦变换矩阵,完成逆向离散余弦变换 double[][] calculateB(double[][] pixel,int size) { sm.mul(Ct, pixel,temp1, size); sm.mul(temp1, C,temp2, size); return temp2; } }
2) 量子化
public class Quantum { //默认的量化矩阵 int[][] stdQuan={ {16,11,10,16,24,40,51,61}, {12,12,14,19,26,58,60,55}, {14,13,16,24,40,57,69,56}, {14,17,22,29,51,87,80,62}, {18,22,37,56,68,109,103,77}, {24,35,55,64,81,104,113,92}, {49,64,78,87,103,121,120,101}, {72,92,95,98,112,100,103,99} }; //根据quality值生成量化矩阵 int quancal(int quality,int i,int j) { if(quality==0) return stdQuan[i][j]; else return (1+((1+i+j)*quality)); } //对像素矩阵进行量化 double[][] calculateF(double pixel[][],int size,int quality) { for(int i=0;i<size;i++) { for(int j=0;j<size;j++) { pixel[i][j]/=quancal(quality,i,j); pixel[i][j]=Math.round(pixel[i][j]); } } return pixel; } //对矩阵进行逆向量子化 double[][] calculateB(double pixel[][],int size,int quality) { for(int i=0;i<size;i++) { for(int j=0;j<size;j++) { pixel[i][j]*=quancal(quality,i,j); } } return pixel; } }
3) Z字形编排
public class zCode { int size = 8; int zlength = 64; //将矩阵Z字形编排 void zEncode(double[][] curM,double[] zpix) { int zct,i,j,fck; for(i=0;i<8;i++) { for(j=0;j<8;j++) { zct=i+j; if(zct>7) { zct=14-zct; fck=7-j; } else fck=j; if((i+j)%2==0) { zct = (1+zct)*zct/2+fck; } else { zct++; zct = (1+zct)*zct/2-fck-1; } if((i+j)>7) zpix[63-zct] = curM[i][j]; else zpix[zct] = curM[i][j]; } } } //将系数数组回复为矩阵形式 void zDecode(double[][] curM,double[] zpix) { int zct,i,j,fck; for(i=0;i<8;i++) { for(j=0;j<8;j++) { zct=i+j; if(zct>7) { zct=14-zct; fck=7-j; } else fck=j; if((i+j)%2==0) { zct = (1+zct)*zct/2+fck; } else { zct++; zct = (1+zct)*zct/2-fck-1; } if((i+j)>7) curM[i][j] = zpix[63-zct]; else curM[i][j] = zpix[zct]; } } } }
4) 分割图像
bi = ImageIO.read(f); int size = bi.getWidth(); //将图像分割成若干个8*8矩阵 int iternum = size/8; FandB fb = new FandB(); for(int i=0;i<iternum;i++) { for(int j=0;j<iternum;j++) { int code = i*64+j; fb.comF(code, pixels,zpixels[code]); } }
4、实验
源图像 默认量子表
quality=5 quality=10
quality=30
随着quality值增大,量子化矩阵中的值也相应增大,那么在量子化后的矩阵中将会有更多的0系数。经过熵编码我们得到的压缩格式也越小。与此同时,quality增大也使图像的质量明显下降。