使用DPCM进行图像压缩的C++实现方法

使用DPCM进行图像压缩的C++实现方法

我们知道,对于图像或视频的每一帧而言,相邻的像素之间有着较强的相关性——除了在边缘、轮廓等位置,相邻像素的像素值相差并不大,也就是说,图像或视频的一帧中存在着很大的空间冗余。而DPCM(Differential Pulse Code Modulation,差分脉冲编码调制)便是一种简单而高效的去冗余算法。

基本原理

DPCM

对于去除引言中提到的空间冗余,一种很自然的想法便是,使用前一个像素的像素值作为当前像素值的预测,然后只存储并传输当前像素值和预测值的差值。

以8 bit量化的图像为例,差值的范围在 [ − 255 , 255 ] [-255, 255] [255,255],完全表示需要9 bit,但并不是在所有图像中都存在由纯黑突变到纯白的情况,因而如果实际的动态范围只有 [ − 63 , 63 ] [-63, 63] [63,63],那么就可以只用7 bit表示,从而实现初步的压缩。

但除此之外,我们可以更多地依靠将差值量化,以进行更进一步的压缩。量化的原理实际上非常简单:例如对于一组取值为 [ 0 , 255 ] [0,255] [0,255]的数据,若要进行4 bit量化,我们只需要将它们分别除以 2 8 − 4 = 16 2^{8-4}=16 284=16并向下取整即可,这样数据范围变成了 [ 0 , 15 ] [0,15] [0,15],也就完成了量化。对于差值信号 d ∈ [ − 255 , 255 ] d\in[-255, 255] d[255,255],若同样要进行4 bit量化,我们就要先将双极性数据转化为单极性,再重复前面的操作即可:
d ^ = ⌊ d + 255 2 9 − 4 ⌋ \hat d = \left\lfloor \dfrac{d+255} {2^{9-4}} \right\rfloor d^=294d+255
我们再将量化后的预测误差与预测值叠加,就得到了该像素的重建(reconstruction)值,同时作为下一个像素的预测值。

至此,我们就完成了DPCM编码。实际上,在编码器中已经包含了解码器,因为解码同样只需要将预测值和量化预测误差相加。

其对应的框图如下:


DPCM编解码器原理框图

PSNR

为了便于我们对实验结果进行定量分析,在这里再简单介绍一下PSNR(Peak Signal to Noise Ratio,峰值信号噪声比)。

