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

JPEG解码 刚换了个破解版markdown编辑器叫MWeb,没成想写完以后有错误无法导出了,烦请各位看官移步: JPEG解码 JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为jpg或jpeg,是最常用的图像文件格式。其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的 阅读详情
AI摘要

本文深入剖析JPEG解码原理,涵盖JPEG编解码流程、DC与AC系数编码、Huffman编码解码过程、JPEG文件格式及Segment组织形式。通过代码解读,详述解码器设计与调试技巧,包括Huffman表构建、DCT逆变换、量化表应用等关键技术。

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

原理分析

JPEG编解码流程图

JPEG编解码流程

  1. 将传入的图像进行零电平偏置,其实就是将所有像素的数值减去128,将其范围从[0,255]变成[-128,127]。
  2. 进行8×8的DCT变换,实现能量集中和去相关,便于去除图像的空间冗余度。
  3. 使用量化表进行量化。利用根据人眼视觉特性设计的量化的量化矩阵进行量化,低频细量化,高频粗量化,进而减少视觉冗余。
  4. 最后对量化后的直流系数进行差分和VLC编码;对交流系数进行zig-zag扫描和游程编码最后再进行VLC编码。从而减少数据冗余。
DC系数编码

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

AC系数编码

首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)]
其中Amplitude表示非零系数的幅度值;Run:表示零的游程即零的个数;Size:
表示非零系数的幅度值的编码位数;

JPEG文件格式

Segment组织形式

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

  • 均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(该长度包含Length本身所占用的 2 byte,指的是length及其后的数据长度)
  • 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
  • Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
JPEG 的 Segment Marker
non-hierarchical Huffman coding
SymbolCode Assignment(0xFF+Marker)Description
S O F 0 SOF_0 SOF0​0xFFC0Baseline DCT
S O F 1 SOF_1 SOF1​0xFFC1Extended sequential DCT
S O F 2 SOF_2 SOF2​0xFFC2Progressive DCT
S O F 3 SOF_3 SOF3​0xFFC3Spatial (sequential) lossless
hierarchical Huffman coding
SymbolCode Assignment(0xFF+Marker)Description
S O F 5 SOF_5 SOF5​0xFFC5Differential sequential DCT
S O F 6 SOF_6 SOF6​0xFFC6Differential progressive DCT
S O F 7 SOF_7 SOF7​0xFFC7Differential spatial lossless
non-hierarchical arithmetic coding
SymbolCode Assignment(0xFF+Marker)Description
JPG0xFFC8Reserved for JPEG extensions
S O F 9 SOF_9 SOF9​0xFFC9Extended sequential DCT
S O F 10 SOF_{10} SOF10​0xFFCAProgressive DCT
S O F 11 SOF_{11} SOF11​0xFFCBSpatial (sequential) Lossless
hierarchical arithmetic coding
SymbolCode Assignment(0xFF+Marker)Description
S O F 1 3 SOF_13 SOF1​30xFFCDDifferential sequential DCT
S O F 1 4 SOF_14 SOF1​40xFFCEDifferential progressive DCT
S O F 15 SOF_{15} SOF15​0xFFCFDifferential progressive DCT
Huffman table specification
SymbolCode Assignment(0xFF+Marker)Description
DHT0xFFC4Define Huffman table(s)
arithmetic coding conditioning specification
SymbolCode Assignment(0xFF+Marker)Description
DAC0xFFCCDefine arithmetic conditioning table
Restart interval termination
SymbolCode Assignment(0xFF+Marker)Description
RSTm0xFFD0~0xFFD7Restart with modulo 8 counter m
Other marker
SymbolCode Assignment(0xFF+Marker)Description
SOI0xFFD8Start of image
EOI0xFFD9End of image
SOS0xFFDAStart of scan
DQT0xFFDBDefine quantization table(s)
DNL0xFFDCDefine number of lines
DRI0xFFDDDefine restart interval
DHP0xFFDEDefine hierarchical progression
EXP0xFFDFExpand reference image(s)
A P P n APP_n APPn​0xFFE0~0xFFEFReserved for application use
J P G n JPG_n JPGn​0xFFF0~0xFFFDReserved for JPEG extension
COM0xFFFEComment
Reserved markers
SymbolCode Assignment(0xFF+Marker)Description
TEM0xFF01For temporary use in arithmetic coding
RES0xFF02~0xFFBFReserved
Segment详细信息
SOI & EOI
标志字节数含义
SOI2字节Start of Image,图像开始
EOI2字节End of Image,图像结束
APP0

共有9个字段

