一、前导知识
1.变换编码:将输入信号变换,得到一些系数表示
去相关:将输入信号去相关,使得在量化时,对各系数单独量化(标量量化),而不会损伤过多效率(与矢量量化相比)
稀疏化:将原始信号的能量压缩到尽可能少的系数→对原始信号只用少数幅值较大的系数表示
可逆:可以重构输入信号
2.离散正交变换
预测编码中所有变换都是正交变换,离散正交变化具有如下特点:
1.正交矩阵的逆就是其转置,对于正交变换,反变换可以得到唯一复原信号。
2.正交变换总能量保持不变。
3.正交变化后个分量之间的相关性被全部去除,使得在量化时,对各系数单独量化不会损失过多效率。
3.DCT变换
变换核矩阵:
二维DCT变换:
变换核可分离,先进行垂直方向的81DCT变换,再进行水平方向的81DCT变换。
二、实验原理
1.JPEG文件
(1)Segment 的组织形式
JPEG 在文件中以 Segment 的形式组织,它具有以下特点:
均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示
Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
(2)JPEG 的 Segment Marker
2.基本JPEG编码器
(1)零偏置(Level Offset)
对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。对于n=8,即将0-255的值域,通过减去128,转换为值域在-128~127之间的值。
目的:使像素的绝对值出现3位10进制的概率大大减少
(2)DCT变换
对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,如图所示,并作为两维离散余弦变换DCT的输入
(3)量化
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。如果原始图象中细节丰富,则去掉的数据较多
,量化后的系数与量化前差别;反之,细节少的原始图象在压缩时去掉的数据少些。
(4)DC系数的差分编码
由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
(5)AC系数的Z字扫描、游程编码
首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)]
Amplitude:表示非零系数的幅度值;Run:表示零的游程即零的个数;Size:表示非零系数的幅度值的编码位数;
(6)对DC和AC系数哈夫曼编码
3.解码重构(与编码相反)
(1)解码Huffman数据
(2)解码DC差值
(3)重构量化后的系数
(4)DCT逆变换
(5)丢弃填充的行/列
(6)反0偏置
(7)对丢失的CbCr分量差值(下采样的逆过程)
(8)YCbCr → RGB
三、程序流程及主要代码
1.读取文件
int main(int argc, char *argv[])
{
int output_format = TINYJPEG_FMT_YUV420P;
char *output_filename, *input_filename;
clock_t start_time, finish_time;
unsigned int duration;
int current_argument;
int benchmark_mode = 0;
#if TRACE
p_trace=fopen(TRACEFILE,"w");
if (p_trace==NULL)
{
printf("trace file open error!");
}
#endif
// 如果命令行参数小于3,显示使用指南
if (argc < 3)
usage();
//使用第一个命令行参数
current_argument = 1;
while (1)
{
//这里我将源代码更改了一下,认为这样更符合逻辑
if (strcmp(argv[current_argument], "--benchmark")==0)
benchmark_mode = 1;
current_argument++; //使用第2个命令行参数
break;
}
//如果参数不符合要求,显示使用指南
if (argc < current_argument+2)
usage();
//第2个参数为输入文静名
input_filename = argv[current_argument];
//根据第3个命令行参数,设置输出格式
if (strcmp(argv[current_argument+1],"yuv420p")==0)
output_format = TINYJPEG_FMT_YUV420P;
else if (strcmp(argv[current_argument+1],"rgb24")==0)
output_format = TINYJPEG_FMT_RGB24;
else if (strcmp(argv[current_argument+1],"bgr24")==0)
output_format = TINYJPEG_FMT_BGR24;
else if (strcmp(argv[current_argument+1],"grey")==0)
output_format = TINYJPEG_FMT_GREY;
else
exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
//第4个命令行参数为输出文件名
output_filename = argv[current_argument+2];
start_time = clock();
//选择不同模式,命令行参数为--benchmark,为load_multiple_times()模式
if (benchmark_mode)
load_multiple_times(input_filename, output_filename, output_format);
else
convert_one_image(input_filename, output_filename, output_format);
finish_time = clock();
duration = finish_time - start_time;
snprintf(error_string, sizeof(error_string),"Decoding finished in %u ticks\n", duration);
#if TRACE
fclose(p_trace);
#endif
return 0;
}
fp = fopen(infilename, "rb");
if (fp == NULL)
exitmessage("Cannot open filename\n");
length_of_file = filesize(fp);
buf = (unsigned char *)malloc(length_of_file + 4);
if (buf == NULL)
exitmessage("Not enough memory for loading file\n");
fread(buf, length_of_file, 1, fp);
fclose(fp);
2.解析 Segment Marker
//在Start of scan标签之前
while (!sos_marker_found)
{
if (*stream++ != 0xff)
goto bogus_jpeg_format;
/* Skip any padding ff byte (this is normal) */
//跳过0xff字节
while (*stream == 0xff)
stream++;
//marker
marker = *stream++;
//chunk_len,包含自身,但不包含0xff+marker2字节
chuck_len = be16_to_cpu(stream);
//指针指向下一个chunk
next_chunck = stream + chuck_len;
//解析各种标签
switch (marker)
{
case SOF:
//SOF
if (parse_SOF(priv, stream) < 0)
return -1;
break;
case DQT:
//DQT
if (parse_DQT(priv, stream) < 0)
return -1;
break;
case SOS:
//SOS
if (parse_SOS(priv, stream) < 0)
return -1;
sos_marker_found = 1;
break;
case DHT:
//DHT
if (parse_DHT(priv, stream) < 0)
return -1;
dht_marker_found = 1;
break;
case DRI:
//DRI
if (parse_DRI(priv, stream) < 0)
return -1;
break;
default:
#if TRACE
fprintf(p_trace,"> Unknown marker %2.2x\n", marker);
fflush(p_trace);
#endif
break;
}
//解析下一个segment
stream = next_chunck;
}
(1)解析 SOI
//是否是JPEG文件
if ((buf[0] != 0xFF) || (buf[1] != SOI)) //JPEG 文件必须以 0xFFD8(SOI)起始
snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");
(2)解析 APP0:检查标识“JFIF”及版本;得到一些参数
(3)解析 DQT:得到量化表长度(可能包含多张量化表);得到量化表的精度;得到及检查量化表的序号(只能是 0 —— 3);
dqt_block_end = stream + be16_to_cpu(stream);//得到量化表长度
stream += 2; /* Skip le