数据压缩原理实验5_JPEG编解码原理及代码分析

来源:互联网 发布:泛微协同办公软件 编辑:程序博客网 时间:2024/06/06 18:28

一、实验原理

1、JPEG编解码原理

JPEG 是Joint Photographic Experts Group(联合图像专家小组)的缩写,是第一个国际图像压缩标准。JPEG图像压缩算法能够在提供良好的压缩性能的同时,具有比较好的重建质量,被广泛应用于图像、视频处理领域。”.jpeg”、”.jpg”等指代的是图像数据经压缩编码后在媒体上的封存形式。
下图为编码的流程图:
这里写图片描述
解码为编码的逆过程,流程图如下:
这里写图片描述

编码的具体过程如下:
(1)8*8DCT
8*8的块分割
将输入图像分为8*8的小块, 每个块里有64个像素。若边缘未满8*8,则用边缘像素进行填充(不建议用黑或白像素填充的原因是可能会破坏图像的原有结构)。
这里写图片描述
零偏置
对于灰度级是2n的像素,通过减去2n1,将无符号的整数值变成有符号数
对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值
目的:使像素的绝对值出现3位10进制的概率大大减少
DCT变换
下图为DCT变换的公式:
这里写图片描述
下图为DCT谱中其中6个频谱特性图:
这里写图片描述
这里写图片描述
由上图可见,第一行是图像行方向上分别从等值,半周余弦,一周余弦、一周半余弦…第一列是图像列方向上也是该规律,而其他的DCT值则是两者交织共同组成的,表现了整个图像不同频率上的细节成分。总体来说,图像的低频部分集中在每个8*8块的左上角,高频部分在右下角。

(2)量化
因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别;反之,细节少的原始图象在压缩时去掉的数据少些。
而量化就是用像素值÷量化表对应值所得的结果。由于上面的人眼视觉特性,量化表左上角的值较小,右上角的值较大,这样就起到了保持低频分量,抑制高频分量的目的。
下图为人眼视觉的敏感度:
这里写图片描述

(3)编码
DC系数差分编码
8X8图像块经过DC丁变换之后得到的DC直流系数有两个特点

  • 系数的数值比较大
  • 相邻8X8图像块的DC系数值变化不大:冗余

根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
DIFFk=DCkDCk1
这里写图片描述

AC系数的Z字扫描、游程编码

  • Z字扫描
    由于经DC丁变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB(End of Block)即可。
    下图为Z字扫描的扫描顺序:
    这里写图片描述

  • 游程编码
    在JPEG和MPEG编码中规定为:(run, level)
    表示连续run个0,后面跟值为level的系数
    Run : 最多15个,用4位表示RRRR
    Level : 类似DC,分成16个类别,用4位表示SSSS表示类别号、类内索引
    对(RRRR, SSSS)联合用Huffman编码
    对类内索引用定长码编码

2、JPEG文件格式

标记码 英文全称/说明 标记结构 意义 SOI start of Image 0xFFD8 图像开始 APPn Application 0xFFEn 应用细节信息 DQT define Quantization Table 0xFFDB 量化表 SOF0 start of Frame 0xFFC0 帧图像开始 DHT Define Huffman Table 0xFFC4 霍夫曼表 SOS Start of Scan 0xFFDA 扫描开始 EOI End of Image 0xFFD9 图像结束

(1)量化表DQT
一般为两个量化表,即亮度和色度各一张,以0xFFDB开始
量化表长度,一般为00 43(或00 84)
量化表信息(1字节)

  • Bit 0~3 QT号(只能取值为0~3,否则错误)
  • Bit 4~7 QT精度(0为8比特,否则表示16比特)

量化表的实际数据

  • 量化表中的数据按照Z字形保存量化表内8*8的数据

(2)DHT霍夫曼表
Huffman表长度:2字节
Huffman表ID号和类型:共1字节,高4位为表的类型,其中0表示DC直流,1表示AC交流;低4位为Huffman表ID
Huffman表位表:16字节的编码,其代码代数和为接下来的编码长度。
Huffman表值表:内容编码信息表示每个霍夫曼码字对应的值

二、代码分析

1、将输出文件保存为YUV文件

tinyjpeg.h

