一.实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二.主要设备
安装Windows和Visual Studio软件的个人计算机。
三.实验原理
1.JPEG编解码原理
JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像sh的损数据尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。
JPEG压缩技术十分先进,它可以用有损压缩方式去除冗余的图像数据,就是可以用较少的磁盘空间得到较好的图像品质。而且JPEG是一种很灵活的格式,具有调节图像质量的功能,它允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在10;1到40;1,压缩比越大,图像品质就越低;相反地,压缩比越小,图像品质就越高。同一幅图像,用JPEG格式存储的文件是其他类型文件的1/10~1/20,通常只有几十KB,质量损失较小,基本无法看出。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网;它可减少图像的传输时间,支持24位真彩色;也普遍应用于需要连续色调的图像中。
JPEG编码器系统框图:
2.JPEG文件格式分析
JPEG文件以segment的形式组织,其中每个segment以一个marker开始,而每个marker均以0xFF和一个marker的标识符开始,随后为2字节的marker长度(不包含marker的起始两字节)和对应的payload(SOI和EOI marker只有2字节的标识符)。连续的0xFF字节并不是marker的起始标志,而是用来填充的特殊字符。此外,0xFF后若为0x00,则跳过此字节不予处理。
概括来说,一共有以下几种字段:
根据实验所给的《JPEG解码调试报告》可以理解:
1)SOI、EOI:
FFD8: SOI,Start of Image,图像开始;所有的 JPEG 文件必须以 SOI 开始。
FFD9: End of Image,图像结束;结束符,JPEG 文件必须以 EOI 结束。
2)APP0:
FFE0: APP0,Application,应用程序保留标记 0;length: 16 byte (2 byte)00 10 4A 46 49 46 00 01 01 01 00 48 00 48 00 00 ;标识符: JFIF (5 byte);Version: 2 byte;Units: 1 byte X and Y are dots per inch;Xdensity: 2 bytes Horizontal pixel density(水平方向点密度);Ydensity: 2 bytes Vertical pixel density(垂直方向点密度);缩略图水平像素数目: 1 byte;缩略图垂直像素数目: 1 byte;缩略图 24bitRGB 点数目: 缩略图水平像素数目 * 缩略图垂直像素数目 = 1 byte
3)DQT:
量化表标记代码:FFDB量化表长度:(17byte),长度本身占 2 字节,精度及量化表 ID:1byte, 低位为量化表的 ID 的索引值,其取值范围为 0~3,所以说最多可能有四张量化表(亮度量化表,色度量化表,可能会有 R、G、B 各一张量化表);而低位为量化精度,有两个可选值,0:8 位;1:16 位,这里是 00,代表量化精度为 8bit,即用一个字节来表示一个量化系数数据:64 字节的量化系数,(一个字节对应一个量化系数,对于 8*8 的宏块来说,共 8*8=64 个量化系数)
4)SOF0:
SOF0: start of frame,帧图像开始;数据长度:长度本身占 2byte;图像精度: 1byte, 若是 08,即精度为一个字节。图像高度:2 byte, 以像素为单位。图像宽度:2 byte, 以像素为单位。颜色分量数:一个字节,这里是 03,代表有三个分量,YCrCb。颜色分量信息:每个分量有三个字节,第一个为分量的 ID,01:Y 02:U 03:V ;第二个字节高位为水平采样因子,低位为垂直采样因子,这里三个分量的采样率相同,所以采样格式为 4:4:4;第三个字节代表这个分量对应的量化表 ID,可以看出,Y 对应的量化表 ID 索引值为 00,而 UV 对应的量化表 ID都为 01,即它们共用一张量化表。
5)DHT:
FFC4:标记代码,2 字节,代表定义 Huffman 表。数据长度: 2 字节 这里是 28 字节的长度(包括长度自身);Huffman 表 ID 号和类型:1 字节,高 4 位为表的类型,0:DC 直流;1:AC交流 可以看出这里是直流表;低四位为 Huffman 表 ID。可以看出这张表是直流 DC 的第 0 张表,在后面的扫描开始的部分中我们可以获右为亮度的直流系数表。不同长度 Huffman 的码字数量:固定为 16 个字节,每个字节代表从长度为 1到长度为 16 的 码 字 的个 数 , 以 表 中 的 分析, 这 16 个字节之后的2+3+1+1+1+1=9 个字节对应的就是每个符字对应的权值,这些权值的含义即为 DC 系数经 DPCM 编码后幅度值的位长。通过码长与码字个数的关系来生成相应码长的码字,再对应上之后的权值即位长根据解码得到的位长来读取之后相应长度的码字,再查可变长二进制编码表,就可以得到直流系数的幅度值,注意这个幅度值是经过 DPCM 差分编码得到的。
6)SOS
FFDA: 标记代码 SOS,Start of Scan,扫描开始 数据长度:2 字节。颜色分量数:1 字节 ,应该和 SOF 中的颜色分量数相同;颜色分量信息:每个分量对应 3 个字节,第一个字节是颜色分量 ID,1,2,3对应 YUV,第二个字节高位为直流分量使用的哈夫曼树编号。压缩图像数据 a)谱选择开始 1 字节 固定值 0x00;b)谱选择结束 1 字节 固定值 0x3F;c)谱选择 1 字节 在基本 JPEG 中总为 00 。
四.实验步骤
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
程序的命令行参数设置方法为:
–benchmark 输入文件名 输出格式 输出文件名
- 第一个参数可以省略
- 输入文件名带.jpeg/.jpg后缀,输出文件名无后缀
-
输出格式:如
yuv420p
在
write_yuv()
中添加输出yuv文件的相关代码:
static void write_yuv(const char* filename, int width, int height, unsigned char** components) {
FILE* F;
char temp[1024];
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);
}
结果如下:
2. 程序调试:
2.1理解程序设计的整体框架
JPEG压缩编码算法采用分层结构设计的思想:
1.读取文件,开辟缓冲区。
2.解析JPEG文件头,解析marker标识符,解析DQT等等。
3.获得量化表,建立哈夫曼码表。
4.解析JPEG’实际数据。
5.按照指定格式输出保存文件。
2理解三个结构体的设计目的
1)struct huffman_table用来存储Huffman码表
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];
};
2)struct component储存当前8×8像块中有关解码的信息。
struct component
{
unsigned int Hfactor; // 水平采样因子
unsigned int Vfactor; // 垂直采样因子
float* Q_table; // 指向该8×8块使用的量化表
struct huffman_table *AC_table; // 指向该块使用的AC Huffman表
struct huffman_table *DC_table; // 指向该块使用的DC Huffman表
short int previous_DC; // 前一个块的直流DCT系数
short int DCT[64]; // DCT系数数组
#if SANITY_CHECK
unsigned int cid;
#endif
};
3)struct jdec_private是数据流结构体,存储JPEG图像宽高、数据流指针、Huffman码表,包含shan上述两个结构体。
struct jdec_private
{
/* Public variables */
uint8_t *components[COMPONENTS]; /* 分别指向YUV三个分量的三个指针 */
unsigned int width, height; /* 图像宽高 */
unsigned int flags;
/* Private variables */
const unsigned char *stream_begin, *stream_end;
unsigned int stream_length;
const unsigned char *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];
};
2.3理解在视音频编解码调试中TRACE的目的和含义
1)会打开和关闭TRACE
#define TRACE 1
#define TRACEFILE "trace_jpeg.txt" // TRACE文件的文件名
在这里, TRACE设置为1表示打开TRACE;若设为0则表示关闭TRACE!
2)trace_jpeg.txt文件为:
3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
/* tinyjpeg.h中添加 */
FILE* qtabFilePtr; // 量化表文件指针
/* tinyjpeg.c中添加*/
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
...
for (i=0; i<8; i++) {
for (j=0; j<8; j++) {
fprintf(qtabFilePtr, "%-6d", ref_table[*zz]);
if (j == 7) {
fprintf(qtabFilePtr, "\n");
}
...
}
}
fprintf(qtabFilePtr, "\n\n");
}
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
...
while (stream < dqt_block_end) // 检查是否还有量化表
{
...
fprintf(qtabFilePtr, "Quantisation table [%d]:\n", qi); // 量化表ID
build_quantization_table(table, stream);
...
}
...
}
/* loadjpeg.c中添加 */
int main(int argc, char *argv[])
{
...
const char* qtabFileName = "q_table.txt"; // 量化表文件名
fopen_s(&qtabFilePtr, qtabFileName, "wb"); // 打开文件
...
fclose(qtabFilePtr);
return 0;
}
输出的量化表为:
huffman码表:
4.输出DC和AC图像。
/* tinyjpeg.h中添加 */
FILE* dcImgFilePtr; // DC图像文件指针
FILE* acImgFilePtr; // AC图像文件指针
/* tinyjpeg.c中添加 */
int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
...
unsigned char* dcImgBuff;
unsigned char* acImgBuff;
unsigned char* uvBuff = 128;
int count = 0;
/* 对每个像块进行解码(8x8 / 8x16 / 16x16) */
for (y = 0; y < priv->height / ystride_by_mcu; y++) {
...
for (x = 0; x < priv->width; x += xstride_by_mcu) {
decode_MCU(priv);
dcImgBuff = (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4 + 0.5); // DCT[0]为DC系数;DC系数范围-512~512;变换到0~255
acImgBuff = (unsigned char)(priv->component_infos->DCT[1] + 128); // 选取DCT[1]作为AC的observation;+128便于观察
fwrite(&dcImgBuff, 1, 1, dcImgFilePtr);
fwrite(&acImgBuff, 1, 1, acImgFilePtr);
count++;
...
}
}
}
}
...
for (int i = 0; i < count / 4 * 2; i++) {
fwrite(&uvBuff, sizeof(unsigned char), 1, dcImgFilePtr);
fwrite(&uvBuff, sizeof(unsigned char), 1, acImgFilePtr);
}
return 0;
}
/* loadjpeg.c中添加 */
int main(int argc, char *argv[]) {
...
const char* dcImgFileName = "test_decoded_dc.yuv"; // DC图像文件名
const char* acImgFileName = "test_decoded_ac.yuv"; // AC图像文件名
fopen_s(&dcImgFilePtr, dcImgFileName, "wb"); // 打开DC图像文件
fopen_s(&acImgFilePtr, acImgFileName, "wb"); // 打开AC图像文件
...
fclose(dcImgFilePtr);
fclose(acImgFilePtr);
return 0;
}