字段字节数含义
数据长度2字节1-9共9个字段的总长度
标识符5字节固定值0x4A46494600,即字符串“JFIF0”
版本号2字节一般是0x0102,表示JFIF的版本号1.2
X和Y的密度单位1字节只有三个值可选
0:无单位;1:点数/英寸;2:点数/厘米
X方向像素密度2字节X方向像素密度
Y方向像素密度2字节Y方向像素密度
缩略图水平像素数目1字节缩略图水平像素数
缩略图垂直像素数目1字节缩略图垂直像素数
缩略图RGB位图长度可能是3的倍数缩略图RGB位图数据
DQT

即定义量化表。

字段字节数含义
数据长度2字节所有字段的总长度
量化表×n数据长度-2字节详细内容见下表

量化表中字段的含义:

字段字节数含义
量化精度及量化表ID1字节高四位精度,0表示8位,1表示16位
低四位为量化表ID,取值范围为0-3
表项(64×(精度+1))字节表格的具体内容

本标记段中,量化表可以重复出现,表示有多个量化表,但最多只能出现4次。

SOF0

即帧图像开始 。

字段字节数含义
数据长度2字节所有字段的总长度
精度1字节每个数据样本的位数
通常是8位,一般软件都不支持 12位和16位
图像高度2字节图像高度(单位:像素)
图像宽度2字节图像宽度(单位:像素)
颜色分量数1字节只有3个数值可选
1:灰度图;
3:YCrCb或YIQ;
4:CMYK
而JFIF中使用YCrCb,故这里颜色分量数恒为3
颜色分量信息颜色分量数×3字节
(通常为9字节)
颜色分量ID 1字节
水平/垂直采样因子 1字节 高四位为水平采样因子,低四位为垂直采样因子
量化表 1字节 当前分量所使用量化表的ID
DHT

即定义哈夫曼表。

字段字节数含义
数据长度2字节所有字段的总长度
Huffman表×n数据长度-2字节详细内容见下表

Huffman表中的内容:

字段字节数含义
表ID和表类型1字节高四位表示类型,0表示直流,1表示交流
低四位为Huffman表ID,DC表和AC表分开编码
不同位数的码字数量16字节不同位数码字的数量
编码内容16个不同位数的码字数量之和(字节)编码的具体内容

Huffman表可以重复出现,但最多4次。对于Huffman表的存储方式在下面会有介绍。

SOS

即扫描开始。

字段字节数含义
数据长度2字节所有字段的总长度
颜色分量数1字节应该和SOF中的相应字段值相同
1:灰度图是;3: YCrCb或YIQ;4:CMYK
颜色分量信息2字节其中包括
颜色分量ID 1字节
直流/交流系数表号 1字节 高四位:直流分量使用的哈夫曼树编号;低四位交流分量使用的哈夫曼树编号。
压缩图像数据2字节谱选择开始 1字节 固定值0x00
谱选择结束 1字节 固定值0x3F
谱选择 1字节 在基本JPEG中总为00

Huffman表存储方式说明

在标记段DHT内,包含了一个或者多个的哈夫曼表。对于单一个哈夫曼表,应该包括了三部分

  • 哈夫曼表ID和表类型
    • 这个字节的值为一般只有四个0x00、0x01、0x10、0x11。
      • 0x00表示DC直流0号表
      • 0x01表示DC直流1号表
      • 0x10表示AC交流0号表
      • 0x11表示AC交流1号表
  • 不同位数的码字数量
  • 编码内容
    • 第一字段:JPEG文件的哈夫曼编码最长只有16位。所以这个字段的16个字节分别表示1-16位的编码码字在哈夫曼树中的个数。
    • 第二字段:这个字段记录了哈夫曼树中各个叶子结点的权。所以,上一字段(不同位数的码字数量)的16个数值之和就应该是本字段的长度,也就是哈夫曼树中叶子结点个数。

举个例子:FF C4 00 3E 00 00 03 01 01 01 01 01 01 01 00 00 00 00 00 00 04 05 06 03 02 01 00 09 07 08

其中

  • 红色部分表示哈夫曼表的ID和表的类型,本例中0x00表示此部分数据描述的是DC交流0号表。
  • 蓝色部分表示为不同码长的码字的数量。本例中,即码长为1的码字有0个,码长为2的码字有3个,码长为3的码字有1个,以此类推。
  • 绿色部分的字节数等于蓝色部分的数值相加。此处为10字节。表示每个叶子节点从小到大(所谓从小到大实际上是指的码长和出现顺序)排序后,对应的权值(权值对直流和交流系数的含义不同)。
建立huffman树/表

Huffman码表的建立方式比较简单,

具体方法为:

  • 第一个码字必定为0
    • 如果第一个码字位数为1,则码字为0;
    • 如果第一个码字位数为2,则码字为00;
    • 以此类推
  • 从第二个码字开始
    • 如果它和它前面的码字位数相同,则当前码字为它前面的码字加1;
    • 如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,直至满足位数长度为止。

按照上述方法,可以根据上一个例子建立一个霍夫曼码表:

