一 基本原理
DPCM
DPCM是差分预测编码调制的缩写,是比较典型的预测编码系统。在DPCM系统中,需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解码器,如编码器中虚线框中所示。在一个DPCM系统中,有两个因素需要设计:预测器和量化器。理想情况下,预测器和量化器应进行联合优化。实际中,采用一种次优的设计方法:分别进行线性预测器和量化器的优化设计。
本文采用横向预测。
MSE 均方误差
PSNR 峰值信号噪声比
一般来说:
- PSNR ≥ 40dB时,图像质量非常好,接近于原图像;
- 30dB ≤ PSNR < 40dB时,图像有可察觉的失真,但质量仍可接受;
- 20dB ≤ PSNR < 30dB时,图像质量较差;
- PSNR < 20dB时,图像质量已经无法接受。
二 实验代码
DPCM实现:
核心buffer:原始图像origiBuf、重建图像reconBuf、残差图像quantiBuf
reconBuf[0] = originBuf[0];
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
if (!j && !i)continue;
pn = originBuf[j * width + i] - reconBuf[j * width + i - 1];//水平方向两个像素之差
qn = pn / 2;//简单量化
toPic = qn + 128;//可视化输出,将电平从-128~128提高至0~255便于显示
toPic = toPic > 255 ? 255 : (toPic < 0 ? 0 : toPic);//防溢出
quantiBuf[j * width + i] = toPic;//预测图像
if (!i) //每行第一个
reconBuf[j * width] = originBuf[j * width];//每行第一个像素的重建值即为本身
else
reconBuf[j * width + i] = qn * 2 + reconBuf[j * width + i - 1];//反量化加上上个像素的重建值即为当前像素的重建值
}
}
计算MSE,PSNR
double MSE = 0, PSNR = 0;
for (int i = 0; i < height * width; i++)
MSE += pow((originBuf[i] - reconBuf[i]), 2);
MSE /= (height * width);
PSNR = 10 * log10((255 * 255) / MSE);//8bit量化
cout << "MSE: " << MSE << endl;
cout << "PSNR: " << PSNR << endl;
计算原图像、重建图像、残差图像的概率分布及可视化输出(via excel)
//计算概率分布
double fre_ori[256] = { 0 }, fre_re[256] = { 0 }, fre_q[256] = { 0 };
frequency(originBuf, height * width, fre_ori);
frequency(reconBuf, height * width, fre_re);
frequency(quantiBuf, height * width, fre_q);
//输出概率分布到csv
FILE* freout1, * freout2, * freout3;
freout1 = fopen(fre_ori_Path, "wb");
freout2 = fopen(fre_re_Path, "wb");
freout3 = fopen(fre_q_Path, "wb");
fprintf(freout1, "Symbol,Frequency\n");
fprintf(freout2, "Symbol,Frequency\n");
fprintf(freout3, "Symbol,Frequency\n");
void frequency(unsigned char* pic, int length, double* f) {
for (int i = 0; i <= 255; i++) {
for (int j = 0; j < length; j++) {
if (pic[j] == i)f[i]++;
}
f[i] /= length;
}
}
三 结果及分析
以我们熟悉的Lena为例
计算得到MSE及PSNR,显而易见,由于PSNR>40dB,重建图像的质量几乎没有损失,接近于原图像。
计算概率分布可得:
观察图像可知,对于原图像,概率在[0,255]内均匀分布;而残差图像集中在[120,140]区间,这对于Huffman编码来说简直是天然优势,概率较低的值用变为长码,概率较高的值编码为短码。
因此进行压缩效率的对比:
可以看到,对原图像进行预测编码后再进行huffman编码,对压缩效率的提升是巨大的。
四 完整实验代码
#include <iostream>
#include <malloc.h>
#include <math.h>
using namespace std;
void frequency(unsigned char* pic, int length, double* f);
int main(int argc,char *argv[])
{
char* filePath, * outPath, * qPath;
char* fre_ori_Path, * fre_re_Path, * fre_q_Path;
int height = atoi(argv[1]);
int width = atoi(argv[2]);
filePath = argv[3];
outPath = argv[4];
qPath = argv[5];
fre_ori_Path = argv[6];
fre_re_Path = argv[7];
fre_q_Path = argv[8];
unsigned char* originBuf, * reconBuf, * quantiBuf;
unsigned char* u, * v;
originBuf = (unsigned char*)malloc(height * width);//原图
reconBuf = (unsigned char*)malloc(height * width);//重建
quantiBuf = (unsigned char*)malloc(height * width);//量化
u = (unsigned char*)malloc(height * width / 4);
v = (unsigned char*)malloc(height * width / 4);
FILE* fp;
fp = fopen(filePath, "rb");
fread(originBuf, sizeof(unsigned char), height * width, fp);
fread(u, sizeof(unsigned char), height * width / 4, fp);
fread(v, sizeof(unsigned char), height * width / 4, fp);
int pn,qn,toPic;
reconBuf[0] = originBuf[0];
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
if (!j && !i)continue;
pn = originBuf[j * width + i] - reconBuf[j * width + i - 1];//水平方向两个像素之差
// cout << int(originBuf[j * width + i])<<" "<<int(reconBuf[j * width + i - 1]);
qn = pn / 2;//简单量化
toPic = qn + 128;//可视化输出,将电平从-128~128提高至0~255便于显示
toPic = toPic > 255 ? 255 : (toPic < 0 ? 0 : toPic);//防溢出
quantiBuf[j * width + i] = toPic;//预测图像
if (!i) //每行第一个
reconBuf[j * width] = originBuf[j * width];//每行第一个像素的重建值即为本身
else
reconBuf[j * width + i] = qn * 2 + reconBuf[j * width + i - 1];//反量化加上上个像素的重建值即为当前像素的重建值
}
}
//计算MSE PSNR
double MSE = 0, PSNR = 0;
for (int i = 0; i < height * width; i++)
MSE += pow((originBuf[i] - reconBuf[i]), 2);
MSE /= (height * width);
PSNR = 10 * log10((255 * 255) / MSE);//8bit量化
cout << "MSE: " << MSE << endl;
cout << "PSNR: " << PSNR << endl;
//计算概率分布
double fre_ori[256] = { 0 }, fre_re[256] = { 0 }, fre_q[256] = { 0 };
frequency(originBuf, height * width, fre_ori);
frequency(reconBuf, height * width, fre_re);
frequency(quantiBuf, height * width, fre_q);
//输出概率分布到csv
FILE* freout1, * freout2, * freout3;
freout1 = fopen(fre_ori_Path, "wb");
freout2 = fopen(fre_re_Path, "wb");
freout3 = fopen(fre_q_Path, "wb");
fprintf(freout1, "Symbol,Frequency\n");
fprintf(freout2, "Symbol,Frequency\n");
fprintf(freout3, "Symbol,Frequency\n");
for (int i = 0; i < 256; i++) {
fprintf(freout1, "%-3d,%-8.2e\n", i, fre_ori[i]);
fprintf(freout2, "%-3d,%-8.2e\n", i, fre_re[i]);
fprintf(freout3, "%-3d,%-8.2e\n", i, fre_q[i]);
}
FILE* fout, * fq;
fout = fopen(outPath, "wb");
fq = fopen(qPath, "wb");
fwrite(reconBuf, sizeof(unsigned char), width * height, fout);
fwrite(u, sizeof(unsigned char), width * height / 4, fout);
fwrite(v, sizeof(unsigned char), width * height / 4, fout);
fwrite(quantiBuf, sizeof(unsigned char), width * height, fq);
fwrite(u, sizeof(unsigned char), width * height / 4, fq);
fwrite(v, sizeof(unsigned char), width * height / 4, fq);
return 0;
}
void frequency(unsigned char* pic, int length, double* f) {
for (int i = 0; i <= 255; i++) {
for (int j = 0; j < length; j++) {
if (pic[j] == i)f[i]++;
}
f[i] /= length;
}
}