JPEG图像压缩算法能够在提供良好的压缩性能的同时,具有比较好的重建质量,被广泛应用于图像、视频处理领域,是第一个国际图像压缩标准。

1JPEG编码基本流程

【数据压缩】JPEG编解码

1)JPEG规定了4种运行模式,以满足不同需要

2)8×8的块编码,将一个宏块作为基本编码单元(MCU),这样可提高编码效率和实时性。

3)零偏置:JPEG编码时,输入的YUV信号由于都是unsigned char类型,取值为0~255,所以需要先进行下拉128操作,让其以0为中心

4DCT变换:对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,作为两维离散余弦变换DCT的输入

5量化:由于DCT变换后零值会有很多,尤其在高频成分中,所以选用了中平量化器量化步距是按照系数所在的位置和所在颜色分量来确定因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化;如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别较大反之,细节少的原始图象在压缩时去掉的数据少些。进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。

6DCAC系数分别编码:经过量化后,DCT的第一个系数为直流DC系数,由于一般图像中的DC系数之间相关性较强,相邻的DC系数之间应该差别不是很大。所以将每个宏块的DC系数取出后做DPCM差分编码,对于差值进行Huffman编码,以达到压缩效果。

而对AC系数,由于一般数值都集中在左上角,而之后基本上都是零值,所以采用之字形扫描的方法,按照低频到高频的顺序将数值取出,当后边都是零的时候给出一个EOB代替。而当高频偶尔有值的时候就很有可能出现很多连零,所以在将AC的系数提出后,先要进行游程化,再进行熵编码,实现压缩效果。

之字形排序优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。

【数据压缩】JPEG编解码

2JPEG文件格式

SOI ,Start of Image,   图像开始 标记代码2字节固定值0xFFD8

EOIEnd of Image,   图像结束2字节 标记代码2字节固定值0xFFD9

APP0 ,Application,应用程序保留标记0标记代码2字节固定值0xFFE0

DQTDefine Quantization Table,定义量化表  标记代码2字节固定值0xFFDB

包含9个具体字段:
  ① 数据长度2字节字段①和多个字段②的总长度
   量化表数据长度-2字节

SOF0Start of Frame,帧图像开始 标记代码2字节固定值0xFFC0

DHTDefine Huffman Table,定义哈夫曼表 标记代码  2字节  固定值0xFFC4

包含2个具体字段:

     ① 数据长度       2字节

      huffman数据长度-2字节

ID和表类型1字节 

   4位:类型,只有两个值可选0DC直流;1AC交流
   4位:哈夫曼表ID,注意,DC表和AC表分开编码

SOSStart of Scan,扫描开始12字节 标记代码2字节固定值0xFFDA

如一张JPEG格式图片用二进制打开可看到:

【数据压缩】JPEG编解码

3JPEG解码

JPEG解码流程则与编码流程相反:

1)、读入文件

2)、解析文件头:

解析SOI,判断为JPEG文件

解析APP0,得到相关参数,有时还有APPn

解析DQT,得到量化表,几张取决于颜色分量

解析SOF0,得到有关实际图像数据的信息,如长宽、采样信息等

解析DHT,得到霍夫曼码表,建立解码用的查找表

解析SOS,得到分量数,图像数据开始,文件头解析结束

3)、解码图像数据

根据采样信息计算MCU大小,不同MCU有不同的解码方案

MCU为单位:

霍夫曼解码,得到DCT数据

DCT数据进行反量化

对实际DCT系数进行DCT反变换,得到本MCU实际数据

解完所有MCU,将其组织为正确顺序的数据,即得到所有分量实际数据    

4)、根据要求的输出格式进行输出

4、主要代码分析

1)理解struct huffman_tablestruct componentstruct jdec_private

huffman table :在霍夫曼解码时,需要用到霍夫曼查找表,码长等诸多信息,我们设计一个结构体来储存

component:解码时,这个结构体来储存当前宏块的所用解码信息

jdec_private:设计一个结构体,将所需要用到的所有数据都存入其中,用它在不同的模块之间通信

2)先将输出文件改成一个yuv文件输出,在write_yuv中修改

static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
  FILE *F,*AcFile,*DcFile;
  char temp[1024];
  snprintf(temp,1024,"%s.yuv",filename);
  F = fopen(temp,"wb");
  fwrite(components[0],width,height,F);
  fwrite(components[1],width*height/4,1,F);
  fwrite(components[2],width*height/4,1,F);//写在一个文件中
  fclose(F);
  AcFile = fopen(AcFilename, "wb");
  DcFile = fopen(DcFilename, "wb");
  fwrite(outDCbuf, width * height *3 /64 , 1, DcFile);//直流分量文件
  fwrite(outACbuf, width * height *3 /64 , 1, AcFile);//交流分量文件
  fclose(DcFile);
  fclose(AcFile);

}