序号码长码字权值
12004
22015
32106
431103
5411102
65111101
761111100
8711111109
98111111107
1091111111108
代码解析

具体实现为:

/*
 * Takes two array of bits, and build the huffman table for size, and code
 * 使用两个关于bits的数组构建出来huffmantable
 * lookup will return the symbol if the code is less or equal than HUFFMAN_HASH_NBITS.
 * lookup表会返回编码后的码字,如果编码长度小于等于HUFFMAN_HASH_NBITS
 * code_size will be used to known how many bits this symbol is encoded.
 * 编码大小会被用来表示这个符号的长度为多少bit
 * slowtable will be used when the first lookup didn't give the result.
 * 当上述查找表无法使用给出答案的时候,使用slowtable
 */
static void build_huffman_table(const unsigned char* bits, const unsigned char* vals, struct huffman_table* table)
{
	//bits为每个长度出现的频次,vals是权值,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]
	 *   huffsize[x]表示用多少bit去表示vals[X]
	 * bits数组表示该huffman编码长度的出现的次数
	 * 下列代码将所有子节点初始化
	 */
	hz = huffsize;
	for (i = 1; i <= 16; i++)
	{
		for (j = 1; j <= bits[i]; j++)
			*hz++ = i;
	}
	*hz = 0;

	//初始化lookup表,全部赋值为最大值。初始化slowtable全为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]
	 *   huffcode[X] 表示用来表示vals[X]的编码
	 */
	code = 0;
	hc = huffcode;
	hz = huffsize;
	nbits = *hz;
	while (*hz)
	{
		//当长度没有变化的时候下一个编码为上一个+1
		while (*hz == nbits)
		{
			*hc++ = code++;
			hz++;
		}
		//当长度发生变换的时候则是+1补零
		code <<= 1;
		nbits++;
	}

	/*
	 * Build the lookup table, and the slowtable if needed.
	 * 建立慢速查找表,如果有需要
	 */
	next_free_entry = -1;
	//如果 huffsize[i]不为0,则执行循环
	for (i = 0; huffsize[i]; i++)
	{
		//获取值,编码,和码长
		val = vals[i];
		code = huffcode[i];
		code_size = huffsize[i];
		//建立权值和码长的关系
		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
			 * 如果code_size小于HUFFMAN_HASH_NBITS,即查找表的固定长度
			 * 就重复 1UL << (HUFFMAN_HASH_NBITS - code_size) 次
			 * 就相当于把表格中码字后边的所有位数补全,并把它的值全赋值成val
			 */
			int repeat = 1UL << (HUFFMAN_HASH_NBITS - code_size);
			code <<= HUFFMAN_HASH_NBITS - code_size;
			while (repeat--)
				table->lookup[code++] = val;

		}
		else
		{
			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 */
		}

	}
}
JPEG中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;
}
直流

对于直流来说,权值是解码时需要额外读入的bit位数。这个再次读入的数通过查表来得到真正的码值。

比如比特流:0110101011

由于前缀码的特性,读入01后码字结束,查表可以得到额外比特数为5,所以需要额外读5位数,此处为10101,译码可以得到21。所以直流系数为21.要注意的是,直流系数是差分编码之后得到的。

实际中的过程是把所有的颜色分量单元按颜色分量(Y、Cr、Cb)分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。也就是说,通过Huffman码表解码出来的直流变量数值只是当前颜色分量单元的实际直流变量减去前一个颜色分量单元的实际直流变量。也就是说,当前直流变量要通过前一个颜色分量单元的实际(非解码)直流分量来校正,即 D C n = D C n − 1 + D i f f DC_n=DC_{n-1}+Diff DCn​=DCn−1​+Diff,其中Diff为差分校正变量,也就是直接解码出来的直流系数。但如果当前颜色分量单元是第一个单元,则解码出来的直流数值就是真正的直流变量。

交流

对于交流系数,用交流哈夫曼树/表查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。

例如,权值0X31可以表示为(3,1)。表明交流系数前面有3个0,此外交流系数的具体值还需要再读入1bit的码字才能得到。

JPEG 的解码流程

  1. 读取文件

  2. 解析 Segment Marker

    1. 解析 SOI

    2. 解析 APP0

      • 检查标识“JFIF”及版本

      • 得到一些参数

    3. 解析 DQT

      • 得到量化表长度(可能包含多张量化表)
      • 得到量化表的精度
      • 得到及检查量化表的序号(只能是 0 ——3)
      • 得到量化表内容(64 个数据)
    4. 解析 SOF0

      • 得到每个 sample 的比特数、长宽、颜色分量数

      • 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)

      • 解析 DHT

    5. 解析 DHT

      • 得到 Huffman 表的类型(AC、DC)、序号
      • 依据数据重建 Huffman 表
    6. 解析 SOS

      • 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)
  3. 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数

  4. 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU中每个分量宏块解码)

    1. 对每个宏块进行 Huffman 解码,得到 DCT 系数
    2. 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
    3. 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
  5. 解析到 EOI,解码结束

  6. 将 Y、Cb、Cr 转化为需要的色彩空间并保存。

