DPCM压缩系统的C++实现

DPCM编码,简称差值编码,是对模拟信号幅度抽样的差值进行量化编码的调制方式(抽样差值的含义请参见“增量调制”)。这种方式是用已经过去的抽样值来预测当前的抽样值,对它们的差值进行编码。差值编码可以提高编码频率,这种技术已应用于模拟信号的数字通信之中。

一.原理

1.DPCM编解码原理

DPCM是一种对模拟信号的编码模式,与PCM不同,
每个抽样值不是独立的编码,而是先根据前一个抽样值计算出一个预测值,再取当前抽样值和预测值之差作编码用.此差值称为预测误差.抽样值和预测值非常接近(因为相关性强),预测误差的可能取值范围比抽样值变化范围小.所以可用少几位编码比特来对预测误差编码,从而降低其比特率.这是利用减小冗余度的办法,降低了编码比特率。

原理图如下:
在这里插入图片描述

  1. 输入图像
  2. 与上一个图像的预测值做差
  3. 将差值进行编码
  4. 编码后的差值:一路直接输出,另一路通过解码器反解出差值,与上一帧的预测值相加,就得到了当前图像的预测值
  5. 当前图像的预测值为下一帧图像到来时做好准备

关键代码

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(2bits1)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=MNi=0Mj=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-Rebuild5bit-Rebuild2bit-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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值