实验4 | DPCM编码算法的实现及联合Huffman编码的压缩效率对比分析,和量化不同比特数的效果对比

1 压缩效果的分析

1.1 压缩效率的对比

我们采用了两种方式对图像进行压缩编码:

  • 第一种是直接使用Huffman编码;
  • 第二种是先对数据进行DPCM差分预测编码,然后再进行Huffman编码。

根据 压 缩 比 = 原 图 大 小 : 压 缩 码 流 大 小 (1) 压缩比=原图大小:压缩码流大小\tag{1} =:(1)得出压缩比:

Huffman OnlyDPCM & Huffman
1.41 : 1 1.41:1 1.41:1 2.25 : 1 2.25:1 2.25:1

DPCM对原始图像经过预测编码以后,对数据的压缩比提升是非常大的,达到了仅Huffman编码的1.6倍。这是为什么呢?我们接下来就对DPCM压缩后的数据进行分析。


1.2 DPCM压缩后的数据分布

使用类似于实验一中统计概率分布的方法,我们得出原图和经过DPCM编码后的数据的概率分布如下。(实现代码置于附录中)
在这里插入图片描述

观察图像,我们发现:

  1. 原图的分布在值域 [ 0 − 225 ] [0-225] [0225]内较为均匀,而残差图的概率分布非常集中,有点像Laplace分布。这是因为量化后,两像素之间的差值集中在 [ 100 − 150 ] [100-150] [100150]这个区间内。这种分布对于变长的Huffman编码来说,优势是巨大的。我们在进行Huffman编码时,需要编写的信源符号更少,且将大概率的值用短码编写。

  2. 通过计算原图和残差图的熵,我们发现是完全一致的:

    result = 0
    pr = residual_data['编码内容'] / residual_data['编码内容'].sum()
    it = iter(pr)  # 创建迭代器对象
    for x in pr:
        if x != 0:
            result += -x * np.log2(x)
    print(result)
    
    残差图
    7.718497.71849

    这种信源的压缩方式,大大降低了数据量,但是信息量没有变化。换言之,这种编码方式实现了能量集中。唯一的误差来源可能就是DPCM的量化和反量化过程。


1.3 DPCM编码后各图像效果

在这里插入图片描述

注:原图带有UV分量,故有细微色彩,但是重建图像只有Y分类,UV被置为128,是完全的灰度图。

我们可以看出,随着量化比特数的降低,重建图像质量肉眼可见的下降。由于采取左向预测,所以从4bit起每一行之间一条一条的印迹非常明显。

残差值由于越来越小,就变得很黑难以看清。

其中1bit的原图需要进行二值化的处理。


1.4 压缩质量

压缩后会带来一定质量的损失。我们采取了PSNR,峰值信噪比作为图像质量评价指标。

1.4.1 PSNR的定义

  1. 给定一张大小为 m × n m \times n m×n的原始干净图像 C \boldsymbol{C} C,和一张同大小的重建的噪声图像 N \boldsymbol{N} N
    我们计算它俩之间的均方误差 M S E MSE MSE
    M S E = 1 m × n ∑ i = 0 m − 1 ∑ i = 0 n − 1 ∣ ∣ C ( i , j ) − N ( i , j ) ∣ ∣ 2 (2) MSE = \frac{1}{m\times n} \sum_{i=0}^{m-1}\sum_{i=0}^{n-1} ||\boldsymbol{C}(i,j)- \boldsymbol{N}(i,j)||^2\tag{2} MSE=m×n1i=0m1i=0n1C(i,j)N(i,j)2(2)
    然后我们计算出原始图像可能出现的最大像素值 M A X C 2 MAX^{2}_{C} MAXC2。对于我们的8位图像数据,这个值为 255 255 255

最后,我们定义 P S N R ( d B ) PSNR(dB) PSNR(dB)为:
P S N R ( d B ) = 10 ⋅ l o g 10 ( M A X C 2 M S E ) (3) PSNR(dB)= 10 \cdot log_{10}(\frac{MAX^{2}_{C}}{MSE})\tag{3} PSNR(dB)=10log10(MSEMAXC2)(3)

P S N R PSNR PSNR值越大,就代表失真越少。

1.4.2 计算PSNR

将两图像字节读取到列表后:

MSE = 0
for idx, _ in enumerate(raw):
    MSE += (raw[idx] - rec[idx]) ** 2

MSE /= (256 * 256)

PSNR= 10 * np.log10((255**2)/MSE)
print(PSNR)

得到重建图像的 P S N R ( d B ) = 51.177 PSNR(dB) = 51.177 PSNR(dB)=51.177


2 DPCM编码算法

2.1 算法实现

