实验五—JEPG 原理分析及 JPEG 解码器的调试


## 实验目的 掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。 ## 实验内容 JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。

一.JPEG编码原理

在这里插入图片描述

1.Level Offset(水平偏移)能量的重新分配

对于灰度级是 2n 的像素,通过减去 2n-1,将无符号的整数值变成有符号数;当n=8时,将灰度值的范围从[0,255]转化为[-128,127]。

2.DCT变换

DCT 变换是指对每个单独的彩色图像分量,把整个分量图像分成 8×8 的图像块,再以
8x8 的图像块为一个单位进行量化和编码处理。从而实现能量集中和去相关,便于去除空间冗余,提高编码效率在这里插入图片描述

3.Uniform scalar quantization 均匀标量量化

因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
通过量化减少数据的编码位数,提高编码效率,但同时也会带来误差。

4.DC系数差分编码

8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:一是系数的数值比较大,二是相邻8×8图像块的DC系数值变化不大:冗余;根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码,再对DIFF进行Huffman编码。
在这里插入图片描述

5.Zig-zag scan

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

6.AC系数的游程编码

在经过之字形扫描排序后的 AC 系数,存在很多连 0。为进一步提高编码效率,对 AC 系数进行游程编码(RLC)处理,再进一步进行 Huffman 编码。在JPEG和MPEG编码中规定为:(run, level)表示连续run个0,后面跟值为level的系数如:0,2,0,0,3,0,-4,0,0,0,-6,0,0,5,7表示为(1, 2), (2, 3) ,…

7.AC 和 DC 系数分别进行 Huffman 编码

JPEG 中共采用了四张 Huffman 码表:亮度 DC、亮度 AC、色度 DC、色度 AC,即分别对
图像的亮度和色度,直流和交流数据进行编码处理。

二、JPEG解码原理

解码就是编码的逆过程,参考师哥的系统图如下。
在这里插入图片描述

三、 JPEG文件格式

使用一张jpg图像
在这里插入图片描述

序号文件格式标记代码详情
1SOI&EOI0xFFD8 0xFFD9在这里插入图片描述
2APPO-应用程序保留标记 00xFFE0在这里插入图片描述
3DQT 定义量化表0xFFDB在这里插入图片描述
4SOF0 帧图像开始0xFFC0在这里插入图片描述
5DHT,定义哈夫曼表0xFFC4在这里插入图片描述
6SOS 扫描开始0xFFDA在这里插入图片描述

实验步骤:

1.理解程序设计的整体框架

解码主要由解码函数完成。

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;//设buf缓冲区
  struct jdec_private *jdec;
  unsigned char *components[3];

  /* 将.jpg文件读入buf中 */
  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);
  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)//解析.jpg文件头
  
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* Get the size of the image */
  tinyjpeg_get_size(jdec, &width, &height);//算.jpg文件的宽高

  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);

  /* 按照指定的文件输出格式保存输出文件 */
  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;
}

1.解析Segment Marker
1.1解析文件头:
在tinyjpeg_parse_header中,解析JPEG的文件头。

int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)//解析JPEG文件头
{
  int ret;

  /* Identify the file */
  if ((buf[0] != 0xFF) || (buf[1] != SOI))
    snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");

  priv->stream_begin = buf+2;
  priv->stream_length = size-2;
  priv->stream_end = priv->stream_begin + priv->stream_length;

  ret = parse_JFIF(priv, priv->stream_begin);//开始解析

  return ret;
}

解析APP0:解析 APP0: 检查标识“ JFIF”及版本,得到一些参数。
解析 DQT: 得到量化表长度(可能包含多张量化表);得到量化表的精度;得到及检查量化表的序号(只能是 0 ~3);得到量化表内容(64 个数据)。
解析 SOF0:得到每个 sample 的比特数、长宽、颜色分量数
得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)。
解析 DHT: 得到 Huffman 表的类型(AC、DC)序号,依据数据重建 Huffman 表。
解析 SOS: 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号。
在这里插入图片描述

  1. 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8 宏块的个数
  2. 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码);对每个宏块进行 Huffman 解码;解码 DC 差值,重构量化后的系数;DCT 逆变换;丢弃填充的行/列;反 0 偏置。
    遇到 Segment Marker RST 时,清空之前的 DC DCT 系数。
  3. 解析到 EOI,解码结束
  4. 将 Y、 Cb、 Cr 转化为需要的色彩空间并保存。
2.理解三个结构体的设计目的
struct huffman_table

目的:用于存储所有用到的哈夫曼码表

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];
};

struct component

用于储存当前8×8像块中有关解码的信息,参与霍夫曼解码,反量化,IDCT 以及彩色空间变换。
Hfactor和 Vfactor 用于说明水平与垂直的采样情况。