enum tinyjpeg_fmt {    ...   /* add by zx */   TINYJPEG_FMT_YUV420,   /* end */};

tinyjpeg.c

int tinyjpeg_decode(struct jdec_private *priv, int pixfmt){        /* add by zx  */              case TINYJPEG_FMT_YUV420:       colorspace_array_conv = convert_colorspace_yuv420p;          if (priv->components[0] == NULL)       priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);       if (priv->components[1] == NULL)       priv->components[1] = (uint8_t *)malloc(priv->width * priv->height/4);       if (priv->components[2] == NULL)       priv->components[2] = (uint8_t *)malloc(priv->width * priv->height/4);       bytes_per_blocklines[0] = priv->width;       bytes_per_blocklines[1] = priv->width/4;       bytes_per_blocklines[2] = priv->width/4;       bytes_per_mcu[0] = 8;       bytes_per_mcu[1] = 4;       bytes_per_mcu[2] = 4;       break;        /* end */}

loadjpeg.c

/* add by zx  *//*** Save a buffer in a file (.YUV) useable by yuvsplittoppm*/static void write_yuv_all(const char *filename, int width, int height, unsigned char **components){    FILE *F;    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);}/* end */
int load_multiple_times(const char *filename, const char *outfilename, int output_format){   ...   switch (output_format)   {    ...     /* add by zx */    case TINYJPEG_FMT_YUV420:      write_yuv_all(outfilename, width, height, components);       break;    /* end */    ...   }   ...}
int main(int argc, char *argv[]){    ...  if (strcmp(argv[current_argument+1],"yuv420p")==0)    output_format = TINYJPEG_FMT_YUV420P;  /* add by zx */  else if (strcmp(argv[current_argument + 1], "yuv420") == 0)      output_format = TINYJPEG_FMT_YUV420;  /* end */  ...  else  ...}

2、以txt文件输出所有的量化矩阵和所有的HUFFMAN码表

tinyjpeg.h

/* add by zx */ #define TABLES 1FILE *p_tables;/* end */

loadjpeg.c

