【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试

目录

一、 实验项目名称

二、 实验目的

三、实验内容

1.JPEG编解码原理

(1)input image:

(2)Level offset

(3)8x8 DCT

(4)Uniform scalar quantization

(5)熵编码

2. 解码步骤

3.JPEG文件格式

三、实验步骤

1.逐步调试JPEG解码器程序

 1)理解三个结构体的设计目的

  2) 理解程序设计的整体框架

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

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

4. 输出DC和某一个AC图像

5. 统计得到的DC和AC图像的概率分布


一、 实验项目名称

JPEG原理分析及JPEG解码器的调试

二、 实验目的

掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。

三、实验内容

1.JPEG编解码原理

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第1张图片  JPEG编码的过程如上图所示。解码是编码的逆过程。

(1)input image:

通常将高度相关的RGB空间转换为相关性较小的YUV空间,并且YUV空间中,图像信息主要包括在Y通道,人眼对色度分量不敏感,对色度分量可以进行下采样,如4:2:2。有Y,U,V三个通道,上述过程是对一个通道进行了处理,并行处理这三个通道。

如果图像任意维度不是8的倍数,则需要填充为8的整数倍,通过复制最后一列或一行。

(2)Level offset

零偏置,对于灰度级为2^n的像素,通过个减去2^(n-1),将无符号的整数值变为有符号数。对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值。这样可以使像素的绝对值出现在3位10进制的概率大大减少。

(3)8x8 DCT

将分量图像分块成一个个8x8的的块,分别进行DCT变换,最后每个块都得到8x8的一个系数块

(4)Uniform scalar quantization

均匀中平量化,有量化表(表里的值为量化步长,值越大压得更狠),对经过8x8 DCT变换后的每一个系数(1个DC系数和63个AC系数)进行量化,实际上是对每个系数进行比特分配,得到每个系数用几比特来表示。

因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。根据人眼的视觉特性,对低频比较敏感,对高频不太敏感,因此对低频分量采取较细的量化,分配的比特数比较多,对高频分量采取较粗的量化,分配比特数比较少。

(5)熵编码

对量化后的DC系数和AC系数进行不同的熵编码

a. DC系数编码

由于直流系数F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的块的直流系数通常具有较大的相关性,所以对DC系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一个像素块直流系数的差值进行无损编码。(注意量化在前一步,先量化再作差)(作的差用Huffman编码)

b. AC系数编码

对于得到的8x8的系数矩阵,系数大多集中在左上角,即低频分量区,因此采用之字形按频率的高低顺序读出,出现连续0的概率提高,这样可以使用游程编码,在最后如果都是0直接给出EOB(End of Block)即可(EOB有一个特殊码字)。

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第2张图片

   

游程编码表示:(run,level),表示连续run个0,后面跟level的系数。比如0,2,0,0,3……,表示为

(1,2),(2,3),……

run:最多15个0,用4位RRRR表示(太长拆成两个,否则位数占用太多);level:分成16个类别,用四位SSSS表示,用定长码编码。对(RRRR,SSSS)用Huffman编码

2. 解码步骤

解码Huffman数据;解码DC差值;重构量化后的系数;DCT逆变换;丢弃填充的行/列;反0偏置;对丢失的CbCr分量差值(下采样的逆过程);YCbCr->RGB

3.JPEG文件格式

JPEG标准中定义了一系列标记(Markers),均以0xFF开始,后跟1个字节的标记标识符,2个字节的标记长度,以及该标记所对应的payload

标记长度部分高位在前,低位在后(不包含该标记的头两个字节即FF和标记标识符)

熵编码部分的数据在0xFF后由编码器插入0x00,解码器解码时则跳过此字节不处理

开始标记SOI(Start Of Image)和结束标记EOI(End Of Image)标记没有payload

名称 标记代码 负载 说明

SOI

0xFFD8

图像的开始

SOF0 0xFFC0 大小可变

帧图像开始

① 精度(1字节)每个颜色分量每个像素的位 数(通常为8)

② 图像高度(2字节)以像素数表示图像的高度

③ 图像宽度(2字节)以像素数表示图像的宽度

④ 颜色分量数(1字节)通常为3

⑤ 对每个颜色分量:

颜色索引ID(1字节,从01,02,03)

Sample factor(1字节,高四位水平因子,低四位垂直因子)

量化表号(1字节)

SOF2 0xFFC2 大小可变 SOF2是progressive DCT
DHT 0xFFC4 大小可变

定义哈夫曼码表,可以有多个

Huffman表的长度

类型(AC或DC) 1字节 0x00表示此部分数据描述的是DC直流0号表

索引(Index)

位表(bit table)

值表(value table)

DQT 0xFFDB 大小可变

定义量化表,可以有多个,一般为亮度和色度各一个

① 量化表长度,一般为00 43(或00 84)

② 量化表信息(1字节)

     Bit 0~3 QT号(只能取值为0~3,否则错误)

     Bit 4~7 QT精度(0为8比特,否则表示16比特)

③ 量化表的实际数据

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

DRI 0xFFDD 2字节 定义重置信号的间隔
SOS 0xFFDA 大小可变 扫描行开始
APP0 0xFFE0 大小可变

应用程序保留标记0

包含9个具体字段:

① 数据长度                2字节

② 标识符                    5字节     固定值字符串“JFIF0”

③ 版本号                    2字节                       

④ X和Y的密度单位    1字节     只有三个值可选   0:无单   位;1:点数/英寸;2:点/厘米

⑤ X方向像素密度               2字节

⑥ Y方向像素密度               2字节

⑦ 缩略图水平像素数目       1字节

⑧ 缩略图垂直像素数目       1字节

⑨ 缩略图RGB位图              3n,n为像素数

APPn 0xFFEn 大小可变

应用程序保留标记n 1~15

EOI 0xFFD9 图像的结束

三、实验步骤

1.逐步调试JPEG解码器程序

将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。

程序调试过程中,应做到:

 1)理解三个结构体的设计目的

  • 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

存放当前8x8块的有关解码的信息,包括当前解码使用的数据以及即时得到的数据

struct component 
{
  unsigned int Hfactor; //水平采样因子
  unsigned int Vfactor; //垂直采样因子
  float *Q_table;		/* Pointer to the quantisation table to use */
  struct huffman_table *AC_table;
  struct huffman_table *DC_table;
  short int previous_DC;	/* Previous DC coefficient */
  short int DCT[64];		/* DCT coef */ //当前DCT系数
#if SANITY_CHECK
  unsigned int cid;
#endif
};
  • struct  jdec_private

JPEG数据流结构体,用于存储JPEG图像的宽高、数据流指针,并包含有上面两个结构体

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

};

  2) 理解程序设计的整体框架

首先读取jpeg文件,开一个缓冲区buf将全部内容放入缓冲区:

/**
 * Load one jpeg image, and decompress it, and save the result.
 */
//将文件放入缓冲区
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");
struct jdec_private *tinyjpeg_init(void)
{
  struct jdec_private *priv;
 
  priv = (struct jdec_private *)calloc(1, sizeof(struct jdec_private));
  if (priv == NULL)
    return NULL;
  return priv;
}

开始分析jpeg文件头部数据

 if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0) //解析头部数据
    exitmessage(tinyjpeg_get_errorstring(jdec));

判断此文件是否为jpeg文件,并且通过函数parse_JFIF分析获取不同标识码的数据: 

int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
  int ret;

  /* Identify the file */
  if ((buf[0] != 0xFF) || (buf[1] != SOI)) //判断是否为jepg文件
    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;
}

这里获取了量化表数据,并将量化表写入trace文件

static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
  int qi;
  float *table;
  const unsigned char *dqt_block_end;
#if 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;
}