我们将DPCM的算法实现如下。其中量化过程由函数quantify()完成。
程序的逻辑非常简单,首先开启三个buffer: origin_Buffer用于存储原始图像数据,reconstructed_buffer用于存储重建图像数据,residual_buffer用于存储残差数据。

由于我们采用左侧像素对图像进行DPCM编码,故每行第一个像素需要将原始值传去。其余的像素值进行计算残差、量化;然后重建时反量化。

完整代码在附录贴出。

void getDPCMResidual(unsigned char * origin_Buffer, unsigned char* residual_buffer,unsigned char * reconstructed_buffer,int width,int height) {
	/*
	:function 该函数采用左侧的像素对图像进行DPCM编码。
	:return  无
	:output  该函数可以输出残差图像、重建图像
	*/
	int yuvsize = width * height;
	for (int i = 0; i < yuvsize; i++) {
		if (i % width == 0) { //若是每行第一个像素
			reconstructed_buffer[i] = [i];
			residual_buffer[i] = origin_Buffer[i];
		}
		else {
			int dn = origin_Buffer[i] - reconstructed_buffer[i - 1];//范围:-255-255
			int dn_after_Q = quantify(dn);//量化到0-255
			residual_buffer[i] = dn_after_Q;//存入残差
			reconstructed_buffer[i] = residual_buffer[i] * 2 + reconstructed_buffer[i - 1];//重建
		}

	}

}

量化函数的实现,则是对 [ − 255 , 255 ] [-255,255] [255,255]的数据先进行右移 1 1 1位,再 + 128 +128 +128,并做上下限,将数据范围变到 [ 0 , 255 ] [0,255] [0,255]中。

unsigned char quantify(int dn) {
	/*
	:function 该函数用于对输入dn进行值的量化
	:return  量化后的值(unsigned char)
	*/
	dn >>= 1;
	int temp = dn + 128;
	if (temp > 255) temp = 255;
	if (temp < 0) temp = 0;
	return temp;
}

2.2 1/2/4 比特的量化设计

先前的运算是将一个9比特的数压缩到8比特(1字节)的量化过程。由于采用右移运算符,量化为1/2/4 比特也非常简单。我们只需要改写quantify()函数即可。

2.2.1 4bits

量化为1bit,我们只需要将原始9位值右移5位,即可获得4bit的数值。然后将范围进行一个线性映射。
修改量化函数:

unsigned char quantify(int dn) {
	dn >>= 5;//范围-16-16
	dn += 16;//0-32
	
	return dn;
}

反量化处乘在取用residual时16即可。


2.2.2 2bits

与上原理完全一致:

unsigned char quantify(int dn) {
	dn >>= 7;//范围-2~2
	dn += 2;//0-4
	
	return dn;
}

反量化处乘在取用residual时64即可。


2.2.3 1bit

量化为1bit,我们只需要将原始值右移8位。图像被二值化,只有两种颜色了。也不需要做值的偏移和防溢出了。反量化时,也只会有两种值,非黑即白。1bit我们还需要对原始值做一个二值化。残差要么是0要么是1。0表示与前值相同,1表示与前值不同。

unsigned char quantify(int dn) {
	dn >>= 8;//范围-1~0
	dn += 1;//0-1
	
	return dn;
}

图像二值化的过程:

	for (int i = 0; i < yuvsize; i++) {
		if (origin_Buffer[i] >= 127) origin_Buffer[i] = 255;
		else origin_Buffer[i] = 0;
	}

反量化则是进行if else的判断,在此略去啦。


3 附录

3.1 输出csv文件

	YUV_out << "编码内容,出现次数" << endl;
	for (int i = 0; i < 256; i++) {
		YUV_out << i << "," << distribution_Y[i] << endl;
	}

3.2 Python作图

# -*- coding:utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from pandas import read_csv

origin = open('原图.csv')
residual = open('残差图像.csv')
origin_data = read_csv(origin)
residual_data = read_csv(residual)
plt.rcParams['font.sans-serif'] = ['Songti SC']  # 指定默认字体

# YUV的作图同下
fig, rgb = plt.subplots()
rgb.plot(origin_data['编码内容'], origin_data['出现次数'] / origin_data['出现次数'].sum(), label='原图', color='red')
rgb.plot(residual_data['编码内容'], residual_data['出现次数'] / residual_data['出现次数'].sum(), label='残差图', color='blue')
rgb.set_xlabel('像素灰度值/残差值')
rgb.set_ylabel('出现频率')
rgb.set_title('灰度值/残差值频率分布图')
rgb.legend()
plt.show()

result = 0
pr = residual_data['编码内容'] / residual_data['编码内容'].sum()
it = iter(pr)  # 创建迭代器对象
for x in pr:
    if x != 0:
        result += -x * np.log2(x)
