文章目录
前言
\qquad DPCM即Differential Pulse Code Modulation,差分预测编码调制,是一种对模拟信号的编码模式。其运行时先根据前一个抽样值计算出一个预测值,再取当前抽样值和预测值之差作编码,并用此差值称为预测误差。因为相邻的数据值相关性较强,抽样值和预测值非常接近,预测误差的取值范围通常就比抽样值范围小,所以可用较短码长的编码方式来对预测误差编码,从而降低其比特率。这也是一种利用减小冗余度的方式,降低了编码比特率,从而达到压缩的方法。
\qquad 本实验则利用C++语言,构建基本的DPCM系统中的残差计算部分,实现yuv文件的拆分预测编码和重建,并计算失真度。同时,对差分误差采用Huffman熵编码,以评测分析DPCM在压缩系统中产生的作用。
一、DPCM系统
1、预测编码
\qquad
在预测编码DPCM过程中,先根据前几个抽样值计算出一个预测值,再取当前抽样值和预测值之差。将此差值编码并传输。此差值称为预测误差。由于抽样值及其预测值之间有较强的相关性,即抽样值和其预测值非常接近,使此预测误差的可能取值范围,比抽样值的变化范围小。所以,可以少用编码比特来对预测误差编码,从而降低其比特率。此预测误差的变化范围较小,它包含的冗余度也小。这就是说,利用减小冗余度的办法,降低了编码比特率。基本原理图如下所示(熵编码部分已被忽略)。
\qquad 需要注意的是预测器(Predictor with delays)的输入是已经解码以后的样本。之所以不用原始样本来做预测,是因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解码器,理解为与解码端对称的效果,如编码器中虚线框中所示。
\qquad 同时本次实验中,预测器逐行预测像素,实验尝试了预测值为当前像素的左方像素时残差图像的效果。对于预测过程中预测所用的像素超出画面的情况(最左边像素点预测),预测所用的灰度值取为128。
2、量化方式
\qquad
实验采用均匀量化方式。在此方式下,量化间隔在每个量化区间中保持不变。量化有如下特征值。
\qquad
在此实验中,量化器对预测误差进行量化。由于预测误差范围为[-255,255],故相当于数据范围长度为512,故在进行n比特量化时,每级的量化间隔均为512/2n。同时,为保证量化后的数据均为正值,在量化之前则需要对误差加上255。
3、量化误差
\qquad
计算量化误差时需要计算其均方误差MSE。若图像大小为M×N,I与K分别为原图像和重建图像,则其均方误差计算公式为:
\qquad
对于图像数据的量化,实验使用峰值信噪比PSNR进行描述。对像素Y分量最大值为255的灰度图像来说,其峰值信噪比(单位为dB)的计算公式如下:
\qquad
实验中使用PSNR对编码性能进行分析,其值越大,代表效果越理想。
二、实验内容
1、材料准备
\qquad
使用bmp转yuv实验的代码,并将帧数改为1(去掉每幅图片转换过程的循环)、输入图片为1张(代码详见博客 BMP格式文件转YUV文件 实验报告),对bmp文件资源进行转换。
\qquad
每个yuv大小均为256×256。
2、DPCM预测量化处理
\qquad 定义DPCM编码函数,代码如下所示。(以左像素预测为例)
//左方预测
void DPCMEncoder( unsigned char* y_buffer, unsigned char* delta_buffer, unsigned char* rebuild_buffer, int w, int h, int bitNum)
{
int delta;
int rebuild_value;
int interval=512/pow(2,bitNum);
for(int i=0;i<h;i++){
for (int j=0;j<w;j++)
{
if (j==0) {
delta=y_buffer[w*i+j]-128;
delta_buffer[w*i+j]=(delta+255)/interval;
rebuild_value=(delta_buffer[w*i+j]*interval)-255+128;
}
else{
delta=y_buffer[w*i+j]-rebuild_buffer[w*i+j-1];
delta_buffer[w*i+j]=(delta+255)/interval;
rebuild_value=(delta_buffer[w*i+j]*interval)-255+rebuild_buffer[j+w*i-1];
}
if(rebuild_value>255)
{
rebuild_value=255;
}
else if(rebuild_value<0)
{
rebuild_value=0;
}
rebuild_buffer[w*i+j]=rebuild_value;
}
}
}
\qquad 在主函数中读取文件Y分量指针y_buffer,创建预测误差图像和重建图像的Y分量指针delta_buffer、rebulid_buffer,并指定量化比特数bitNum,即可调用函数进行编码与量化。
3、PSNR计算
\qquad 定义计算PSNR的函数如下。
double PSNRCal(unsigned char* buf1, unsigned char* buf2)
{
double mse=0;
double delta=0;
double result=0;
for(int v=0;v<height;v++)
{
for(int u=0;u<width;u++)
{
delta=buf1[v*width+u]-buf2[v*width+u];
mse+=delta*delta;
}
}
mse=mse/(width*height);
result=10*log10(255*255/mse);
return result;
}
4、Huffman熵编码
\qquad
实验使用了实验素材中huff_run.exe程序,并编辑Windows批处理文件以进行文件的批量编码,直接进行Huffman熵编码。编码文件存储于.huff文件中。以实验过程中对各素材8bit量化误差图像进行熵编码的批处理文件为例。
三、实验结果
1、不同比特量化
\qquad 实验对Lena.yuv进行了1bit、2bit、4bit、8bit量化编码,其结果如下。
1bit | 2bit | 4bit | 8bit | |
---|---|---|---|---|
预测误差图像 | ||||
重建图像 | ||||
PSNR | 7.15474 | 11.8464 | 23.1104 | 51.1338 |
\qquad 从结果中可以看出,在输入输出yuv文件大小均为256×256的情况下,1bit、2bit量化已经完全无法对图像进行重建。4bit量化会对图像产生一定的劣化,而8bit量化则效果最好。
2、图像预测误差与PSNR
\qquad 实验选用了6张图片,分别进行了8bit量化与图片重建,并分别计算了PSNR。
原图片 | 预测误差图片 | 重建图片 | PSNR |
---|---|---|---|
51.1338 | |||
51.4629 | |||
50.1159 | |||
51.0313 | |||
51.8819 | |||
50.641 |
3、概率分布分析
\qquad 采用第一次实验(RGB格式文件三通道分量的熵计算)类似的统计方法,对原yuv文件与预测误差文件的y分量概率密度进行统计(于csv文件),并使用MatLab进行画图。以Lena、Camman和Noise三个图像为例。
图片文件 | 原图片概率密度 | 预测误差图片概率密度 |
---|---|---|
Lena | ||
Camman | ||
Noise |
\qquad
比较原文件和预测误差文件的像素概率密度,可以看到在进行预测之后,相对原文件,预测误差文件的概率会进行集中。因设置的图像横坐标为[-1,256],所以不难看出集中位置即为中位线127。
\qquad
同时,实验发现Lena和Camman两文件的情况类似,因为两原文件均存在概率密度较大和较小的像素灰度区域,所以在进行预测时,向127进行集中的趋势较为明显。而在此过程前,本人就已对Noise文件感兴趣,直觉此文件的像素密度应较为平缓,与其他文件不同。基于此猜想进行实验,发现Noise文件的预测误差图像概率密度集中程度可能不如前两者那么明显,但的确有了很大的概率向127靠拢的效果,同时接近灰度谷值和峰值的概率密度也小了很多。
\qquad 因为DPCM预测的概率集中特性,其文件概率集中导致的方差增大的效果会有助于Huffman编码提升编码效率。
4、文件压缩效果
\qquad 对实验所用的六个文件,将原文件与8bit预测误差文件分别进行Huffman编码,并比较所得.huff文件的大小。(单位:字节)
图片文件 | 原大小 | DPCM与熵编码组合 | 压缩率 | 仅熵编码 | 压缩率 |
---|---|---|---|---|---|
Lena | 98304 | 46642 | 0.474 | 69885 | 0.711 |
Camman | 98304 | 43384 | 0.441 | 67503 | 0.687 |
Clown | 98304 | 47516 | 0.483 | 72448 | 0.737 |
Fruit | 98304 | 42600 | 0.433 | 72871 | 0.741 |
Odie | 98304 | 13574 | 0.138 | 19422 | 0.198 |
Noise | 98304 | 74853 | 0.761 | 71132 | 0.724 |
\qquad
可以看到,与上节猜想相同,对于大多含有自身的较为集中的灰度概率的图片来说,DPCM可以明显将其概率密度进行集中,从而使方差增大,以实现Huffman编码的高效率压缩。其中Odie文件(一条修勾)因其大部分为黑色,且主角为白色,其概率密度集中度较高,方差较大。故其压缩效率勇往直前。
\qquad
而对于Noise在这张特殊性较高的图片,其本身虽然概率密度曲线较平缓,但除接近像素灰度峰值与谷值的两个突变以外,其他灰度概率密度均较小。所以本身Noise图片的概率密度在接近像素灰度峰值与谷值处较为集中,方差较大。但进行了DPCM之后,从概率分布图中也可看出,其概率密度虽向127集中,但相较于原图像反而较为平缓,方差反而减小,进而对Huffman编码的压缩效率产生了反向提升。
\qquad
5、简单尝试:上方像素预测
\qquad 由于本人对第一节课所讲的预测方向不同导致的预测误差图像不同,以及通过预测误差图像判断预测方向的点印象较为深刻,实验尝试了将Lena和Camman图像的预测方向改为上方像素作为预测。函数代码改动如下。
//上方预测
void DPCMEncoder( unsigned char* y_buffer, unsigned char* delta_buffer, unsigned char* rebuild_buffer, int w, int h, int bitNum)
{
int delta;
int rebuild_value;
int interval=512/pow(2,bitNum);
for(int i=0;i<h;i++){
for (int j=0;j<w;j++)
{
if (i==0) {
delta=y_buffer[w*i+j]-128;
delta_buffer[w*i+j]=(delta+255)/interval;
rebuild_value=(delta_buffer[w*i+j]*interval)-255+128;
}
else{
delta=y_buffer[w*i+j]-rebuild_buffer[w*i+j-width];
delta_buffer[w*i+j]=(delta+255)/interval;
rebuild_value=(delta_buffer[w*i+j]*interval)-255+rebuild_buffer[j+w*i-width];
}
if(rebuild_value>255)
{
rebuild_value=255;
}
else if(rebuild_value<0)
{
rebuild_value=0;
}
rebuild_buffer[w*i+j]=rebuild_value;
}
}
}
\qquad 结果如下。
原图片 | 左方像素预测 | 上方像素预测 |
---|---|---|
\qquad
与预测效果相同,大致分析灰度值变化边界的“阴影”走向,结合原图像中灰度增加的方向,即可大致分辨出预测方向。本博客不再赘述。
实验代码
1、DPCM_Main.cpp
#include<iostream>
#include<malloc.h>
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
using namespace std;
const int height=256;
const int width=256;
//左方预测
void DPCMEncoder( unsigned char* y_buffer, unsigned char* delta_buffer, unsigned char* rebuild_buffer, int w, int h, int bitNum)
{
int delta;
int rebuild_value;
int interval=512/pow(2,bitNum);
for(int i=0;i<h;i++){
for (int j=0;j<w;j++)
{
if (j==0) {
delta=y_buffer[w*i+j]-128;
delta_buffer[w*i+j]=(delta+255)/interval;
rebuild_value=(delta_buffer[w*i+j]*interval)-255+128;
}
else{
delta=y_buffer[w*i+j]-rebuild_buffer[w*i+j-1];
delta_buffer[w*i+j]=(delta+255)/interval;
rebuild_value=(delta_buffer[w*i+j]*interval)-255+rebuild_buffer[j+w*i-1];
}
if(rebuild_value>255)
{
rebuild_value=255;
}
else if(rebuild_value<0)
{
rebuild_value=0;
}
rebuild_buffer[w*i+j]=rebuild_value;
}
}
}
上方预测
//void DPCMEncoder( unsigned char* y_buffer, unsigned char* delta_buffer, unsigned char* rebuild_buffer, int w, int h, int bitNum)
//{
// int delta;
// int rebuild_value;
// int interval=512/pow(2,bitNum);
//
// for(int i=0;i<h;i++){
// for (int j=0;j<w;j++)
// {
// if (i==0) {
// delta=y_buffer[w*i+j]-128;
// delta_buffer[w*i+j]=(delta+255)/interval;
// rebuild_value=(delta_buffer[w*i+j]*interval)-255+128;
// }
// else{
// delta=y_buffer[w*i+j]-rebuild_buffer[w*i+j-width];
// delta_buffer[w*i+j]=(delta+255)/interval;
// rebuild_value=(delta_buffer[w*i+j]*interval)-255+rebuild_buffer[j+w*i-width];
// }
//
// if(rebuild_value>255)
// {
// rebuild_value=255;
// }
// else if(rebuild_value<0)
// {
// rebuild_value=0;
// }
//
// rebuild_buffer[w*i+j]=rebuild_value;
// }
// }
//}
int main()
{
FILE* fp1;
fp1=fopen("Lena256B.yuv","rb");
unsigned char *y_buffer,*u_buffer,*v_buffer;
y_buffer=(unsigned char *)malloc(height * width);
u_buffer=(unsigned char *)malloc(height * width*0.25);
v_buffer=(unsigned char *)malloc(height * width*0.25);
fread(y_buffer,sizeof(unsigned char),height*width,fp1);
fread(u_buffer,sizeof(unsigned char),height*width*0.25,fp1);
fread(v_buffer,sizeof(unsigned char),height*width*0.25,fp1);
unsigned char *delta_buffer,*rebuild_buffer;
delta_buffer=(unsigned char *)malloc(height*width);
rebuild_buffer=(unsigned char *)malloc(height*width);
DPCMEncoder(y_buffer,delta_buffer,rebuild_buffer,height,width,8);
FILE *fp2;
fp2=fopen("LenaD_8.yuv","wb");
fwrite(delta_buffer,sizeof(unsigned char),height*width,fp2);
fwrite(u_buffer,sizeof(unsigned char),height*width*0.25,fp2);
fwrite(v_buffer,sizeof(unsigned char),height*width*0.25,fp2);
FILE *fp3;
fp3=fopen("LenaRe_8.yuv", "wb");
fwrite(rebuild_buffer,sizeof(unsigned char),height * width,fp3);
fwrite(u_buffer,sizeof(unsigned char),height*width*0.25,fp3);
fwrite(v_buffer,sizeof(unsigned char),height*width*0.25,fp3);
free(y_buffer);
free(u_buffer);
free(v_buffer);
free(delta_buffer);
free(rebuild_buffer);
fclose(fp1);
fclose(fp2);
fclose(fp3);
return 0;
}
2、PSNR.cpp
#include<iostream>
#include<malloc.h>
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
using namespace std;
const int height=256;
const int width=256;
double PSNRCal(unsigned char* buf1, unsigned char* buf2)
{
double mse=0;
double delta=0;
double result=0;
for(int v=0;v<height;v++)
{
for(int u=0;u<width;u++)
{
delta=buf1[v*width+u]-buf2[v*width+u];
mse+=delta*delta;
}
}
mse=mse/(width*height);
result=10*log10(255*255/mse);
return result;
}
int main()
{
FILE *file1, *file2;
file1=fopen("Lena256B.yuv", "rb");
unsigned char *y_buffer1;
y_buffer1=(unsigned char *)malloc(height * width);
fread(y_buffer1, sizeof(unsigned char), height * width, file1);
fclose(file1);
file2=fopen("LenaRe_8.yuv", "rb");
unsigned char *y_buffer2;
y_buffer2=(unsigned char *)malloc(height * width);
fread(y_buffer2, sizeof(unsigned char), height * width, file2);
fclose(file1);
double psnr = PSNRCal(y_buffer1, y_buffer2);
cout << psnr;
return 0;
}
3、CalProb.cpp
\qquad 说明:统计像素概率分布,并输出至csv文件的源代码。(输出csv目的为MatLab画图时方便直接读取文件)
#include<iostream>
#include<math.h>
#include<malloc.h>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
using namespace std;
int main()
{
FILE *fp1=NULL;
fp1=fopen("LenaD_8.yuv","rb");
unsigned char *y_buffer;
y_buffer =(unsigned char*)malloc(256*256);
fread(y_buffer,1,256*256,fp1);
unsigned char *ptr=y_buffer;
unsigned int y[256]={};
for(int i(0);i<(256*256);i++)
{
y[*(ptr)]+=1;
ptr++;
}
double prob[256]={};
for(int j(0);j<256;j++)
{
prob[j]=(double)y[j]/256/256;
}
ofstream outFile;
outFile.open("LenaD8.csv", ios::out);
for(int i(0);i<256;i++)
{
outFile<<prob[i]<<endl;
}
outFile.close();
free(y_buffer);
fclose(fp1);
}
4、drawprob.m
\qquad 说明:使用matlab进行概率密度绘画分析。
clc;
clear all;
close all;
x=0:1:255;
data=csvread('LenaD_8.csv');
plot(x,data,'k','LineWidth',1.5);
title('Lena 8bit Delta Prob.');
xlabel('Grayscale');
ylabel('Probability');
xlim([-1,256]);