实验名称:JPEG原理分析及JPEG解码器的调试
文章目录
实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
提示:以下是本篇文章正文内容,下面案例可供参考
一、实验原理
1.JPEG编解码原理:
(1).JPEG编码原理
零偏置(Level Offset):对于灰度级是的像素,通过减去,将无符号的整数值变成有符号数;对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值
DCT变换:对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,不足8×8的图像块,取边缘像素补齐,对每个块做DCT变换,直流系数在每个块的左上角,右下角的分量频率高。
DC系数的差分编码:8×8图像块经过DCT变换之后得到的DC直流系数(系数的数值比较大;相邻8×8图像块的DC系数值变化不大),JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行Huffman编码
AC系数的Z字扫描:经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会,使用游程编码。
(2).解码原理
解码Huffman数据→解码DC差值→重构量化后的系数→DCT逆变换→丢弃填充的行/列→反0偏置→对丢失的CbCr分量差值(下采样的逆过程)→YCbCr →RGB
2. JPEG文件格式
二、实验步骤
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为YUV文件。
write_yuv()函数修改,代码如下:
static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
FILE *F;
char temp[1024];
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.调试过程中应注意:
(1).理解程序设计的整体框架:
解码开始于convert_one_image()函数
tinyjpeg_init()用于初始化
tinyjpeg_parse_header()用于解码JPEG文件头,函数前几句主要验证文件是否为JPEG文件
parse_JFIF()用于解析各种标签(SOF,SOS,DHT...)
parse_SOF()用于解析SOF标签,主要用于在解码过程中提取一些信息,比如图像宽,高,颜色分量数等
parse_DHT() 调用build_huffman_table建立哈夫曼表(AC/DC),用于解析DHT标签
parse_DQT() 调用build_quantization_table建立量化表,用于解析DQT标签
parse_SOS()用于解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号
解码数据开始于tinyjpeg_decode()函数
对每个宏块进行Huffman 解码,提取 DCT系数表
对每个宏块的DCT系数进行 IDCT,解码后得到 Y、Cb 、Cr数据表
遇到 Segment Marker RST 时,清空之前的DCT系数
(2).理解三个结构体的设计目的
struct huffman_table:用于存储Huffman码表
struct huffman_table
{
// 快速查找表
short int lookup[HUFFMAN_HASH_SIZE];
//码字长度:给出一个符号被编码的比特数
unsigned char code_size[HUFFMAN_HASH_SIZE];
// 存储未在查找表中编码的值的空间
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
struct component:存储当前有关解码的信息、DCT变换后的值;并指向量化表和DC、AC的Huffman表
struct component
{
unsigned int Hfactor; //水平采样
unsigned int Vfactor; // 垂直采样
float *Q_table; // 指向要使用的量化表的指针
struct huffman_table *AC_table; // AC哈夫曼表
struct huffman_table *DC_table; //DC哈夫曼表
short int previous_DC; //前一个块的直流系数
short int DCT[64]; //DCT系数矩阵
#if SANITY_CHECK // 调试使用
unsigned int cid;
#endif
};
struct jdec_private:用于存储JPEG图像的宽高、码流长度、数据流指针、量化表、Huffman码表,色彩空间转换等。
struct jdec_private
{
/* public variables */
uint8_t *components[COMPONENTS];
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]; /* 量化表 */
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC哈夫曼表 */
struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC哈夫曼表 */
int default_huffman_table_initialized;
int restart_interval;
int restarts_to_go; /* 在此重启间隔内剩余的 MCU */
int last_rst_marker_seen; /* Rst标记每次递增 */
/* IDCT 之后用于存储每个组件的临时空间 */
uint8_t Y[64*4], Cr[64], Cb[64];
jmp_buf jump_state;
/* 内部指针用于色彩空间转换 */
uint8_t *plane[COMPONENTS];
};
(3).理解在视音频编解码调试中TRACE的目的和含义:trace用来记录解码过程,存放每个解码步骤完成后得到数据。Trace打开(置1)的时候,就可以进行上述说明的信息的输出,Trace关闭(置0)的话,就是直接跳过这些代码的编译,不进行上说说明信息的输出。
#if TRACE
/* 输出内容 */
p_trace=fopen(TRACEFILE,"w");
if (p_trace==NULL)
{
printf("trace file open error!");
}
#endif
3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
输出所有的量化矩阵
输出HUFFMAN表
4.输出DC图像并统计其概率分布
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
/*add by dyq*/
priv->DC_Image = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);
priv->AC_Image = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);
/* end */
unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
/*
typedef void (*decode_MCU_fct) (struct jdec_private *priv);
typedef void (*convert_colorspace_fct) (struct jdec_private *priv);
这两个函数里面结构体作为函数参数实现面向过程程序设计中又分层次的思想。
*/
decode_MCU_fct decode_MCU;
const decode_MCU_fct *decode_mcu_table;
const convert_colorspace_fct *colorspace_array_conv;
convert_colorspace_fct convert_to_pixfmt;
/*...省略部分代码*/
decode_mcu_table = decode_mcu_3comp_table;
return -1;
}
//选择MCU的长宽
xstride_by_mcu = ystride_by_mcu = 8;
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1)
{
decode_MCU = decode_mcu_table[0];
convert_to_pixfmt = colorspace_array_conv[0];
} else if (priv->component_infos[cY].Hfactor == 1) {
decode_MCU = decode_mcu_table[1];
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16;
} else if (priv->component_infos[cY].Vfactor == 2) {
decode_MCU = decode_mcu_table[3];
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16;
ystride_by_mcu = 16;
} else {
decode_MCU = decode_mcu_table[2];
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16;
}
resync(priv);
/*...省略部分代码*/
/* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
for (y=0; y < priv->height/ystride_by_mcu; y++)
{
//trace("Decoding row %d\n", y);
priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
for (x=0; x < priv->width; x+=xstride_by_mcu)
{
//在此处取直流分量和交流分量进行输出
getDAC(priv, xstride_by_mcu, ystride_by_mcu);//add by dyq
decode_MCU(priv);//huffman 解码
convert_to_pixfmt(priv);//变换
priv->plane[0] += bytes_per_mcu[0];
priv->plane[1] += bytes_per_mcu[1];
priv->plane[2] += bytes_per_mcu[2];
if (priv->restarts_to_go>0)
{
priv->restarts_to_go--;
if (priv->restarts_to_go == 0)
{
priv->stream -= (priv->nbits_in_reservoir/8);
resync(priv);
if (find_next_rst_marker(priv) < 0)
return -1;
}
}
}
}
/* add by dyq */
//全部取出后作范围调整[0,255]
adjustDAC(priv, xstride_by_mcu, ystride_by_mcu);
/* end */
return 0;
}
static void getDAC(struct jdec_private *priv, int xstride_by_mcu , int ystride_by_mcu)
{
static long int i = 0;
//因为宏块为8*8 因此DC图像应该为实际图像大小除以宏块大小
if (i < priv->height*priv->width / (xstride_by_mcu * ystride_by_mcu) )
{
priv->DC_Image[i] = priv->component_infos[0].DCT[0];
priv->AC_Image[i] = priv->component_infos[0].DCT[1];
}
i++;
}
static void adjustDAC(struct jdec_private *priv, unsigned int xstride_by_mcu, unsigned int ystride_by_mcu)
{
int i;
short int min_AC, max_AC, min_DC, max_DC, size;
unsigned char *temp_AC, *temp_DC;
size = priv->height*priv->width / (xstride_by_mcu * ystride_by_mcu);
temp_AC = (unsigned char*)malloc(sizeof(unsigned char)*size);
temp_DC = (unsigned char*)malloc(sizeof(unsigned char)*size);
//将AC/DC分量范围调整到[0-255]
min_AC = priv->AC_Image[0];
max_AC = priv->AC_Image[0];
min_DC = priv->DC_Image[0];
max_DC = priv->DC_Image[0];
//遍历寻找最大最小值,进行中心化操作
for (i = 0; i < size; i++)
{
if (priv->AC_Image[i] < min_AC)
min_AC = priv->AC_Image[i];
if (priv->AC_Image[i] > max_AC)
max_AC = priv->AC_Image[i];
if (priv->DC_Image[i] < min_DC)
min_DC = priv->DC_Image[i];
if (priv->DC_Image[i] > max_DC)
max_DC = priv->DC_Image[i];
}
//范围调整
for (i = 0; i < size; i++)
{
temp_AC[i] = (unsigned char)(255 * (priv->AC_Image[i] - min_AC) / (max_AC - min_AC));
temp_DC[i] = (unsigned char)(255 * (priv->DC_Image[i] - min_DC) / (max_DC - min_DC));
}
fwrite(temp_AC, 1, size, AC_FILE);
fwrite(temp_DC, 1, size, DC_FILE);
free(temp_AC);
free(temp_DC);
}
DC图像
统计概率分布
5.输出某一个AC值图像并统计其概率分布
AC图像
统计概率分布