print(result)

3.3 DPCM实现

# include<iostream>
# include <fstream>
using namespace std;



unsigned char quantify(int dn) {
	/*
	:function 该函数用于对输入dn进行值的量化
	:return  量化后的值(unsigned char)
	*/
	dn /= 2;
	int temp = dn + 128;
	if (temp > 255) temp = 255;
	if (temp < 0) temp = 0;
	return temp;
}


void getDPCMResidual(unsigned char * origin_Buffer, unsigned char* residual_buffer,unsigned char * reconstructed_buffer,int width,int height) {
	/*
	:function 该函数采用左侧的像素对图像进行DPCM编码。
	:return  无
	:output  该函数可以输出残差图像、重建图像
	*/
	int yuvsize = width * height;
	for (int i = 0; i < yuvsize; i++) {
		if (i % width == 0) { //若是每行第一个像素
			reconstructed_buffer[i] = origin_Buffer[i];
			residual_buffer[i] = origin_Buffer[i];
		}
		else {
			int dn = origin_Buffer[i] - reconstructed_buffer[i - 1];//范围:-255-255
			int dn_after_Q = quantify(dn);//量化到0-255
			residual_buffer[i] = dn_after_Q;//存入残差
			reconstructed_buffer[i] = residual_buffer[i] * 2 + reconstructed_buffer[i - 1];//重建
		}

	}

}


void output_csv(unsigned char* origin_Buffer, unsigned char* residual_buffer) {
	/*
	:output 导出数据便于分析
	*/
	RGB_out << "R,G,B" << endl;
	for (int i = 0; i < 256; i++) {
		RGB_out << distribution_R[i] << "," << distribution_G[i] << "," << distribution_B[i] << endl;
	}
	YUV_out << "Y,U,V" << endl;
	for (int i = 0; i < 256; i++) {
		YUV_out << distribution_Y[i] << "," << distribution_U[i] << "," << distribution_V[i] << endl;
	}

}
int main(int argc, char** argv) {
/*
:input 输入的yuv图像名 输出的yuv残差文件名 yuv图像的宽和高
:output 输出的yuv残差文件
*/
	ifstream originYUV(argv[1], ios::binary);
	ofstream residualYUV(argv[2], ios::binary);
	ofstream reconstructedYUV("重建图像.yuv", ios::binary);
	if (!originYUV || !residualYUV || !reconstructedYUV) {
		cout << "open file failed!" << endl;
		return 0;
	}
	int width = atoi(argv[3]);
	int height = atoi(argv[4]);
	int size = width * height;
	int yuvsize = size; //我们无需考虑UV通道,只需考虑Y分量即可,UV通道置为128
	unsigned char* origin_Buffer = new unsigned char[yuvsize]; 
	unsigned char* residual_buffer = new unsigned char[yuvsize];
	unsigned char* reconstructed_buffer = new unsigned char[yuvsize];

	originYUV.read((char*)origin_Buffer, yuvsize);

	getDPCMResidual(origin_Buffer, residual_buffer, reconstructed_buffer, width, height);

	residualYUV.write((char*)residual_buffer, size);
	reconstructedYUV.write((char*)reconstructed_buffer, size);

	unsigned char* temp = new unsigned char[yuvsize/2];
	for (int i = 0; i < yuvsize / 2; i++) {
		temp[i] = 128;
	}

	output_csv(origin_Buffer, residual_buffer);

	residualYUV.write((char*)temp, size / 2);//写UV数据
	reconstructedYUV.write((char*)temp, size / 2);
	delete[] origin_Buffer;
	delete[] residual_buffer;
	delete[] reconstructed_buffer;
	reconstructedYUV.close();
	originYUV.close();
	residualYUV.close();

	return 0;
}

3.4 计算PSNR

import numpy as np

fraw = open("原始图像.yuv", "rb")
frec = open("重建图像.yuv", "rb")
raw = []
rec = []

i = 0
while i < 256 * 256:
    i += 1
    buf1 = fraw.read(1)
    buf2 = frec.read(1)
    if buf1:
        buf1 = int.from_bytes(buf1, byteorder='big')
        buf2 = int.from_bytes(buf2, byteorder='big')
        raw.append(buf1)
        rec.append(buf2)

MSE = 0
for idx, _ in enumerate(raw):
    MSE += (raw[idx] - rec[idx]) ** 2

MSE /= (256 * 256)

PSNR= 10 * np.log10((255**2)/MSE)
print(PSNR)

4 参考文献

  1. Python处理字节及进制转换的一些土方法
  2. 图像质量评价指标之 PSNR 和 SSIM
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值