数据压缩作业七:JPEG原理分析及解码器调试

JPEG

JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。

其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例

概述

JPEG在文件中以Segment的形式组织 ,它具有以下特点:

1、均以0xFF开始,后跟1byte的Marker和2byte的Segment length(包含表示Length本身所占用的2byte,不含0xFF+Marker所占用的2byte);

2、采用 Motorola序(相对于Intel序),即保存时高位在前,低位在后;

3、Data部分中,0xFF后若为0x00,则跳过此字节不予处理。

JPEG编码

1)零偏置:JPEG编码将图像分为8×8的块作为数据处理的最小单位,对于灰度级为2^n 的像素,通过减去2^(n-1),将无符号整数变为有符号数,即值域变为正负对称,将像素灰度的绝对值控制在小范围内,提高编码效率

2)DCT变换:将图像分为8×8的像块,如果图像的宽(高)不是8的整数倍,使用图像边缘像素填充,以不改变频谱分布。实现能量集中和去相关,降低空间冗余度

3)量化:利用人眼视觉特性设计而成的矩阵量化DCT系数,减小视觉冗余。因为人眼对亮度信号比色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值;根据人眼对低频敏感,对高频不太敏感,对低频分量采取较细的量化,对高频分量采取较粗的量化。JPEG标准中采用中平型均匀量化,输入DCT系数,输出量化系数

4)DC系数差分编码:8×8像块经过DCT后得到的DC系数有两个特点:一是系数的值较大;二是相邻像块的DC系数存在相关性(即存在冗余),可采用DPCM对相邻图像块之间量化DC系数的插值进行编码;

5)AC系数之字形扫描:由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出

6)AC系数游程编码:当遇到很多连续的0时,为缩短数据长度,编码非零系数level和它之前0的个数run,(Run,level)。如:0,0,3,0,2,0,0,0,1–>游程编码:(2,3),(2,2),(3,1)在最后,如果都是零,给出 EOB (End of Block)即可

7)Huffman编码:对DC系数DPCM的结果和AC系数RLE的结果进行Huffman编码

JPEG解码

  1. 读取文件
  2. 解析文件segment
  3. 以MCU为单位进行编码
  4. 丢弃填充的行或列
  5. 反0偏置
  6. 对丢失的CbCr分量插值
  7. YCbCr →RGB

代码分析

结构体分析

huffman_table,该结构体用来存储Huffman表

struct huffman_table
{
  /* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
   * if the symbol is <0, then we need to look into the tree table */
  short int lookup[HUFFMAN_HASH_SIZE];       //获取权值对应的码字
  /* code size: give the number of bits of a symbol is encoded */
  unsigned char code_size[HUFFMAN_HASH_SIZE];   //获取权值对应的码长
  /* some place to store value that is not encoded in the lookup table 
   * FIXME: Calculate if 256 value is enough to store all values
   */
  uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];    //当码长>9时,交给该表处理
};

Component,该结构体用来存储解码信息,这一段定义了水平方向和垂直方向的采样因子,量化表的指针,AC系数和DC系数的Huffman码表的指针

struct component    //8x8块结构体
{
  unsigned int Hfactor;
  unsigned int Vfactor;     //水平垂直采样因子
  float *Q_table;		/* Pointer to the quantisation table to use */    //指向该component解码时要用的量化表
  struct huffman_table *AC_table;
  struct huffman_table *DC_table;    //分别对应AC系数和DC系数的Huffman表
  //以上是解码过程中要使用的东西,以下是解码过程中临时存储的东西
  short int previous_DC;	/* Previous DC coefficient */    //存储前一个DC值,用于DPCM解码
 //在进行Huffman编码之前,DC系数采用了DPCM,因此每解完一个DC系数就要存起来用于下一个DC系数的解码
  short int DCT[64];		/* DCT coef */      //保存DCT的系数
#if SANITY_CHECK
  unsigned int cid;
#endif
};

Jdec_private,定义了JPEG数据流结构体,用来指示解码过程中所用到的信息,如图像数据、量化表、Huffman码表等,并定义了存储IDCT解码后的像素值的变量

struct jdec_private
{
  /* Public variables */
  uint8_t *components[COMPONENTS];  //定义指针数组,指向三种分量用于存放解码后数据的数组的地址
  unsigned int width, height;	/* Size of the image */  //图像宽高
  unsigned int flags;

