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

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
Symbol Code Assignment(0xFF+Marker) Description
S O F 0 SOF_0 SOF0 0xFFC0 Baseline DCT
S O F 1 SOF_1 SOF1 0xFFC1 Extended sequential DCT
S O F 2 SOF_2 SOF2 0xFFC2 Progressive DCT
S O F 3 SOF_3 SOF3 0xFFC3 Spatial (sequential) lossless
hierarchical Huffman coding
Symbol Code Assignment(0xFF+Marker) Description
S O F 5 SOF_5 SOF5 0xFFC5 Differential sequential DCT
S O F 6 SOF_6 SOF6 0xFFC6 Differential progressive DCT
S O F 7 SOF_7 SOF7 0xFFC7 Differential spatial lossless
non-hierarchical arithmetic coding
Symbol Code Assignment(0xFF+Marker) Description
JPG 0xFFC8 Reserved for JPEG extensions
S O F 9 SOF_9 SOF9 0xFFC9 Extended sequential DCT
S O F 10 SOF_{10} SOF10 0xFFCA Progressive DCT
S O F 11 SOF_{11} SOF11 0xFFCB Spatial (sequential) Lossless
hierarchical arithmetic coding
Symbol Code Assignment(0xFF+Marker) Description
S O F 1 3 SOF_13 SOF13 0xFFCD Differential sequential DCT
S O F 1 4 SOF_14 SOF14 0xFFCE Differential progressive DCT
S O F 15 SOF_{15} SOF15 0xFFCF Differential progressive DCT
Huffman table specification
Symbol Code Assignment(0xFF+Marker) Description
DHT 0xFFC4 Define Huffman table(s)
arithmetic coding conditioning specification
Symbol Code Assignment(0xFF+Marker) Description
DAC 0xFFCC Define arithmetic conditioning table
Restart interval termination
Symbol Code Assignment(0xFF+Marker) Description
RSTm 0xFFD0~0xFFD7 Restart with modulo 8 counter m
Other marker
Symbol Code Assignment(0xFF+Marker) Description
SOI 0xFFD8 Start of image
EOI 0xFFD9 End of image
SOS 0xFFDA Start of scan
DQT 0xFFDB Define quantization table(s)
DNL 0xFFDC Define number of lines
DRI 0xFFDD Define restart interval
DHP 0xFFDE Define hierarchical progression
EXP 0xFFDF Expand reference image(s)
A P P n APP_n APPn 0xFFE0~0xFFEF Reserved for application use
J P G n JPG_n JPGn 0xFFF0~0xFFFD Reserved for JPEG extension
COM 0xFFFE Comment
Reserved markers
Symbol Code Assignment(0xFF+Marker) Description
TEM 0xFF01 For temporary use in arithmetic coding
RES 0xFF02~0xFFBF Reserved
Segment详细信息
SOI & EOI
标志 字节数 含义
SOI 2字节 Start of Image,图像开始
EOI 2字节 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字节 详细内容见下表

量化表中字段的含义:

字段 字节数 含义
量化精度及量化表ID 1字节 高四位精度,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,直至满足位数长度为止。

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

序号 码长 码字 权值
1 2 00 4
2 2 01 5
3 2 10 6
4 3 110 3
5 4 1110 2
6 5 11110 1
7 6 111110 0
8 7 1111110 9
9 8 11111110 7
10 9 111111110 8
代码解析

具体实现为:

/*
 * 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值