首先计算均方误差 MSE(Mean Square Error):
M S E = ∑ i = 0 M ∑ j = 0 N [ f ( i , j ) − f ′ ( i , j ) ] 2 M N {\rm MSE} = \frac{\sum_{i = 0}^M \sum_{j=0}^N \left[f(i,j)-f'(i,j)\right]^{2}}{MN} MSE=MNi=0Mj=0N[f(i,j)f(i,j)]2

其中 M , N M,N M,N为图像的宽和高, f ( i , j ) , f ’ ( i , j ) f(i,j),f’(i,j) f(i,j),f(i,j)为原图像和重建图像在 ( i , j ) (i,j) (i,j)的像素值。PSNR(单位:dB)以如下方式计算:
P S N R = 10 lg ⁡ ( 2 b i t s − 1 ) 2 M S E {\rm PSNR} = 10\lg \dfrac {\left(2^{\rm bits}-1\right)^2}{\rm MSE} PSNR=10lgMSE(2bits1)2

其中 b i t s {\rm bits} bits为原图像地量化比特数,即8。

一般来说:

  • PSNR ≥ 40 dB \text {PSNR} \ge 40 \text {dB} PSNR40dB时,图像质量非常好,接近于原图像;
  • 30 dB ≤ PSNR < 40 dB 30 \text {dB} \le \text {PSNR} < 40 \text {dB} 30dBPSNR<40dB时,图像有可察觉的失真,但质量仍可接受;
  • 20 dB ≤ PSNR < 30 dB 20 \text {dB} \le \text {PSNR} < 30 \text {dB} 20dBPSNR<30dB时,图像质量较差;
  • PSNR < 20 dB \text {PSNR} < 20 \text {dB} PSNR<20dB时,图像质量已经无法接受。

Huffman编码

Huffman是一种无失真信源编码算法,编码后可以得到即时的最佳码。在这里我们直接调用现有的Huffman编码程序,将原图像及量化后的预测误差图像进行压缩编码。

核心代码

前面没有详细说明对于每一行的第一个像素的预测值取值的问题,对于该问题有两种处理方法:

  • 每行第一个像素的预测值均取128;
  • 每一行第一个像素的预测误差直接取0,并将该像素值赋给重建图像对应像素。

这里我们选择以一种方法,并根据前面的原理可以写出如下的代码:

int w = 256;
int h = 256;

int PixelOverflow(int value, int thLower, int thUpper) {
	if (value < thLower) {
		return thLower;
	} else if (value > thUpper) {
		return thUpper;
	} else {
		return unsigned char(value);
	}
}

void DpcmEncoding(unsigned char* yBuff, unsigned char* qPredErrBuff, unsigned char* reconBuff, int qBits) {
	int prediction;
	int predErr;	// Prediction error
	int invPredErr;	// Inverse quantised value of quantised prediction error

	for (int i = 0; i < h; i++) {
		prediction = 128;	// The prediction of the first pixel of each row set to be 128
		predErr = yBuff[i * w] - prediction;	// predErr with the domain of [-128, 128] (8-bit)
		int temp = (predErr + 128) / pow(2, 8 - qBits);	// qBits-bit quantisation
														// (predErr + 128) with the domain of [0, 256]
		qPredErrBuff[i * w] = PixelOverflow(temp, 0, pow(2, qBits) - 1);
		invPredErr = qPredErrBuff[i * w] * pow(2, 8 - qBits) - 128;	// Inverse quantisation
		reconBuff[i * w] = PixelOverflow(invPredErr + prediction, 0, 255);	// Reconstruction level

		for (int j = 1; j < w; j++) {	// Strat from the second pixel of each row
			prediction = reconBuff[i * w + j - 1];	// The previous pixel value set as prediction
			predErr = yBuff[i * w + j] - prediction;	// predErr with the domain of [-255, 255] (9-bit)
			int temp = (predErr + 255) / pow(2, 9 - qBits);	// qBits-bit quantisation
															// (predErr + 255) with the domain of [0, 510]; [0, 2^(qBits) - 1] after division
			qPredErrBuff[i * w + j] = PixelOverflow(temp, 0, (pow(2, qBits) - 1));	// (predErr + 255) with the domain of [0, 255]
			invPredErr = qPredErrBuff[i * w + j] * pow(2, 9 - qBits) - 255;
			reconBuff[i * w + j] = PixelOverflow(invPredErr + prediction, 0, 255);	// Reconstruction level
		}
	}
}

这里需要说明的是,由于量化后得到的是一组索引值,因而需要通过反量化恢复正确的灰度值范围。

实验结果分析

量化预测误差图像、重建图像

以8 bit Lena灰度图作为测试图像,进行DPCM + 8 bit量化时的原图、预测误差图像和重建图像如下:


原图、预测误差图像、重建图像(8 bit量化)

下面再对比一下7 bit—1 bit量化的效果:


7 bit—1 bit量化的重建图像

在5 bit量化时,不仔细分辨不会看出较大差异;4 bit时,已经出现可察觉的失真;3 bit及以下出现较大的失真,图像质量难以接受。

由于量化预测误差范围为 [ 0 , 2 b i t s ] \left[0,2^{{\rm bits}}\right] [0,2bits],因而随着量化比特数降低,预测误差图像中的人物边缘将越来越不明显,因此这里就不再展示。

在这里我们也可以发现一点,例如对于量化误差进行1 bit量化,并不代表重建图像只有0和255两个亮度电平,这也是相比于直接对原图像进行量化的优势之一。

定量分析

计算PMF和熵

首先可以计算原图和预测误差的概率密度函数以及对应的信源熵。具体方法可参考求RGB图像各分量的概率分布和熵

可以看到,原图像中存在较大的相关性,进行了DPCM编码后,相关性得到了较好的去除,从而实现了压缩。

计算PSNR

我们可以通过计算重建图像的PSNR来定量计算图像的压缩质量,相应的代码如下:

void PrintPSNR(unsigned char* oriBuffer, unsigned char* recBuffer, int qBits, const char* psnrFileName) {
	double mse;
	double sum = 0;
	double temp;
	double psnr;

	for (int i = 0; i < w * h; i++) {
		temp = pow((oriBuffer[i] - recBuffer[i]), 2);
		sum += temp;
	}
	mse = sum / (w * h);
	psnr = 10 * log10(255 * 255 / mse);

	/* Output the stats into a csv file */
	FILE* outFilePtr;
	if (fopen_s(&outFilePtr, psnrFileName, "ab") == 0) {
		cout << "Successfully opened \"" << psnrFileName << "\".\n";
	} else {
		cout << "WARNING!! Failed to open \"" << psnrFileName << "\".\n";
		exit(-1);
	}
	fprintf(outFilePtr, "%d,%lf\n", qBits, psnr);

	fclose(outFilePtr);
}

计算结果如下:

可以看出,从客观评价(定量)角度对图像质量的分析与前面从主观评价角度对图像质量的分析基本吻合。

计算压缩比

最后我们可以将经过了Huffman编码的原图像,与进行了DPCM和Huffman两种编码的图像(量化后的预测误差图像)进行对比,并计算压缩比,结果如图。

完整代码

最后附上完整代码,供大家参考,如有问题欢迎评论区交流、指正。

declarations.h

#pragma once

/* Global variables */
extern int w;	// Width of image
extern int h;	// Height of image

/* Functions */
void DpcmEncoding(unsigned char* yBuff, unsigned char* qPredErrBuff, unsigned char* reconBuff, int qBits);
void PrintPMF_Entropy(unsigned char* buffer, int qBits, const char* pmfFileName, const char* entrFileName);
void PrintPSNR(unsigned char* oriBuffer, unsigned char* recBuffer, int qBits, const char* psnrFileName);

DPCM.cpp

#include <iostream>
#include "declarations.h"

int w = 256;
int h = 256;

int PixelOverflow(int value, int thLower, int thUpper) {
	if (value < thLower) {
		return thLower;
	} else if (value > thUpper) {
		return thUpper;
	} else {
		return unsigned char(value);
	}
}
void DpcmEncoding(unsigned char* yBuff, unsigned char* qPredErrBuff, unsigned char* reconBuff, int qBits) {
	int prediction;
	int predErr;	// Prediction error
	int invPredErr;	// Inverse quantised value of quantised prediction error

	for (int i = 0; i < h; i++) {
		prediction = 128;	// The prediction of the first pixel of each row set to be 128
		predErr = yBuff[i * w] - prediction;	// predErr with the domain of [-128, 128] (8-bit)
		int temp = (predErr + 128) / pow(2, 8 - qBits);	// qBits-bit quantisation
														// (predErr + 128) with the domain of [0, 256]
		qPredErrBuff[i * w] = PixelOverflow(temp, 0, pow(2, qBits) - 1);
		invPredErr = qPredErrBuff[i * w] * pow(2, 8 - qBits) - 128;	// Inverse quantisation
		reconBuff[i * w] = PixelOverflow(invPredErr + prediction, 0, 255);	// Reconstruction level

		for (int j = 1; j < w; j++) {	// Strat from the second pixel of each row
			prediction = reconBuff[i * w + j - 1];	// The previous pixel value set as prediction
			predErr = yBuff[i * w + j] - prediction;	// predErr with the domain of [-255, 255] (9-bit)
			int temp = (predErr + 255) / pow(2, 9 - qBits);	// qBits-bit quantisation
															// (predErr + 255) with the domain of [0, 510]; [0, 2^(qBits) - 1] after division
			qPredErrBuff[i * w + j] = PixelOverflow(temp, 0, (pow(2, qBits) - 1));	// (predErr + 255) with the domain of [0, 255]
			invPredErr = qPredErrBuff[i * w + j] * pow(2, 9 - qBits) - 255;
			reconBuff[i * w + j] = PixelOverflow(invPredErr + prediction, 0, 255);	// Reconstruction level
		}
	}
}

Stats.cpp

#include <iostream>
#include "declarations.h"
using namespace std;

void PrintPMF_Entropy(unsigned char* buffer, int qBits, const char* pmfFileName, const char* entrFileName) {
	int count[256] = { 0 };	// Counter
	double freq[256] = { 0 };	// Frequency
	double entropy = 0;

	/* Compute the frequency of each greyscale */
	for (int i = 0; i < w * h; i++) {
		int index = (int)buffer[i];
		count[index]++;
	}

	/* Compute the PMF & entropy */
	for (int i = 0; i < 256; i++) {
		freq[i] = (double)count[i] / (w * h);
		if (freq[i] != 0) {
			entropy += (-freq[i]) * log(freq[i]) / log(2);
		}
	}

	/* Output the stats into a csv file */
	FILE* pmfFilePtr;
	FILE* entrFilePtr;
	if (fopen_s(&pmfFilePtr, pmfFileName, "wb") == 0) {
		cout << "Successfully opened \"" << pmfFileName << "\".\n";
	} else {
		cout << "WARNING!! Failed to open \"" << pmfFileName << "\".\n";
		exit(-1);
	}
	if (fopen_s(&entrFilePtr, entrFileName, "ab") == 0) {
		cout << "Successfully opened \"" << entrFileName << "\".\n";
	} else {
		cout << "WARNING!! Failed to open \"" << entrFileName << "\".\n";
		exit(-1);
	}


	fprintf(pmfFilePtr, "Symbol,Frequency\n");
	for (int i = 0; i < 256; i++) {
		fprintf(pmfFilePtr, "%-3d,%-8.2e\n", i, freq[i]);	// 将数据输出到文件中(csv文件以“,”作为分隔符)
	}
	fprintf(entrFilePtr, "%d,%.4lf\n", qBits, entropy);

	fclose(pmfFilePtr);
	fclose(entrFilePtr);
}

void PrintPSNR(unsigned char* oriBuffer, unsigned char* recBuffer, int qBits, const char* psnrFileName) {
	double mse;
	double sum = 0;
	double temp;
	double psnr;

	for (int i = 0; i < w * h; i++) {
		temp = pow((oriBuffer[i] - recBuffer[i]), 2);
		sum += temp;
	}
	mse = sum / (w * h);
	psnr = 10 * log10(255 * 255 / mse);

	/* Output the stats into a csv file */
	FILE* outFilePtr;
	if (fopen_s(&outFilePtr, psnrFileName, "ab") == 0) {
		cout << "Successfully opened \"" << psnrFileName << "\".\n";
	} else {
		cout << "WARNING!! Failed to open \"" << psnrFileName << "\".\n";
		exit(-1);
	}
	fprintf(outFilePtr, "%d,%lf\n", qBits, psnr);

	fclose(outFilePtr);
}

main.cpp

#include <iostream>
#include <string>
#include <sstream>
#include "declarations.h"
using namespace std;

int main(int argc, char* argv[]) {
    int qBits = 1;
    const char* orFileName = "Lena.yuv";
    const char* qpeFileName = "Lena_QPE (1 bit).yuv";   // Name of quantised prediction error file
    const char* recFileName = "Lena_reconstruction (1 bit).yuv";    // Name of reconstruction level file
    FILE* oriFilePtr;
    FILE* qpeFilePtr;
	FILE* recFilePtr;

	/* Open the files */
    if (fopen_s(&oriFilePtr, orFileName, "rb") == 0) {
        cout << "Successfully opened \"" << orFileName << "\".\n";
    } else {
        cout << "WARNING!! Failed to open \"" << orFileName << "\".\n";
        exit(-1);
    }
    if (fopen_s(&qpeFilePtr, qpeFileName, "wb") == 0) {
        cout << "Successfully opened \"" << qpeFileName << "\".\n";
    } else {
        cout << "WARNING!! Failed to open \"" << qpeFileName << "\".\n";
        exit(-1);
    }
    if (fopen_s(&recFilePtr, recFileName, "wb") == 0) {
        cout << "Successfully opened \"" << recFileName << "\".\n";
    } else {
        cout << "WARNING!! Failed to open \"" << recFileName << "\".\n";
        exit(-1);
    }

    /* Space allocation */
    unsigned char* oriYBuff = new unsigned char[w * h];
    unsigned char* qpeYBuff = new unsigned char[w * h];
    unsigned char* recYbuff = new unsigned char[w * h];
    unsigned char* uBuff = new unsigned char[w * h / 4];
    unsigned char* vBuff = new unsigned char[w * h / 4];

    /* Read greyscale data */
    fread(oriYBuff, sizeof(unsigned char), w * h, oriFilePtr);

    /* DPCM */
    DpcmEncoding(oriYBuff, qpeYBuff, recYbuff, qBits);
    memset(uBuff, 128, w * h / 4);
    memset(vBuff, 128, w * h / 4);
    fwrite(qpeYBuff, sizeof(unsigned char), w * h, qpeFilePtr);
    fwrite(uBuff, sizeof(unsigned char), w * h / 4, qpeFilePtr);    // Greyscale image
    fwrite(vBuff, sizeof(unsigned char), w * h / 4, qpeFilePtr);
    fwrite(recYbuff, sizeof(unsigned char), w * h, recFilePtr);
    fwrite(uBuff, sizeof(unsigned char), w * h / 4, recFilePtr);    // Greyscale image
    fwrite(vBuff, sizeof(unsigned char), w * h / 4, recFilePtr);

    /* Write stats into csv files */
    PrintPMF_Entropy(oriYBuff, qBits, "Lena-PMF (1 bit).csv", "Lena-entropy.csv");
    PrintPMF_Entropy(qpeYBuff, qBits, "Lena_QPE-PMF (1 bit).csv", "Lena_QPE-entropy.csv");
    PrintPSNR(oriYBuff, recYbuff, qBits, "Lena_reconstruction-PSNR.csv");

    fclose(oriFilePtr);
    fclose(qpeFilePtr);
    fclose(recFilePtr);
    delete[]oriYBuff;
    delete[]qpeYBuff;
    delete[]recYbuff;
    delete[]uBuff;
    delete[]vBuff;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DPCM(Differential Pulse Code Modulation)是一种用于压缩图像的技术,它可以利用图像中的冗余信息来减少数据量,从而实现图像的压缩。下面是使用MATLAB实现DPCM压缩图像的示例代码: ```matlab % 读取原始图像 img = imread('lena.bmp'); % 转换为灰度图像 img_gray = rgb2gray(img); % DPCM 压缩图像 % 设置预测误差的阈值 threshold = 10; % 初始化预测误差数组 err = zeros(size(img_gray)); % 对第一行和第一列直接进行编码 err(1,:) = double(img_gray(1,:)); err(:,1) = double(img_gray(:,1)); for i = 2:size(img_gray, 1) for j = 2:size(img_gray, 2) % 计算预测值 predict = (double(img_gray(i-1,j)) + double(img_gray(i,j-1))) / 2; % 计算预测误差 err(i,j) = double(img_gray(i,j)) - predict; % 如果预测误差小于阈值,直接编码为0 if abs(err(i,j)) < threshold err(i,j) = 0; end end end % 解码压缩后的图像 % 初始化解码后的图像 img_decompress = zeros(size(img_gray)); % 对第一行和第一列直接进行解码 img_decompress(1,:) = err(1,:); img_decompress(:,1) = err(:,1); for i = 2:size(img_gray, 1) for j = 2:size(img_gray, 2) % 计算预测值 predict = (double(img_decompress(i-1,j)) + double(img_decompress(i,j-1))) / 2; % 解码预测误差 if err(i,j) == 0 img_decompress(i,j) = predict; else img_decompress(i,j) = predict + err(i,j); end end end % 显示原始图像和压缩后的图像 subplot(1,2,1); imshow(img_gray); title('Original Image'); subplot(1,2,2); imshow(uint8(img_decompress)); title('DPCM Compressed Image'); ``` 在上述代码中,我们首先读取原始图像,并将其转换为灰度图像。然后,我们使用DPCM算法对图像进行压缩,其中我们设置了一个预测误差的阈值,如果预测误差小于该阈值,则直接编码为0。最后,我们使用解码算法对压缩后的图像进行解码,并将解码后的图像与原始图像一起显示,以便比较它们的差异。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值