Lab4 DPCM 压缩系统的实现和分析——C语言代码实现
一、实验原理
原理实现框图如下:
- 将输入的像素值与前一个像素值的预测值作差,并对差值进行编码
- 编码后的差值一路直接输出
- 编码后的差值另一路通过解码器解出差值,并与前一个像素的预测值相加,得到当前像素的预测值
需要注意的是:在DPCM编码器中实际内嵌了一个解码器,如编码器中虚线框中所示。
举例说明:
二、实验内容
实验目标:验证DPCM编码的编码效率。
- 先读取一个256级的灰度图像
- 根据自己设定的预测方法计算预测误差,并进行量化
- 在DPCM编码器实现的过程中可同时输出预测误差图像和重建图像
- 将预测误差图像写入文件并将该文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比
- 将原始图像文件输入Huffman编码器,得到输出码流、给出概率分布图并计算压缩比
- 比较两种系统的编码效率,如压缩比和压缩质量(PSNR)
本实验采用8bit量化,分别进行向左和向上的预测。
三、实现代码
(头文件省略)
1、向左进行DPCM
void DPCMLeft(int Width,int Height,void *yBuff,void *recBuff,void *errBuff)//DPCM向左预测
{
unsigned char *yB=NULL;
yB = (unsigned char *)yBuff;
unsigned char *recB=NULL;
recB = (unsigned char *)recBuff;
unsigned char *errB=NULL;
errB = (unsigned char *)errBuff;
int P1,P2;//P1为当前值与预测值的误差,P2为量化后的误差
unsigned char P3;//P3为反量化后的误差
for(int i=0;i<Height;i++)
{
for(int j=0;j<Width;j++)
{
if(j == 0)//向左进行预测时,图像最左边一列的像素值直接输出,无需进行差分预测
{
*(recB+j+i*Width)=*(yB+j+i*Width);//当前值即为重建值,作为下一个像素的参考值
*(errB+j+i*Width)=0;//误差为0
}
else//当不是最左边一列的像素时,进行DPCM
{
P1=*(yB+j+i*Width)-*(recB+(j-1)+i*Width);//求当前值与参考值的差值
if(P1%2==0)//对差值进行8bit均匀量化,并进行+128的偏移以输出
P2=P1/2+128;
else
P2=(P1-1)/2+128;
*(errB+j+i*Width)=unsigned char(P2);//将误差写入errB缓存区域
P3=unsigned char(P2*2);//对量化后的误差反量化
*(recB+j+i*Width)=*(recB+(j-1)+i*Width)+P3;
//将参考值与反量化得到的误差相加,作为当前像素的重建值,即下一个像素的参考值
}
}
}
}
2、向上进行DPCM
void DPCMUp(int Width,int Height,void *yBuff,void *recBuff,void *errBuff)//DPCM向上预测
{
unsigned char *yB=NULL;
yB = (unsigned char *)yBuff;
unsigned char *recB=NULL;
recB = (unsigned char *)recBuff;
unsigned char *errB=NULL;
errB = (unsigned char *)errBuff;
int P1,P2;//P1为当前值与预测值的误差,P2为量化后的误差
unsigned char P3;//P3为反量化后的误差
for(int i=0;i<Height;i++)
{
for(int j=0;j<Width;j++)
{
if(i == 0)//向上进行预测时,图像最上边一列的像素值直接输出,无需进行差分预测
{
*(recB+j+i*Width)=*(yB+j+i*Width);//当前值即为重建值,作为下一个像素的参考值
*(errB+j+i*Width)=0;//误差为0
}
else//当不是最上边一列的像素时,进行DPCM
{
P1=*(yB+j+i*Width)-*(recB+j+(i-1)*Width);//求当前值与参考值的差值
if(P1%2==0)//对差值进行8bit均匀量化,并进行+128的偏移以输出
P2=P1/2+128;
else
P2=(P1-1)/2+128;
*(errB+j+i*Width)=unsigned char(P2);//将误差写入errB缓存区域
P3=unsigned char(P2*2);//对量化后的误差反量化
*(recB+j+i*Width)=*(recB+j+(i-1)*Width)+P3;
//将参考值与反量化得到的误差相加,作为当前像素的重建值,即下一个像素的参考值
}
}
}
}
3、PSNR计算
int simplest_yuv420_psnr(void *yBuff1,void *yBuff2, int w, int h, int num)//计算Y分量的PSNR
{
unsigned char *yB1=NULL;
yB1 = (unsigned char *)yBuff1;
unsigned char *yB2=NULL;
yB2 = (unsigned char *)yBuff2;
for (int i = 0; i < num; i++)
{
double mse_sum = 0, mse = 0, psnr = 0;
for (int j = 0; j < h ; j++)
{
for (int k = 0; k < w; k++)
{
mse_sum += pow((double)(*(yB1+k+j*w) - *(yB2+k+j*w)), 2);//取每个差值的平方,并进行累加
}
}
mse = mse_sum / (w * h); //根据公式计算mse
psnr = 10 * log10(255.0 * 255.0 / mse); //根据公式计算psnr
printf("%5.3f\n", psnr);
}
system("pause");
return 0;
}
4、主函数
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <malloc.h>
#include <string.h>
#include"DPCM.h"
int main(int argc, char** argv)
{
int frameWidth;
int frameHeight;
char* yFileName = NULL;//原始的灰度文件
FILE* yFile = NULL;
unsigned char* y_Buf = NULL;
char* recFileName = NULL;//重建的图像文件
FILE* recFile = NULL;
unsigned char* rec_Buf = NULL;
char* errFileName = NULL;//量化后的预测误差文件
FILE* errFile = NULL;
unsigned char* err_Buf = NULL;
unsigned char* u_Buf = NULL;
unsigned char* v_Buf = NULL;
yFileName = argv[1];
recFileName = argv[2];
errFileName = argv[3];
frameWidth = atoi(argv[4]);
frameHeight = atoi(argv[5]);
fopen_s(&yFile,yFileName, "rb");//打开灰度文件
if (yFile == NULL)
{
printf("cannot find y file\n");
exit(1);
}
recFile = fopen(recFileName, "wb");//打开重建图像文件
if (recFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
errFile = fopen(errFileName, "wb");//打开量化后的误差文件
if (errFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
y_Buf = (unsigned char*)malloc(frameWidth * frameHeight * sizeof(unsigned char)); //开辟5个缓存空间
u_Buf = (unsigned char*)malloc(frameWidth * frameHeight * sizeof(unsigned char) / 4);
v_Buf = (unsigned char*)malloc(frameWidth * frameHeight * sizeof(unsigned char) / 4);
rec_Buf = (unsigned char*)malloc(frameWidth * frameHeight * sizeof(unsigned char));
err_Buf = (unsigned char*)malloc(frameWidth * frameHeight * sizeof(unsigned char)*1.5);
if (y_Buf == NULL || rec_Buf == NULL || err_Buf == NULL || u_Buf == NULL ||v_Buf == NULL)
{
printf("wrong malloc\n");
exit(1);
}
fread(y_Buf, 1, frameWidth * frameHeight, yFile);//读取灰度文件数据
if (y_Buf == NULL)
{
printf("wrong fread\n");
exit(1);
}
//DPCMLeft(frameWidth,frameHeight,y_Buf,rec_Buf,err_Buf);//向左进行预测
DPCMUp(frameWidth,frameHeight,y_Buf,rec_Buf,err_Buf);//向上进行预测
for(int i=0;i<frameHeight/2;i++)
{
for(int j=0;j<frameWidth/2;j++)
{
*(u_Buf+j+i*frameWidth/2)=128;
*(v_Buf+j+i*frameWidth/2)=128;
}
}
fwrite(rec_Buf, 1, frameWidth * frameHeight, recFile);//将数据写入文件进行输出
fwrite(err_Buf, 1, frameWidth * frameHeight, errFile);
fwrite(u_Buf, 1, frameWidth * frameHeight / 4, errFile);
fwrite(v_Buf, 1, frameWidth * frameHeight / 4, errFile);
//计算PSNR
simplest_yuv420_psnr(y_Buf,rec_Buf,frameWidth,frameHeight,1);
free(y_Buf);
free(rec_Buf);
free(err_Buf);
free(u_Buf);
free(v_Buf);
fclose(yFile);
fclose(recFile);
fclose(errFile);
}
5、计算Y分量的概率分布
参考1.2 分析RGB和YUV文件三个通道的概率分布,并计算各自的熵(编程实现)
四、实验结果
本次实验用Lena256B.yuv进行验证,Lena256B.yuv的大小为98,304 字节。
向左预测量化后的误差图像err_L.yuv和向上预测量化后的误差图像err_L2.yuv的大小均为98,304 字节。
原图进行huffcode | 向左预测量化后的误差图像进行huffcode | 向上预测量化后的误差图像进行huffcode | |
---|---|---|---|
文件图片 | |||
概率分布图 | |||
熵编码后文件大小 | 69,885 字节 | 45,410 字节 | 39,966 字节 |
压缩比 | 71.09% | 46.19% | 40.66% |
PSNR | ∞ | 51.177 | 51.185 |
总结:
- 经过熵编码之后,图像的大小会被压缩。
- 经过DPCM+熵编码之后,图像大小比直接使用熵编码减小的更多。
- 经过DPCM+熵编码的PSNR值比只经过熵编码的PSNR值小,表明经过DPCM预测编码后重建出的图像质量比原始图像的质量差。
- 理论上,量化比特数的影响:随着量化比特数的减小,压缩效率应该越来越高,但同时PSNR的值应该越来越小,表明重建出的图像质量应该越来越差。
综上,量化应采用合适的量化比特数,既保证画面质量,又可以达到较高的压缩效率。