一、实验原理
JPEG编码框架:
上图为编码过程,解码是编码的逆过程
1.零偏置Level offset
将灰度级为2n的像素,减去2n-1,此时将无符号数值变为有符号数,对于本实验,n=8,即每个像素的灰度减128
2.88DCT变换
首先将图像分为88的若干块,以方便进行DCT变换;
DCT变换的作用为能量集中和去相关。DCT变换后多数低频信息集中在左上角,且数值较大
3.量化
由于人眼对低频更加敏感,故只需要对左上角的低频部分进行细量化,高频部分进行粗量化
人眼对亮度信号更加敏感,对色度信号不敏感,所以亮度与色度分量分别采取不同的量化表,以在保证图像质量的前提下,提高压缩比;
4.编码
DC系数:差分编码
由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
编码方法:
码字由两部分组成:类别ID+类内索引
将每一个DIFF经查下表,判断出在哪一个范围内,记下类别ID,作为码字的第一部分,DIFF的真值作为码字的第二部分。
AC系数:游程编码
首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)]
Amplitude:表示非零系数的幅度值;Run:表示零的游程即零的个数;Size:表示非零系数的幅度值的编码位数;
JPEG文件格式
SOI,Start of Image,图像开始
APP0,Application,应用程序保留标记0
DQT,Define Quantization Table,定义量化表
SOF0,Start of Frame,帧图像开始
DHT,Define Huffman Table,定义哈夫曼表
SOS,Start of Scan,扫描开始 12字节
EOI,End of Image,图像结束 2字节
Segment 的组织形式
JPEG 在文件中以 Segment 的形式组织,它具有以下特点:
均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
JPEG 的 Segment Marker
JPEG解码流程
1 读取文件
2 解析 Segment Marker
2.1 解析 SOI
2.2 解析 APP0
检查标识“JFIF”及版本
得到一些参数
2.3 解析 DQT
得到量化表长度(可能包含多张量化表)
得到量化表的精度
得到及检查量化表的序号(只能是 0 —— 3)
得到量化表内容(64 个数据)
2.4 解析 SOF0
得到每个 sample 的比特数、长宽、颜色分量数
得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)
2.5 解析 DHT
得到 Huffman 表的类型(AC、DC)、序号
依据数据重建 Huffman 表
2.6 解析 SOS
得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT中序号对应)
3 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数
4 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码)
4.1 对每个宏块进行 Huffman 解码,得到 DCT 系数
4.2 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
4.3 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
5 解析到 EOI,解码结束
6 将 Y、Cb、Cr 转化为需要的色彩空间并保存。
二、实验代码
任务一: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);
//写入yuv输出文件
snprintf(temp, 1024, "%s.yuv", filename);
fopen_s(&F, 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);
}
任务二:理解代码
struct huffman_table :用来存放huffman解码后的DC、AC系数
struct component :用来存放IDCT反变化后的颜色分量
struct jdec_private :用来统筹整个解码过程,包括huffman_table,component
TRACE的目的主要是输出中间过程中的某些变量,或者错误信息。
TRACE随着文件的解析,输出中间信息,输出到txt文件。
在main中,以下代码用来打开trace文件:
#if TRACE
p_trace=fopen(TRACEFILE,"w");//p_trace是追踪文件
if (p_trace==NULL)
{
printf("trace file open error!");
}
#endif
关闭trace
#if TABLES
fclose(hufftable);
#endif
任务三:以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
量化表:
修改parce_DQT
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi;
float *table;
const unsigned char *dqt_block_end;
#if TRACE
fprintf(p_trace,"> DQT marker\n");
fflush(p_trace);
#endif
dqt_block_end = stream + be16_to_cpu(stream);
//得到量化表长度(可能包含多张量化表)
stream += 2; /* Skip length */
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
table = priv->Q_tables[qi];
//得到量化表内容
build_quantization_table(table, stream);
//输出量化表
FILE *quanty = fopen("quantification.txt", "a");
if (quanty == NULL)
{
printf("Fail to open quantification.txt\n");
return 0;
}
fprintf(quanty,"\n量化表\n");
for (int i = 0; i < 64; i++<