1 实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
2 JPEG编解码原理
- 两张量化表——亮度,色度
- 四张Huffman码表——亮度DC,亮度AC,色度DC,色度AC
2.1 Level Offset 零偏置
-
对于灰度级是 2n 的像素,通过减去 2n-1 ,将无符号的整数值变成有符号数。
例如:n=8,灰度级0 ~ 255,通过减去128,转化为-128 ~ 127 -
目的:使像素的绝对值出现3位10进制的概率大大降低。
2.2 DCT变换
离散DCT变换的变换核矩阵为:
一维DCT变换
若N=8,则每个变换核矩阵的物理意义如下图所示:
二维DCT变换
变换对象为图像块。
变换核可分离,先进行垂直方向的8 * 1DCT变换,再进行水平方向的8 * 1DCT变换。
二维DCT变换的性质:
- 二维DCT变换可以使DCT系数矩阵中数值较大的低频分量数据向左上方集中,使数值较小的高频分量数据向右下方集中。
- 具有很好的去相关性和能量集中。
- 去相关: 变换后的系数为正交频率分量的幅度,幅度之间没有相关性——每个数值可以独立编码(有记忆 ——> 无记忆)
- 能量集中: 变换系数的能量往往被集中在少数低频样值(幅度大)上,大多数高频样值幅度小——可以非均匀量化(人眼对低频敏感,所以可以对高频粗量化来实现数据压缩)
在JPEG编解码原理中,对每个单独的彩色图像分量,把整个分量图像分成8*8的图像块,如下图所示,并作为二维DCT的输入:
2.3 量化
- 采用中平型均匀量化器
- 因为人眼对亮度信号比对色差信号敏感,因此使用两种量化表:亮度量化值和色差量化值。
- 根据人眼视觉特性(对低频敏感,对高频不太敏感),对低频分量采取较细的量化,对高频分量采取较粗的量化。
- 人眼对亮度的敏感性如下图所示:
2.4 DC系数的差分编码
-
8 * 8图像块经过DCT变换后得到的DC系数有两个特点:
系数数值比较大;
相邻 8 * 8 图像块的DC系数值变化不大——冗余 -
根据这个特点,JPEG算法使用了DPCM技术,对相邻图像块之间量化DC系数的差值DIFF进行Huffman编码:
2.5 AC系数的zizag(z字扫描)
由于经过DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用z字形按频率由低到高的顺序读出,可以出现很多连零的机会。
可以使用游程编码。尤其在最后,如果都是0,直接给出EOB(end of block)即可。
2.5 AC系数的游程编码
编码要求:
2.6 AC系数和DC系数的Huffman编码
采用4张Huffman码表:亮度AC、亮度DC、色度AC、色度DC。
3 JPEG码流结构分析
利用JPEGParser分析码流结构。选取的照片为testrgb-2x2.jpg:
输出文档命名为out.txt。
命令行参数如下图:
运行程序后,生成了码流结构分析文档:
分析:
0xFFD8:表示图像的开始(SOI=Start of Image)
0xFFE0:应用程序保留标记0(APP0)
0xFFDB:标记着量化表的开始
0xDB后的两个字节,图中为0043,表示量化表的长度
量化表长度的后一个字节,图中为00,表示量化表信息:
bit0-bit3 | bit4-bit7 |
---|---|
量化表号(只能取 0-3,否则错误) | 量化表精度(0为8bit,否则为16bit) |
后面的64个数据为量化表实际信息。
0xFFDB:标记着第二张量化表的开始(共两张量化表,亮度+色度)。其余信息同上。
0xFFC0:帧图像的开始(SOF0,start of frame marker),其余信息如下表:
0xFFC4:Huffman码表开始的标志
0xFFC4后面的第三个字节表示Huffman表类型,上面四张图依次表示DC直流0号表、AC交流0号表、DC直流1号表、AC交流1号表。(其实我们可以通过数据的长短来判断DC和AC,短的自然是DC)
其余信息如下面例子所示:
0xFFDA:扫描开始(编码开始)(SOS=Start of Scan)、
最后,0xFFD9:标记图像结束(EOI=End of Image)
4 JPEG解码流程
4.1 读取文件
4.2 解析 Segment Marker
4.2.1 解析 SOI
4.2.2 解析 APP0
- 检查标识“JFIF”及版本
- 得到一些参数
4.2.3 解析 DQT
- 得到量化表长度(可能包含多张量化表)
- 得到量化表的精度
- 得到及检查量化表的序号(只能是 0 - 3)
- 得到量化表内容(64 个数据)
4.2.4 解析 SOF0
- 得到每个 sample 的比特数、长宽、颜色分量数
- 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)
4.2.5 解析 DHT
- 得到 Huffman 表的类型(AC、DC)、序号
- 依据数据重建 Huffman 表
4.2.6 解析 SOS
- 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT
中序号对应)
4.3 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数
4.4 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码)
4.4.1 对每个宏块进行 Huffman 解码,得到 DCT 系数
4.4.2 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
4.4.3 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
4.5 解析到 EOI,解码结束
4.6 将 Y、Cb、Cr 转化为需要的色彩空间并保存。
5 实验代码分析
5.1 将输出文件保存为可供YUVViewer观看的YUV文件
只需在函数 write_yuv 中增加如下代码即可:
运行程序:
成功生成了yuv文件:
5.2 三个重要结构体
5.2.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
*如果符号小于0,则在慢查找表中查找*/
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];
};
用来存储哈夫曼码表,分为快查找表和慢查找表,主要目的是提高解码效率。
5.2.2 struct component
struct component
{
unsigned int Hfactor;//水平采样情况
unsigned int Vfactor;//垂直采样情况
float *Q_table; /* Pointer to the quantisation table to use 指向量化表的指针*/
struct huffman_table *AC_table;//指向AC Huffman表
struct huffman_table *DC_table;//指向DC Huffman表
short int previous_DC; /* Previous DC coefficient 前一个DC系数*/
short int DCT[64]; /* DCT coef DCT系数*/
#if SANITY_CHECK
unsigned int cid;
#endif
};
用来保存一个MCU(最小结构单元)的信息,每处理完一个MCU,值都会更新。
5.2.3 struct 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 3张量化表,实际只使用2张(亮度+色度) */
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC huffman tables 4张DC Huffman码表,实际只用2张(亮度+色度) */
struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC huffman tables 4张AC Huffman码表,实际只用2张(亮度+色度) */
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];
};
定义了指向三个components的指针和三个components结构体;定义了图像的宽高、码流长度、始末指针,还有三张量化表(实际只用到两张,亮度+色度)、DC Huffman表和AC Huffman表各四张(实际各用两张:亮度DC、亮度AC、色度DC、色度AC)。
5.3 Trace的目的
Trace主要用于输出各种中间变量,将Trace设置为1时打开,设置为0时关闭。
#define TRACE 1
5.4 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
将Trace打开,运行程序后,我们发现已经可以输出所有Huffman码表。
部分Huffman码表截图:
所以,我们需要增加代码,让它可以输出所有量化表:
- 在 tinyjpeg.h 中增加量化表的文件指针
- 在loadjpeg.c的主函数中增加如下代码,将量化表信息写入txt文件:
- 在tinyjpeg.c中添加如下代码
函数 build_quantization_table() 中:
函数 parse_DQT 中
运行loadjpeg.c,发现 quantization_table.txt 已经生成:
5.5 输出DC图像和AC图像
增加如下代码来实现:
在tinyjpeg.h中定义文件指针:
宏定义文档名:
在tinyjpeg.c中,开缓冲区:
在loadjpeg.c的main函数中:
运行结果:
5.6 统计DC图像和AC图像的概率分布
调用数据压缩作业1-2中的代码,将文件目录和文件大小修改一下,统计概率分布。
- DC图像
利用Excel画图:
- AC图像
利用Excel画图:
从对比可以看出,DC图像的方差更大,信息熵更大,包含着更多的图像信息。