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

文章目录

  • 前言
  • 一、JPEG
    • 1、JPEG概述
    • 2、文件结构
    • 3、编码流程
    • 4、解码流程
  • 二、实验内容
    • 1、tinyjpeg-internal.h定义结构体
    • 2、JPEG文件主要操作代码
  • 三、实验结果
    • 1、YUV图像
    • 2、量化表
    • 3、霍夫曼码表
    • 4、分量图像


前言

  JPEG( Joint Photographic Experts Group)是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。其主要是采用预测编码(DPCM)离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式。它的特点在于够将图像压缩在很小的储存空间,但不可避免地一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例。使用JPEG方法将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。

  然而,JPEG编码可以用有损压缩方式去除冗余的图像数据。换句话说,就是可以用较少的磁盘空间得到较好的图像品质。而且JPEG是一种很灵活的格式,具有调节图像质量的功能,它允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在10:1到40:1。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网;它可减少图像的传输时间,支持24位真彩色;也普遍应用于需要连续色调的图像中。

  本实验则在已经对JPEG的原理进行学习理解的基础上,对C语言实现的JPEG分析和解码程序进行分析,进行JPEG向YUV的转换,对JPEG文件的等信息进行分析。


一、JPEG

1、JPEG概述

  JPEG文件格式是JPEG(联合图像专家组)标准的产物,该标准由ISO与CCI TT(国际电报电话咨询委员会)共同制定,是面向连续色调静止图像的一种压缩标准。其压缩步骤大致分为四步:

  • 颜色转换
      由于JPEG只支持YUV颜色模式,而不支持RGB颜色模式,所以在将彩色图像进行压缩之前,必须先对颜色模式进据转换。转换完成之后还需要进行数据采样。一般采用的采样比例是2:1:1或4:2:2。由于在执行了此项工作之后,每两行数据只保留一行,因此,采样后图像数据量将压缩为原来的一半。

  • DCT变换
      DCT(DiscreteConsineTransform)是将图像信号在频率域上进行变换,分离出高频和低频信息的处理过程。然后再对图像的高频部分(即图像细节)进行压缩,以达到压缩图像数据的目的。首先将图像划分为多个8*8的矩阵。然后对每一个矩阵作DCT变换(变换公式此略)。变换后得到一个频率系数矩阵,其中的频率系数都是浮点数。

  • 量化
      由于在后面编码过程中使用的码本都是整数,因此需要对变换后的频率系数进行量化,将之转换为整数。由于进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。

  • 编码
      编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)。在JPEG中,采用曲徊序列,即以矩阵对角线的法线方向作“之”字排列矩阵中的元素。这样做的优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。行程长度编码是非常简单和常用的编码方式,在此不再赘述。编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。

  本实验则对JPEG的DCT变换、量化和编码操作进行学习与代码分析。

2、文件结构

  此实验使用实验素材中的图片进行 .jpg文件结构分析。使用HexFlex软件进行十六进制分析。
