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

该实验旨在深入理解JPEG编解码原理,通过调试解码器程序,实现JPEG到YUV的转换。解码流程包括读取文件信息、颜色分量解码、反量化与反Zig-zag编码等步骤。实验要求包括逐步调试程序,理解关键结构体设计,输出量化矩阵和HUFFMAN码表,并统计DC和AC值的概率分布。
摘要由CSDN通过智能技术生成

一、实验目的

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

二、实验原理

(一)JPGE文件格式:

1.文件格式:

SOI:Start of Image 图像开始;
APP0:Application 应用程序保留标记0;
DQT:Define Quantization Table 定义量化表;
SOF0:Start of Frame 帧图像开始;
DHT:Define Huffman Table 定义哈夫曼表;
SOS:Start of Scan 扫描开始12字节;
EOI:End of Image 图像结束2字节。

2.解码流程:
①读入文件的相关信息;
②初步了解图像数据流的结构;
③颜色分量单元的内部解码;
④直流系数的差分编码;
⑤反量化&反Zig-zag编码;
⑥反离散余弦变换。

3.解码程序实现:HUffman查找表实现
两种主要方法:①Huffman树的遍历;②lookup查找表

(二)JPGE编解码原理
在这里插入图片描述
JPEG编码的过程如上图所示。解码是编码的逆过程。

三、实验要求

1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。

2.程序调试过程中,应做到:
① 理解程序设计的整体框架
② 理解三个结构体的设计目的
• struct huffman_table
• struct component
• struct jdec_private
③ 理解在视音频编解码调试中TRACE的目的和含义
• 会打开和关闭TRACE
• 会根据自己的要求修改TRACE

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

4. 输出DC图像并统计其概率分布。

5. 输出某一个AC值图像并统计其概率分布。

四、代码实现

Huffman解码过程:

static int get_next_huffman_code(struct jdec_private* priv, struct huffman_table* huffman_table)
{
	int value, hcode;
	unsigned int extra_nbits, nbits;
	uint16_t* slowtable;
	//读取HUFFMAN_HASH_NBITS比特,并赋值给hcode
	look_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, HUFFMAN_HASH_NBITS, hcode);
	//在霍夫曼码表中找到对应的权值
	value = huffman_table->lookup[hcode];
	if (__likely(value >= 0))
	{
		unsigned int code_size = huffman_table->code_size[value];
		//跳过code_size比特
		skip_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, code_size);
		//返回权值
		return value;
	}

	/* Decode more bits each time ...每次读取更多bit */
	for (extra_nbits = 0; extra_nbits < 16 - HUFFMAN_HASH_NBITS; extra_nbits++)
	{
		nbits = HUFFMAN_HASH_NBITS + 1 + extra_nbits;
		//读取n比特
		look_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, nbits, hcode);
		slowtable = huffman_table->slowtable[extra_nbits];
		/* Search if the code is in this array */
		while (slowtable[0]) {
			if (slowtable[0] == hcode) {
				//如果找到,那么将当前的bit数跳过并返回权值
				skip_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, nbits);
				return slowtable[1];
			}
			slowtable += 2;//查找下一个位置有没有
		}
	}
	return 0;
}

加载一个jpeg图像,对其进行解压缩,并存储结果

/**
 * Load one jpeg image, and decompress it, and save the result.
 * 加载一个jpeg图像,对其进行解压缩,并存储结果
 */
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);
  fclose(fp);

  /* Decompress it 初始化解码器*/
  jdec = tinyjpeg_init();
  if (jdec == NULL)
    exitmessage("Not enough memory to alloc the structure need for decompressing\n");
  //对header进行解码
  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;
}

对图片进行解码

/**
 * 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;
	}
	//选择合适的采样方式来进行解码
	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;
}

将YCrCb换成YUV4:2:0格式

static void YCrCB_to_YUV420P_1x1(struct jdec_private* priv)
{
	const unsigned char* s, * y;
	unsigned char* p;
	int i, j;

	p = priv->plane[0];
	y = priv->Y;
	for (i = 0; i < 8; i++)
	{
		memcpy(p, y, 8);
		p += priv->width;
		y += 8;
	}

	p = priv->plane[1];
	s = priv->Cb;
	for (i = 0; i < 8; i += 2)
	{
		for (j = 0; j < 8; j += 2, s += 2)
			*p++ = *s;
		s += 8; /* Skip one line */
		p += priv->width / 2 - 4;
	}

	p = priv->plane[2];
	s = priv->Cr;
	for (i = 0; i < 8; i += 2)
	{
		for (j = 0; j < 8; j += 2, s += 2)
			*p++ = *s;
		s += 8; /* Skip one line */
		p += priv->width / 2 - 4;
	}
}

保存DC和直流分量

/*
 * Decode all the 3 components for 1x1
 */
static void decode_MCU_1x1_3planes(struct jdec_private* priv)
{
	// Y
	process_Huffman_data_unit(priv, cY);
#if  TRACE
	save_DC_AC(priv, cY);
#endif //  TRACE
	IDCT(&priv->component_infos[cY], priv->Y, 8);
	// Cb
	process_Huffman_data_unit(priv, cCb);
#if  TRACE
	save_DC_AC(priv, cCb);
#endif //  TRACE
	IDCT(&priv->component_infos[cCb], priv->Cb, 8);

	// Cr
	process_Huffman_data_unit(priv, cCr);
#if  TRACE
	save_DC_AC(priv, cCr);
#endif //  TRACE
	IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

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

static int parse_DHT(struct jdec_private* priv, const unsigned char* stream)
{
	...
#if TRACE
		fprintf(p_trace, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
		fprintf(Huffman_code, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
		fflush(p_trace);
#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 void build_huffman_table(const unsigned char* bits, const unsigned char* vals, struct huffman_table* table)
{
	...
	for (i = 0; huffsize[i]; i++)
	{
		//获取值,编码,和码长
		val = vals[i];
		code = huffcode[i];
		code_size = huffsize[i];
#if TRACE
		fprintf(p_trace, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
		fprintf(Huffman_code, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
		fflush(p_trace);
#endif
		//建立权值和码长的关系
		table->code_size[val] = code_size;
		...
}

五、实验结果

将解码后的JPEG文件的输出文件保存为可供YUVViewer观看的YUV文件:
在这里插入图片描述
以txt文件输出所有的量化矩阵和所有的HUFFMAN码表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值