  /* Private variables */
  const unsigned char *stream_begin, *stream_end;   //标记数据流的开始和结束
  unsigned int stream_length;   //数据流长度

  const unsigned char *stream;	/* Pointer to the current stream */   //当前解码流指针,用指向函数的指针来操作函数
  unsigned int reservoir, nbits_in_reservoir;

  struct component component_infos[COMPONENTS];   //存放三种分量的component信息
  float Q_tables[COMPONENTS][64];		/* quantization tables */  //每个分量都有一张量化表
  struct huffman_table HTDC[HUFFMAN_TABLES];	/* DC huffman tables   */
  struct huffman_table HTAC[HUFFMAN_TABLES];	/* AC huffman tables   */
  int default_huffman_table_initialized;
  int restart_interval;
  int restarts_to_go;				/* MCUs left in this restart interval */
  int last_rst_marker_seen;			/* Rst marker is incremented each time */

  /* Temp space used after the IDCT to store each components */
  uint8_t Y[64*4], Cr[64], Cb[64];        //保存每个块经过IDCT解码后的像素

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];    //用于彩色空间转换

};

解码核心函数

int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{    
  FILE *fp;
  unsigned int length_of_file;
  unsigned int width, height;
  unsigned char *buf;
  struct jdec_private *jdec;   //定义结构体
  unsigned char *components[3];

  /* Load the Jpeg into memory */      //将所有文件全部读入
  fp = fopen(infilename, "rb");     //打开输入文件
  if (fp == NULL)
    exitmessage("Cannot open filename\n"); 
  length_of_file = filesize(fp);    //获取输入文件大小
  buf = (unsigned char *)malloc(length_of_file + 4);
  if (buf == NULL)
    exitmessage("Not enough memory for loading file\n");
  fread(buf, length_of_file, 1, fp);    //将文件内容读入buf
  fclose(fp);   //关闭文件

  /* Decompress it */
  jdec = tinyjpeg_init();   //初始化
  if (jdec == NULL)
    exitmessage("Not enough memory to alloc the structure need for decompressing\n");

  if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)   //文件是否可解码
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* Get the size of the image */
  tinyjpeg_get_size(jdec, &width, &height);    //得到文件大小

  snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
  if (tinyjpeg_decode(jdec, output_format) < 0)    //解码
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* 
   * Get address for each plane (not only max 3 planes is supported), and
   * depending of the output mode, only some components will be filled 
   * RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
   */
  tinyjpeg_get_components(jdec, components);

  /* Save it */
  switch (output_format)    //解码后按照想要的格式保存文件内容
   {
    case TINYJPEG_FMT_RGB24:
    case TINYJPEG_FMT_BGR24:
      write_tga(outfilename, output_format, width, height, components);
      break;
    case TINYJPEG_FMT_YUV420P:
      write_yuv(outfilename, width, height, components);
      break;
    case TINYJPEG_FMT_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

  /* Only called this if the buffers were allocated by tinyjpeg_decode() */
  tinyjpeg_free(jdec);
  /* else called just free(jdec); */

  free(buf);
  return 0;
}

parse_DQT 解码量化表

static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
  int qi;
  float *table;
  const unsigned char *dqt_block_end;
#if TRACE    //写到trace文件中
  fprintf(p_trace,"> DQT marker\n");
  fflush(p_trace);
#endif
  dqt_block_end = stream + be16_to_cpu(stream);   //量化表最后的位置
  stream += 2;	/* Skip length */

  while (stream < dqt_block_end)
   {
     qi = *stream++;
#if SANITY_CHECK
     if (qi>>4)
       snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
     if (qi>4)
       snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
#endif
     table = priv->Q_tables[qi];
     build_quantization_table(table, stream);    //得到量化表
     stream += 64;
   }
#if TRACE
  fprintf(p_trace,"< DQT marker\n");
  fflush(p_trace);
#endif
  return 0;
}

parse_DHT 解码Huffman码表,同时写入trace文件

static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
  unsigned int count, i;
  unsigned char huff_bits[17];
  int length, index;

  length = be16_to_cpu(stream) - 2;    //表长(可能有多张表)
  stream += 2;	/* Skip length */     