JPEG 原理分析及 JPEG 解码器的调试_第1张图片
  JPEG文件数据以数据段(Segment)为单位,以高位在前低位在后的形式进行存储,不同的段以首部两个字节进行标志,且大多段的有效数据长度(不含段头)均在段头后的2个字节进行标识。不同的段其段头两字节有特殊标识。

  JPEG文件不同段类型分为:

  • 文件开始标记 SOI
     标记固定为0xFFD8,是所有的JPEG文件的文件头。
    在这里插入图片描述

  • 应用程序保留标记 APP0
     固定标记为0xFFE0,其中包含如图所示字段。
    JPEG 原理分析及 JPEG 解码器的调试_第2张图片
    在这里插入图片描述
     同时部分JPEG文件可能还含有APPn标记,以携带图片对应的应用程序信息(摄制设备等)。其中n的值为1-15,对应的固定标记为0xFFE1-0xFFEF。

  • 定义量化表 DQT
     固定标记为0xFFDB,在JPEG文件中通常存在两个,分别为亮度信息的量化表与色度信息的量化表。每个量化表的长度一般为0x0043(若文件中两量化表合在一起长度则为0x0084)。
    JPEG 原理分析及 JPEG 解码器的调试_第3张图片
    JPEG 原理分析及 JPEG 解码器的调试_第4张图片

  • 帧图像开始 SOF0
     固定标记为0xFFC0,其内容如下。
    JPEG 原理分析及 JPEG 解码器的调试_第5张图片
    JPEG 原理分析及 JPEG 解码器的调试_第6张图片

  • 定义霍夫曼表 DHT
     固定标记为0xFFC4,后面两字节为DHT段长度。
     DHT段第五字节则是霍夫曼表ID和类型。其中高四位为表类型,0和1分别代表当前表为直流分量霍夫曼表和交流分量霍夫曼表。低四位则为表ID,表示当前表为第几个表。
     其后的16字节则代表霍夫曼编码后对应码长的码字个数。霍夫曼码长为1至16的码字个数分别由这16个字节进行表示。
     再其后的字节则为编码内容,表示每个符字对应的权值。
    在这里插入图片描述
     上图即为示例文件中某个ID为0的直流分量霍夫曼表。可看到此表中2-16位码字应共有1+2+5+3+3+3+2+5+3+4+2+2+2+1+5=43个。选中后面表示每种码字的权值,可看到的确位43个。
    JPEG 原理分析及 JPEG 解码器的调试_第7张图片
     而其中权值含义则为在解码过程中,此码字后的数据码字的位长。根据位长读取紧随其后的码字,即可进行解码。

  • 定义扫描起点 SOS
     固定标志为0xFFDA,除段长度2字节外,存储包含颜色分量信息和压缩图像数据。
    JPEG 原理分析及 JPEG 解码器的调试_第8张图片
    图中0xFFDA后即为此段数据。

3、编码流程

  JPEG编码的过程如下图所示。而解码则是编码的逆过程。
JPEG 原理分析及 JPEG 解码器的调试_第9张图片
  如图所示。过程分别为:

  • Level Offset 零偏置
      对于灰度级峰值为2n的图片,通过将像素减去2n-1,将无符号数转换为有符号数,从而使像素的绝对值出现3位10进制的概率大大减少,缩小数据范围。

  • 8×8 DCT变换
     对每个单独的图像分量,进行8×8块为单位的DCT变换,将系数向矩阵左上位置进行集中,以适合后续的交流直流分量分别编码的方式。

  • Uniform Scalar Quantization 量化表量化
     对亮度信息和色度信息分别根据量化表进行量化。

  • 编码
     编码分为上下两路,分别对量化后的系数矩阵作直流分量和交流分量的编码。
     其中直流分量采用DPCM(差分脉冲调制编码)的形式进行编码之后,再对编码所得的预测差值进行霍夫曼熵编码。
     而对量化矩阵中的交流分量,由于DCT变换后的系数矩阵能量大多数存在于左上角,故使用之字形扫描。
    JPEG 原理分析及 JPEG 解码器的调试_第10张图片
     而对之字形扫描之后的数据进行游程编码得到(RRRR,SSSS)格式的游程码数据,再对数据进行固定格式的霍夫曼编码,从而得到最终编码数据。
    JPEG 原理分析及 JPEG 解码器的调试_第11张图片

4、解码流程

 解码则是对编码的逆操作,其步骤大致为:

  • 解码霍夫曼编码码字数据
  • 分别解码直流分量和交流分量
  • 矩阵反量化
  • DCT逆变换
  • 丢弃补充的行和列(在编码过程中防止图像行列无法被8整除而进行边缘拓展)
  • 反0偏置
  • 对插值Cb Cr分量进行插值
  • 还原图像数据信息(向RGB转换)

