DPCM 压缩系统的实现和分析


前言

\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量化编码,其结果如下。

1bit2bit4bit8bit
预测误差图像在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
重建图像在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
PSNR7.1547411.846423.110451.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与熵编码组合压缩率仅熵编码压缩率
Lena98304466420.474698850.711
Camman98304433840.441675030.687
Clown98304475160.483724480.737
Fruit98304426000.433728710.741
Odie98304135740.138194220.198
Noise98304748530.761711320.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]);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值