数据压缩 实验五 JPEG原理分析JPEG解码器的调试
实验原理
1.JPEG原理及编码流程
JPEG是常见的一种图像格式,由ISO与CCITT建立并开发,是一个国际数字图像压缩标准。JPEG文件的扩展名为.jpg或.jpeg,用有损方式去除冗余的图像与彩色数据,在获取极高的压缩率同时能展现十分生动的静态图像,JPEG被认为是目前压缩比最高的静态图像,它被广泛地应用于多媒体与网络程序中。
根据人眼视觉特性:眼睛对亮度的敏感程度要大于对色彩的敏感程度。在图像中,为了利用人类的种视角特性,从而降低数据量,通常将RGB空间表示的彩色图像变换到YCbCr颜色空间中。由于人眼对亮度Y的敏感度大于色差CrCb,因此可以在适当程度上对CrCb进行削弱以达到压缩的目的。由于原始图像是由很多独立的像素组成的,其实人眼对于每个细微像素的分辨能力很弱,只有众多像素集合一块,才能呈现出颜色连续变化的图像,因此图像中相邻两像素点,其彩色分量在很大程度上是接近的。在一幅图像内,包含了各种频率的分量,但大多数分量都属于低频信号,只在占图像区域比例很小的图像边缘的像素才含有高频信号。因此在对图像编码的时候,在图像质量不出现可察觉损失的情况下,对包含信息量大的低频谱区分配较多比特数,对包含信息量较低的高频谱区域分配较少的比特数,就能达到数据压缩目的。
DCT变换是将图像的色彩空间域转换到频谱域。DCT过程并不产生压缩作用,其作用是将图像数据去相关化、能量集中,去除图像数据内部的相关性后,以便在对这些图像数据分类处理——即对不同的频路部分进行不同的量化。
量化编码是JPEG编码中产生信息损失的根源,也是图像质量下降的最主要原因。简单的说,就是将频谱领域中的每个值,除以量化表中对应的常数,且四舍五入取最接近的整数,这样会把很多高频的成分四舍五入为0。量化后左上角的值较小,右下角的值较大,这样就保持低频分量、抑制高频分量的目的。这一步在实现的时候会对Y进行细量化,对Cr、Cb采用粗量化,依次来提高压缩比。因此存在两张不同的表。
经过DCT变换后,图像中的低频分量会集中在左上角,而右下角有较多的0值,因此采用Z字形编排。JPEG算法使用了差分脉冲编码(DPCM)技术,对相邻图像块之间量化DC洗漱的差值进行单独编码,从而再次利用相邻特性简化数据。并对剩余的63个交流(AC)系数进行游程编码。
为了进一步提高压缩比例,JPEG算法对DPCM编码后的直流系数与游程编码后的交流系数使用Huffman熵编码。使用huffman码表可以简单的查表进行编码。对于AC与DC所采用的码表是不同的,对于色差和亮度的霍夫曼码表也不同。因此应该有四个霍夫曼码表。
JPEG编码流程:
JPEG编码流程如图,解码为逆过程
(1)零偏置(level offset)
对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数;对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值。这样做的目的是: 使像素的绝对值出现3位10进制的概率大大减少。
(2)8x8 DCT变换
DCT变换是指对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,再以8x8的图像块为一个单位进行量化和编码处理。我们可以利用DCT变换去相关的特性,去除冗余信息,提高编码效率。
(3)量化
我们可以通过量化减少数据的编码位数,提高编码效率;因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值;根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
(4)DC系数差分编码
8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值比较大和相邻8×8图像块的DC系数值变化不大:冗余;根据这个特点, JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:DIFFk=DCK−DCK−1,再对DIFF进行Huffman编码。
(5)AC系数的之字形扫描与游程编码
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。zigzag扫描如下图:
在经过之字形扫描排序后的AC系数,存在很多连0。为了进一步提高编码效率,因此对AC系数进行游程编码(RLC)处理之后,再进一步进行Huffman编码。
(6)AC和DC系数分别进行Huffman编码
JPEG*采用了四张Huffman码表:亮度DC、亮度AC、色度DC、色度AC,即分别对图像的亮度和色度,直流和交流数据进行编码处理。
2.JPEG文件格式分析
JPEG文件的存储格式有很多种,但最常用的是JFIF格式,即JPEG File Interchange Format。JPEG文件大体可以分为两个部分:
(1)标记码:由两个字节构成,其中,前一个字节是固定值0XFF代表了一个标记码的开始,后一个字节不同的值代表着不同的含义。需要提醒的是,连续的多个0XFF可以理解为一个0XFF,并表示一个标记码的开始。另外,标记码在文件中一般是以标记代码的形式出现的。例如,SOI的标记代码是0XFFD8,即,如果JPEG文件中出现了0XFFD8,则代表此处是一个SOI标记。
(2)压缩数据:一个完整的两字节标记码的后面,就是该标记码对应的压缩数据了,它记录了关于文件的若干信息。
3.JPEG解码流程
1 .读取文件
2. 解析 Segment Marker
解析 SOI
解析 APP0:检查标识“ JFIF”及版本,得到一些参数
解析 DQT: 得到量化表长度(可能包含多张量化表);
得到量化表的精度;
得到及检查量化表的序号(只能是 0 —— 3);
得到量化表内容( 64 个数据)
解析 SOF0:得到每个 sample 的比特数、长宽、颜色分量数
得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)
解析 DHT:得到 Huffman 表的类型( AC、 DC)、序号,依据数据重建 Huffman 表
解析 SOS:得到解析每个颜色分量的 DC、 AC 值所使用的 Huffman 表序号(与 DHT中序号对应)
3. 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数
4. 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码)
对每个宏块进行 Huffman 解码,得到 DCT 系数
对每个宏块的 DCT 系数进行 IDCT,得到 Y、 Cb、 Cr
遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
5. 解析到 EOI,解码结束
6. 将 Y、 Cb、 Cr 转化为需要的色彩空间并保存。
代码分析
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
tinyjpeg.h中增加YUV类型
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
//Add by ts
TINYJPEG_FMT_YUV,
//End
};
loadjpeg.c中判断输出格式时增加YUV类型
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
//Add by ts
case TINYJPEG_FMT_YUV:
write_YUV(outfilename, width, height, components);
break;
//End
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
loadjpeg.c的main函数中增加相应代码
input_filename = argv[current_argument];
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;
//Add by ts
else if (strcmp(argv[current_argument+1],"yuv")==0)
output_format = TINYJPEG_FMT_YUV;
//End
else
exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
output_filename = argv[current_argument+2];
在tinyjpeg.c增加case TINYJPEG_FMT_YUV项
decode_mcu_table = decode_mcu_3comp_table;//
switch (pixfmt) {
case TINYJPEG_FMT_YUV420P:
……;
case TINYJPEG_FMT_RGB24:
……;
case TINYJPEG_FMT_BGR24:
……;
case TINYJPEG_FMT_GREY:
……;
//Add by ts
case TINYJPEG_FMT_YUV:
colorspace_array_conv = convert_colorspace_yuv420p;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
if (priv->components[1] == NULL)
priv->components[1] = (uint8_t *)malloc(priv->width * priv->height/4);
if (priv->components[2] == NULL)
priv->components[2] = (uint8_t *)malloc(priv->width * priv->height/4);
bytes_per_blocklines[0] = priv->width;
bytes_per_blocklines[1] = priv->width/4;
bytes_per_blocklines[2] = priv->width/4;
bytes_per_mcu[0] = 8;
bytes_per_mcu[1] = 4;
bytes_per_mcu[2] = 4;
break;
//End
default:
2.程序调试过程中,应做到:
2.1 理解程序设计的整体框架
2.2 理解以下三个结构体的设计目的
/* 存放DC系数、AC系数的哈夫曼码表 */
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];
};
/* 存放8*8数据块组成的MCU数据值 */
struct component
{
unsigned int Hfactor;//水平采样
unsigned int Vfactor;//垂直采样
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;//交流huffman结构体
struct huffman_table *DC_table;//直流huffman结构体
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK//SANITY_CHECK为1
unsigned int cid;//???
#endif
};
/* 包含struct huffman_table和struct component,是一个综合的结构体 */
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 YUV*/
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的目的和含义
会打开和关闭TRACE
会根据自己的要求修改TRACE
实验结果
1.原始图片
2.TXT输出结果
3.输出的DC、AC图像
图像 | freq | |
---|---|---|
DC | ||
AC |
下一篇: 深入解析数据压缩算法