二、实验内容

 实验通过对JPEG文件解码工程进行调试,对工程运行逻辑进行分析和理解,并加入实验内容,以实现实验目的。
 工程jpeg_dec共有三个头文件 stdint.h、tinypeg.h和tinyjpeg-internal.h,以及三个代码代码.c文件 jidctflc.c、loadjpeg.c和tinyjpeg.c。

1、tinyjpeg-internal.h定义结构体

① 霍夫曼码表结构体

//霍夫曼码表
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];
	};

② 图像块结构体

//一个8×8的块(DCT变换及量化编码单元)
struct component
	{
	unsigned int Hfactor; //水平采样因子
	unsigned int Vfactor; //垂直采样因子
	float* Q_table;		/* Pointer to the quantisation table to use */
	struct huffman_table* AC_table; //交流DCT系数的哈夫曼码表指针
	struct huffman_table* DC_table; //直流DCT系数的哈夫曼码表指针
	short int previous_DC; //前一个块的直流系数(用于进行预测差分编码)
	short int DCT[64]; //存储当前块的8×8 DCT系数
#if SANITY_CHECK
	unsigned int cid;
#endif
	};

③ 解码信息结构体

//文件基本解码信息
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;

	/* Pointer to the current stream */
	const unsigned char* stream; //指向文件流的指针
	unsigned int reservoir, nbits_in_reservoir;

	struct component component_infos[COMPONENTS];

	/* quantization tables */
	float Q_tables[COMPONENTS][64]; //量化表

	/* Huffman Tables   */
	struct huffman_table HTDC[HUFFMAN_TABLES]; //直流系数霍夫曼表
	struct huffman_table HTAC[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]; //存储反DCT变换之后的分量(4:2:0)

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

	/* 添加:用于存放直流系数(DCT[0])和交流系数(DCT[1])的数组 */
	int* dc, * ac; //用于读取的指针
	unsigned char* outdc, * outac; //用于输出的指针

	};

2、JPEG文件主要操作代码

 实验中对JPEG进行解码等主要操作均在tinyjpeg.c文件中。此实验通过代码运行逻辑顺序进行分析理解,并在过程中通过添加代码完成实验要求。

① 解析JPEG文件数据段
 对读取过程中的文件段头(两字节)标志进行读取,并判断段的种类,调用相应的函数进行操作。

//判断jpeg文件
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)) //以0xFFD8开始
		snprintf(error_string, sizeof(error_string), "Not a JPG file ?\n");

	priv->stream_begin = buf + 2; //跳过0xFFD8头,读取文件头
	priv->stream_length = size - 2; //除去0xFFD8头的长度,为文件流总长度
	priv->stream_end = priv->stream_begin + priv->stream_length; //利用头指针和长度直接定位文件尾指针

	ret = parse_JFIF(priv, priv->stream_begin);

	return ret;
	}

 读取函数parse_JFIF()如下。