部分代码解释

convert_one_image

加载一个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;
}
tinyjpeg_decode

对图片进行解码。

/**
 * 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_to_YUV420P_1x1

将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;
	}
}
resync

初始化jdec_private。

static void resync(struct jdec_private* priv)
{
	int i;

	/* Init DC coefficients 初始化DC系数*/
	for (i = 0; i < COMPONENTS; i++)
		priv->component_infos[i].previous_DC = 0;

	priv->reservoir = 0;
	priv->nbits_in_reservoir = 0;
	if (priv->restart_interval > 0)
		priv->restarts_to_go = priv->restart_interval;
	else
		priv->restarts_to_go = -1;
}
decode_MCU_1x1_3planes

即对每个通道进行解码。类似的函数还有多个。

static void decode_MCU_1x1_3planes(struct jdec_private* priv)
{
	// Y
	process_Huffman_data_unit(priv, cY);
	IDCT(&priv->component_infos[cY], priv->Y, 8);

	// Cb
	process_Huffman_data_unit(priv, cCb);
	IDCT(&priv->component_infos[cCb], priv->Cb, 8);

	// Cr
	process_Huffman_data_unit(priv, cCr);
	IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
process_Huffman_data_unit
/**
 *
 * Decode a single block that contains the DCT coefficients.
 * 解码出单个DCT系数的块
 * The table coefficients is already dezigzaged at the end of the operation.
 * 表格的系数已经在操作最后进行了反zig-zag变换
 */
static void process_Huffman_data_unit(struct jdec_private* priv, int component)
{
	unsigned char j;
	unsigned int huff_code;
	unsigned char size_val, count_0;

	//获取该component的信息
	struct component* c = &priv->component_infos[component];
	short int DCT[64];


	/* Initialize the DCT coef table 初始化DCT系数*/
	memset(DCT, 0, sizeof(DCT));

	/* DC coefficient decoding 直流系数解码*/
	huff_code = get_next_huffman_code(priv, c->DC_table); //对霍夫曼编码进行解码
	//trace("+ %x\n", huff_code);
	if (huff_code) {	//如果权值不为零
		//读取额外的bit
		get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, huff_code, DCT[0]);
		//由于直流是差分编码,所以需要与前一个值相加才能获得真是值
		DCT[0] += c->previous_DC;
		c->previous_DC = DCT[0];
	}
	else {
		DCT[0] = c->previous_DC;
	}

	/* AC coefficient decoding 交流系数解码*/
	j = 1;
	while (j < 64)
	{
		huff_code = get_next_huffman_code(priv, c->AC_table);
		//trace("- %x\n", huff_code);
		//前四位为需要读取的额外bit,后四位是需要跳过的0的个数
		size_val = huff_code & 0xF;
		count_0 = huff_code >> 4;

		if (size_val == 0)
		{ /* RLE */
			if (count_0 == 0)
				break;	/* EOB found, go out EOB块结尾*/
			else if (count_0 == 0xF)
				j += 16;	/* skip 16 zeros */
		}
		else
		{
			j += count_0;	/* skip count_0 zeroes */
			if (__unlikely(j >= 64))
			{
				snprintf(error_string, sizeof(error_string), "Bad huffman data (buffer overflow)");
				break;
			}
			get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, size_val, DCT[j]);
			j++;
		}
	}

	for (j = 0; j < 64; j++)
		c->DCT[j] = DCT[zigzag[j]]; //进行反DCT变换

实验步骤

  1. 逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
  2. 程序调试过程中,做到
    • 理解程序设计的整体框架
    • 理解三个结构体的设计目的
      • struct huffman_table
      • struct component
      • struct jdec_private
    • 理解在视音频编解码调试中TRACE的目的和含义
      • 会打开和关闭TRACE
      • 会根据自己的要求修改TRACE
  3. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
  4. 输出DC图像并统计其概率分布。
  5. 输出某一个AC值图像并统计其概率分布。

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

由于使用的块是8×8的,所以当图像不够一整个块的时候会填充一些额外字节。所以所选图像的宽最好是8的倍数。
过程中用到的图片,大小320×256:
football
根据程序,输入如下命令行参数:
在这里插入图片描述
为了将输出文件保存为可供YUVViewer观看的YUV文件,找到保存yuv文件的函数:

static void write_yuv(const char *filename, int width, int height, unsigned char **components)

进行如下修改:

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

使用YUVviewer打开:
在这里插入图片描述
可见成功输出了yuv文件。

