实验目的
掌握DPCM编解码系统的基本原理。设计均匀量化器对误差图像进行量化,观察分析量化及预测结果;使用Huffman编码程序对原始图像和预测误差图像进行编码,统计符号概率并分析其压缩效率。
实验内容
-
DPCM编解码原理
在预测编码DPCM中,先根据前几个抽样值计算出一个预测值,再取当前抽样值和预测值之差。将此差值编码并传输。此差值称为预测误差。由于抽样值及其预测值之间有较强的相关性,即抽样值和其预测值非常接近,使此预测误差的可能取值范围,比抽样值的变化范围小。所以,可以少用编码比特来对预测误差编码,从而降低其比特率。此预测误差的变化范围较小,它包含的冗余度也小。这就是说,利用减小冗余度的办法,降低了编码比特率。
需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是
因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解码器,如编码器中虚线框中所示。
本次实验中,预测器逐行预测像素,预测值为当前像素的左方像素。对于每行第一个像素预测值为128。 -
量化器设计
采用均匀量化方式对预测误差进行量化
编码器将每个区间/bin的索引发给解码器,解码器用重构水平表示该区间内所有的值。
考虑到8bit灰度图像量化后误差值范围[-255,255],在8bit量化时,将该范围 / 2 压缩至[-127,127]并进行+127电平提升使得其可以被8bit unsigned char 型数据表示;在4bit量化时,将该范围 / 32 压缩至[-7,7],理论上需要进行7电平提升,但编程时考虑到令unsigned char型数据表示方便,同时便于计算误差,将其电平提升至127。 -
图像量化误差分析
MSE(Mean squared error)均方误差:
对于给定m*n大小图片,有
其中,I为参考图像,K为噪声图像。
PSNR (Peak Signal-to-Noise Ratio) 峰值信噪比:
因为图像像素最大值为255,所以MAXi取255即可,PSNR使用dB表示图像峰值信噪比,越大说明图像质量越好。实验采用使用PSNR分析图片量化后误差。 -
Huffman编码
Huffman编码原理在这里不深入讨论,使用现有Huffman编码器对数据进行编码,实验关心的内容在于预测和量化对于图像压缩率的影响。当符号集较大且概率分布不是很悬殊时,即经过DPCM系统的误差值,Huffman的平均码长可接近于熵。而对于概率分布较为悬殊的消息集合,如原始图像,上限可能很大,意味着编码剩余度较大。
实验结果
-
DPCM预测误差以及PSNR:
实验中仅对部分图像进行了4bit量化。
可见,在8bit量化时由于解码值与原始像素值误差为±1,且不会发生过载,所以恢复的图像仍能保持较高的PSNR,质量较好。而4bit量化时由于量化区间过宽,量化误差较大,图片PSNR低且主观感受上质量很差。 -
编码文件尺寸:
可见,DPCM+Huffman编码对于大部分像素间具有较强相关性的图像有着比单纯Huffman编码更好的编码效率,且8bit均匀量化时量化误差几乎无法察觉,图像质量令人满意。但是对于噪声图像这种像素间不存在相关性的图像而言,DPCM+Huffman编码压缩效率可能不降反升。需要指出的是,YUV灰度文件中U、V色度通道的值均为127,这部分数据存在很强的相关性。 -
像素概率分布分析
从中选择4号,5号图片的Y通道做概率统计分析,两张图片均为8bit量化:
由图片原始概率分布及预测误差概率分布可以看出DPCM编码具有很好的概率集中的作用,编码后将码字集中于127附近,使得最终文件的概率分布更适应于如Huffman编码这样基于概率分布的编码,进一步提高了媒体文件文件压缩的效率。
程序代码
读取YUV文件的程序见:BMP文件读取及转换至YUV色彩空间
使用此代码时需要在构造函数加入对YUV三个通道均赋值为127的语句,改动如下:
YUV_raw_image::YUV_raw_image(uint2 w, uint2 h, uint2 f_rate, chrominace_mode m, uint4 num)
{
init(w, h, f_rate, m);
frame_num = num;
data = new YUV_per_frame[frame_num];
for (int i = 0; i < frame_num; i++) {
uint1* ptr = new uint1[frame_size]{ 0 };
if (ptr == nullptr) {
cerr << __func__ << "\tCan not get memory!" << endl;
exit(1);
}
(data + i)->Y_data = ptr; //set values of pixel to 0
(data + i)->U_data = ptr + frame_Y_size;
(data + i)->V_data = ptr + frame_Y_size + frame_U_size;
}
for (int i = 0; i < frame_num; i++) {
for (int u = 0; u < frame_size; u++) {
*((data + i)->Y_data + u) = 127;
}
}
}
dpcm.h:
#pragma once
#include "YUV_raw.h"
uint1 clamp(int value);
bool DpcmEncoder(uint1* data, int width, int height, int level,
uint1* rebuild, uint1* diff, uint1* err);
double calculate_MSE(uint1* data, int width, int height);
double calculate_PSNR(uint1* data, int width, int height);
dpcm.cpp
#include "dpcm.h"
#include <math.h>
#include <iostream>
using namespace std;
uint1 clamp(int value)
{
if (value > 255) {
return 255;
}
else if (value < 0) {
return 0;
}
else {
return (uint1)value;
}
}
bool DpcmEncoder(uint1* data, int width, int height, int level, uint1* rebuild, uint1* diff, uint1* err)
{
uint1* DataLine = nullptr;
uint1* DiffLine = nullptr;
uint1* RebuildLine = nullptr;
uint1* ErrLine = nullptr;
int last_pix = 0; // last pixel
int different = 0; // different before quantify
uint1 q_key = 0; // quantify output
int q_value = 0; // value after quantify
for (int loop_line = 0; loop_line < height; loop_line++) {
DataLine = data + loop_line * width; //get data ptr per line
DiffLine = diff + loop_line * width;
RebuildLine = rebuild + loop_line * width;
ErrLine = err + loop_line * width;
for (int loop_i = 0; loop_i < width; loop_i++) {
// get value of prediction pixel
// predictor
last_pix = (loop_i) ? RebuildLine[loop_i - 1] : 128;
// caulate diff
different = DataLine[loop_i] - last_pix;
quantify
switch (level) {
case(2): {
DiffLine[loop_i] = (different / 128) + 127;
q_value = (DiffLine[loop_i] - 127) * 128;
}break;
case(4): {
DiffLine[loop_i] = (different / 32) + 127;
q_value = (DiffLine[loop_i] - 127) * 32;
}break;
case(8): {
DiffLine[loop_i] = (different / 2) + 127;
q_value = (DiffLine[loop_i] - 127) * 2;
}break;
default: {
cout << __func__ <<
"\tInvalid quantification bit num! run as default value 8"
<< endl;
DiffLine[loop_i] = (different / 2) + 127;
q_value = (DiffLine[loop_i] - 127) * 2;
}break;
}
// storage decode value and calculate error
RebuildLine[loop_i] = clamp(last_pix + q_value);
ErrLine[loop_i] = clamp(127 + RebuildLine[loop_i] - DataLine[loop_i]);
}
}
return true;
}
double calculate_MSE(uint1* data_err, int width, int height)
{
int num = width * height;
int accumulate_error = 0;
double avg_error = 0;
for (int i = 0; i < num; i++) {
accumulate_error += (data_err[i] - 127) * (data_err[i] - 127);
}
avg_error = (double)accumulate_error / (double)num;
return avg_error;
}
double calculate_PSNR(uint1* data, int width, int height)
{
double mse = calculate_MSE(data, width, height);
double psnr = 10 * log10((double)65025 / mse);
return psnr;
}