static int parse_JFIF(struct jdec_private* priv, const unsigned char* stream)
	{
	int chuck_len;
	int marker;
	int sos_marker_found = 0;
	int dht_marker_found = 0;

	const unsigned char* next_chunck;//为了实现块的系数的预测编码,定义临近块指针

	/* Parse marker */
	while (!sos_marker_found)
		{
		if (*stream++ != 0xff)
			goto bogus_jpeg_format;
		/* Skip any padding ff byte (this is normal) */
		while (*stream == 0xff)
			stream++;

		marker = *stream++;
		chuck_len = be16_to_cpu(stream);
		next_chunck = stream + chuck_len;
		switch (marker)//按照段头标志进行识别
			{
			case SOF:
				if (parse_SOF(priv, stream) < 0)
					return -1;
				break;
			case DQT:
				if (parse_DQT(priv, stream) < 0)
					return -1;
				break;
			case SOS:
				if (parse_SOS(priv, stream) < 0)
					return -1;
				sos_marker_found = 1;
				break;
			case DHT:
				if (parse_DHT(priv, stream) < 0)
					return -1;
				dht_marker_found = 1;
				break;
			case DRI:
				if (parse_DRI(priv, stream) < 0)
					return -1;
				break;
			default:
#if TRACE
				fprintf(p_trace, "> Unknown marker %2.2x\n", marker);
				fflush(p_trace);
#endif
				break;
			}

		stream = next_chunck; //下一个数据块,继续进行扫描
		}

	if (!dht_marker_found) {
#if TRACE
		fprintf(p_trace, "No Huffman table loaded, using the default one\n");
		fflush(p_trace);
#endif
		build_default_huffman_tables(priv);
		}

#ifdef SANITY_CHECK
	if ((priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)
		|| (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor))
		snprintf(error_string, sizeof(error_string), "Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");
	if ((priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor)
		|| (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))
		snprintf(error_string, sizeof(error_string), "Vertical sampling factor for Y should be greater than vertical sampling factor for Cb or Cr\n");
	if ((priv->component_infos[cCb].Hfactor != 1)
		|| (priv->component_infos[cCr].Hfactor != 1)
		|| (priv->component_infos[cCb].Vfactor != 1)
		|| (priv->component_infos[cCr].Vfactor != 1))
		snprintf(error_string, sizeof(error_string), "Sampling other than 1x1 for Cr and Cb is not supported");
#endif

	return 0;
bogus_jpeg_format:
#if TRACE
	fprintf(p_trace, "Bogus jpeg format\n");
	fflush(p_trace);
#endif
	return -1;
	}

② DQT段操作函数
 对DQT段进行解析,以得到量化表信息。同时加入代码,来输出所有的量化矩阵。

/* 解析DQT(0xFFDB),获取量化矩阵元素 */
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; //跳过长度信息的两字节,指向有效数据

	/* 是否已经达到了尾部 */
	while (stream < dqt_block_end)
		{
		qi = *stream++;

#if SANITY_CHECK
		//高四位为量化位数,此字节为0x00为8位,0x01为16位
		if (qi >> 4)
			snprintf(error_string, sizeof(error_string), "16 bits quantization table is not supported\n");
		//低四位为量化表ID
		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);

		/* 以之字形扫描顺序输出量化矩阵 */
		FILE* fp = fopen("table.txt", "a");
		if (fp == NULL)
			{
			printf("Fail to open file!\n");
			exit(0);
			}

		fprintf(fp, "量化表\n");
		for (int i = 0; i < 64; i++)
			{
			if (i != 0 && i % 8 == 0)
				fprintf(fp, "\n");
			fprintf(fp, "%f\t   ", table[i]);

			}
		printf(fp, "\n");

		stream += 64;
		}

#if TRACE
	fprintf(p_trace, "< DQT marker\n");
	fflush(p_trace);
#endif
	return 0;
	}

③ SOF段操作函数

/* 解析SOF0(FFC0),得到图像基本数据 */
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);

	//不包含FFC0头情况下,第三四字节为图像高度,第五六字节为图像宽度,后面紧跟一字节为颜色分量数(03)
	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; i < nr_components; i++) {
		cid = *stream++; //颜色分量ID
		sampling_factor = *stream++; //水平和垂直采样因子
		Q_table = *stream++; //颜色分量的量化表序号
		c = &priv->component_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;
	}

④ DHT段操作函数
 通过解析DHT数据段,来得到霍夫曼码表信息,同时加入代码输出所有霍夫曼码表。

