DPCM
原理
如图所示,首先输入一个图像,与上一个图像的预测值做差,将差值进行编码。
编码后的差值有两个去向,一是直接输出,二是通过解码器反解出差值,与上一帧的预测值相加,就得到了当前图像的预测值,为下一帧图像到来时做好准备。
实现功能
本次采用左侧预测,并且默认最左侧像素前的真实值为均为128。并且实现了如下功能:
- 进行不同量化bit数的差分预测编码
- 将编码结果进行输出并进行霍夫曼编码
- 分别计算原图像和量化后的图像进行概率分布
- 分别计算原图像经过熵编码和经过DPCM+熵编码的图像的压缩比
- 比较二者压缩效率
- 计算重建图像的PSNR
代码
#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;
const string inPath = "seed.yuv";
const int maxn = 1E5 + 5;
const int maxV = 255, N = 256;
int height = 500, width = 500;
unsigned char *yBuffer, *rebuild, *predict, *tmp, *code;
int bitNum; double delta;
unsigned char limit( int val )
{
if( val > 255 ) return 255;
if( val < 0 ) return 0;
return val;
}
//量化
unsigned char Q( int val )
{
val += 255;
int ans = val / delta;
if( ans == (1 << bitNum) ) ans --;
return ans;
}
//反量化
int deQ( unsigned char val )
{
int ans = (val + 0.5)* delta + 0.5;
ans -= 255;
if( ans > 255 ) ans = 255;
return ans;
}
//计算PSNR
double PSNR( unsigned char* standard, unsigned char* image )
{
double psnr = 0, MSE = 0;
for( int i = 0; i < height * width; ++ i ) MSE += 1ll* (image[i] - standard[i]) * (image[i] - standard[i]);
MSE /= height * width;
long long t = 1 << bitNum; t -= 1; t *= t;
psnr = 10 * log10( t / MSE);
return psnr;
}
//得到频率分布
void GetFrequency( unsigned char* buffer, double* frequency )
{
int length = height * width;
for( int i = 0; i < length; ++ i ) frequency[buffer[i]] ++;
for( int i = 0; i < N; ++ i ) frequency[i] /= length;
}
int main()
{
{
int length = height * width;
ifstream in( inPath, ios::binary );
yBuffer = new unsigned char[length];
rebuild = new unsigned char[length];
predict = new unsigned char[length];
code = new unsigned char[length];
tmp = new unsigned char[length];
in.read(( char * ) yBuffer, length );
in.read(( char * ) tmp, length );
//获得原图像的概率分布
ofstream out;
out.open(inPath + ".csv", ios::out);
double num[N] = {0};
GetFrequency( yBuffer, num );
for( int i = 0; i < N; ++ i )
out << i << "," << num[i] << endl;
out.close();
//进行不同bit数的差分脉冲编码
for ( auto i : { 1, 2, 4, 8 } )
{
bitNum = i;
int n = 1 << bitNum;
delta = 1.0 * ( 2 * maxV ) / n;
for ( int h = 0; h < height; ++h )
{
for ( int w = 0; w < width; ++w )
{
int val = 0;
if ( !w ) val = int( yBuffer[h * width + w] ) - 128;
else val = int( yBuffer[h * width + w] ) - rebuild[h * width + w - 1];
predict[h * width + w] = val + 128;
code[h * width + w] = Q( val );
int d = deQ( code[h * width + w] );
if ( !w ) rebuild[h * width + w] = d + 128;
else rebuild[h * width + w] = limit(d + rebuild[h * width + w - 1]);
}
}
//输出结果
string suf = "";
suf += ( '0' + i );
suf += "bit";
ofstream out;
out.open( "predict" + suf + ".yuv", ios::binary );
out.write(( char * ) predict, length );
out.write(( char * ) tmp, length );
out.write(( char * ) tmp, length );
out.close();
out.open( "rebuild" + suf + ".yuv", ios::binary );
out.write(( char * ) rebuild, length);
out.write(( char * ) tmp, length );
out.write(( char * ) tmp, length);
out.close();
out.open( "code" + suf + ".code", ios::binary );
out.write((char*)code, length);
out.close();
//获得误差的概率分布
double residualFre[N] = {0};
GetFrequency( code, residualFre );
out.open("code" + suf + ".csv", ios::out);
for( int i = 0; i < (1 << bitNum); ++ i ) out << i << "," << residualFre[i] << endl;
cout << i << "bit psnr: "<< PSNR( yBuffer, rebuild ) << endl;
}
delete[] yBuffer;
delete[] predict;
delete[] rebuild;
delete[] tmp;
in.close();
}
return 0;
}
实验过程
-
通过上面的代码生成编码、预测误差图像、重建图像;计算原图及编码的概率分布,以及重建图像的PSNR
-
利用Huffman编码程序对原图像及量化后的编码进行熵编码。
-
比较文件大小、压缩比等参数。
实验结果
此处原文件大小为500* 500 * 3 = 750,000 字节
原图像 | 8bit量化 | 4bit量化 | 2bit量化 | 1bit量化 | |
---|---|---|---|---|---|
量化误差图像 | |||||
重建图像 | |||||
概率分布 | |||||
熵编码后的文件大小(Byte) | 252993 | 139725 | 51741 | 46871 | 46871 |
压缩比 | 33.7% | 18.6% | 6.90% | 6.25% | 6.25% |
PSNR | ∞ | 51.1529 | 28.6534 | 16.822 | 10.8348 |
可见经过熵编码之后,图像的大小会减小。
经过DCMP+熵编码之后,图像大小比直接使用熵编码减小的更多,随着量化比特数的减小,压缩效率越来越高,但是变化越来越缓慢;于此同时,图像质量,也就是PSNR的值迅速恶化,重建出的图像质量越来越差,到1bit是就已经很模糊了。
综上,量化应采用合适的量化比特数,使之既不至于太影响画面,又可以达到较高的压缩效率。