基本原理
预测编码本质上利用了信源相邻符号之间的相关性,即根据某一模型利用以往的样本值对新样本进行预测,然后将样本的实际值与其预测值相减得到一个误差值,最后对这一误差值进行编码。这种编码方式很适用于去除图像或视频这样的信源的空间冗余,即每一帧相邻的像素之间有着较强的相关性,相邻像素的像素值相差并不大。
DPCM
DPCM是差分预测编码调制的缩写,是比较典型的预测编码系统。在DPCM系统中,需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解码器,如编码器中虚线框中所示
在一个DPCM系统中,有两个因素需要设计:预测器和量化器。理想情况下,预测器和量化器应进行联合优化。实际中,采用一种次优的设计方法:分别进行线性预测器和量化器的优化设计。
PSNR
为了便于我们对实验结果进行定量分析,先简单介绍一下PSNR(Peak Signal to Noise Ratio,峰值信号噪声比)。
计算均方误差 MSE(Mean Square Error):
其中bits为原图像地量化比特数。
一般来说:
PSNR ≥ 40 dB时,图像质量非常好,接近于原图像;
30 dB ≤ PSNR < 40 dB 时,图像有人眼可察的失真,但质量尚可;
20 dB ≤ PSNR < 30 dB 时,图像质量较差;
PSNR < 20 dB 时,图像质量人眼无法接受。
Huffman编码
Huffman是一种无失真信源编码算法,编码后可以得到即时的最佳码。我们直接调用现有Huffman编码程序,将原图像及量化后的预测误差图像进行压缩编码。
代码实现
实现内容包括如下:
输入图片,根据给定的量化比特数进行量化和预测
void DpcmEncoding(unsigned char* yBuff, unsigned char* qPredErrBuff, unsigned char* reconBuff, int qBits) {
int prediction;
int predErr; // 预测误差
int invPredErr; // 量化预测误差的逆量化值
for (int i = 0; i < h; i++) {
prediction = 128; //预测每行的第一个像素集为128
predErr = yBuff[i * w] - prediction; // 预出错域为 [-128, 128] (8-bit)
int temp = (predErr + 128) / pow(2, 8 - qBits); // qBits-bit 量化
// (predErr + 128) with the domain of [0, 256]
qPredErrBuff[i * w] = PixelOverflow(temp, 0, pow(2, qBits) - 1);
invPredErr = qPredErrBuff[i * w] * pow(2, 8 - qBits) - 128; // 逆量化
reconBuff[i * w] = PixelOverflow(invPredErr + prediction, 0, 255); // 重建量化级别
for (int j = 1; j < w; j++) { // 从每行的第二个像素开始
prediction = reconBuff[i * w + j - 1]; // 设置为预测的上一个像素值
predErr = yBuff[i * w + j] - prediction; // predErr with the domain of [-255, 255] (9-bit)
int temp = (predErr + 255) / pow(2, 9 - qBits); // qBits-bit 量化
// (predErr + 255) with the domain of [0, 510]; [0, 2^(qBits) - 1] after division
qPredErrBuff[i * w + j] = PixelOverflow(temp, 0, (pow(2, qBits) - 1)); // (predErr + 255) with the domain of [0, 255]
invPredErr = qPredErrBuff[i * w + j] * pow(2, 9 - qBits) - 255;
reconBuff[i * w + j] = PixelOverflow(invPredErr + prediction, 0, 255); // 重建级别
}
}
}
同时输出预测误差图像和重建图像,计算:
原图像的概率分布和熵;预测误差图像的概率分布和熵;重建图像的PSNR
void PrintPMF_Entropy(unsigned char* buffer, int qBits, const char* pmfFileName, const char* entrFileName) {
int count[256] = { 0 }; // 计数器
double freq[256] = { 0 }; // 频率
double entropy = 0;
/* 计算每个灰度的频率 */
for (int i = 0; i < w * h; i++) {
int index = (int)buffer[i];
count[index]++;
}
/* 计算PMF和熵 */
for (int i = 0; i < 256; i++) {
freq[i] = (double)count[i] / (w * h);
if (freq[i] != 0) {
entropy += (-freq[i]) * log(freq[i]) / log(2);
}
}
/* 将统计数据输出到csv文件中 */
FILE* pmfFilePtr;
FILE* entrFilePtr;
if (fopen_s(&pmfFilePtr, pmfFileName, "wb") == 0) {
cout << "Successfully opened \"" << pmfFileName << "\".\n";
}
else {
cout << "WARNING!! Failed to open \"" << pmfFileName << "\".\n";
exit(-1);
}
if (fopen_s(&entrFilePtr, entrFileName, "ab") == 0) {
cout << "Successfully opened \"" << entrFileName << "\".\n";
}
else {
cout << "WARNING!! Failed to open \"" << entrFileName << "\".\n";
exit(-1);
}
fprintf(pmfFilePtr, "Symbol,Frequency\n");
for (int i = 0; i < 256; i++) {
fprintf(pmfFilePtr, "%-3d,%-8.2e\n", i, freq[i]); // 将数据输出到文件中(csv文件以“,”作为分隔符)
}
fprintf(entrFilePtr, "%d,%.4lf\n", qBits, entropy);
fclose(pmfFilePtr);
fclose(entrFilePtr);
}
void PrintPSNR(unsigned char* oriBuffer, unsigned char* recBuffer, int qBits, const char* psnrFileName) {
double mse;
double sum = 0;
double temp;
double psnr;
for (int i = 0; i < w * h; i++) {
temp = pow((oriBuffer[i] - recBuffer[i]), 2);
sum += temp;
}
mse = sum / (w * h);
psnr = 10 * log10(255 * 255 / mse);
/* 将统计数据输出到csv文件中 */
FILE* outFilePtr;
if (fopen_s(&outFilePtr, psnrFileName, "ab") == 0) {
cout << "Successfully opened \"" << psnrFileName << "\".\n";
}
else {
cout << "WARNING!! Failed to open \"" << psnrFileName << "\".\n";
exit(-1);
}
fprintf(outFilePtr, "%d,%lf\n", qBits, psnr);
fclose(outFilePtr);
}
将预测误差图像写入文件并将该文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。
或者直接将原始图像文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。
最后比较两种系统(1.DPCM+熵编码和2.仅进行熵编码)之间的编码效率(压缩比和图像质量)。压缩质量以PSNR进行计算。
量化预测误差QPE(quantisied prediction error);概率分布PMF
int main(int argc, char* argv[]) {
int qBits = 8;
const char* orFileName = "Lena.yuv";
const char* qpeFileName = "Lena_QPE (8 bit).yuv"; //量化预测误差文件的名称
const char* recFileName = "Lena_reconstruction (8 bit).yuv"; // 重建级文件的名称
FILE* oriFilePtr;
FILE* qpeFilePtr;
FILE* recFilePtr;
/* 打开文件 */
if (fopen_s(&oriFilePtr, orFileName, "rb") == 0) {
cout << "Successfully opened \"" << orFileName << "\".\n";
}
else {
cout << "WARNING!! Failed to open \"" << orFileName << "\".\n";
exit(-1);
}
if (fopen_s(&qpeFilePtr, qpeFileName, "wb") == 0) {
cout << "Successfully opened \"" << qpeFileName << "\".\n";
}
else {
cout << "WARNING!! Failed to open \"" << qpeFileName << "\".\n";
exit(-1);
}
if (fopen_s(&recFilePtr, recFileName, "wb") == 0) {
cout << "Successfully opened \"" << recFileName << "\".\n";
}
else {
cout << "WARNING!! Failed to open \"" << recFileName << "\".\n";
exit(-1);
}
/* 缓冲区分配 */
unsigned char* oriYBuff = new unsigned char[w * h];
unsigned char* qpeYBuff = new unsigned char[w * h];
unsigned char* recYbuff = new unsigned char[w * h];
unsigned char* uBuff = new unsigned char[w * h / 4];
unsigned char* vBuff = new unsigned char[w * h / 4];
/* 读取灰度数据 */
fread(oriYBuff, sizeof(unsigned char), w * h, oriFilePtr);
/* DPCM */
DpcmEncoding(oriYBuff, qpeYBuff, recYbuff, qBits);
memset(uBuff, 128, w * h / 4);
memset(vBuff, 128, w * h / 4);
fwrite(qpeYBuff, sizeof(unsigned char), w * h, qpeFilePtr);
fwrite(uBuff, sizeof(unsigned char), w * h / 4, qpeFilePtr); // Greyscale image
fwrite(vBuff, sizeof(unsigned char), w * h / 4, qpeFilePtr);
fwrite(recYbuff, sizeof(unsigned char), w * h, recFilePtr);
fwrite(uBuff, sizeof(unsigned char), w * h / 4, recFilePtr); // Greyscale image
fwrite(vBuff, sizeof(unsigned char), w * h / 4, recFilePtr);
/* 在csv中写入数据 */
PrintPMF_Entropy(oriYBuff, qBits, "Lena-PMF (8 bit).csv", "Lena-entropy.csv");
PrintPMF_Entropy(qpeYBuff, qBits, "Lena_QPE-PMF (8 bit).csv", "Lena_QPE-entropy.csv");
PrintPSNR(oriYBuff, recYbuff, qBits, "Lena_reconstruction-PSNR.csv");
fclose(oriFilePtr);
fclose(qpeFilePtr);
fclose(recFilePtr);
delete[]oriYBuff;
delete[]qpeYBuff;
delete[]recYbuff;
delete[]uBuff;
delete[]vBuff;
}
实验结果
DPCM部分
量化比特数bit | PSNR(客观评价) | 预测误差图像 | 重建图像(主观评价) |
8 | 51.147337 | ||
4 | 23.13889 | ||
2 | 11.93524 | ||
1 | 9.95645 |
- 8bit量化时,重建效果较好;4bit量化时出现肉眼可察的失真;2bit量化以下时图像质量无法接受。
- 在这里我们也可以发现一点,例如对于量化误差进行1 bit量化,并不代表重建图像只有0和255两个亮度电平,这也是相比于直接对原图像进行量化的优势之一。
- 对于PSNR的计算符合上文的分析。
即一般来说:
PSNR ≥ 40 dB时,图像质量非常好,接近于原图像;
30 dB ≤ PSNR < 40 dB 时,图像有人眼可察的失真,但质量尚可;
20 dB ≤ PSNR < 30 dB 时,图像质量较差;
PSNR < 20 dB 时,图像质量人眼无法接受。
量化比特数bit | 原文件概率分布 | 预测误差概率分布 |
8 | ||
4 | ||
2 | ||
1 |
量化比特数bit | 原文件熵 | DPCM后熵 |
8 | 7.0534 | 4.6098 |
4 | 1.2735 | |
2 | 0.3984 | |
1 | 0.1545 |
可以看到,原图像中存在较大的相关性,进行了DPCM编码后,相关性得到了较好的去除,从而实现了压缩。
哈夫曼编码部分
将原始图像文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。最
后比较两种系统(1.DPCM+熵编码和2.仅进行熵编码)之间的编码效率。
DPCM+8bit量化+熵编码-----压缩比: 96/46=2.1
仅进行熵编码---------压缩比:96/69=1.4
调用了已给的哈夫曼程序后,可以看到经过DPCM编码后再进行哈夫曼编码得到的文件体积明显更小,即系统的压缩效果更好。