代码理解

程序设计的整体框架

本次程序大致分为三部分:

  • 异常处理,包括trace记录
  • 文件读写
  • 解码
    • 读取Segment信息
    • 针对每个Segment进行相应的解码

解码过程则是按照文章最开始的流程图进行的。

结构体设计的目的
huffman_table

创立一个快速查找表用于快速解码。另外如果查找失败的话则使用慢速查找表。
最主要的目的就是加速解码的过程。

struct huffman_table
{
  /* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
   * 快速查找表,使用HUFFMAN_HASH_NBITS个比特可以快速找到符号
   * if the symbol is <0, then we need to look into the tree table 
   * 如果符号小于0,那么需要在慢速查找表中查找,lookup中返回的是权值*/
  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];
};
component

componet主要是用来保存一个MCU块的信息。每处理一个新的MCU块后,信息都会更新。用它来保存一些解码过程中需要用到的信息,如量化表,前一个直流的值,和当前MCU的DCT块从而使解码更加简单。

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

jdec_private

该结构体使用最为广泛,是解码过程中频繁用到的东西,起到整合基本信息、连接各个部分的作用。他保存着图像的基本信息和各通道的信息。包括了前两个结构体的内容。由于是采用指针方式,并不占用额外的空间。

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 剩余的MCU*/
  int last_rst_marker_seen;			/* Rst marker is incremented each time 上一个看到的marker*/

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

};
trace的目的和含义

trace的目的主要是输出中间过程中的某些变量,或者错误信息。方便理解程序运行的过程以及哪部分可能会出错等。
trace还可以顺便理解程序运行的顺序和方式。

打开和关闭trace

本代码中trace的打开和关闭非常简单.

#define  snprintf _snprintf//add by nxn
#define TRACE 1//add by nxn
#define  TRACEFILE "trace_jpeg.txt"//add by nxn

发现有TRACE字段,将TRACE设置为1为打开,设置为0为关闭。

按照需求修改trace

如需要在trace记录中输出直流系数,只需要在process_Huffman_data_unit函数中添加

#if TRACE
	fprintf(p_trace, "%d ", DCT[0]);
#endif

即可。
完整代码如下:
为了方便,还可以继续输出一些标志,在解码前添加:
在这里插入图片描述
输出结果:
在这里插入图片描述

添加额外代码解决后续问题

在tinyjpeg.h中添加如下声明:

FILE* p_trace;//add by nxn
FILE* DC_coff, DC_freq;
FILE* AC_coff, AC_freq;
FILE* Huffman_code;
FILE* quantization_table;
int* AC_buffer[3], * DC_buffer[3];
int AC_pos[3], DC_pos[3];
int AC_max[3], DC_max[3], AC_min[3], DC_min[3];

在主程序中为霍夫曼表和量化矩阵分配文件指针。

#if TRACE
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
	  printf("trace file open error!");
  }
  
  DC_coff = fopen("DC_coff.yuv", "wb+");
  if (DC_coff == NULL)
  {
      printf("DC file open error!");
  }
  AC_coff = fopen("AC_coff.yuv", "wb+");
  if (DC_coff == NULL)
  {
      printf("AC file open error!");
  }
  Huffman_code = fopen("Huffman_code.txt", "w+");
  if (Huffman_code == NULL)
  {
      printf("Huffman_code file open error!");
  }
  quantization_table = fopen("quantization_table.txt", "w+");
  if (DC_coff == NULL)
  {
      printf("quantization_table file open error!");
  }
#endif

在tinyjpeg.c中实现保存即获得概率分布的函数。