/* DHT数据解析(0xFFC4,一般为两个),存储霍夫曼码表 */
static int parse_DHT(struct jdec_private* priv, const unsigned char* stream)
	{
	unsigned int count, i;

	unsigned char huff_bits[17]; //码长上限为16,此数组存储特定码长的码字个数
	int length, index;

	length = be16_to_cpu(stream) - 2; //除去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++;

		huff_bits[0] = 0;
		count = 0;
		for (i = 1; i < 17; i++) {
			huff_bits[i] = *stream++; //统计特定码长的码字个数
			count += huff_bits[i]; // count个字节对应的就是每个字符对应的权值
			}

#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

		/* 添加:以txt文件输出所有的HUFFMAN码表 */
		FILE* fp = fopen("huffman.txt", "a");
		if (fp == NULL)
			{
			printf("Fail to open file!\n");
			exit(0);  //退出程序(结束程序)
			}
		// 高四位为1是交流分量表
		if (index & 0xf0)
			{
			fprintf(fp, "huffman_table	AC%d号表\n", (index & 0xf));
			build_huffman_table(huff_bits, stream, &priv->HTAC[index & 0xf]); //建立霍夫曼表
			}
		else
			{
			fprintf(fp, "huffman_table	DC%d号表\n", (index & 0xf));
			build_huffman_table(huff_bits, stream, &priv->HTDC[index & 0xf]);
			}
			
		length -= 1;
		length -= 16;
		length -= count;
		stream += count; //向后继续推进,直到length为0
		}

#if TRACE
	fprintf(p_trace, "< DHT marker\n");
	fflush(p_trace);
#endif

	return 0;
	}

⑤ SOS段操作函数

/* 解析SOS(0xFFDA),以得到颜色分量的霍夫曼表序号(对应DHT中的序号数据) */
static int parse_SOS(struct jdec_private* priv, const unsigned char* stream)
	{
	unsigned int i, cid, table;
	unsigned int nr_components = stream[2]; //读取颜色分量数(仅支持3)

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

#if SANITY_CHECK
	if (nr_components != 3)
		snprintf(error_string, sizeof(error_string), "We only support YCbCr image\n");
#endif

	stream += 3;

	for (i = 0; i < nr_components; i++) { //对每个颜色进行表查找和处理
		cid = *stream++; //表ID
		table = *stream++; //表数据

#if SANITY_CHECK
		if ((table & 0xf) >= 4)
			snprintf(error_string, sizeof(error_string), "We do not support more than 2 AC Huffman table\n");
		if ((table >> 4) >= 4)
			snprintf(error_string, sizeof(error_string), "We do not support more than 2 DC Huffman table\n");
		if (cid != priv->component_infos[i].cid)
			snprintf(error_string, sizeof(error_string), "SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
				i, cid, i, priv->component_infos[i].cid);
#if TRACE
		fprintf(p_trace, "ComponentId:%d  tableAC:%d tableDC:%d\n", cid, table & 0xf, table >> 4);
		fflush(p_trace);
#endif
#endif
		//查找的量化表数据进行取值
		priv->component_infos[i].AC_table = &priv->HTAC[table & 0xf]; //低四位为AC
		priv->component_infos[i].DC_table = &priv->HTDC[table >> 4]; //高四位为DC
		}

	priv->stream = stream + 3;

#if TRACE
	fprintf(p_trace, "< SOS marker\n");
	fflush(p_trace);
#endif
	return 0;
	}

⑥ 对读取结构体数据进行处理
 在此函数中,对每个块的水平和垂直采样情况进行解析,计算MCU。同时通过调用不同的MCU读取和处理函数进行数据处理。
 同时在此函数中添加代码以实现输出原JPEG图像的直流分量图像和其中一个交流分量图像。