static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)
{
  int i, width, height, nr_components, cid, sampling_factor;
  int Q_table;
  struct component *c;
#if TRACE
  fprintf(p_trace,"> SOF marker\n");
  fflush(p_trace);
#endif
  print_SOF(stream);

  height = be16_to_cpu(stream+3);
  width  = be16_to_cpu(stream+5);
  nr_components = stream[7];
#if SANITY_CHECK
  if (stream[2] != 8)
    snprintf(error_string, sizeof(error_string),"Precision other than 8 is not supported\n");
  if (width>JPEG_MAX_WIDTH || height>JPEG_MAX_HEIGHT)
    snprintf(error_string, sizeof(error_string),"Width and Height (%dx%d) seems suspicious\n", width, height);
  if (nr_components != 3)
    snprintf(error_string, sizeof(error_string),"We only support YUV images\n");
  if (height%16)
    snprintf(error_string, sizeof(error_string),"Height need to be a multiple of 16 (current height is %d)\n", height);
  if (width%16)
    snprintf(error_string, sizeof(error_string),"Width need to be a multiple of 16 (current Width is %d)\n", width);
#endif
  stream += 8;
  for (i=0; icomponent_infos[i];
#if SANITY_CHECK
     c->cid = cid;
     if (Q_table >= COMPONENTS)
       snprintf(error_string, sizeof(error_string),"Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS-1);
#endif
     c->Vfactor = sampling_factor&0xf;
     c->Hfactor = sampling_factor>>4;
     c->Q_table = priv->Q_tables[Q_table];
#if TRACE
     fprintf(p_trace,"Component:%d  factor:%dx%d  Quantization table:%d\n",
           cid, c->Hfactor, c->Hfactor, Q_table );
	 fflush(p_trace);
#endif

  }
  priv->width = width;
  priv->height = height;
#if TRACE
  fprintf(p_trace,"< SOF marker\n");
  fflush(p_trace);
#endif

  return 0;
}

这里获取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 )
       build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
     else
       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;
}

static int parse_DRI(struct jdec_private *priv, const unsigned char *stream)
{
  unsigned int length;

#if TRACE
  fprintf(p_trace,"> DRI marker\n");
  fflush(p_trace);
#endif

  length = be16_to_cpu(stream);

#if SANITY_CHECK
  if (length != 4)
    snprintf(error_string, sizeof(error_string),"Length of DRI marker need to be 4\n");
#endif

  priv->restart_interval = be16_to_cpu(stream+2);

#if TRACE
  fprintf(p_trace,"Restart interval = %d\n", priv->restart_interval);
  fprintf(p_trace,"< DRI marker\n");
  fflush(p_trace);
#endif
  return 0;
}

 获取文件大小

 /* Get the size of the image */
  tinyjpeg_get_size(jdec, &width, &height);  //获得文件大小
void tinyjpeg_get_size(struct jdec_private *priv, unsigned int *width, unsigned int *height)
{
  *width = priv->width;
  *height = priv->height;
}

解码jpeg文件,以MCU为单位进行解码

snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
  if (tinyjpeg_decode(jdec, output_format) < 0)  //解码文件
    exitmessage(tinyjpeg_get_errorstring(jdec));
/**
 * Decode and convert the jpeg image into @pixfmt@ image
 *
 * Note: components will be automaticaly allocated if no memory is attached.
 */
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
  unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
  unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
  decode_MCU_fct decode_MCU;
  const decode_MCU_fct *decode_mcu_table;
  const convert_colorspace_fct *colorspace_array_conv;
  convert_colorspace_fct convert_to_pixfmt;

  if (setjmp(priv->jump_state))
    return -1;

  /* To keep gcc happy initialize some array */
  bytes_per_mcu[1] = 0;
  bytes_per_mcu[2] = 0;
  bytes_per_blocklines[1] = 0;
  bytes_per_blocklines[2] = 0;

  decode_mcu_table = decode_mcu_3comp_table;
  switch (pixfmt) {
     case TINYJPEG_FMT_YUV420P:
       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;

     case TINYJPEG_FMT_RGB24:
       colorspace_array_conv = convert_colorspace_rgb24;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_BGR24:
       colorspace_array_conv = convert_colorspace_bgr24;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_GREY:
       decode_mcu_table = decode_mcu_1comp_table;
       colorspace_array_conv = convert_colorspace_grey;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
       bytes_per_blocklines[0] = priv->width;
       bytes_per_mcu[0] = 8;
       break;

     default:
#if TRACE
		 fprintf(p_trace,"Bad pixel format\n");
		 fflush(p_trace);
#endif
       return -1;
  }
  //重要解码部分
  //mcu类似于视频编码的宏块
  xstride_by_mcu = ystride_by_mcu = 8;
  if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
     decode_MCU = decode_mcu_table[0];
     convert_to_pixfmt = colorspace_array_conv[0];