//保存直流或交流的系数
void save_DC_AC(struct jdec_private* priv, int component)
{
	int DC = (priv->component_infos[component]).DCT[0], AC = priv->component_infos[component].DCT[1];
	DC_buffer[component][DC_pos[component] ++] = DC;
	DC_max[component] = DC_max[component] < DC ? DC : DC_max[component];
	DC_min[component] = DC_min[component] < DC ? DC_min[component] : DC;
	AC_buffer[component][AC_pos[component] ++] = AC;
	AC_max[component] = DC_max[component] < AC ? AC : DC_max[component];
	AC_min[component] = DC_min[component] < AC ? DC_min[component] : AC;
}
//初始话
void save_Init(struct jdec_private* priv)
{
	for (int i = 0; i < 3; ++i)
	{
		AC_buffer[i] = (int*)malloc(priv->width * priv->height / 16);
		DC_buffer[i] = (int*)malloc(priv->width * priv->height / 16);
		AC_pos[i] = 0; DC_pos[i] = 0;
		AC_max[i] = -1E9; AC_min[i] = 1E9;
		DC_max[i] = -1E9; DC_min[i] = 1E9;

	}
}
//输出直流和交流的频率分布
void get_freq()
{
	double* AC_fre[3], * DC_fre[3];
	for (int i = 0; i < 3; ++i)
	{
		AC_fre[i] = (int*)malloc((AC_max[i] - AC_min[i] + 1) * sizeof(double) );
		DC_fre[i] = (int*)malloc((DC_max[i] - DC_min[i] + 1) * sizeof(double));
		memset(AC_fre[i], 0, (AC_max[i] - AC_min[i] + 1) * sizeof(double));
		memset(DC_fre[i], 0, (DC_max[i] - DC_min[i] + 1) * sizeof(double));
	}
	for (int i = 0; i < 3; ++i)
	{
		for (int j = 0; j < DC_pos[i]; ++j)
		{
			AC_fre[i][AC_buffer[i][j] - AC_min[i]] ++;
			DC_fre[i][DC_buffer[i][j] - DC_min[i]] ++;
		}

	}
	char AC_file_name[] = "freq_DC_x.csv";
	char DC_file_name[] = "freq_AC_x.csv";
	for (int i = 0; i < 3; ++i)
	{
		DC_file_name[8] = i + '0';
		AC_file_name[8] = i + '0';
		FILE* DC = fopen(DC_file_name, "w+"), * AC = fopen(AC_file_name, "w+");
		fprintf(DC, "value, frequency\n"); fprintf(AC, "value, frequency\n");
		for (int j = 0; j < DC_max[i] - DC_min[i] + 1; ++j)
			fprintf(DC, "%d, %lf\n", j + DC_min[i], (1.0 * DC_fre[i][j]) / DC_pos[i]);
		for (int j = 0; j < AC_max[i] - AC_min[i] + 1; ++j)
			fprintf(AC, "%d, %lf\n", j + AC_min[i], (1.0 * AC_fre[i][j]) / AC_pos[i]);
		fclose(DC); fclose(AC);
	}
}

保存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);
}

在tinyjpeg_decoder中添加如下代码:

int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
    ...
#if TRACE
	fprintf(p_trace, "\nInput 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);
	//输出直流和交流的各个分量
	for (int comp = 0; comp < 3; ++comp)
	{
		for (int i = 0; i < AC_pos[comp]; ++i)
		{
			unsigned char t_AC = 1.0 * (AC_buffer[comp][i] - DC_min[comp]) / (AC_max[comp] - AC_min[comp]) * 255;
			unsigned char t_DC = 1.0 * (DC_buffer[comp][i] - AC_min[comp]) / (DC_max[comp] - DC_min[comp]) * 255;
			fwrite(&t_DC, 1, 1, DC_coff);
			fwrite(&t_AC, 1, 1, AC_coff);
		}
	}
	//获取频率分布
	get_freq();
#endif
	return 0;
}
以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。

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

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

在build_huffman_table中添加:

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

结果
其中一部分如下:
在这里插入图片描述
量化矩阵
在parse_DQT添加如下代码:

static int parse_DQT(struct jdec_private* priv, const unsigned char* stream)
{
	...
	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
#if TRACE
		fprintf(quantization_table, "original quantization table [%d]\n", qi);
#endif
		table = priv->Q_tables[qi];
		build_quantization_table(table, stream);
		stream += 64;
#if TRACE
		fprintf(quantization_table, "scaled quantization table [%d]\n", qi);
		for (int i = 0; i < 8; ++i)
			for (int j = 0; j < 8; ++j)
				if (j == 7) fprintf(quantization_table, "%10.3f\n", table[i * 8 + j]);
				else fprintf(quantization_table, "%10.3f\t", table[i * 8 + j]);
#endif
	...
	}

在build_quantization_table中添加:

static void build_quantization_table(float* qtable, const unsigned char* ref_table)
{
	...
	for (i = 0; i < 8; i++) {
		for (j = 0; j < 8; j++) {
			*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
		}
	}
#if TRACE
	for (int i = 0; i < 8; ++i)
		for (int j = 0; j < 8; ++j)
			if (j == 7) fprintf(quantization_table, "%10d\n", (int)ref_table[zigzag[i * 8 + j]]);
			else fprintf(quantization_table, "%10d\t", (int)ref_table[zigzag[i * 8 + j]]);
#endif
}

结果
在这里插入图片描述

输出某一个DC值图像并统计其概率分布

由于橄榄球的图像过小,仅保留直流或交流分量后,图片显示不清晰,于是采用下面的两个图片:

  1. 长和宽均为1024像素。
    在这里插入图片描述
  2. 分辨率为1440×1080
    在这里插入图片描述
    利用上文中的代码,可以得到DC系数图像:
图片1图片2
在这里插入图片描述在这里插入图片描述

如果仅看Y分量的DC系数,则是:

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

概率分布为:
图1:

