DPCM编码,简称差值编码,是对模拟信号幅度抽样的差值进行量化编码的调制方式(抽样差值的含义请参见“增量调制”)。这种方式是用已经过去的抽样值来预测当前的抽样值,对它们的差值进行编码。差值编码可以提高编码频率,这种技术已应用于模拟信号的数字通信之中。
一.原理
1.DPCM编解码原理
DPCM是一种对模拟信号的编码模式,与PCM不同,
每个抽样值不是独立的编码,而是先根据前一个抽样值计算出一个预测值,再取当前抽样值和预测值之差作编码用.此差值称为预测误差.抽样值和预测值非常接近(因为相关性强),预测误差的可能取值范围比抽样值变化范围小.所以可用少几位编码比特来对预测误差编码,从而降低其比特率.这是利用减小冗余度的办法,降低了编码比特率。
原理图如下:
- 输入图像
- 与上一个图像的预测值做差
- 将差值进行编码
- 编码后的差值:一路直接输出,另一路通过解码器反解出差值,与上一帧的预测值相加,就得到了当前图像的预测值
- 当前图像的预测值为下一帧图像到来时做好准备
关键代码
DPCM函数:
void DPCM(void* yBuf, void* quanBuf, void* rebuildBuf, unsigned int width, unsigned int height,int QuanBits)
{
unsigned char* y_buffer = NULL;
unsigned char* quan_buffer = NULL;
unsigned char* rebuild_buffer = NULL;
int ForeError = 0;
int i = 0;
int j = 0;
y_buffer = (unsigned char*)yBuf;
quan_buffer = (unsigned char*)quanBuf;
rebuild_buffer = (unsigned char*)rebuildBuf;
for (i = 0; i < width; i++)
{
for (j = 0; j < height; j++)
{
if (j == 0)
{
ForeError = *y_buffer - 128;
ForeError = (ForeError + 128) / (pow(2, 8 - QuanBits));
*quan_buffer = Overflow(ForeError, 0, pow(2, QuanBits) - 1);
ForeError = *quan_buffer * pow(2, 8 - QuanBits) - 128;
*rebuild_buffer = ForeError+128;
*rebuild_buffer = Overflow(*rebuild_buffer, 0, 255);
y_buffer++;
quan_buffer++;
rebuild_buffer++;
}
else
{
ForeError = *y_buffer - *(rebuild_buffer - 1);
ForeError = (ForeError + 255) / (pow(2, 9 - QuanBits));
*quan_buffer = Overflow(ForeError, 0, pow(2, QuanBits) - 1);
ForeError = *quan_buffer * pow(2, 9 - QuanBits) - 255;
*rebuild_buffer = (unsigned char)ForeError + *(rebuild_buffer - 1);
*rebuild_buffer = Overflow(*rebuild_buffer, 0, 255);
y_buffer++;
quan_buffer++;
rebuild_buffer++;
}
}
}
}
2.PSNR原理
PSNR是“Peak Signal to Noise Ratio”的缩写,即峰值信噪比。通常在经过影像压缩之后,输出的影像都会在某种程度与原始影像不同。为了衡量经过处理后的影像品质,我们通常会参考PSNR值来衡量某个处理程序能否令人满意。公式如下:
P
S
N
R
=
10
l
o
g
10
(
2
b
i
t
s
−
1
)
2
M
S
E
PSNR=10 log_{10}\frac{(2^{bits}-1)^2}{MSE}
PSNR=10log10MSE(2bits−1)2
bits为原图像量化比特数,即8
MSE(Mean Square Error)为均方误差:
M
S
E
=
∑
i
=
0
M
∑
j
=
0
N
[
f
(
i
,
j
)
−
f
′
(
i
,
j
)
]
M
N
MSE=\frac{∑_{i=0}^{M} ∑ _{j=0}^{N}[f(i,j)−f ^{'}(i,j)] }{MN}
MSE=MN∑i=0M∑j=0N[f(i,j)−f′(i,j)]
PSNR≥40dB时,图像质量非常好,接近于原图像;
30dB≤PSNR<40dB时,图像有可察觉的失真,但质量仍可接受;
20dB≤PSNR<30dB时,图像质量较差;
PSNR<20dB时,图像质量已经无法接受。
代码实现:
void PSNR(unsigned char* oriBuf, unsigned char* recBuf, int qBits, const char* PsnrFileName)
{
FILE* PsnrFile;
if (fopen_s(&PsnrFile, PsnrFileName, "ab") == 0)
{
cout << "File opened! " << PsnrFileName << "." << endl;
}
else
{
cout << "File not opened! " << PsnrFileName << "." << endl;
exit(0);
}
double MSE;
double Sum = 0;
double temp;
double PSNR;
for (int i = 0; i < Width * Height; i++)
{
temp = pow((oriBuf[i] - recBuf[i]), 2);
Sum += temp;
}
MSE = Sum / (Width * Height);
PSNR = 10 * log10((pow(2,8)-1) * (pow(2, 8) - 1) / MSE);
fprintf(PsnrFile, "%d,%lf\n", qBits, PSNR);
}
3.Huffman编码
Huffman编码是一种无失真的编码方法,不引入任何失真。并且所编出的码字为即时码。在这里我们直接调用一个现有的Huffman编码程序,将原图像及量化后的预测误差图像进行压缩编码。
二.实验结果
在本次实验中,我们采用固定预测器和均匀量化器。预测器采用左侧、上方预测均可。
对于每一行的第一个像素的预测值取值的问题,对于该问题有两种处理方法:
- 每行第一个像素的预测值均取128;
- 每一行第一个像素的预测误差直接取0,并将该像素值赋给重建图像对应像素。
我采用了左侧预测,并且默认最左侧像素前的真实值为均为128。并且实现了如下功能:
- 进行不同量化bit数的差分预测编码
- 将编码结果进行输出并进行霍夫曼编码
- 分别计算原图像和量化后的图像进行概率分布
- 分别计算原图像经过熵编码和经过DPCM+熵编码的图像的压缩比
- 比较二者压缩效率
- 计算重建图像的PSNR
结果分析:
我们先来看一下经过DPCM 8Bit量化后的图像
原图 | 预测误差图像 | 重建图 |
---|---|---|
可以发现,重建图的质量很高
再来看看7bit到1bit的效果:
(图片过多,按跨度放三张图以供分析)
7bit-Rebuild | 5bit-Rebuild | 2bit-Rebuild |
---|---|---|
可以看出,当量化比特数为5时,图片已经略微失真,当其值为2时,出现较大失真,图像质量难以接受。
计算得到:
可以看到,原图像中存在较大的相关性,进行了DPCM编码后,相关性得到了较好的去除,从而实现了压缩。
从8bits - 1bits,PSNR值逐渐减小,图像质量也越来越差,这也符合之前的实验观察结果
Huffman编码:
将8bit至1bit量化输出的predict.yuv文件和原图seed.yuv输入Huffman编码器。命令行输入如下:
计算可得压缩比:
量化比特数越大,压缩效果越好。
附:完整代码
header.h:
#pragma once
void DPCM(void* yBuf, void* quanBuf, void* rebuildBuf, unsigned int width, unsigned int height, int QuanBits);
void PMF_Entropy(unsigned char* buffer, int qBits, const char* pmfFileName, const char* entrFileName);
void PSNR(unsigned char* oriBuffer, unsigned char* recBuffer, int qBits, const char* psnrFileName);
DPCM.cpp:
#include <cstdio>
#include <fstream>
#include <windows.h>
#include <math.h>
#include <cmath>
#include "标头.h"
int Overflow(int X, int Low, int High)
{
if (X < Low)
{
return Low;
}
else if (X > High)
{
return High;
}
else
{
return unsigned char(X);
}
}
void DPCM(void* yBuf, void* quanBuf, void* rebuildBuf, unsigned int width, unsigned int height,int QuanBits)
{
unsigned char* y_buffer = NULL;
unsigned char* quan_buffer = NULL;
unsigned char* rebuild_buffer = NULL;
int ForeError = 0;
int i = 0;
int j = 0;
y_buffer = (unsigned char*)yBuf;
quan_buffer = (unsigned char*)quanBuf;
rebuild_buffer = (unsigned char*)rebuildBuf;
for (i = 0; i < width; i++)
{
for (j = 0; j < height; j++)
{
if (j == 0)
{
ForeError = *y_buffer - 128;
ForeError = (ForeError + 128) / (pow(2, 8 - QuanBits));
*quan_buffer = Overflow(ForeError, 0, pow(2, QuanBits) - 1);
ForeError = *quan_buffer * pow(2, 8 - QuanBits) - 128;
*rebuild_buffer = ForeError+128;
*rebuild_buffer = Overflow(*rebuild_buffer, 0, 255);
y_buffer++;
quan_buffer++;
rebuild_buffer++;
}
else
{
ForeError = *y_buffer - *(rebuild_buffer - 1);
ForeError = (ForeError + 255) / (pow(2, 9 - QuanBits));
*quan_buffer = Overflow(ForeError, 0, pow(2, QuanBits) - 1);
ForeError = *quan_buffer * pow(2, 9 - QuanBits) - 255;
*rebuild_buffer = (unsigned char)ForeError + *(rebuild_buffer - 1);
*rebuild_buffer = Overflow(*rebuild_buffer, 0, 255);
y_buffer++;
quan_buffer++;
rebuild_buffer++;
}
}
}
}
PMF_Entropy.cpp:
#include <iostream>
#include <cstdio>
#include <fstream>
#include <cmath>
#include "标头.h"
using namespace std;
#define Width 500
#define Height 500
void PMF_Entropy(unsigned char* buffer, int qBits, const char* PmfFileName, const char* EntropyFileName)
{
int count[500] = { 0 }; // Counter
double freq[500] = { 0 }; // Frequency
double entropy = 0;
for (int i = 0; i < Width * Height; i++)
{
int index = (int)buffer[i];
count[index]++;
}
for (int i = 0; i < 500; i++)
{
freq[i] = (double)count[i] / (Width * Height);
if (freq[i] != 0)
{
entropy += (-freq[i]) * log(freq[i]) / log(2);
}
}
FILE* PmfFile;
FILE* EntropyFile;
if (fopen_s(&PmfFile, PmfFileName, "wb") == 0)
{
cout << "File opened! " << PmfFileName << "." << endl;
}
else
{
cout << "File not opened! " << PmfFileName << "." << endl;
exit(0);
}
if (fopen_s(&EntropyFile, EntropyFileName, "ab") == 0)
{
cout << "File opened! " << EntropyFileName << "." << endl;
}
else
{
cout << "File not opened! " << EntropyFileName << "." << endl;
exit(0);
}
fprintf(PmfFile, "Symbol-%d bits quantization,Frequency\n",qBits);
for (int i = 0; i < 500; i++)
{
fprintf(PmfFile, "%-3d,%-5.9e\n", i, freq[i]); // 将数据输出到文件中(csv文件以“,”作为分隔符)
}
fprintf(EntropyFile, "%d,%.50f\n", qBits, entropy);
fclose(PmfFile);
fclose(EntropyFile);
}
PSNR.cpp:
#include <iostream>
#include <cstdio>
#include <fstream>
#include <cmath>
#include "标头.h"
void PSNR(unsigned char* oriBuf, unsigned char* recBuf, int qBits, const char* PsnrFileName)
{
FILE* PsnrFile;
if (fopen_s(&PsnrFile, PsnrFileName, "ab") == 0)
{
cout << "File opened! " << PsnrFileName << "." << endl;
}
else
{
cout << "File not opened! " << PsnrFileName << "." << endl;
exit(0);
}
double MSE;
double Sum = 0;
double temp;
double PSNR;
for (int i = 0; i < Width * Height; i++)
{
temp = pow((oriBuf[i] - recBuf[i]), 2);
Sum += temp;
}
MSE = Sum / (Width * Height);
PSNR = 10 * log10((pow(2,8)-1) * (pow(2, 8) - 1) / MSE);
fprintf(PsnrFile, "%d,%lf\n", qBits, PSNR);
}
main.cpp:
#include <iostream>
#include <cstdio>
#include <fstream>
#include <windows.h>
#include "标头.h"
using namespace std;
#define IMAGE_WIDTH 500
#define IMAGE_HEIGHT 500
int main(int argc, char* argv[4])
{
int QuanBits = 7; //量化bit数
int size = IMAGE_WIDTH * IMAGE_HEIGHT;
FILE* fsrc;
FILE* fobj;
FILE* fore;
char* fsrcFileName = NULL;
char* fobjFileName = NULL;
char* foreFileName = NULL;
fsrcFileName = argv[1];
fobjFileName = argv[2];
foreFileName = argv[3];
if (fopen_s(&fsrc, fsrcFileName, "rb") == 0)
{
cout << "File opened! " << fsrcFileName << "." << endl;
}
else
{
cout << "File not opened! " << fsrcFileName << "." << endl;
exit(0);
}
if (fopen_s(&fobj, fobjFileName, "wb+") == 0)
{
cout << "File opened! " << fobjFileName << "." << endl;
}
else
{
cout << "File not opened! " << fobjFileName << "." << endl;
exit(0);
}
if (fopen_s(&fore, foreFileName, "wb+") == 0)
{
cout << "File opened! " << foreFileName << "." << endl;
}
else
{
cout << "File not opened! " << foreFileName << "." << endl;
exit(0);
}
unsigned char* oriYBuff = new unsigned char[size];
unsigned char* newUBuff = new unsigned char[size / 4];
unsigned char* newVBuff = new unsigned char[size / 4];
unsigned char* QuanBuff = new unsigned char[size];
unsigned char* RebuildBuff = new unsigned char[size];
memset(newUBuff, 128, size / 4);
memset(newVBuff, 128, size / 4);
fread(oriYBuff, sizeof(unsigned char), size, fsrc);
DPCM(oriYBuff, QuanBuff, RebuildBuff, IMAGE_WIDTH, IMAGE_HEIGHT, QuanBits);
fwrite(QuanBuff, sizeof(unsigned char), size, fobj);
fwrite(newUBuff, sizeof(unsigned char), size / 4, fobj);
fwrite(newVBuff, sizeof(unsigned char), size / 4, fobj);
fwrite(RebuildBuff, sizeof(unsigned char), size, fore);
fwrite(newUBuff, sizeof(unsigned char), size / 4, fore);
fwrite(newVBuff, sizeof(unsigned char), size / 4, fore);
PMF_Entropy(oriYBuff, QuanBits, "seed-PMF.csv", "seed-Entropy.csv");
PMF_Entropy(QuanBuff, QuanBits, "seedQPE-PMF.csv", "seedQPE-Entropy.csv");
PSNR(oriYBuff, RebuildBuff, QuanBits, "seedRebuild-PSNR.csv");
delete[]oriYBuff;
delete[]QuanBuff;
delete[]RebuildBuff;
delete[]newUBuff;
delete[]newVBuff;
return 0;
}