实验原理:
01.JPEG的编解码原理:
输入图像的YUV数据先进行偏置,再将图片按8x8的块进行DCT变换编程8x8的系数块,接着再根据8x8的量化表对系数块进行量化,量化后的8x8的系数块需要对其进行不同的操作,其中左上角的直流系数进行,交流系数先之字形扫描,再根据对应的AC霍夫曼码表进行可变长编码(VLC编码),得到编码输出。
而JPEG的解码过程就是编码的逆过程,要想从一张.jpg格式的文件得到图像的原始yuv数据,则在熟悉解码过程的基础上还要了解.jpg文件的数据组织格式
02.JPEG文件格式分析:
文件以segment的方式组织。每一个segment的特点如下:
1.由0xFF(一字节)、marker(一字节)、segment length(两字节,用来表示该segment的长度,包括segment length的两字节,不包括0xFF和marker的两字节);
2.采用Motorola序(相对于Intel序),即高位在前,低位在后;
3.Data部分中,若FF后为00,则跳过这字节不处理。
以本次实验所用的文件为例进行分析(仅截取部分图像数据),按顺序依次有一下的标识符:
FFD8:SOI,图像的开始,所有jpeg文件都必须以这个开头;
FFE0:应用程序保留标记0;
FFDB:定义量化表,每个文件最多有四张量化表,此次实验所用图片中共有2个FFDB,因此共有2张量化表;
FFC0:SOF0,帧图像开始;
FFC4:定义霍夫曼表,本文件中共有4个FFC4,因此共有4张霍夫曼码表;
FFDA:SOS,扫描开始;
FFD9:图像结束。
实验程序分析:
本次实验的程序中共有三个结构体用来存储数据:huffman_table,component,jdec_private,这三个结构体之间是依次被包含的关系,对应了图像数据的三个层次。
huffman_table:
component:
jdec_private:
解码流程如下:
1.在主函数中打开文件,将图像数据读入到缓存区中;
2.按顺序依次解析segment marker,在解析的过程中得到图像相关的参数并生成霍夫曼码表和量化表,为之后的解码做准备;
3.根据每个分量的水平、垂直采样分子计算MCU的大小,计算每个MCU中有多少8x8的块;
4.对每个MCU进行解码,
5.解析到EOI,解析结束;
6.将解析生成的Y、Cb、Cr照要求转换为需要的彩色空间并输出。
在解析的过程中会同时输出trace文件,输出一些图像相关的参数及信息帮助理解解码过程及文件的结构,若想关闭只需要将tinyjpeg.h中的TRACE变量设为1。
实验结果分析:
第一步:将.jpg图像转换为.yuv文件并查看,代码如下:
输出的yuv文件如下:
第二步:将该文件中所有的霍夫曼码表输出到一个txt文件中,代码如下:
01.先声明文件指针变量,这一步最开始我考虑的是将文件指针变量声明在main函数中,但是之后发现这样就得在很多函数中加入文件指针的形参,很容易就混淆造成程序有bug,因此这种方法不可行,最后仿照trace文件指针声明方法将其声明在头文件中,作为全局变量:
02.在主函数中打开文件:
03.在主函数末尾关闭文件:
04.输出霍夫曼码表(包括符号值、对应码字、对应码长,代码添加在build_huffman_table和parse_DHT中):
05.最终输出的码表结果如下:
可以看到这张图片包含了四张霍夫曼码表,与上面分析文件数据时的结论相同。
第三步:输出文件中所有的量化表,代码如下:
(文件相关操作已包含在第二步)
添加在make_quantization_table中的代码,其中的zz是一个表示位置的8x8的数组,由于在图片编码时对DCT变换后的数据要进行之字形扫描,因此在解码时需要将数据恢复到其原来的位置,因此用一个表示位置的数组来将ref_table中的数重新排列放在q_table中:
添加在parse_DQT函数中的代码:
最终得到的量化表是:
可以看到该文件只有两张量化表,与文件分析的结论相同。
第四步:输出每个块的直流系数和一个交流系数,并分析其数据的概率分布:
输出的DC图像和AC图像如下,原始图像的尺寸为1024x1024,该图像中的块是16x16的,因此AC图像和DC图像的尺寸为64x64。DC图像能看清楚图像的大概轮廓,说明图像的直流分量包含了图像的大部分信息。AC图像随着频率的增高其轮廓越不明显,说明了DCT变换的能量集中的特性,高频部分的值很小。
将DC和AC图像的数据进行概率分布的统计,结果如下:
DC:
AC: