实验内容:将预测误差图像写入文件并将该文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比。最后比较两种系统(1.DPCM+熵编码和2.仅进行熵编码)之间的编码效率(压缩比和图像质量)。压缩质量以PSNR进行计算。
文章目录
(一)DPCM编解码原理
其中,
d
n
d_n
dn是预测误差,
Q
Q
Q是量化器,
P
P
P中存储的是重建值。
关键代码
DPCM()函数
void DPCM(unsigned char* YOrigi, unsigned char* YError, unsigned char* YRestr, int height, int width, int bits)
{
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++)
DPCM_Pixel(YOrigi, YError, YRestr, i * width, j, bits);//for each pixel,use the function DPCM_Pixel() to calculate the YRestr[i] and YError[i]
}
DPCM_Pixel()函数
void DPCM_Pixel(unsigned char* YOrigi, unsigned char* YError, unsigned char* YRestr, int pre, int j, int bits)
{
/*for the first Pixel,suppose YError[0]=128-YOrigi[0],
then quantify it and store*/
int E_temp, R_temp;
switch (j)
{
case 0:
E_temp = 128 - YOrigi[pre + j];
E_temp = ErrorQuantity(E_temp, bits);//quantity
YError[pre + j] = E_temp;
E_temp = invErrorQuantity(E_temp, bits);//inverse quantization
R_temp = 128 - E_temp;
R_temp = OverflowX(R_temp, 255, 0);//prevent overflow
YRestr[pre + j] = (unsigned char)R_temp;
break;
default:
E_temp = YOrigi[pre + j] - YRestr[pre + j - 1];
E_temp = ErrorQuantity(E_temp, bits);//quantity
YError[pre + j] = E_temp;
E_temp = invErrorQuantity(E_temp, bits);//inverse quantization
R_temp = E_temp + YRestr[pre + j - 1];
R_temp = OverflowX(R_temp, 255, 0);//prevent overflow
YRestr[pre + j] = (unsigned char)R_temp;
break;
}
}
(二)PSNR原理
P
S
N
R
PSNR
PSNR(Peak signal-to-noise ratio,常缩写为
P
S
N
R
PSNR
PSNR)是一个表示信号最大可能功率的比值的工程术语。由于许多信号都有非常宽的动态范围,峰值信噪比常用对数单位(db)来表示。
计算
P
S
N
R
PSNR
PSNR要先知道
M
S
E
MSE
MSE的计算。
M
S
E
MSE
MSE是均方误差。若有两个
m
×
n
m\times n
m×n单色图像
I
I
I和
K
K
K,他们的均方误差定义为:
M
S
E
=
1
m
n
Σ
i
=
0
m
−
1
Σ
j
=
0
n
−
1
[
I
(
i
,
j
)
−
K
(
i
.
j
)
]
2
MSE=\frac{1}{mn} \Sigma^{m-1}_{i=0} \Sigma^{n-1}_{j=0}[I(i,j)-K(i.j)]^2
MSE=mn1Σi=0m−1Σj=0n−1[I(i,j)−K(i.j)]2
P
S
N
R
PSNR
PSNR定义为:
P
S
N
R
=
10
×
l
o
g
10
(
M
A
X
I
2
M
S
E
)
PSNR=10\times log_{10}(\frac{MAX_I^2}{MSE})
PSNR=10×log10(MSEMAXI2)
其中,
M
A
X
I
MAX_I
MAXI表示图像点颜色的最大数值。
一般来说
PSNR>40dB:图像质量非常好(非常接近原始图像)
30<PSNR<40dB:图像质量是好的(失真可以察觉但可以接受)
20<PSNR<30dB:图像质量差
PSNR<20:图像不可接受
关键代码
PSNR()函数
double PSNR(unsigned char* YOrigi, unsigned char* YRestr, int height, int width)
{
int fmax = pow(2, 8) - 1;
int a = fmax * fmax;
double mean_se = MSE(YOrigi, YRestr, height, width);
double peak_SNR = 10 * log10((double)a / mean_se);
return peak_SNR;
}
MSE()函数
double MSE(unsigned char* YOrigi, unsigned char* YRestr, int height, int width)
{
int size = height * width;
long long int sum = 0;
double mean;
for (int i = 0; i < size; i++)
{
long long int temp = (long long int)(YOrigi[i] - YRestr[i]) * (long long int)(YOrigi[i] - YRestr[i]);
sum += temp;
}
mean = (double)sum / (double)size;
return mean;
}
(三)Huffman编码原理
H u f f m a n Huffman Huffman编码在很多地方都有提到,在此不再赘述。对此进行一个总结: H u f f m a n Huffman Huffman编码是一种无失真的编码方法,不引入任何失真。并且所编出的码字为即时码。 H u f f m a n Huffman Huffman编码非常的实用。
对Huffman编码的结果分析
老师给了一个.exe文件,用来存放 H u f f m a n Huffman Huffman编码的结果。
其中第一列是信源符号
a
a
a,第二列是信源符号出现的次数
b
b
b,第三列是编码的码字
c
c
c。
对原文件和DPCM编码后输出的error文件都进行此操作,即可判断DPCM+熵编码和只进行熵编码两种方案的区别,压缩比直接由.huff文件大小比上原.yuv文件大小计算得。
(四)完整实验过程
4.1 DPCM编码 && PSNR
完整代码
解决方案资源管理器如下:
main.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
#include "DPCM_code.h"
using namespace std;
int main(int argc, char** argv)
{
//moon图片464 538,使用的是4:4:4的yuv文件
int width = atoi(argv[1]);
int height = atoi(argv[2]);
int bits = atoi(argv[3]);
int Ysize = height * width;
int Esize = height * width * 2;
ifstream OrigiFile(argv[4], ios::binary);
ofstream ErrorFile(argv[5], ios::binary);
ofstream RestrFile(argv[6], ios::binary);
if (!OrigiFile) { cout << "error to open OrigiFile!" << endl; }
if (!ErrorFile) { cout << "error to open ErrorFile!" << endl; }
if (!RestrFile) { cout << "error to open RestrFile!" << endl; }
unsigned char* YOrigi = new unsigned char[Ysize];
unsigned char* YError = new unsigned char[Ysize];
unsigned char* YRestr = new unsigned char[Ysize];
unsigned char* E_File = new unsigned char[Esize];
//读入Y分量和其余分量
OrigiFile.read((char*)YOrigi, Ysize);
OrigiFile.read((char*)E_File, Esize);
//进行预测和量化
DPCM(YOrigi, YError, YRestr, height, width, bits);
//对重建图像进行重置,用以判断解码端工作效果
for (int i = 0; i < Ysize; i++)
YRestr[i] = 0;
//从YError解出量化后的yuv文件
RDPCM(YError, YRestr, height, width, bits);
double peak_SNR = PSNR(YOrigi, YRestr, height, width);
cout << peak_SNR << endl;
ErrorFile.write((char*)YError, Ysize);
ErrorFile.write((char*)E_File, Esize);
RestrFile.write((char*)YRestr, Ysize);
RestrFile.write((char*)E_File, Esize);
OrigiFile.close();
ErrorFile.close();
RestrFile.close();
delete[]YOrigi;
delete[]YError;
delete[]YRestr;
delete[]E_File;
return 0;
}
DPCM_Code.h
#pragma once
void DPCM(unsigned char* YOrigi, unsigned char* YError, unsigned char* YRestr, int height, int width, int bits);
void DPCM_Pixel(unsigned char* YOrigi, unsigned char* YError, unsigned char* YRestr, int pre, int j, int bits);
void RDPCM(unsigned char* YError, unsigned char* YRestr, int height, int width, int bits);
void RDPCM_Pixel(unsigned char* YError, unsigned char* YRestr, int pre, int j, int bits);
double PSNR(unsigned char* YOrigi, unsigned char* YRestr, int height, int width);
DPCM_Code.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
#include <cmath>
#include "DPCM_code.h"
using namespace std;
//double sign(double x)
//{
// if (x < 0)
// return -1;
// else if (x > 0)
// return 1;
// else
// return 0;
//}
int OverflowX(int x, int High, int Low)
{
if (x > High)
return High;
else if (x < Low)
return Low;
else
return x;
}
double MSE(unsigned char* YOrigi, unsigned char* YRestr, int height, int width)
{
int size = height * width;
long long int sum = 0;
double mean;
for (int i = 0; i < size; i++)
{
long long int temp = (long long int)(YOrigi[i] - YRestr[i]) * (long long int)(YOrigi[i] - YRestr[i]);
sum += temp;
}
mean = (double)sum / (double)size;
return mean;
}
int ErrorQuantity(int X, int bits)
{
X = X + 255;//化为无符号数
X = X / 2;//将误差范围归一到[0,255]
X = floor(X / pow(2, 8 - bits));
X = X * pow(2, 8 - bits);
X = OverflowX(X, 255, 0);
return X;
}
int invErrorQuantity(int X, int bits)
{
X = X * 2;//将误差放回原来的范围
X = X - 255;//将无符号数转为有符号数
return X;
}
void DPCM_Pixel(unsigned char* YOrigi, unsigned char* YError, unsigned char* YRestr, int pre, int j, int bits)
{
/*for the first Pixel,suppose YError[0]=128-YOrigi[0],
then quantify it and store*/
int E_temp, R_temp;
switch (j)
{
case 0:
E_temp = 128 - YOrigi[pre + j];
E_temp = ErrorQuantity(E_temp, bits);//quantity
YError[pre + j] = E_temp;
E_temp = invErrorQuantity(E_temp, bits);//inverse quantization
R_temp = 128 - E_temp;
R_temp = OverflowX(R_temp, 255, 0);//prevent overflow
YRestr[pre + j] = (unsigned char)R_temp;
break;
default:
E_temp = YOrigi[pre + j] - YRestr[pre + j - 1];
E_temp = ErrorQuantity(E_temp, bits);//quantity
YError[pre + j] = E_temp;
E_temp = invErrorQuantity(E_temp, bits);//inverse quantization
R_temp = E_temp + YRestr[pre + j - 1];
R_temp = OverflowX(R_temp, 255, 0);//prevent overflow
YRestr[pre + j] = (unsigned char)R_temp;
break;
}
}
void RDPCM_Pixel(unsigned char* YError, unsigned char* YRestr, int pre, int j, int bits)
{
int E_temp, R_temp;
switch (j)
{
case 0:
E_temp = YError[pre + j];
E_temp = invErrorQuantity(E_temp, bits);
R_temp = 128 - E_temp;
R_temp = OverflowX(R_temp, 255, 0);
YRestr[pre + j] = (unsigned char)R_temp;
break;
default:
E_temp = YError[pre + j];
E_temp = invErrorQuantity(E_temp, bits);
R_temp = E_temp + YRestr[pre + j - 1];
R_temp = OverflowX(R_temp, 255, 0);
YRestr[pre + j] = (unsigned char)R_temp;
break;
}
}
void DPCM(unsigned char* YOrigi, unsigned char* YError, unsigned char* YRestr, int height, int width, int bits)
{
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++)
DPCM_Pixel(YOrigi, YError, YRestr, i * width, j, bits);//for each pixel,use the function DPCM_Pixel() to calculate the YRestr[i] and YError[i]
}
void RDPCM(unsigned char* YError, unsigned char* YRestr, int height, int width, int bits)
{
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++)
RDPCM_Pixel(YError, YRestr, i * width, j, bits);
}
double PSNR(unsigned char* YOrigi, unsigned char* YRestr, int height, int width)
{
int fmax = pow(2, 8) - 1;
int a = fmax * fmax;
double mean_se = MSE(YOrigi, YRestr, height, width);
double peak_SNR = 10 * log10((double)a / mean_se);
return peak_SNR;
}
PSNR输出结果表
P
S
N
R
PSNR
PSNR的结果总结成表格,结果如下:
可以发现,量化bit数越大,重建的图像质量越好,与常识相符。
用
Y
U
V
YUV
YUV文件查看器打开文件进行验证。(由于图片数较多,以下就仅使用8bit、4bit、1bit量化的文件)
比特数 | 结果 |
---|---|
8bit量化 | ![]() |
4bit量化 | ![]() |
1bit量化 | ![]() |
可以发现,8bit量化的时候基本可以还原原图像,而4bit量化就出现了轻微的块效应,1bit量化图像就完全不可用了,符合预期。
信源符号概率分布图
由于要画概率分布图,在DPCM的main()函数代码中添加如下代码段,用来输出
void Pro(unsigned char* YOrigi, int Ysize)
{
for (int i = 0; i < Ysize; i++)
Probability[YOrigi[i]] = Probability[YOrigi[i]] + 1;
for (int i = 0; i < 256; i++)
Probability[i] = Probability[i] / 256;
}
完整的main.cpp代码修改如下:
#include <iostream>
#include <cstdio>
#include <fstream>
#include "DPCM_code.h"
using namespace std;
double Probability[256] = { 0 };
void Pro(unsigned char* YOrigi, int Ysize)
{
for (int i = 0; i < Ysize; i++)
Probability[YOrigi[i]] = Probability[YOrigi[i]] + 1;
for (int i = 0; i < 256; i++)
Probability[i] = Probability[i] / 256;
}
int main(int argc, char** argv)
{
//moon图片464 538,使用的是4:4:4的yuv文件
int width = atoi(argv[1]);
int height = atoi(argv[2]);
int bits = atoi(argv[3]);
int Ysize = height * width;
int Esize = height * width * 2;
ifstream OrigiFile(argv[4], ios::binary);
ofstream ErrorFile(argv[5], ios::binary);
ofstream RestrFile(argv[6], ios::binary);
ofstream ProbaFile(argv[7], ios::binary);
if (!OrigiFile) { cout << "error to open OrigiFile!" << endl; }
if (!ErrorFile) { cout << "error to open ErrorFile!" << endl; }
if (!RestrFile) { cout << "error to open RestrFile!" << endl; }
if (!ProbaFile) { cout << "error to open ProbaFile!" << endl; }
unsigned char* YOrigi = new unsigned char[Ysize];
unsigned char* YError = new unsigned char[Ysize];
unsigned char* YRestr = new unsigned char[Ysize];
unsigned char* E_File = new unsigned char[Esize];
//读入Y分量和其余分量
OrigiFile.read((char*)YOrigi, Ysize);
OrigiFile.read((char*)E_File, Esize);
//进行预测和量化
DPCM(YOrigi, YError, YRestr, height, width, bits);
//对重建图像进行重置,用以判断解码端工作效果
for (int i = 0; i < Ysize; i++)
YRestr[i] = 0;
//从YError解出量化后的yuv文件
RDPCM(YError, YRestr, height, width, bits);
double peak_SNR = PSNR(YOrigi, YRestr, height, width);
cout << peak_SNR << endl;
//计算概率分布图
if (argv[8][0] == 'O')
Pro(YOrigi, Ysize);
else if (argv[8][0] == 'E')
Pro(YError, Ysize);
//输出概率分布
for (int i = 0; i < 256; i++)
{
ProbaFile << i << "\t" << Probability[i] << endl;
//cout << i << "\t" << Probability[i] << endl;
}
ErrorFile.write((char*)YError, Ysize);
ErrorFile.write((char*)E_File, Esize);
RestrFile.write((char*)YRestr, Ysize);
RestrFile.write((char*)E_File, Esize);
OrigiFile.close();
ErrorFile.close();
RestrFile.close();
delete[]YOrigi;
delete[]YError;
delete[]YRestr;
delete[]E_File;
return 0;
}
为了简便起见,以4bit量化和8bit量化的error概率分布图和原图的概率分布图为例,命令行输入如下:
做出的图表如下(原文件,1bit量化,2bit量化,4bit量化,8bit量化):
4.2 Huffman编码
压缩比输出结果表
将8bit至1bit量化输出的E.yuv文件和原图HisSeed.yuv输入
H
u
f
f
m
a
n
Huffman
Huffman编码器。命令行输入如下:
输出是.huff文件和.txt文件。比较生成的.huff文件的文件大小。文件大小和压缩比计算如下表所示:
可以发现,进行了量化后的文件的压缩比随着量化比特数的增加而增大,即量化比特数越大,压缩效果越好。
(五)结果汇总
以2bit量化、8bit量化为例。
bit数 | error图查看 | 重建图查看 | PSNR值 | 压缩比 | 信源符号概率分布图 |
---|---|---|---|---|---|
2 | ![]() | ![]() | 12.0588dB | 38.31% | ![]() |
8 | ![]() | ![]() | 51.1307dB | 83.06% | ![]() |