#if TRACE
     fprintf(p_trace,"Use decode 1x1 sampling\n");
	 fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Hfactor == 1) {
     decode_MCU = decode_mcu_table[1];
     convert_to_pixfmt = colorspace_array_conv[1];
     ystride_by_mcu = 16;
#if TRACE
     fprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");
	 fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Vfactor == 2) {
     decode_MCU = decode_mcu_table[3];
     convert_to_pixfmt = colorspace_array_conv[3];
     xstride_by_mcu = 16;
     ystride_by_mcu = 16;
#if TRACE 
	 fprintf(p_trace,"Use decode 2x2 sampling\n");
	 fflush(p_trace);
#endif
  } else {
     decode_MCU = decode_mcu_table[2];
     convert_to_pixfmt = colorspace_array_conv[2];
     xstride_by_mcu = 16;
#if TRACE
     fprintf(p_trace,"Use decode 2x1 sampling\n");
	 fflush(p_trace);
#endif
  }

  resync(priv);

  /* Don't forget to that block can be either 8 or 16 lines */
  bytes_per_blocklines[0] *= ystride_by_mcu;
  bytes_per_blocklines[1] *= ystride_by_mcu;
  bytes_per_blocklines[2] *= ystride_by_mcu;

  bytes_per_mcu[0] *= xstride_by_mcu/8;
  bytes_per_mcu[1] *= xstride_by_mcu/8;
  bytes_per_mcu[2] *= xstride_by_mcu/8;

  /* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
  for (y=0; y < priv->height/ystride_by_mcu; y++)
   {
     //trace("Decoding row %d\n", y);
     priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
     priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
     priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
     for (x=0; x < priv->width; x+=xstride_by_mcu)
      {
	decode_MCU(priv);  
	convert_to_pixfmt(priv);
	priv->plane[0] += bytes_per_mcu[0];
	priv->plane[1] += bytes_per_mcu[1];
	priv->plane[2] += bytes_per_mcu[2];
	if (priv->restarts_to_go>0)
	 {
	   priv->restarts_to_go--;
	   if (priv->restarts_to_go == 0)
	    {
	      priv->stream -= (priv->nbits_in_reservoir/8);
	      resync(priv);
	      if (find_next_rst_marker(priv) < 0)
		return -1;
	    }
	 }
      }
   }
#if TRACE
  fprintf(p_trace,"Input file size: %d\n", priv->stream_length+2);
  fprintf(p_trace,"Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
  fflush(p_trace);
#endif

  return 0;
}

 保存数据,这里实验保存为yuv文件

  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); //实验选择输出yuv420
      break;
    case TINYJPEG_FMT_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

这里写成了".Y"、".U"、".V"三个文件,我们将其写入一个文件 

/**
 * Save a buffer in three files (.Y, .U, .V) useable by yuvsplittoppm
 */
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);
}

将下面代码替换上面的写入三个文件的相应代码

  //写成一个文件yuv文件,420格式
  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);

最终得到输出的YUV文件

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第3张图片

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

TRACE在代码中定义为1,通过TRACE可以将解码过程中得到的文件数据并行记录在TRACEFILE文件中

#define TRACE 1//add by nxn
#define  TRACEFILE "trace_jpeg.txt"//add by nxn
  • 会打开和关闭TRACE

关闭时定义为0就可以了,开启时定义为1

  • 会根据自己的要求修改TRACE

找到下面这样的代码可以修改TRACEFILE文件中的内容

#if TRACE

#endif

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

  • 添加存储量化矩阵和Huffman的文件

头文件添加这两个文件以及对应指针的定义 

FILE* q_trace;          //量化表指针
FILE* h_trace;          //Huffman表指针
#define QFILE "Q.txt"   //量化表
#define HFILE "H.txt"   //Huffman表

 main函数中添加相应代码

 q_trace = fopen(QFILE, "w"); //打开量化表文件
  if (q_trace == NULL)
  {
      printf("QFILE open error!");
  }
  h_trace = fopen(HFILE, "w"); //打开Huffman表文件
  if (h_trace == NULL)
  {
      printf("HFILE open error!");
  }
  fclose(q_trace);
  fclose(h_trace);