3)在定义了自己需要输出表格的文件指针c_file,输出的AC图像AcFileDC图像DcFile。之后out_str[20]用作在c_file中输出的字符串。随后为ACDC图像分别开了缓冲区。在之后进行了宏定义,除了三个文件名外,ACNUM为提取的AC图像所在的DCT的位置索引。

在tinyjpeg.h中添加

FILE *c_file;//类似与trace的文件,不过要输出txt
FILE *AcFile ;
FILE *DcFile ;
static char out_str[20];
static unsigned char *outDCbuf = NULL;
static unsigned char *outACbuf = NULL;

#define ACNUM 2
#define C_TABLE 1
#define C_FILENAME "table.txt"
#define AcFilename "acfile.yuv"
#define DcFilename "dcfile.yuv"

在main函数中:

#if C_TABLE
  c_file = fopen(C_FILENAME,"w");
  if(c_file == NULL)
  {
   printf("table file open error!");
  }
 
#endif

4)提取量化表。ref_table是以zz作为索引进行反之字形变换存储。而之字形取值就是通过提前建立zig-zag顺序表来实现,先建立一个存有排序后编号的数组,把这个数组中的数再作为另一个数组的标号。

static const unsigned char zigzag[64] =

{

   0,  1,  5,  6, 14, 15, 27, 28,

   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

};


build_quantization_table函数中添加:

//get the Q table
#if C_TABLE
  snprintf(out_str, sizeof(out_str),"%d", ref_table[*(zz-1)]);//zz就是按照zigzag索引来的
  fprintf(c_file,"%s\t",out_str);
  if(j == 7)
   fprintf(c_file,"\n\n");
  fflush(c_file);
#endif

5)提取Huffman表。在build_huffman_table函数中仿照了TRACE直接在函数中将valcodecodesize输出到了txt中。

#if C_TABLE
     fprintf(c_file,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
  fflush(c_file);
#endif

#if C_TABLE
     fprintf(c_file,"\n\n");
  fflush(c_file);
#endif

6)提取DCAC系数输出图像。具体数值从jdecprivite结构体中component结构体里的DCT中直接提取的,对于UV的写入直接采用了在Y的基础上分别加上1倍和2倍图像size的办法找到对应的位置,而且没有进行下采样。还需注意取值范围,由于是8x8的宏块,所以在给DC赋值的时候需要做除以8的操作,使得取值范围变为-128~128,且所有数值均需要上拉128,让取值范围变为0~255

tinyjpeg_decode函数中添加:

//full the DC and AC buffer
  //DC:
  *(outDCbuf + dct_count) = (unsigned char)((priv->component_infos[cY]).DCT[0]/8 +128);
  *(outDCbuf + dct_count + priv->height * priv->width /64) =
   (unsigned char)((priv->component_infos[cCb].DCT[0])/8 + 128) ;//U
  *(outDCbuf + dct_count + 2 * priv->height * priv->width /64) =
   (unsigned char)((priv->component_infos[cCr].DCT[0])/8 + 128) ;//V

  //AC:
  *(outACbuf + dct_count) = (unsigned char)((priv->component_infos[cY]).DCT[ACNUM] +128 );
  *(outACbuf + dct_count + priv->height * priv->width /64) =
   (unsigned char)((priv->component_infos[cCb]).DCT[ACNUM] +128 );
  *(outACbuf + dct_count + 2 * priv->height * priv->width /64) =
   (unsigned char)((priv->component_infos[cCr]).DCT[ACNUM] +128 ); 

 

5、实验结果分析

1)直流交流图片分析

直流分量的图形大概还原出原图像的低频部分,效果类似略缩图,而低频的AC系数也大概反映图像的轮廓,而随着AC系数的索引增大,可以看出图像越来越接近平坦,即图像的值越来越小。下图以此为DC图和索引为1AC,索引为2AC,索引为5AC

【数据压缩】JPEG编解码

2txt文件内容的展示

【数据压缩】JPEG编解码【数据压缩】JPEG编解码

【数据压缩】JPEG编解码

3)将DCAC经过Huffman编码器统计概率分布

【数据压缩】JPEG编解码

【数据压缩】JPEG编解码

【数据压缩】JPEG编解码

【数据压缩】JPEG编解码

(4)蚊子噪声

原图:

【数据压缩】JPEG编解码

JPEG压缩后造成的蚊子噪声:(本来底为白色,为了看清楚蚊子噪声,将底置黑)

【数据压缩】JPEG编解码

【抱歉,csdn博客写新文章时添加图片或代码有错位,所以代码直接粘贴上来,不美观请见谅】

相关文章: