目录
实验名称:JPEG原理分析及JPEG解码器的调试
实验目的:掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
JPEG编码原理
- 图像分块:由于后面的DCT变换是是对8x8的子块进行处理的,因此,必须先将原始图像数据分成8*8的小块。
- 零偏置(Level Offset):将所有像素的电平减去128,电平范围从[0,255]变成[-128,127]
- 8×8 DCT变换:将低频部分集中在每个8*8块的左上角,高频部分在右下角。实现能量集中和去相关,去除图像的空间冗余。
- 量化:根据人眼视觉特性,低频细量化,高频粗量化,减小视觉冗余;JPEG使用的颜色是YUV格式。Y分量代表了亮度信息,UV分量代表了色差信息。根据人眼对亮度信息更敏感,对色度信息没那么敏感,我们还可以对Y采用细量化,对UV采用粗量化,可进一步提高压缩比。
- 编码:直流系数进行差分和VLC编码,交流系数进行之字形扫描、游程编码和VLC编码,减少数据冗余。
- DC 系数编码:由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
- AC 系数编码:首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)] Amplitude:表示非零系数的幅度值;Run:表示零的游程即零的个数;Size: 表示非零系数的幅度值的编码位数;
JPEG文件格式
Segment 的组织形式
- 均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示 Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
- 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
- Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
JPEG 的 Segment Marker
JPEG 的解码流程
实验要求及实现
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
为输出YUV文件,需要在write_yuv函数下增加如下代码:
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);
调试:
用YUV播放器打开test.yuv
2. 程序调试过程中,应做到:
- 理解程序设计的整体框架
- 理解三个结构体的设计目的
- struct huffman_table
- struct component
- struct jdec_private
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
* 快速查找表,使用HUFFMAN_HASH_NBITS位我们可以直接得到符号,
* 如果符号小于0,则需要查看慢速查找表 */
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];
};
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 ,即DCT系数 */
#if SANITY_CHECK
unsigned int cid;
#endif
};
struct jdec_private //保存着图像的基本信息,各通道的信息及需要用的量化表,解码后分量的值等
{
/* Public variables */
uint8_t *components[COMPONENTS]; //一个MCU指向它包含的块
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]; //解码后Y,Cr,Cb分量的值
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
- 理解在视音频编解码调试中TRACE的目的和含义
TRACE随着文件解析,输出中间信息,方便了解整个程序运行的过程。
- 会打开和关闭TRACE
输出txt文件在loadjpeg.h中打开和关闭。
#define snprintf _snprintf //add by nxn
#define TRACE 1 //add by nxn,若要关闭TRACE,则将TRACE设为0
#define TRACEFILE "trace_jpeg.txt" //add by nxn
- 会根据自己的要求修改TRACE
通过在系统各处添加trace,可以在需要的时候输出想要的中间结果,这些中间信息在tinyjpeg.c中输出。
3. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
tinyjpeg.h补充
FILE* Q_table;
FILE* Huffman_code;
loadjpeg.c的main中补充
Q_table=fopen("Q_table.txt","wb");
Huffman_code = fopen("Huffman_code.txt", "wb");
parse_DQT函数:解析量化表,得到量化表长度,检查量化表是否被支持,得到量化表内的数据
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi;
float *table; //指向量化表的指针
const unsigned char *dqt_block_end; //指向量化表的结束位置
#if TRACE
fprintf(p_trace,"> DQT marker\n");
fflush(p_trace);
#endif
dqt_block_end = stream + be16_to_cpu(stream); //量化块结束位置
stream += 2; /* Skip length 跳过两字节的存储长度 */
while (stream < dqt_block_end) //将量化表中的值逐个赋给qi
{
qi = *stream++;
#if SANITY_CHECK
if (qi>>4)
snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
if (qi>4)
snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
#endif
table = priv->Q_tables[qi]; //初始化量化表
build_quantization_table(table, stream); //得到量化表内容,将文档数据流赋值给量化表
stream += 64; //指向下一个8*8的块
}
#if TRACE //输出量化表
fprintf(Q_table, "scaled quantization table [%d]", qi);
for (int i = 0; i < 64; i++)
{
if ((!(i % 8))) //8个一行输出
{
fprintf(Q_table, "\n%f", table[i]);
}
else
{
fprintf(Q_table, " %f", table[i]);
}
}
fprintf(Q_table, "\n");
}
#endif
#if TRACE
fprintf(p_trace,"< DQT marker\n");
fflush(p_trace);
#endif
return 0;
}
parse_DHT函数:得到 Huffman 表的类型( AC、 DC)、序号。添加如下代码:
#if TRACE
fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
fflush(p_trace);
fprintf(Huffman_code,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
fflush(Huffman_code);
#endif
build_huffman_table函数:重建 Huffman 表 。添加如下代码:
#if TRACE
fprintf(p_trace, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
fflush(p_trace);
fprintf(Huffman_code, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
fflush(Huffman_code);
#endif
实验结果:
4. 输出DC图像并统计其概率分布。
5. 输出某一个AC值图像并统计其概率分布。
tinyjpeg.h补充
FILE* AC_file, * DC_file;
void outputDCAC(struct jdec_private* priv);
loadjpeg.c的main中补充
DC_file = fopen("DC_img.yuv", "w");
AC_file = fopen("AC_img.yuv", "w");
tinyjpeg.c补充
static void outputDCAC(struct jdec_private* priv)
{
int dcmax, dcmin;
int acmax, acmin;
unsigned char* temp; //记录归一化后的值
dcmin = priv->DCimg[0];
dcmax = priv->DCimg[0];
acmin = priv->ACimg[0];
acmax = priv->ACimg[0];
temp = (unsigned char*)malloc(sizeof(unsigned char) * (priv->height * priv->width / 64));
for (int i = 0; i < (priv->height*priv->width / 64); i++) //找出最大最小值
{
if (priv->DCimg[i] > dcmax)
dcmax = priv->DCimg[i];
if (priv->DCimg[i] < dcmin)
dcmin = priv->DCimg[i];
if (priv->ACimg[i] > acmax)
acmax = priv->ACimg[i];
if (priv->ACimg[i] < acmin)
acmin = priv->ACimg[i];
}、
for (int i = 0; i < (priv->height*priv->width / 64); i++)
{
temp[i] = (unsigned char)(255 * (priv->DCimg[i] - dcmin) / (dcmax - dcmin));
}
fwrite(temp, 1, priv->width*priv->height / 64, DC_file);
for (int i = 0; i < (priv->height*priv->width / 64); i++) //DC_file为128*128
{
temp[i] = (unsigned char)(255*(priv->ACimg[i] - acmin)/ (acmax - acmin));
}
fwrite(temp, 1, priv->width*priv->height / 64, AC_file); //AC_file为128*128
}
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
priv->DCimg = (int*)malloc(sizeof(int) * priv->width * priv->height / 64);
priv->ACimg = (int*)malloc(sizeof(int) * priv->width * priv->height / 64);
static long i = 0;//i为DCT块的个数
if (i < priv->height * priv->width / 64) //8x8块
{
priv->DCimg[i] = priv->component_infos[cY].DCT[0];//直流DCT[0]
priv->ACimg[i] = priv->component_infos[cY].DCT[1];//选取DCT[64]的第二个ac系数(取值范围(1,63))
}
i++;
outputDCAC(priv);
......
}
概率分布图调用DPCM编码博文中出现的Count函数处理输出的AC_img.yuv和DC_img.yuv
void Count(unsigned char* Buff, double* freq, FILE* outfile)
{
int num[256] = { 0 };
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < width * height; j++)
{
if (i == Buff[j])
{
num[i]++;
}
}
}
fprintf(outfile, "symbol\tfreq\n");
for (int i = 0; i < 256; i++)
{
freq[i] = double(num[i]) / (width * height);
fprintf(outfile, "%d\t%f\n", i, freq[i]);
}
}
得到AC_freq.txt和DC_freq.txt
实验结果:
yuv打开方式:128*128 400格式
yuv图像 | 概率分布图 | |
DC | ||
AC |