Y Y Y C b C_b Cb​ C r C_r Cr​
在这里插入图片描述在这里插入图片描述在这里插入图片描述

图2

Y Y Y C b C_b Cb​ C r C_r Cr​
在这里插入图片描述在这里插入图片描述在这里插入图片描述
输出某一个AC值图像并统计其概率分布

该交流系数为DCT表中第一行第2个数。

利用上文中的代码,可以得到AC系数图像:

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

如果仅看Y分量的AC系数,则是:

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

概率分布为:
图1

Y Y Y C b C_b Cb​ C r C_r Cr​
在这里插入图片描述在这里插入图片描述在这里插入图片描述

图2

Y Y Y C b C_b Cb​ C r C_r Cr​
在这里插入图片描述在这里插入图片描述在这里插入图片描述
实验5 | JPEG原理分析 & 文件格式分析 & 解码原理 & JPEG解码器的调试 1 JPEG原理 1.1 JPEG简述 JPEG(Joint Photographic Experts Group)是联合图像专家组的英文缩写。 该组织从1986年正式开始制订静止数字图像的压缩编码标准,该标准于1992年正式通过,称为JPEG标准。 JPEG是第一个数字图像压缩的国际标准,它不仅适于静止图像的压缩,对 于电视图像序列的帧内压缩也常采用JPEG算法,因此JPEG是一个适用范围广泛的通用标准。 1.2 JPEG编码过程 1.2.0 RGB to YUV 1.2.1 Level Offset 阅读详情

相关推荐

JPEG解码详解--实例

根据前面学习到的jpeg解码知识,编写jpeg解码code,此处采用实际的解码过程对jpeg进行讲解 如图1,为实际的jpg图像数据,根据JPEG格式,解析出对应的标识符与对应的数据信息。 FF D8:SOI 文件头 FF E0:APP0 图像信息识别 FF DB:DQT 量化表 FF C0:SOF0 帧开始 FF C4:DHT Huffman表 FF DA:SOS 扫描行开始 encode数据 FF D9:SOI 文件尾 扫描文件头,得到有效的DQT量化表信息,如...

u010192735的专栏 5711

数据压缩8 | 实验5 | JPEG原理分析及JPEG解码器的调试

目录一、内容概述1. JPEG编解码原理2. JPEG文件格式二、实验思路三、调试过程四、分析总结 一、内容概述 1. JPEG编解码原理 2. JPEG文件格式 二、实验思路 逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。 程序调试过程中,应做到: ①理解程序设计的整体框架;理解三个结构体的设计目的:struct huffman_table struct component struct jdec_private ②理解在视音频编解码调试

月婵婵的程序媛生涯 1446

数据压缩实验五 JPEG原理分析JPEG解码器的调试

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

Alfred512的博客 1503

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

JPEG图像压缩算法能够在提供良好的压缩性能的同时,具有比较好的重建质量,被广泛应用于图像、视频处理领域,网站上80%的图像都采用了JPEG压缩标准。

weixin_45671402的博客 3121

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

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

weixin_46352852的博客 1134

数据压缩原理 实验五 JPEG原理分析及JPEG解码器的调试

数据压缩原理 实验五 JPEG原理分析及JPEG解码器的调试

liu_yuchang的博客 480

数据压缩原理实验5 JPEG原理分析及JPEG解码器的调试

数据压缩原理实验5 JPEG原理分析及JPEG解码器的调试 班级:17广电工方向三 姓名:利满雯 学号:201710413068 一、实验原理 1. JPEG编码原理(解码是编码的逆过程) (1)图像预处理 将输入图像分成若干个8×8的小块。在此过程中需对图像的宽和高进行剪裁剪,使其都为8的倍数,不足的部分复制与其最近的像素值。目的是方便进行8×8 DCT变换。 (2)零偏置(Level Offset) 对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。例如对于n=8,即0255的值

ManWen_Li的博客 1679

数据压缩实验五:JPEG原理分析及JPEG解码器的调试

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

weixin_46723979的博客 876

实验5:JPEG解码器