//主要解码过程
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;
		}

	/* 添加:分别输出DC分量图像和AC分享图像 */
	FILE* DC_image = fopen("DCimage.yuv", "wb");
	FILE* AC_image = fopen("ACimage.yuv", "wb");

	priv->dc = (int*)malloc(sizeof(int) * (priv->width * priv->height) / 64); // 每个8x8的块仅有一个直流分量
	priv->ac = (int*)malloc(sizeof(int) * (priv->width * priv->height) / 64);// 因为只输出某一AC值图像,所以AC和DC所占空间相同

	xstride_by_mcu = ystride_by_mcu = 8; //初始化为4:4:4,即MCU宽高比为1:1,均为8像素

	if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
		//Y分量垂直和水平的采样因子相等(4:4:4),每个MCU仅包含一个Y分量,且MCU的宽和高不变
		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) {
		//水平采样为1,垂直采样为2(4:2:2),每个MCU包含2个Y分量,且MCU的高需要增加一倍
		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) {
		//水平采样和垂直采样均为2(4:2:0或4:1:1),每个MCU包含4个Y分量,且MCU的宽和高都需要增加一倍
		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 {
		//水平采样和垂直采样均为2(另一种4:2:2),每个MCU包含4个Y分量,且MCU的宽需要增加一倍
		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)
			{
			// 每解码一个8x8的宏块,将支流系数(DCT[0])和交流系数(DCT[1])分别赋值给定义的dc和ac参数
			int i = 0;
			if (i < priv->width * priv->height / 64)
				{
				priv->dc[i] = priv->component_infos[0].DCT[0];
				priv->ac[i] = priv->component_infos[0].DCT[1];
				}
			i++;

			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) //查找RST标记
						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

	/* 根据DCT的能量守恒特性,反DCT之后的DC取值最大可达到8*256,且反差分编码后数据可能为负数。因此需要对得到的直流和交流系数进行处理,使其值分布在[0,255]之间*/
	priv->outdc = (unsigned char*)malloc(sizeof(unsigned char) * (priv->width * priv->height) / 64);
	priv->outac = (unsigned char*)malloc(sizeof(unsigned char) * (priv->width * priv->height) / 64);
	int dcmax = priv->dc[0];
	int acmax = priv->ac[0];
	int dcmin = priv->dc[0];
	int acmin = priv->ac[0];

	for (int j = 0; j < priv->width * priv->height / 64; j++)
		{
		if (priv->dc[j] > dcmax)
			dcmax = priv->dc[j];
		if (priv->dc[j] < dcmin)
			dcmin = priv->dc[j];
		if (priv->ac[j] > acmax)
			acmax = priv->ac[j];
		if (priv->ac[j] < acmin)
			acmin = priv->ac[j];
		}

	// 数据范围处理
	for (int k = 0; k < priv->width * priv->height / 64; k++)
		{
		priv->outdc[k] = (unsigned char)(255 * (priv->dc[k] - dcmin) / (dcmax - dcmin));
		priv->outac[k] = (unsigned char)(255 * (priv->ac[k] - acmin) / (acmax - acmin));
		}

	fwrite(priv->outdc, 1, priv->width * priv->height / 64, DC_image);
	fwrite(priv->outac, 1, priv->width * priv->height / 64, AC_image);

	return 0;
	}

三、实验结果

 对工程进行生成和运行,可得到实验输出结果:

1、YUV图像

JPEG 原理分析及 JPEG 解码器的调试_第12张图片

2、量化表

 实验中向txt文件中对JPEG文件的两个量化表进行了输出。
JPEG 原理分析及 JPEG 解码器的调试_第13张图片

3、霍夫曼码表

 实验中向txt文件中对JPEG文件的所有霍夫曼码表进行了输出。
 其中包括:

  • 直流码表0:
    JPEG 原理分析及 JPEG 解码器的调试_第14张图片
  • 直流码表1:
    JPEG 原理分析及 JPEG 解码器的调试_第15张图片
  • 交流码表0
    JPEG 原理分析及 JPEG 解码器的调试_第16张图片
  • 交流码表1
    JPEG 原理分析及 JPEG 解码器的调试_第17张图片
     同时在huffman文件中存储了所有码表的完整信息。
    JPEG 原理分析及 JPEG 解码器的调试_第18张图片

4、分量图像

 实验中输出了原图像中的直流分量图像和交流分量图像,并对两图像的概率密度分布进行了绘图。

直流分量 交流分量
输出图像 JPEG 原理分析及 JPEG 解码器的调试_第19张图片 JPEG 原理分析及 JPEG 解码器的调试_第20张图片
概率分布 JPEG 原理分析及 JPEG 解码器的调试_第21张图片 JPEG 原理分析及 JPEG 解码器的调试_第22张图片

你可能感兴趣的:(图像处理,c++,c语言)