struct component 
{
  unsigned int Hfactor;//水平采样
  unsigned int Vfactor;//垂直采样
  float *Q_table;		/* Pointer to the quantisation table to use */
  struct huffman_table *AC_table;//指向AC分量进行解码时所需的码表
  struct huffman_table *DC_table;//指向DC分量进行解码时所需的码表
  short int previous_DC;	/* Previous DC coefficient */
  short int DCT[64];		/* DCT 系数矩阵*/
#if SANITY_CHECK
  unsigned int cid;
#endif
};

struct jdec_private

码流结构体,包含了所有完整的内容的定义,用于存储JPEG图像宽高、数据流指针、Huffman码表、图像数据等内容。

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];
  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];

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];

};
3.理解在视音频编解码调试中TRACE的目的和含义

含义:TRACE相当于一个开关,设为1时,则打开TRACE,正常运行里面的程序,设为0时,相当于关闭TRACE,则直接跳过TRACE里的程序。
目的:通过获取一些调试信息,来协助程序的调试。

#if TRACE//开关,可以打开
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
	  printf("trace file open error!");
  }
#endif

实验结果:

1.输出yuv文件

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

  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);
  snprintf(temp, 1024, "%s.YUV", filename);//将字符串赋给temp,即temp=filename.yuv
  F = fopen(temp, "wb");
  fwrite(components[0], width*height, 1, F);//该文件先把components[0]往里开始写,再往里写components[1],再往里写component[2]。(先写ybuffer再写ubuffer再写vbuffer)
  fwrite(components[1], width*height/4, 1, F);
  fwrite(components[2], width*height/4, 1, F);
  fclose(F);
}

在这里插入图片描述

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

static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
  /* Taken from libjpeg. Copyright Independent JPEG Group's LLM idct.
   * For float AA&N IDCT method, divisors are equal to quantization
   * coefficients scaled by scalefactor[row]*scalefactor[col], where
   *   scalefactor[0] = 1
   *   scalefactor[k] = cos(k*PI/16) * sqrt(2)    for k=1..7
   * We apply a further scale factor of 8.
   * What's actually stored is 1/divisor so that the inner loop can
   * use a multiplication rather than a division.
   */
  int i, j;
  static const double aanscalefactor[8] = {
     1.0, 1.387039845, 1.306562965, 1.175875602,
     1.0, 0.785694958, 0.541196100, 0.275899379
  };
  const unsigned char *zz = zigzag;

  for (i=0; i<8; i++) {
     for (j=0; j<8; j++) {
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
	   fprintf(p_trace,"%-6d ",ref_table[*zz++]);//输出ref_table
       if (j == 7) {
			  fprintf(p_trace, "\n");}

	   
     }
   }

}

在这里插入图片描述

static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)
{
  unsigned int i, j, code, code_size, val, nbits;
  unsigned char huffsize[HUFFMAN_BITS_SIZE+1], *hz;
  unsigned int huffcode[HUFFMAN_BITS_SIZE+1], *hc;
  int next_free_entry;

  /*
   * Build a temp array 
   *   huffsize[X] => numbers of bits to write vals[X]
   */
  hz = huffsize;
  for (i=1; i<=16; i++)
   {
     for (j=1; j<=bits[i]; j++)
       *hz++ = i;
   }
  *hz = 0;

  memset(table->lookup, 0xff, sizeof(table->lookup));
  for (i=0; i<(16-HUFFMAN_HASH_NBITS); i++)
    table->slowtable[i][0] = 0;

  /* Build a temp array
   *   huffcode[X] => code used to write vals[X]
   */
  code = 0;
  hc = huffcode;
  hz = huffsize;
  nbits = *hz;
  while (*hz)
   {
     while (*hz == nbits)
      {
	*hc++ = code++;
	hz++;
      }
     code <<= 1;
     nbits++;
   }

  /*
   * Build the lookup table, and the slowtable if needed.
   */
  next_free_entry = -1;
  for (i=0; huffsize[i]; i++)
   {
     val = vals[i];
     code = huffcode[i];
     code_size = huffsize[i];
	#if TRACE
     fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
	 fflush(p_trace);
    #endif
     table->code_size[val] = code_size;
     if (code_size <= HUFFMAN_HASH_NBITS)
      {
	/*
	 * Good: val can be put in the lookup table, so fill all value of this
	 * column with value val 
	 */
	int repeat = 1UL<<(HUFFMAN_HASH_NBITS - code_size);
	code <<= HUFFMAN_HASH_NBITS - code_size;
	while ( repeat-- )
	  table->lookup[code++] = val;

      }
     else
      {
	/* Perhaps sorting the array will be an optimization */
	uint16_t *slowtable = table->slowtable[code_size-HUFFMAN_HASH_NBITS-1];
	while(slowtable[0])
	  slowtable+=2;
	slowtable[0] = code;
	slowtable[1] = val;
	slowtable[2] = 0;
	/* TODO: NEED TO CHECK FOR AN OVERFLOW OF THE TABLE */
      }

   }
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值