一、实验课程名称:数据压缩原理与应用 A
二、实验项目名称:DPCM 压缩系统的实现和分析
三、实验目的
掌握DPCM编解码系统的基本原理。初步掌握实验用C/C++/Python等语言编程实现DPCM
编码器,并分析其压缩效率。
四、主要设备
安装 Windows 和 Visual Studio 软件的个人计算机
五、实验内容
1.DPCM编解码原理
DPCM是差分预测编码调制的缩写,是比较典型的预测编码系统。在DPCM系统中,
需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是
因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实
际内嵌了一个解码器,如编码器中虚线框中所示。
在一个DPCM系统中,有两个因素需要设计:预测器和量化器。理想情况下,预测器
和量化器应进行联合优化。实际中,采用一种次优的设计方法:分别进行线性预测器和
量化器的优化设计。 2.DPCM编码系统的设计
在本次实验中,我们采用固定预测器和均匀量化器。预测器采用左侧、上方预测均可。
2、DPCM编码系统的设计
量化器采用8比特均匀量化。本实验的目标是验证DPCM编码的编码效率。首先读取一个
256级的灰度图像,采用自己设定的预测方法计算预测误差,并对预测误差进行8比特均匀
量化(基本要求)。还可对预测误差进行1比特、2比特和4比特的量化设计(提高要求)。
在DPCM编码器实现的过程中可同时输出预测误差图像和重建图像。将预测误差图像
写入文件并将该文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。
将原始图像文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。最
后比较两种系统(1.DPCM+熵编码和2.仅进行熵编码)之间的编码效率(压缩比和图像质
量)。压缩质量以PSNR进行计算。
七、实验结果
量化误差并重建图像
以8bit Lena灰度图测试,进行DPCM + 8 bit量化:
原始图像 | 预测误差 | 重建图像 |
下面再对比一下4 bit、2bit和1 bit量化的效果:
可以看出从4 bit图像的颗粒感增强,但不影响观看,2 bit和1 bit出现明显失真,影响图像质量。并且1 bit两化并不是只有两个电平构成,而是相对于原图像而言。
定量分析
首先可以计算原图和预测误差的概率密度函数以及对应的信源熵
计算PSNR来定量计算比较质量
从客观评价(定量)角度对图像质量的分析与前面从主观评价角度对图像质量的分析基本吻合。
代码
main.cpp
#include <iostream>
#include <string>
#include <sstream>
#include "declarations.h"
using namespace std;
int main(int argc, char* argv[]) {
int qBits = 1;
const char* orFileName = "Lena256B.yuv";
const char* qpeFileName = "Lena_QPE (1 bit).yuv"; // Name of quantised prediction error file
const char* recFileName = "Lena_reconstruction (1 bit).yuv"; // Name of reconstruction level file
FILE* oriFilePtr;
FILE* qpeFilePtr;
FILE* recFilePtr;
/* Open the files */
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);
}
/* Space allocation */
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];
/* Read greyscale data */
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);
/* Write stats into csv files */
PrintPMF_Entropy(oriYBuff, qBits, "Lena-PMF (1 bit).csv", "Lena-entropy.csv");
PrintPMF_Entropy(qpeYBuff, qBits, "Lena_QPE-PMF (1 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;
}
Stats.cpp
#include <iostream>
#include "declarations.h"
using namespace std;
void PrintPMF_Entropy(unsigned char* buffer, int qBits, const char* pmfFileName, const char* entrFileName) {
int count[256] = { 0 }; // Counter
double freq[256] = { 0 }; // Frequency
double entropy = 0;
/* Compute the frequency of each greyscale */
for (int i = 0; i < w * h; i++) {
int index = (int)buffer[i];
count[index]++;
}
/* Compute the PMF & entropy */
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.0);
}
}
/* Output the stats into a csv file */
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.0);
sum += temp;
}
mse = sum / (w * h);
psnr = 10 * log10(255 * 255 / mse);
/* Output the stats into a csv file */
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);
}
DPCM.cpp
#include <iostream>
#include "declarations.h"
int w = 256;
int h = 256;
int PixelOverflow(int value, int thLower, int thUpper) {
if (value < thLower) {
return thLower;
} else if (value > thUpper) {
return thUpper;
} else {
return unsigned char(value);
}
}
void DpcmEncoding(unsigned char* yBuff, unsigned char* qPredErrBuff, unsigned char* reconBuff, int qBits) {
int prediction;
int predErr; // Prediction error
int invPredErr; // Inverse quantised value of quantised prediction error
for (int i = 0; i < h; i++) {
prediction = 128; // The prediction of the first pixel of each row set to be 128
predErr = yBuff[i * w] - prediction; // predErr with the domain of [-128, 128] (8-bit)
int temp = (predErr + 128) / pow(2.0, 8 - qBits); // qBits-bit quantisation
// (predErr + 128) with the domain of [0, 256]
qPredErrBuff[i * w] = PixelOverflow(temp, 0, pow(2.0, qBits) - 1);
invPredErr = qPredErrBuff[i * w] * pow(2.0, 8 - qBits) - 128; // Inverse quantisation
reconBuff[i * w] = PixelOverflow(invPredErr + prediction, 0, 255); // Reconstruction level
for (int j = 1; j < w; j++) { // Strat from the second pixel of each row
prediction = reconBuff[i * w + j - 1]; // The previous pixel value set as prediction
predErr = yBuff[i * w + j] - prediction; // predErr with the domain of [-255, 255] (9-bit)
int temp = (predErr + 255) / pow(2.0, 9 - qBits); // qBits-bit quantisation
// (predErr + 255) with the domain of [0, 510]; [0, 2^(qBits) - 1] after division
qPredErrBuff[i * w + j] = PixelOverflow(temp, 0, (pow(2.0, qBits) - 1)); // (predErr + 255) with the domain of [0, 255]
invPredErr = qPredErrBuff[i * w + j] * pow(2.0, 9 - qBits) - 255;
reconBuff[i * w + j] = PixelOverflow(invPredErr + prediction, 0, 255); // Reconstruction level
}
}
}
declarations.h
#pragma once
/* Global variables */
extern int w; // Width of image
extern int h; // Height of image
/* Functions */
void DpcmEncoding(unsigned char* yBuff, unsigned char* qPredErrBuff, unsigned char* reconBuff, int qBits);
void PrintPMF_Entropy(unsigned char* buffer, int qBits, const char* pmfFileName, const char* entrFileName);
void PrintPSNR(unsigned char* oriBuffer, unsigned char* recBuffer, int qBits, const char* psnrFileName);