DPCM编解码原理
DPCM是差分预测编码调制的缩写,是比较典型的预测编码系统,是一个负反馈系统,其编码原理框图如下:
DPCM利用信源相邻符号之间的相关性进行预测编码。在编码端,输入一个样本值,与上一个样本的预测值作差,对差值进行量化,得到的量化结果一方面作为编码端输出,另一方面反量化后作为预测器的输入,与上一个样本的预测值相加,作为当前样本的预测值(或说重建值)。
在DPCM系统中,需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解码器,如编码器中虚线框中所示。
DPCM编码系统的设计
设计思路
在一个DPCM系统中,有两个因素需要设计:预测器和量化器。理想情况下,预测器和量化器应进行联合优化。实际中,采用一种次优的设计方法:分别进行线性预测器和量化器的优化设计。
在本次实验中,我们采用固定预测器和均匀量化器。
预测器采用左侧预测,量化器采用8比特均匀量化。
设计步骤
根据DPCM原理框图,我们需要对量化器、反量化器、DPCM编码器分别进行设计。
本实验中还会将预测误差图像写入文件并将该文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。最后比较两种系统(1.DPCM+熵编码和2.仅进行熵编码)之间的编码效率(压缩比和图像质量),其中压缩质量以PSNR进行计算,故还需设计PSNR计算器。
量化器和反量化器
DPCM对当前像素值与预测值相减得到的误差值进行量化,误差值的范围在[-255,255]之间,需要用9bit表示,同时,为了使得预测误差范围均为正值,所以要对误差值+255之后再进行量化。因此,对于nbit量化,量化后误差值的计算公式为:(原始误差值+255)/2(9-n)。反量化即将该公式反过来即可。
//量化
int Quant(int n, int error) {
//n:量化bit数 error:原始误差值
//注意error要+255将误差值抬为正值再做量化
return ((error + 255) / pow(2, (9 - n)));
}
//反量化
int iQuant(int n, unsigned char ex_predict) {
//n:量化bit数 ex_predict:上一次预测值
return (ex_predict * pow(2, (9 - n)) - 255);
}
DPCM编码器
由于本实验使用的是左侧预测,所以需要将第一列的预测值设置为128,并将第一列与其他列分开处理。
该编码器的参数有:原始图像、预测误差图像、重建图像、原始图像的宽高以及量化bit数。
注意,对于可能溢出的情况还要进行单独处理。
void DPCM(unsigned char* origin_pic, unsigned char* predict_pic, unsigned char* rebuild_pic, int width, int height, int n) {
int error;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (j == 0) { //第一列以128进行预测
error = (origin_pic[i * width + j]) - 128; //误差值
predict_pic[i * width + j] = Quant(n, error); //量化误差值
rebuild_pic[i * width + j] = iQuant(n, predict_pic[i * width + j]) + 128; //重建值
}
else { //其他列都以前一列进行预测
error = (origin_pic[i * width + j]) - rebuild_pic[i * width + j - 1]; //误差值
predict_pic[i * width + j] = Quant(n, error); //量化误差值
rebuild_pic[i * width + j] = iQuant(n, predict_pic[i * width + j]) + rebuild_pic[i * width + j - 1]; //重建值
}
}
}
int max = pow(2, n) - 1; //当前量化的最大值
for (int i = 0; i < width * height; i++) { //处理可能溢出的情况
if (predict_pic[i] < 0) predict_pic[i] = 0;
if (predict_pic[i] > max)predict_pic[i] = max;
if (rebuild_pic[i] < 0) rebuild_pic[i] = 0;
if (rebuild_pic[i] > 255) rebuild_pic[i] = 255;
}
}
PSNR计算器
PSNR是峰值信号噪声比,是一种评价图像的客观标准,其计算公式为:
P
S
N
R
=
10
∗
log
10
(
2
n
−
1
)
2
M
S
E
PSNR=10*\log_{10}{\frac{(2^n-1)^2}{MSE}}
PSNR=10∗log10MSE(2n−1)2其中n为量化bit数,MSE为图像的均方误差,计算公式为:
M
S
E
=
1
M
∑
m
=
1
M
(
y
m
−
y
^
m
)
2
MSE=\frac{1}{M}\displaystyle\sum_{m=1}^{M}(y_m-\hat y_m)^2
MSE=M1m=1∑M(ym−y^m)2其中M为图像的长x高,
y
m
y_m
ym与
y
^
m
\hat y_m
y^m分别为原始图像和重建图像在相应位置的像素值。
利用PSNR的值对图像质量进行评判:
PSNR高于40dB说明图像质量极好,即非常接近原始图像;
PSNR在30~40dB通常表示图像质量是好的,即失真可以察觉但是可以接受;
PSNR在20~30dB说明图像质量差;
PSNR低于20dB的图像通常不可以被接受。
double PSNR(unsigned char* origin_pic, unsigned char* rebuild_pic, int width, int height, int n) {
double mse = 0, psnr = 0;
for (int i = 0; i < width * height; i++) {
mse += pow((origin_pic[i] - rebuild_pic[i]), 2);
}
mse = mse / (width * height);
int index = pow(2, n) - 1;
psnr = 10 * log10(pow(index, 2) / mse);
return psnr;
}
概率计算器
由于本实验要绘制概率分布图,故需要统计图像中每个灰度级出现的该率,代码如下:
void Frequency(unsigned char* buffer, double* frequency, int height, int width)
{
int size = height * width;
for (int i = 0; i < size; i++) frequency[buffer[i]] += 1;
for (int i = 0; i < 256; i++) frequency[i] /= size;
}
主函数
代码如下:
int main(int argc, char** argv) {
char* origin_pic = argv[1]; //原始图像
char* predict_pic = argv[2]; //预测误差图像
char* rebuild_pic = argv[3]; //重建图像
int qbit = atoi(argv[4]); //量化bit数
FILE* ori_file = NULL; //原始yuv
FILE* pred_file = NULL; //预测误差yuv
FILE* re_file = NULL; //重建yuv
//int width = 768, height = 512; //Birds
int width = 256, height = 256; //除了Birds以外所有图片都是256*256的
unsigned char* y_buffer = new unsigned char[width * height];
unsigned char* u_buffer = new unsigned char[(width * height) / 4];
unsigned char* v_buffer = new unsigned char[(width * height) / 4]; //原始图像的yuv
unsigned char* pred_buffer = new unsigned char[width * height]; //预测误差图像buffer
unsigned char* re_buffer = new unsigned char[width * height]; //重建图像buffer
ori_file = fopen(origin_pic, "rb");
if (ori_file == NULL) {
cout << "Can't open the origin image!" << endl;
}
else {
cout << "The origin image has been opened!" << endl;
}
pred_file = fopen(predict_pic, "wb");
if (pred_file == NULL) {
cout << "Can't open the predict image!" << endl;
}
else {
cout << "The predict image has been opened!" << endl;
}
re_file = fopen(rebuild_pic, "wb");
if (re_file == NULL) {
cout << "Can't open the rebuild image!" << endl;
}
else {
cout << "The rebild image has been opened!" << endl;
}
//读原文件
fread(y_buffer, 1, width * height, ori_file);
fread(u_buffer, 1, (width * height) / 4, ori_file);
fread(v_buffer, 1, (width * height) / 4, ori_file);
//计算原文件的概率分布
FILE* orig;
orig = fopen("G:/数据压缩/实验/实验4DPCM/test/results/Frequency/Birds_origin.txt", "wb");
double frequency[256] = { 0 };
Frequency(y_buffer, frequency, height, width);
fprintf(orig, "%s\t%s\n", "symbol", "freq");
for (int i = 0; i < 256; i++)
fprintf(orig, "%d\t%f\n", i, frequency[i]);
//DPCM
DPCM(y_buffer, pred_buffer, re_buffer, width, height, qbit);
//计算PSNR
double psnr;
psnr = PSNR(y_buffer, re_buffer, width, height, qbit);
cout << "The PSNR of " << origin_pic << " is " << psnr << endl;
//写预测误差文件
fwrite(pred_buffer, 1, width * height, pred_file);
fwrite(u_buffer, 1, (width * height) / 4, pred_file);
fwrite(v_buffer, 1, (width * height) / 4, pred_file);
//计算预测误差文件的概率分布
FILE* pred;
pred = fopen("G:/数据压缩/实验/实验4DPCM/test/results/Frequency/Birds_predict_8bit.txt", "wb");
double frequency_[256] = { 0 };
Frequency(pred_buffer, frequency_, height, width);
fprintf(pred, "The PSNR of '%s' is %f\n", origin_pic, psnr);
fprintf(pred, "%s\t%s\n", "symbol", "freq");
for (int i = 0; i < 256; i++)
{
fprintf(pred, "%d\t%f\n", i, frequency_[i]);
}
//写重建图像文件
fwrite(re_buffer, 1, width * height, re_file);
fwrite(u_buffer, 1, (width * height) / 4, re_file);
fwrite(v_buffer, 1, (width * height) / 4, re_file);
//关闭文件
fclose(ori_file);
fclose(pred_file);
fclose(re_file);
delete[] y_buffer;
delete[] u_buffer;
delete[] v_buffer;
delete[] pred_buffer;
delete[] re_buffer;
return 0;
}
实验结果
8bit量化压缩质量
由以上结果可知,使用8bit量化可以较好地还原原始图像,且得到的PSNR值都在50以上,说明图像质量极好,接近原始图像,人眼几乎无法看出异常。
Huffman编码效率
8个样本结果相似,下面以其中四个为例,其中压缩比=压缩前文件大小/压缩后文件大小:
原始图像 | 原始图像大小(KB) | 直接熵编码大小(KB) | 直接熵编码压缩比 | 量化bit数 | 预测误差图像大小(KB) | DPCM+熵编码大小(KB) | DPCM+熵编码压缩比 |
---|---|---|---|---|---|---|---|
Birds.yuv | 576 | 520 | 1.108 | 8 | 576 | 346 | 1.665 |
Clown256B.yuv | 96 | 74 | 1.297 | 8 | 96 | 48 | 2.000 |
Lena256B.yuv | 96 | 73 | 1.315 | 8 | 96 | 46 | 2.087 |
Zone256B.yuv | 96 | 63 | 1.524 | 8 | 96 | 72 | 1.333 |
可以看到,对于大多数图像来说,经过DPCM预测编码之后,误差图像的压缩效率有了明显的提高。但其中的Zone256B这张图片,使用DPCM预测编码后压缩效率并没有提高,这可能是因为预测编码是利用相邻信源之间的相关性进行的,相邻像素之间的相关性越大,预测就会更加准确,而Zone256B这张图片在水平方向上相邻像素点的灰度值变化很大,所以DPCM效果不是特别显著,这点对于另一实验样本Noise256B也成立。
概率分布图
图像 | 概率分布图 |
---|---|
Birds.yuv | |
Birds_predict.yuv | |
Clown256B.yuv | |
Clown256B_predict.yuv | |
Lena256B.yuv | |
Lena256B_predict.yuv | |
Zone256B.yuv | |
Zone256B_predict.yuv |
以上四个样本可以说明当图像的预测误差图像的该率分布符合拉普拉斯分布时,用DPCM+熵编码的方式可以获得较好的压缩效率。