量化表,在parse_DQT中添加如下代码:

     fprintf(q_trace, "Q:%d\n", qi);
     fflush(q_trace);

在build_quantization_table中添加修改如下代码:

  for (i=0; i<8; i++) {
     for (j=0; j<8; j++) {
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
       *zz--;
       fprintf(q_trace, "%d\t", ref_table[*zz++]);
     }
      fprintf(q_trace, "\n");
   }

得到量化表结果如图:

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第4张图片

 Huffman表,在 parse_DHT的trace中添加如下代码:

fprintf(h_trace, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
fflush(h_trace);

在build_huffman_table 的trace中添加如下代码:

fprintf(h_trace, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(h_trace);

最终结果如下:

 亮度分量 DC

Huffman table DC[0] length=10
val=04 code=00000000 codesize=02
val=05 code=00000001 codesize=02
val=06 code=00000002 codesize=02
val=03 code=00000006 codesize=03
val=02 code=0000000e codesize=04
val=01 code=0000001e codesize=05
val=00 code=0000003e codesize=06
val=09 code=0000007e codesize=07
val=07 code=000000fe codesize=08
val=08 code=000001fe codesize=09

  亮度分量 AC

Huffman table AC[0] length=43
val=00 code=00000000 codesize=02
val=01 code=00000002 codesize=03
val=03 code=00000003 codesize=03
val=02 code=00000008 codesize=04
val=04 code=00000009 codesize=04
val=05 code=0000000a codesize=04
val=11 code=0000000b codesize=04
val=21 code=0000000c codesize=04
val=22 code=0000001a codesize=05
val=31 code=0000001b codesize=05
val=61 code=0000001c codesize=05
val=06 code=0000003a codesize=06
val=12 code=0000003b codesize=06
val=a1 code=0000003c codesize=06
val=32 code=0000007a codesize=07
val=41 code=0000007b codesize=07
val=62 code=0000007c codesize=07
val=13 code=000000fa codesize=08
val=51 code=000000fb codesize=08
val=23 code=000001f8 codesize=09
val=42 code=000001f9 codesize=09
val=71 code=000001fa codesize=09
val=81 code=000001fb codesize=09
val=91 code=000001fc codesize=09
val=15 code=000003fa codesize=10
val=52 code=000003fb codesize=10
val=63 code=000003fc codesize=10
val=07 code=000007fa codesize=11
val=14 code=000007fb codesize=11
val=33 code=000007fc codesize=11
val=53 code=000007fd codesize=11
val=16 code=00000ffc codesize=12
val=43 code=00000ffd codesize=12
val=08 code=00001ffc codesize=13
val=b1 code=00001ffd codesize=13
val=34 code=00003ffc codesize=14
val=c1 code=00003ffd codesize=14
val=24 code=00007ffc codesize=15
val=d1 code=0000fffa codesize=16
val=09 code=0000fffb codesize=16
val=72 code=0000fffc codesize=16
val=f0 code=0000fffd codesize=16
val=a2 code=0000fffe codesize=16

色度分量 DC

Huffman table DC[1] length=11
val=04 code=00000000 codesize=02
val=05 code=00000001 codesize=02
val=06 code=00000002 codesize=02
val=03 code=00000006 codesize=03
val=02 code=0000000e codesize=04
val=01 code=0000001e codesize=05
val=00 code=0000003e codesize=06
val=07 code=0000007e codesize=07
val=0a code=000000fe codesize=08
val=09 code=000001fe codesize=09
val=08 code=000003fe codesize=10

 色度分量 AC

Huffman table AC[1] length=28
val=00 code=00000000 codesize=02
val=04 code=00000001 codesize=02
val=01 code=00000004 codesize=03
val=02 code=00000005 codesize=03
val=03 code=00000006 codesize=03
val=31 code=0000001c codesize=05
val=61 code=0000001d codesize=05
val=11 code=0000003c codesize=06
val=12 code=0000003d codesize=06
val=05 code=0000007c codesize=07
val=21 code=0000007d codesize=07
val=13 code=000000fc codesize=08
val=14 code=000000fd codesize=08
val=41 code=000000fe codesize=08
val=51 code=000001fe codesize=09
val=06 code=000007fc codesize=11
val=22 code=000007fd codesize=11
val=32 code=000007fe codesize=11
val=07 code=00001ffc codesize=13
val=15 code=00001ffd codesize=13
val=42 code=00001ffe codesize=13
val=08 code=0000fff8 codesize=16
val=71 code=0000fff9 codesize=16
val=23 code=0000fffa codesize=16
val=24 code=0000fffb codesize=16
val=33 code=0000fffc codesize=16
val=81 code=0000fffd codesize=16
val=a1 code=0000fffe codesize=16

4. 输出DC和某一个AC图像

实验中原代码有DCT数组来存取DC分量和AC分量,在8*8宏块中左上角第一个值就是DC分量,若原图为1024*1024大小,则共有128*128个DC分量,这些DC分量组成了DC图像,因此取出DC[0]即可,而剩下便是AC数据,这里取了DC[2]作为某一幅AC图像,代码如下:

int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
  unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
  unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
  decode_MCU_fct decode_MCU;
  const decode_MCU_fct *decode_mcu_table;
  const convert_colorspace_fct *colorspace_array_conv;
  convert_colorspace_fct convert_to_pixfmt;
  /*——————下面是添加代码——————————————————*/
  FILE* DC;
  FILE* AC;
  DC = fopen("dc.yuv", "w");
  AC = fopen("ac.yuv", "w");
  if (DC == NULL)
  {
      printf("dc.yuv open error!");
  }
  if (AC == NULL)
  {
      printf("ac.yuv open error!");
  }
  unsigned char* uv = 128;     //uv分量统一设置为128,只管亮度
  unsigned char* DCbuf, * ACbuf;
  int n = 0;
  /*——————结束————————————————————————*/

  if (setjmp(priv->jump_state))
    return -1;

  /* To keep gcc happy initialize some array */
  bytes_per_mcu[1] = 0;
  bytes_per_mcu[2] = 0;
  bytes_per_blocklines[1] = 0;
  bytes_per_blocklines[2] = 0;

  decode_mcu_table = decode_mcu_3comp_table;
  switch (pixfmt) {
     case TINYJPEG_FMT_YUV420P:
       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;

     case TINYJPEG_FMT_RGB24:
       colorspace_array_conv = convert_colorspace_rgb24;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_BGR24:
       colorspace_array_conv = convert_colorspace_bgr24;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_GREY:
       decode_mcu_table = decode_mcu_1comp_table;
       colorspace_array_conv = convert_colorspace_grey;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
       bytes_per_blocklines[0] = priv->width;
       bytes_per_mcu[0] = 8;
       break;

     default:
#if TRACE
		 fprintf(p_trace,"Bad pixel format\n");
		 fflush(p_trace);
#endif
       return -1;
  }
  //重要解码部分
  //mcu类似于视频编码的宏块
  xstride_by_mcu = ystride_by_mcu = 8;
  if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
     decode_MCU = decode_mcu_table[0];
     convert_to_pixfmt = colorspace_array_conv[0];
#if TRACE
     fprintf(p_trace,"Use decode 1x1 sampling\n");
	 fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Hfactor == 1) {
     decode_MCU = decode_mcu_table[1];
     convert_to_pixfmt = colorspace_array_conv[1];
     ystride_by_mcu = 16;
#if TRACE
     fprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");
	 fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Vfactor == 2) {
     decode_MCU = decode_mcu_table[3];
     convert_to_pixfmt = colorspace_array_conv[3];
     xstride_by_mcu = 16;
     ystride_by_mcu = 16;
#if TRACE 
	 fprintf(p_trace,"Use decode 2x2 sampling\n");
	 fflush(p_trace);
#endif
  } else {
     decode_MCU = decode_mcu_table[2];
     convert_to_pixfmt = colorspace_array_conv[2];
     xstride_by_mcu = 16;
#if TRACE
     fprintf(p_trace,"Use decode 2x1 sampling\n");
	 fflush(p_trace);
#endif
  }

  resync(priv);

  /* Don't forget to that block can be either 8 or 16 lines */
  bytes_per_blocklines[0] *= ystride_by_mcu;
  bytes_per_blocklines[1] *= ystride_by_mcu;
  bytes_per_blocklines[2] *= ystride_by_mcu;

  bytes_per_mcu[0] *= xstride_by_mcu/8;
  bytes_per_mcu[1] *= xstride_by_mcu/8;
  bytes_per_mcu[2] *= xstride_by_mcu/8;

  /* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
  for (y=0; y < priv->height/ystride_by_mcu; y++)
   {
     //trace("Decoding row %d\n", y);
     priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
     priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
     priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
     for (x=0; x < priv->width; x+=xstride_by_mcu)
      {
	decode_MCU(priv);  //核心解码函数,以MCU为单位进行解码
	convert_to_pixfmt(priv);
	priv->plane[0] += bytes_per_mcu[0];
	priv->plane[1] += bytes_per_mcu[1];
	priv->plane[2] += bytes_per_mcu[2];
	if (priv->restarts_to_go>0)
	 {
	   priv->restarts_to_go--;
	   if (priv->restarts_to_go == 0)
	    {
	      priv->stream -= (priv->nbits_in_reservoir/8);
	      resync(priv);
	      if (find_next_rst_marker(priv) < 0)
		return -1;
	    }
	 }
    /*——————下面是添加代码——————————————————*/
    //写入亮度分量
    DCbuf = (unsigned char)(priv->component_infos->DCT[0] /4.0 + 128);
    fwrite(&DCbuf, 1, 1, DC);
    ACbuf = (unsigned char)(priv->component_infos->DCT[2] + 128);
    fwrite(&ACbuf, 1, 1, AC);
    n++;
    /*——————结束————————————————————————*/
      }
   }