int main(int argc, char *argv[]){.../* add by zx */ #if TABLES  snprintf(temp, 1024, "tables_of_%s.txt", output_filename);  p_tables = fopen(temp, "w");#endif/* end */.../* add by zx */ #if TABLES  fclose(p_tables);#endif/* end */...}

tinyjpeg.c

static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table){    ...    /* add by zx */#if TABLES     /*分别输出:huffman码长、对应码字、对应符号。*/     fprintf(p_tables, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);     fflush(p_tables);#endif     /* end */     ...}
static void build_quantization_table(float *qtable, const unsigned char *ref_table){  ...  for (i=0; i<8; i++)   {     for (j=0; j<8; j++)     {        *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];        /* add by zx */  #if TABLES       // ref_table指针中存取着之字形扫描的量化表值,因此需要利用zigzag[64]进行正确的排序        fprintf(p_tables, "%2d\t", ref_table[*(zigzag+i * 8 + j)]);       //用于限定一行输出8个量化值,使量化表以8×8的二维矩阵的形式输出       if (j == 7)                  fprintf(p_tables, "\n");        fflush(p_tables);#endif         /* end */     }   }   ...}
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream){  ...  while (stream < dqt_block_end)     {     qi = *stream++;  /* add by zx */ #if TABLES  fprintf(p_tables, "Quantization_tables [%d] \n", qi);    fflush(p_tables);#endif  /* end */       table = priv->Q_tables[qi];     build_quantization_table(table, stream);     stream += 64;   }   ...}
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream){...  while (length>0)   {  ...  /* add by zx */  #if TABLES     //输出AC交流及DC直流Huffman表表头 其ID以及长度      fprintf(p_tables,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);     fflush(p_tables);   #endif   /* end */  ...  }...}

3、输出DC图像及某个AC图像

tinyjpeg-internal.h

struct jdec_private{  /* add by zx */  // 用于存放输出的DC、AC图像   short int *DC_Image;  short int *AC_Image;  /* end */}

tinyjpeg.h

/* add by zx */FILE *DC_FILE;FILE *AC_FILE;static void output_DC_AC(struct jdec_private *priv);    //用于输出图像static void mapping_DC_AC(struct  jdec_private *priv);  //用于限幅映射/* end */

loadjpeg.c

int main(int argc, char *argv[]){  ...  /* add by zx */  char temp[1024];  snprintf(temp, 1024, "DC_%s.yuv", output_filename);  DC_FILE = fopen(temp, "wb");  if (DC_FILE == NULL)  {      printf("DC_FILE file open failed");  }  snprintf(temp, 1024, "AC_%s.yuv", output_filename);  AC_FILE = fopen(temp, "wb");  if (AC_FILE == NULL)  {      printf("AC_FILE file open failed");  }  /* end */  ...  /* add by zx */  fclose(AC_FILE);  fclose(DC_FILE);  /* end */  ...}

tinyjpeg.c

static void decode_MCU_1x1_3planes(struct jdec_private *priv){  // Y  process_Huffman_data_unit(priv, cY);  /* add by zx */  //以1x1为例,其余的也照此添加  output_DC_AC(priv);  /* end */  IDCT(&priv->component_infos[cY], priv->Y, 8);  // Cb  process_Huffman_data_unit(priv, cCb);  IDCT(&priv->component_infos[cCb], priv->Cb, 8);  // Cr  process_Huffman_data_unit(priv, cCr);  IDCT(&priv->component_infos[cCr], priv->Cr, 8);}
void tinyjpeg_free(struct jdec_private *priv){  ...  /* add by zx */  if (priv->AC_Image) free(priv->AC_Image);  if (priv->DC_Image) free(priv->DC_Image);  /* end */  free(priv);}
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt){  ...  /* add by zx */  priv->DC_Image = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);  priv->AC_Image = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);  /* end */  ...  /* add by zx */  mapping_DC_AC(priv);  /* end */  ...}
/* add by zx */static void output_DC_AC(struct jdec_private *priv){    static long int i = 0;    //因为宏块为8*8 因此DC图像应该为实际图像大小除以64    if (i < priv->height*priv->width / 64)    {        priv->DC_Image[i] = priv->component_infos[0].DCT[0];        priv->AC_Image[i] = priv->component_infos[0].DCT[1];    }    i++;}static void mapping_DC_AC(struct jdec_private *priv){    int i;    short int min_AC, max_AC, min_DC, max_DC, size;    unsigned char *temp_AC, *temp_DC;    size = priv->height*priv->width / 64;    temp_AC = (unsigned char*)malloc(sizeof(unsigned char)*size);    temp_DC = (unsigned char*)malloc(sizeof(unsigned char)*size);    //因为AC分量有负值,因此将其归至0-255需要对应映射    //原来DC分量的范围为0-8*255,如果将其归至0-255直接除以8即可    //但是y分量在操作时有-128 因此DC分量变得也有负值,因此也和AC分量一样进行对应映射    min_AC = priv->AC_Image[0];    max_AC = priv->AC_Image[0];    min_DC = priv->DC_Image[0];    max_DC = priv->DC_Image[0];    //寻找AC及DC分量中的最大最小值    for (i = 0; i < size; i++)    {        if (priv->AC_Image[i] < min_AC)            min_AC = priv->AC_Image[i];        if (priv->AC_Image[i] > max_AC)            max_AC = priv->AC_Image[i];        if (priv->DC_Image[i] < min_DC)            min_DC = priv->DC_Image[i];        if (priv->DC_Image[i] > max_DC)            max_DC = priv->DC_Image[i];    }    //进行映射    for (i = 0; i < size; i++)    {        temp_AC[i] = (unsigned char)(255 * (priv->AC_Image[i] - min_AC) / (max_AC - min_AC));        temp_DC[i] = (unsigned char)(255 * (priv->DC_Image[i] - min_DC) / (max_DC - min_DC));    }    fwrite(temp_AC, 1, size, AC_FILE);    fwrite(temp_DC, 1, size, DC_FILE);    free(temp_AC);    free(temp_DC);}/* end */

三、实验结果

1、以txt文件输出所有的量化矩阵和所有的HUFFMAN码表

这里写图片描述
这里写图片描述

2、DC\AC图像输出及其概率分布

数据 输出图像 概率分布 DC(DCT[0]) 这里写图片描述 这里写图片描述 AC(DCT[1]) 这里写图片描述 这里写图片描述