实验目的:
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
主要设备:
安装Windows和Visual Studio软件的个人计算机
实验内容:
一、JPEG:
1.基本定义:
JPEG(Joint Photographic Experts Group)是JPEG标准的产物,该标准由国际标准化组织(ISO)制订,是面向连续色调静止图像的一种压缩标准。 [1] JPEG格式是最常用的图像文件格式,后缀名为.jpg或.jpeg。
2.JPEG编码基本原理:
如上图所示,JPEG的编码基本原理如上图所示:
(1)Level offset(零电平偏置):
将输入的图像的所有像素点的值-128,使其原先范围由0~255变为-128~127
(2)8✖️8DCT变换:
将零电平偏置之后的图像分成88像素的块来处理,不足88的,则取边缘像素补齐,对每个块做DCT变换,直流系数在每个块的左上角,越右下角的分量频率越高。
(3)Uniform scalar quantization(量化):
使用根据人眼视觉特性设计的量化的量化矩阵对DCT变换之后的结果进行量化,从而进而减少视觉冗余。
(4)DCT变换后DC系数值较大,且相邻块变化不大,利用这个特性对其进行DPCM,对相邻块的DC差值huffman编码
(5)对AC系数做之字形扫描,再进行游程编码和huffman编码
3.JPEG文件格式:
(1)Segment 的组织形式
JPEG 在文件中以 Segment 的形式组织,它具有以下特点:
(a)均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的Segment length(包含表示 Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
(b) 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
©Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
(2)JPEG的Segment Marker
(备注:此部分截图中的表格均为三列格式:分别为:“symbol(符号)”,“Code Assignment(0xFF+Marker)(标记代码)”,“Description(说明)”)
(a)Start Of Frame markers, non hierarchical Huffman coding
(b)Start Of Frame markers, hierarchical Huffman coding
©Start Of Frame markers, non-hierarchical arithmetic coding
(d)Start Of Frame markers, hierarchical arithmetic coding
(e)Huffman table specification
(f)arithmetic coding conditioning specification
(g)Restart interval termination
(h)Other marker
(i)Reserved markers
(3)部分Segment marker介绍:
(a)SOI:START OF IMAGE,图像开始,标记代码两字节,固定值0xFFD8
(b)EOI:END OF IMAGE,图像结束,标记代码两字节,固定值0xFFD9
(c)APP0应用程序保留标记0:
标记代码:2字节,固定值0xFFE0
(d)DQT定义量化表:标记代码2字节,固定值0xFFDB
包含9个具体字段:
数据长度:2字节,字段1和多个字段2的总长度
量化表:数据长度-2字节
a)精度及量化表ID:1字节
高4位:精度,只有两个可选值 0:8位,1:16位
b)表项:(64*(精度+1))字节
(e)SOF0:帧图像开始:标记代码2字节,固定值0xFFC0
包含9个具体字段:
数据长度:2字节,整个部分6个字段的总长度
精度:1字节,代表每个数据样本的位数,通常为8位
图像高度:2字节,单位像素
图像宽度:2字节,单位像素
颜色分量数:1字节,3个数值可选:
1:灰度图,3:YCrCb或者YIQ,4:CMYK
而JFIF中使用TCrCb,故这里颜色分量数恒为3
颜色分量信息:通常为9字节(颜色分量数✖️3字节)
ea)颜色分量ID,1字节
eb)水平/垂直采样因子,1字节,高4位:水平采样因子;第四位,垂直采样因子
ec)量化表,1字节,当前分量使用的量化表ID
(f)DHT:定义huffman表,标记代码2字节,0xFFC4
(g)SOS:扫描开始,标记代码2字节,固定值0xFFDA
3.JPEG解码基本原理
(1)读取文件
(2)解析segment marker
(a)解析 SOI
(b)解析 APP0
检查标识“JFIF”及版本并得到一些参数
© 解析 DQT
得到量化表长度(可能包含多张量化表)
得到量化表的精度
得到及检查量化表的序号(只能是 0 —— 3)
得到量化表内容(64 个数据)
(d)解析 SOF0
得到每个 sample 的比特数、长宽、颜色分量数
得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表 序号(与 DQT 中序号对应)
(e) 解析 DHT
得到 Huffman 表的类型(AC、DC)、序号
依据数据重建 Huffman 表 3.2.6 解析 SOS
得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)
(3)依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8 宏块的个数
(4) 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解 码)
(a)对每个宏块进行 Huffman 解码,得到 DCT 系数
(b)对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
©遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
(5)解析到 EOI,解码结束
(6) 将 Y、Cb、Cr 转化为需要的色彩空间并保存。
二、具体实验步骤
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
在本次实验中,我们主要采用本图片,test.jpg进行操作:(嗯,看着有点晕其实)
首先在命令行中按照主程序中的设置添加参数,先运行一下代码:
而后我们便可以发现文件夹中生成了三个名为test的新文件,即:test.u,test.v,test.y,分别存储的是test.jpg图片转化为yuv文件之后的三个分量。
所以,我们需要对于原来的代码进行修改:
首先,我们找到write_yuv这个函数,从代码注释中,我们可以看出,这段就是使之输出test.u,test.v,test.y的相关代码:
所以,我们对本段代码进行如下添加,使之可以输出YUV文件:
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);
}
其中,需要注意的是,最开始输入命令行的时候可能出现报错:
“错误 D8016 “/ZI”和“/Gy-”命令行选项不兼容 ”
在查找了一下之后,发现需要做以下操作即可解决:
最后运行程序,可以看到,程序生成了一个test.yuv的文件,使用pyuv打开可以看到其和原图JPG形式的图片没有差别:
至此,本实验第一步完成。
2. 程序调试:
(1)程序运行的整体框架:(本部分的大部分介绍都以注释的形式,写在代码中)
首先介绍三个比较重要的结构体:
(a)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 */
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];
};
从这个结构体中,我们可以看出,其包含三个部分:其中lookup为short int型,用来加速查表,如果查找失败的话则就要使用慢速查找表。code_size为码字的长度。定义这个结构体的目的就是用来快速查找,从而加速程序的运行速度。
(b)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;
struct huffman_table *DC_table;
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK
unsigned int cid;
#endif
};
这个结构体中,首先定义的Hfactor和Vfactor分别对应horizontal和vertical即水平和垂直方向的采样信息。而后定义Q_table则是对应此次DCT变换所对应的量化表,DCT[64]则代表这个8✖️8的宏块所存储的DCT系数,previousDC则指的是前一个直流系数。
这部分的主要用途就是用来存储每一个MCU的信息,用来进行DCT的变换。
(c)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 */
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 */
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];
};
此部分的结构体则是包含整合了以上两个结构体中的内容,在解码过程中,我们会频繁使用到这个结构体。在这个