数据压缩实验之 JPEG原理分析及JPEG解码器的调试
2021/6/8 10:21:22
本文主要是介绍数据压缩实验之 JPEG原理分析及JPEG解码器的调试,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 1.实验名称
- 2.实验目的
- 3.主要设备
- 4.实验内容
- 4.1 JPEG文件格式
- 4.2 JPEG编解码原理
- 4.2.1 编码流程
- 4.2.2 解码流程
- 5.实验步骤
- 5.1 逐步调试JPEG解码器程序
- 5.1.1 理解程序设计的整体框架
- 5.1.2 理解三个结构体的设计目的
- 5.1.3 理解在视音频编解码调试中TRACE的目的和含义
- 5.2 改写程序,将输出文件保存为可供YUVViewer观看的YUV文件
- 5.3 改写程序,以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
- 5.4 改写程序,输出DC图像、某一个AC值图像并分别统计它们的概率分布
1.实验名称
JPEG原理分析及JPEG解码器的调试
2.实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
3.主要设备
安装Windows和Visual Studio软件的个人计算机
4.实验内容
4.1 JPEG文件格式
依次 | 文件格式 | 标记代码 | 具体字段 |
---|---|---|---|
① | SOI(图像开始) | 0xFFD8 | ----------------------------------- |
② | APP0(应用程序保留标记0) | 0xFFE0 | |
③ | DQT(定义量化表) | 0xFFDB | |
④ | SOF0(帧图像开始) | 0xFFC0 | |
⑤ | DHT(定义哈夫曼表) | 0xFFC4 | |
⑥ | SOS(扫描开始) | 0xFFDA | |
⑦ | EOI(图像结束 2字节) | 0xFFD9 | ------------------------------------- |
根据上表,用FlexHEX打开一张JPG格式的图片,并用红笔标注出每个标记代码:
依次验证每一字段,发现的确如此。
4.2 JPEG编解码原理
4.2.1 编码流程
①零偏置
目的:保证输入图像的采样有近似地集中在零附近的动态范围。
实现方法:对于灰度级是
2
n
2^n
2n的像素,通过减去
2
n
−
1
2^{n-1}
2n−1,将无符号的整数值变成有符号数.
②
8
∗
8
8*8
8∗8 DCT变换
目的:实现能量集中和去相关,便于去除空间冗余,以达到压缩图像数据的目的。
实现方法:首先对图像进行
8
∗
8
8*8
8∗8的分块(若图像的宽高不是8的倍数,可对图像进行补0,补至8的倍数),之后进行DCT(离散余弦变换)变换,经DCT变换后,
8
∗
8
8*8
8∗8的图像块将变为
8
∗
8
8*8
8∗8的DCT系数块。
DCT变换计算公式:
其中
③量化
利用人眼对高频细节不甚敏感的特性,对高频的AC分量进行粗量化,对低频的DC分量进行细量化。
利用人眼对色度细节不甚敏感的特性:对色度分量进行粗量化,对亮度分量进行细量化。
JPEG算法提供了两张标准的量化系数矩阵:
标准亮度量化表
标准色差量化表
④编码
对DC系数:差分编码
由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
对AC系数:游程编码
首先对AC系数的之字形扫描
出现很多连零,使用游程编码。最后如果都是零,给出EOB (End of Block)即可。
⑤Huffman编码
Huffman编码几乎是所有压缩算法的基础,它的基本原理是根据数据中元素的使用频率,调整元素的编码长度,以得到更高的压缩比。JPEG 中共采用了四张 Huffman 码表:亮度 DC、亮度 AC、色度 DC、色度 AC,即分别对图像的亮度和色度,直流和交流数据进行编码处理。
JPEG建立Huffman表的方法:
第一个码字必定为0.
如果第一个码字位数为1,则码字为0
如果第一个码字位数为2,则码字为00
以此类推
从第二个码字开始:
如果它和它前面的码字位数相同,则当前码字为它前面的码字加1;
如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,直至满足位数长度为止。
直流系数权值就是解码时再需要读入的bit位数。
交流系数权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。
4.2.2 解码流程
5.实验步骤
5.1 逐步调试JPEG解码器程序
5.1.1 理解程序设计的整体框架
1.读取文件
在主函数main函数中,打开输入输出文件,并解析了输出格式:
int main(int argc, char *argv[])
整个JPEG解码过程,由下面的covert_one函数实现:
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
2.解析 Segment Marker(tinyjpeg_parse_header中)
解析文件头:
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
解析DQT:
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
解析SOF:
static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)//基线余弦变换
解析DHT:
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)//解析Huffman码表
建立量化表:
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
重建 Huffman 表 :
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)//创建码表
解析SOS:
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
3.依据每个分量的水平、垂直采样因子计算 MCU 的大小,得到每个 MCU 中
8
∗
8
8*8
8∗8宏块个数
4.对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码)
static void decode_MCU_1x1_3planes(struct jdec_private *priv)
static void decode_MCU_1x1_1plane(struct jdec_private *priv)//采样格式为1:1:1
static void decode_MCU_2x1_3planes(struct jdec_private *priv)
等类似的函数。
对一个
8
∗
8
8*8
8∗8的彩色分量单元进行解码:
5.解完所有 MCU,解码结束
5.1.2 理解三个结构体的设计目的
jdec_private结构体: 通过阅读代码注释,可发现该结构体指示了解码过程中的码流参数(开始、结束位置,持续时间),量化表,霍夫曼码表以及图像数据(宽高比),y、u、v分量,最小单元MUC和中间变量等。
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 */ 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]; };
struct component 结构体: 由代码可知,Hfactor和 Vfactor 用于说明水平与垂直的采样情况, Q_table指向该component所对应的量化表; AC_table,DC_table用于指向对DC分量与AC分量进行霍夫曼解码时所需的码表;previous_DC是用于保存前一个块的DC值,用于DPCM解码;**DCT[64]**则是相应一个块的DCT系数矩阵,故用于参与霍夫曼解码,反量化,IDCT 以及彩色空间变换
struct component { unsigned int Hfactor; unsigned int Vfactor; float *Q_table; /* Pointer to the quantisation table to use */ struct huffman_table *AC_table; struct huffman_table *DC_table; short int previous_DC; /* Previous DC coefficient */ short int DCT[64]; /* DCT coef */ #if SANITY_CHECK unsigned int cid; #endif };
struct huffman_table结构体: 该结构体创建一个查找表用于解码,若码长大于9,则由slowtable处理。
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]; };
5.1.3 理解在视音频编解码调试中TRACE的目的和含义
TRACE的目的:随着文件的解析,输出中间过程中的某些变量,或者错误信息到txt文件。
在tinyjpeg.h中,令TRACE=1,即打开TRACE,令TRACE=0,即关闭TRACE。
#define TRACE 1
Trace打开的时候,就可以进行上述说明的信息的输出,Trace关闭的话,就是直接跳过这些代码的编译,不进行上说说明信息的输出。
在代码中寻找到以下格式的段便可编辑TRACE:
#if TRACE ............. .............. #endif
5.2 改写程序,将输出文件保存为可供YUVViewer观看的YUV文件
在write_yuv函数里添加这一段:
snprintf(temp, 1024, "%s.yuv", filename);//add by chencheng 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);
结果:
打开output.yuv图像:
5.3 改写程序,以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
输出所有的量化矩阵添加的关键代码如下:
quantity_table = fopen(QUANTITYFILE, "w+"); if (quantity_table==NULL) { printf("quantity table file open error!"); } huffman_table = fopen(HUFFMANFILE, "w+");
#if TRACE fprintf(p_trace,"< DQT marker\n"); PrintQtable(table, qi); fflush(p_trace); #endif
static void PrintQtable(float* qtable, int qi) { fprintf(quantity_table, "Qtable[%d] is:\n", qi); int i, j; for (i = 0; i < 8; i++) { for (j = 0; j < 8; j++) { if (j == 7) { fprintf(quantity_table, "%10.3f\n", qtable[i * 8 + j]); } else { fprintf(quantity_table, "%10.3f\t", qtable[i * 8 + j]); } } } }
结果:
输出所有的HUFFMAN码表添加的关键代码:
if (huffman_table == NULL) { printf("huffman table file open error!"); }
fprintf(huffman_table, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fprintf(huffman_table, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
结果:
5.4 改写程序,输出DC图像、某一个AC值图像并分别统计它们的概率分布
在tinyjpeg_decode中修改:
//打开AC、DC文件 FILE *DCFile = fopen("DC.yuv", "wb"); FILE *ACFile = fopen("AC.yuv", "wb"); if (DCFile == NULL) { printf("Fail to open DC\n"); return 0; } if (ACFile == NULL) { printf("Fail to open AC\n"); return 0; } unsigned char acy = 0; unsigned char dcy = 0; if (setjmp(priv->jump_state)) return -1;
dcy = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4 + 0.5 ); fwrite(&dcy, 1, 1, DCFile); acy = (unsigned char)(priv->component_infos->DCT[1] + 128); fwrite(&acy, 1, 1, ACFile); for (int i = 0; i < 256; i++) { if (dcy == i) dcfreq[i]++; if (acy == i) acfreq[i]++; } } } for (int i = 0; i < 256; i++) { dcfreq[i] = dcfreq[i] / (priv->width*priv->height / 64); acfreq[i] = acfreq[i] / (priv->width*priv->height / 64); } //输出概率分布 FILE *ACfre = fopen("AC频率.txt", "wb"); if (ACfre == 0) { printf("Fail to open AC频率\n"); return 0; } fprintf(ACfre, "数值\t概率\n"); for (int i = 0; i < 256; i++) { fprintf(ACfre, "%d\t%f\n", i, acfreq[i]); } FILE *DCfre = fopen("DC频率.txt", "wb"); if (DCfre == 0) { printf("Fail to open DC频率\n"); return 0; } fprintf(DCfre, "数值\t概率\n"); for (int i = 0; i < 256; i++) { fprintf(DCfre, "%d\t%f\n", i, dcfreq[i]); }
unsigned char uv = 128; for (int i = 0; i < priv->width*priv->height / 32; i++) { fwrite(&uv, 1, 1, ACFile); fwrite(&uv, 1, 1, DCFile); }
结果:
打开DC.yuv图像
使用MATLAB绘制DC图像的概率分布:
打开AC.yuv图像:
由DC、AC图像可以看出,DC图像基本保留了原图的细节。
使用MATLAB绘制某一AC值图像的概率分布:
这篇关于数据压缩实验之 JPEG原理分析及JPEG解码器的调试的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南