实验要求: 将输出文件保存为可供YUVViewer观看的YUV文件 以TXT文件输出 输出DC图像并经过Huffman统计其概率分布 实验原理: JPEG编解码 JPEG码流分析 实验过程: 1.JPEG解码器-将JPG文件解码并转换为YUV文件 static void write_yuv(const char filename, int width, int height, unsigned char **components) { FILE F; char temp[1024]; snprintf(

weixin_48846782的博客 526

数据压缩(十一)——JPEG原理分析及JPEG解码器调试

文章目录一、JPEG编码原理二、JPEG解码原理三、调试JPEG解码器程序2.1 理解程序设计的整体框架2.2 理解三个结构体的设计目的2.2.1 struct huffman_table2.2.2 struct component2.2.3 struct jdec_private2.3 理解在视音频编解码调试中TRACE的目的和含义四、修改JPEG解码器将输出文件保存为YUV文件以TXT文件输出所有量化矩阵和HUFFMAN码表输出DC图像并统计其概率分布输出某一个AC值图像并统计其概率分布 一、JPEG

CRR的博客 2769

【数据压缩】实验:JPEG原理分析及JPEG解码器的调试

JPEG是Joint Photographic Experts Group(联合图像专家小组)的缩写,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。JPEG主要采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,从而去除冗余的图像和彩色数据,属于有损压缩格式,在获得极高的压缩率的同时能展现十分丰富生动的图像。JPEG图像压缩算法能够在提供良好的压缩性能的同时,具有比较好的重建质量,被广泛应用于图像、视频处理领域。(1)图像预处理(2)零偏置电平下移对输入图片做零偏置电平下移

weixin_48946798的博客 2157

【数据压缩9】Lab5 JPEG原理分析及JPEG解码器的调试

【数据压缩9】Lab5 JPEG原理分析及JPEG解码器的调试 文章目录【数据压缩9】Lab5 JPEG原理分析及JPEG解码器的调试一、实验目的二、JPEG原理分析1. JPEG编解码原理(1)零偏置电平下移(Level Offset)(2)离散余弦变换(8×8 DCT)(3)量化(Quantization)(4)DC系数——差分编码(Differential indices)(5) AC系数——之字形扫描和游程编码(Zig-Zag+RLE)之字形扫描游程编码(6)Huffman编码2. JPEG文件格式

yaoyaxuan的博客 425

37、数据与图像压缩算法解析

本文深入解析了数据与图像压缩中的核心算法,重点介绍了算术编码中的QM-编码器与解码器的工作原理,包括子区间分配、概率估计及区间反转问题的处理。同时详细阐述了JPEG图像压缩标准的四种操作模式,特别是其无损编码算法基于预测编码(DPCM)的实现机制,涵盖预测函数选择、预测误差分类与Huffman编码过程。文章还比较了算术编码与Huffman编码的优劣,并分析了不同JPEG模式的应用场景,为图像压缩技术的实际应用提供了全面指导。

sat99的博客 74

一文搞懂JPEG编解码

JPEG 看着简单,实际工程中很多坑来源于对编解码底层流程、内存模型、码流组织方式理解不足。很多问题并不是图片文件损坏,而是解码器能力、内存预算、图片模式不匹配。做嵌入式图像项目,优先锁定基线 JPEG 格式,再根据设备 RAM 大小选择整帧解码或者 TJpgDec 流式解码,结合硬件 2D 引擎完成缩放显示,是稳定性最高的落地方案。

RFCEO的博客 548

JPEG、PNG编解码库及例子

改资源包含JPEG,PNG编解码库,以及本人编写的列子,保证大家快速上手使用JPEG,PNG,BMP格式图片的编解码。和我的博客JPEG图片格式叠加PNG图片格式的水印相对应。

【论文学习】《Fully-Convolutional Siamese Networks for Object Tracking》2016年SiamFC 全卷积孪生网络用于目标跟踪

在这项工作中,我们脱离了跟踪中采用的传统在线学习方法,并展示了一种专注于在离线阶段学习强嵌入的替代方法。与在分类设置中的使用不同,我们证明,对于跟踪应用,孪生全卷积深度网络能够更有效地使用可用数据。这不仅反映在测试时,通过执行有效的空间搜索,也反映在训练时,其中每个子窗口都有效地代表一个有用的样本,而几乎没有额外的成本。实验表明,深度嵌入为在线跟踪器提供了自然丰富的特征来源,并使简单的测试时策略表现良好。我们相信这种方法是对更复杂的在线跟踪方法的补充,并期望未来的工作能够更彻底地探索这种关系。

SkyLeopard的博客 2419

深入瑞芯微MPP JPEG解码器:从寄存器配置到硬件加速全解析

本文深入解析瑞芯微MPP JPEG解码器的硬件加速机制,从寄存器配置到DMA传输优化,全面剖析JPEG解码流程。通过实战技巧如量化表预处理、零拷贝配置和中断合并策略,显著提升解码性能。适用于智能安防、工业视觉等需要高效JPEG解码的场景。

weixin_30485379的博客 433

WSL 运行make提示/lib/modules/xxx/build: No such file or directory. Stop.错误解决办法

WSL运行make提示/lib//modules/xxx文件或目录不存在。 WSL2的内核是修改过的,无法使用 ubuntu上游的内核头文件和modules文件,因此,我们需要手动编译并安装一个版本。

weixin_45668903的博客 1万+
上一篇: 最小二乘法,梯度下降,牛顿法以及高斯牛顿法
下一篇: 完全重建QMF滤波器
Cross_Entropy
博客等级 码龄7年 69粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值