#if TRACE
  fprintf(p_trace,"Input file size: %d\n", priv->stream_length+2);
  fprintf(p_trace,"Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
  fflush(p_trace);
#endif
  /*——————下面是添加代码——————————————————*/
  for (int j = 0; j < n * 0.25 * 2; j++)
  {
      //写入uv分量
      fwrite(&uv, sizeof(unsigned char), 1, DC);
      fwrite(&uv, sizeof(unsigned char), 1, AC);

  }
  fclose(DC);
  fclose(AC);
  /*——————结束————————————————————————*/
  return 0;
}

得到的DC图像和AC图像如下:

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第5张图片【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第6张图片

5. 统计得到的DC和AC图像的概率分布

 编写代码统计概率:

#include 
#include 
int main()
{
	int size = 128 * 128;

	FILE* DC;
	FILE* AC;
	FILE* DC_out;
	FILE* AC_out;
	DC = fopen("dc.yuv", "rb");
	if (DC == NULL) {
		printf("dc.yuv open wrong!");
	}
	AC = fopen("ac.yuv", "rb");
	if (AC == NULL) {
		printf("ac.yuv open wrong!");
	}
	DC_out = fopen("dc_out.txt", "w");
	if (DC_out == NULL) {
		printf("dc_out.txt open wrong!");
	}
	AC_out = fopen("ac_out.txt", "w");
	if (AC_out == NULL) {
		printf("ac_out.txt open wrong!");
	}
	unsigned char* DCbuf = new unsigned char[size];
	unsigned char* ACbuf = new unsigned char[size];
	fread(DCbuf, 1, size, DC);
	fread(ACbuf, 1, size, AC);
	double freqDC[256] = { 0 };
	double freqAC[256] = { 0 };

	for (int i = 0; i < size; i++) {
		freqDC[DCbuf[i]] += 1;
		freqAC[ACbuf[i]] += 1;
	}
	for (int i = 0; i < 256; i++) {
		freqDC[i] = freqDC[i] / size;
		freqAC[i] = freqAC[i] / size;
		fprintf(DC_out, "%d\t%f\n", i, freqDC[i]);
		fprintf(AC_out, "%d\t%f\n", i, freqAC[i]);
	}
	fclose(DC);
	fclose(AC);
	fclose(DC_out);
	fclose(AC_out);
}

 将统计的概率作图:

DC图像:

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第7张图片

 AC图像:

【数据压缩】第七次作业——JPEG原理分析及JPEG解码器的调试_第8张图片

 可以明显看到AC图像含的数据很少,而DC图像数据含的很多

你可能感兴趣的:(linq,c#)