#if TRACE
  fprintf(p_trace,"> DHT marker (length=%d)\n", length);
  fflush(p_trace);
#endif

  while (length>0) {
     index = *stream++;

     /* We need to calculate the number of bytes 'vals' will takes */
     huff_bits[0] = 0;
     count = 0;
     for (i=1; i<17; i++) {
	huff_bits[i] = *stream++;
	count += huff_bits[i];
     }
#if SANITY_CHECK
     if (count >= HUFFMAN_BITS_SIZE)
       snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
     if ( (index &0xf) >= HUFFMAN_TABLES)
       snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE
     fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
	 fflush(p_trace);
#endif
#endif

     if (index & 0xf0 )   //AC系数的Huffman表
       build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
     else    //DC系数的Huffman表
       build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);

     length -= 1;
     length -= 16;
     length -= count;
     stream += count;
  }
#if TRACE
  fprintf(p_trace,"< DHT marker\n");
  fflush(p_trace);
#endif
  return 0;
}

保存yuv文件

static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
  FILE *F;
  char temp[1024];

  //yuv写成三个文件
  snprintf(temp, 1024, "%s.Y", filename);
  F = fopen(temp, "wb");
  fwrite(components[0], width, height, F);
  fclose(F);
  snprintf(temp, 1024, "%s.U", filename);
  F = fopen(temp, "wb");
  fwrite(components[1], width*height/4, 1, F);
  fclose(F);
  snprintf(temp, 1024, "%s.V", filename);
  F = fopen(temp, "wb");
  fwrite(components[2], width*height/4, 1, F);
  fclose(F);


  printf("write yuv begin!\n");
  //yuv都写入一个文件
  snprintf(temp, 1024, "%s.yuv", filename);
  F = fopen(temp, "wb");
  fwrite(components[0], width, height, F);   //写Y
  fwrite(components[1], width * height / 4, 1, F);
  fwrite(components[2], width * height / 4, 1, F);    //写UV
  fclose(F);   //关闭文件
  printf("write yuv done!\n");

}

 Main.cpp

int main(int argc, char *argv[])
{
  int output_format = TINYJPEG_FMT_YUV420P;
  char *output_filename, *input_filename;
  clock_t start_time, finish_time;
  unsigned int duration;
  int current_argument;
  int benchmark_mode = 0;
#if TRACE     //设定trace,边解码边写入文件
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
	  printf("trace file open error!");
  }
#endif
  if (argc < 3)
    usage();

  current_argument = 1;
  while (1)
   {
     if (strcmp(argv[current_argument], "--benchmark")==0)   //strcmp字符串比较,用于比较两个字符串,如果二者相等返回0
       benchmark_mode = 1;   //如果相等,说明设置了benchmark,因此benchmark_mode置1
     else
       break;
     current_argument++;
   }

  if (argc < current_argument+2)
    usage();

  input_filename = argv[current_argument];    //输入文件名,是argv[1]
  if (strcmp(argv[current_argument+1],"yuv420p")==0)    //argv[2]判断输出格式,这里选择yuv420p
    output_format = TINYJPEG_FMT_YUV420P;
  else if (strcmp(argv[current_argument+1],"rgb24")==0)
    output_format = TINYJPEG_FMT_RGB24;
  else if (strcmp(argv[current_argument+1],"bgr24")==0)
    output_format = TINYJPEG_FMT_BGR24;
  else if (strcmp(argv[current_argument+1],"grey")==0)
    output_format = TINYJPEG_FMT_GREY;
  else
    exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
  output_filename = argv[current_argument+2];   //输出文件名,设为argv[3]

  start_time = clock();    //开始


  if (benchmark_mode)   //是否多次解码,若设置了benchmark_mode就调用load_multiple_times,否则就调用convert_one_image
    load_multiple_times(input_filename, output_filename, output_format);
  else
      convert_one_image(input_filename, output_filename, output_format);    //核心函数



  finish_time = clock();  //结束
  duration = finish_time - start_time;
  snprintf(error_string, sizeof(error_string),"Decoding finished in %u ticks\n", duration);
#if TRACE
  fclose(p_trace);
#endif
  return 0;
}

实验结果

 输出的Huffman码表

 AC&DC图像及其概率分布

图像概率分布
AC
DC
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值