使用C++实现DPCM编码(左向预测8bit、4bit、2bit、1bit和上向预测8bit)
一、DPCM是什么?
DPCM–差分脉冲编码调制(Differential Pulse code modulation,DPCM),是一种对模拟信号的编码模式,与PCM不同每个抽样值不是独立的编码,而是先根据前一个抽样值计算出一个预测值,再取当前抽样值和预测值之差作编码用。此差值称为预测误差.抽样值和预测值非常接近(因为相关性强),预测误差的可能取值范围比抽样值变化范围小。所以可用少几位编码比特来对预测误差编码,从而降低其比特率.这是利用减小冗余度的办法,降低了编码比特率。
➢ 根据某一模型利用以往的样本值对新样本进行预测,然后将样本的实际值与其预测值相减得到一个误差值,最后对这一误差值进行编码。
➢ 如果模型足够好,且样本序列在时间上相关性较强,则误差信号的幅度将远远小于原始信号,从而得到较大的数据压缩。
在DPCM系统中,需要注意的是预测器的输入是已经解码以后的样本。之所以不用原始样本来做预测,是因为在解码端无法得到原始样本,只能得到存在误差的样本。因此,在DPCM编码器中实际内嵌了一个解码器,如编码器中虚线框中所示。
二、使用C++实现DPCM编码(左向预测8bit、4bit、2bit、1bit和上向预测8bit)
1、实验数据描述
此次用的是Lena.yuv,YUV灰度图像,如下图
因为所用图片为灰度图像,所以需要先把文件中所有值赋为128,以便将UV分量填满。
//给Buf128全赋值为128
for (int i = 0; i < frameWidth * frameHeight / 2; i++)
{
Buf128[i] = 128;
}
2、左向预测8bit、4bit、2bit、1bit
1)左向预测8bit
因为是左向预测,所以图片中每一行的第一个值无法预测,所以直接将预测值设为本身的值,插值设为0。
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
若不是每一行的第一个值,那就需要先先计算插值a,将这个像素值减去左侧像素的预测值。但是因为图像是8bit(灰度值为0-255),所以如果相减值就会变为-255——+255因此如果想进行8bit量化,需要将插值a/2+127(更新插值d=a/2+127)(量化插值b=a/2)。之后需要进行反量化值c,将量化插值b乘2(c=b*2)。之后再将反量化值c与左侧像素预测值相加得到本像素预测值
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 2;//进行量化
c = b * 2;//进行反量化
d = b + 128;//进行+128
yChazhiBuf[j + i * frameWidth] = d;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
2)左向预测4bit
4bit量化与8bit量化的区别在于需要将插值a(-255–+255)转变为4bit插值d,所以需要将插值a/32+7(更新插值d=a/32+7)(量化插值b=a/32),。之后需要进行反量化值c,将插值a乘16(c=b*16)。之后再将反量化值c与左侧像素预测值相加得到本像素预测值
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 32;//进行量化
c = b * 32;//进行反量化
d = b + 7;//进行+7
yChazhiBuf[j + i * frameWidth] = d*16;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
3)左向预测2bit
2bit量化与8bit量化的区别在于需要将插值a(-255–+255)转变为2bit插值d,所以需要将插值a/128+2(更新插值d=a/128+2)(量化插值b=a/128),。之后需要进行反量化值c,将插值a乘128(c=b*128)。之后再将反量化值c与左侧像素预测值相加得到本像素预测值
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 128;//进行量化
c = b * 128;//进行反量化
d = b + 2;//进行+2
yChazhiBuf[j + i * frameWidth] = d*64;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
4)左向预测1bit
1bit量化与8bit量化的区别在于需要将插值a(-255–+255)转变为1bit插值d,所以需要将插值a/256+1(更新插值d=a/256+1)(量化插值b=a/256)。之后需要进行反量化值c,将插值a乘256(c=b*256)。之后再将反量化值c与左侧像素预测值相加得到本像素预测值
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 256;//进行量化
c = b * 256;//进行反量化
d = b + 1;//进行+1
yChazhiBuf[j + i * frameWidth] = d*256;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
5)左向预测8bit、4bit、2bit、1bit整体代码
//左向预测8bit、4bit、2bit、1bit-计算差值与预测值
for (int i = 0; i < frameHeight; i++)
{
for (int j = 0; j < frameWidth; j++)
{
if (x == 8)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 2;//进行量化
c = b * 2;//进行反量化
d = b + 128;//进行+128
yChazhiBuf[j + i * frameWidth] = d;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
if (x == 4)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 16;//进行量化
c = b * 16;//进行反量化
d = b + 16;//进行+64
yChazhiBuf[j + i * frameWidth] = d*8;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
if (x == 2)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 64;//进行量化
c = b * 64;//进行反量化
d = b + 4;//进行+4
yChazhiBuf[j + i * frameWidth] = d*32;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
if (x == 1)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 128;//进行量化
c = b * 128;//进行反量化
d = b + 2;//进行+2
yChazhiBuf[j + i * frameWidth] = d*64;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
}
}
3、上向预测8bit
因为是上向预测,所以图片中每一列的第一个值无法预测,所以直接将预测值设为本身的值,插值设为0。
若不是每一列的第一个值,那就需要先先计算插值a,将这个像素值减去上侧像素的预测值。但是因为图像是8bit(灰度值为0-255),所以如果相减值就会变为-255——+255因此如果想进行8bit量化,需要将插值a/2+127(更新插值d=a/2+127)(量化插值b=a/2),。之后需要进行反量化值c,将量化插值b乘2(c=b*2)。之后再将反量化值c与上侧像素预测值相加得到本像素预测值
//上预测8bit
for (int i = 0; i < frameWidth; i++)
{
for (int j = 0; j < frameHeight; j++)
{
if (x == 99)
{
if (j == 0)
{
yYuceBuf[i] = yBuf[i];
yChazhiBuf[i] = 0;
}
else
{
a = yBuf[i + j * frameWidth] - yYuceBuf[i + (j - 1) * frameWidth];//计算插值
b = a / 2;//进行量化
c = b * 2;//进行反量化
d = b + 127;//进行+127
yChazhiBuf[i + j * frameWidth] = d;
yYuceBuf[i + j * frameWidth] = yYuceBuf[i + (j - 1) * frameWidth] + c;
}
}
}
}
4、整体代码
#include "iostream"
#include"stdio.h"
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include"math.h"
#pragma warning (disable:4996)
using namespace std;
#define u_int8_t unsigned __int8
#define u_int unsigned __int32
int main(int argc, char** argv)
{
char* yuvFileName = NULL;
char* yuceFileName = NULL;
char* chazhiFileName = NULL;
u_int8_t* yBuf = NULL;
u_int8_t* yuvBuf = NULL;
u_int8_t* yYuceBuf = NULL;
u_int8_t* yChazhiBuf = NULL;
u_int8_t* Buf128 = NULL;
FILE* yuvFile = NULL;
FILE* chazhiFile = NULL;
FILE* yuceFile = NULL;
u_int frameWidth = 0;
u_int frameHeight = 0;
int a = 0;//量化前插值
int b = 0;//量化后值
int c = 0;//反量化后值
int d = 0;//+128之后值
int x = 0;//选择4bit,8bit量化
//读取数据yuv文件数据
yuvFileName = argv[1];
yuceFileName = argv[2];
chazhiFileName = argv[3];
frameWidth = atoi(argv[4]);
frameHeight = atoi(argv[5]);
yuvFile = fopen(yuvFileName, "rb");
yuvBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3 / 2);
yBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
yYuceBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
yChazhiBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
Buf128 = (u_int8_t*)malloc(frameWidth * frameHeight / 2);
fread(yuvBuf, 1, frameWidth * frameHeight * 3 / 2, yuvFile);
yuceFile = fopen(yuceFileName, "wb");
chazhiFile = fopen(chazhiFileName, "wb");
//给Buf128全赋值为128
for (int i = 0; i < frameWidth * frameHeight / 2; i++)
{
Buf128[i] = 128;
}
//读取y数据
for (int i = 0; i < frameWidth * frameHeight; i++)
{
yBuf[i] = yuvBuf[i];
}
//选择4bit,8bit量化,2bit量化,1bit量化
while ((x != 4) && (x != 8) && (x != 2) && (x != 1) && (x != 99))
{
cout << "如想左向预测:选择8bit量化或者4bit量化或者2bit量化或者1bit量化,请输入“8”或“4”或“2”或“1”" << endl<< "如想上向预测请输入“99”(8bit)" << endl;
cin >> x;
if (x == 8)
{
cout << "您选择了左向预测8bit量化";
}
else if (x == 4)
{
cout << "您选择了左向预测4bit量化";
}
else if (x == 2)
{
cout << "您选择了左向预测2bit量化";
}
else if (x == 1)
{
cout << "您选择了左向预测1bit量化";
}
if (x == 99)
{
cout << "您选择了上向预测";
}
else
{
cout << "您输入有误请重新输入";
}
}
//左向预测8bit、4bit、2bit、1bit-计算差值与预测值
for (int i = 0; i < frameHeight; i++)
{
for (int j = 0; j < frameWidth; j++)
{
if (x == 8)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 2;//进行量化
c = b * 2;//进行反量化
d = b + 128;//进行+128
yChazhiBuf[j + i * frameWidth] = d;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
if (x == 4)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 16;//进行量化
c = b * 16;//进行反量化
d = b + 16;//进行+64
yChazhiBuf[j + i * frameWidth] = d*8;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
if (x == 2)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 64;//进行量化
c = b * 64;//进行反量化
d = b + 4;//进行+4
yChazhiBuf[j + i * frameWidth] = d*32;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
if (x == 1)
{
if (j == 0)
{
yYuceBuf[j + i * frameWidth] = yBuf[j + i * frameWidth];
yChazhiBuf[j + i * frameWidth] = 0;
}
else
{
a = yBuf[j + i * frameWidth] - yYuceBuf[j + i * frameWidth - 1];//计算插值
b = a / 128;//进行量化
c = b * 128;//进行反量化
d = b + 2;//进行+2
yChazhiBuf[j + i * frameWidth] = d*64;
yYuceBuf[j + i * frameWidth] = yYuceBuf[j + i * frameWidth - 1] + c;
}
}
}
}
//上预测
for (int i = 0; i < frameWidth; i++)
{
for (int j = 0; j < frameHeight; j++)
{
if (x == 99)
{
if (j == 0)
{
yYuceBuf[i] = yBuf[i];
yChazhiBuf[i] = 0;
}
else
{
a = yBuf[i + j * frameWidth] - yYuceBuf[i + (j - 1) * frameWidth];//计算插值
b = a / 2;//进行量化
c = b * 2;//进行反量化
d = b + 128;//进行+128
yChazhiBuf[i + j * frameWidth] = d;
yYuceBuf[i + j * frameWidth] = yYuceBuf[i + (j - 1) * frameWidth] + c;
}
}
}
}
fwrite(yYuceBuf, frameWidth * frameHeight, 1, yuceFile);
fwrite(yChazhiBuf, frameWidth * frameHeight, 1, chazhiFile);
fwrite(Buf128, frameWidth * frameHeight / 2, 1, chazhiFile);
fwrite(Buf128, frameWidth * frameHeight / 2, 1, yuceFile);
fclose(chazhiFile);
fclose(yuceFile);
fclose(yuvFile);
free(yBuf);
free(yuvBuf);
free(yYuceBuf);
free(yChazhiBuf);
free(Buf128);
return 0;
}
5、实验结果分析
1)左向预测8bit、4bit、2bit、1bit结果对比
由图像可知,量化bit数的减少会导致插值边缘越来越不明显,并且得到的恢复图像与原始图像的差距也越来越大,与预期相符
1)上向预测8bit与左向预测8bit对比
由图可知,圈出的地方,上向预测是白,左向预测是黑,因为通过原图可看出,在这个部分,下侧像素比上侧像素要白,也就是下侧灰度值(down)大于上册灰度值(up),所以up-down为正值因此为白;而这个部分的右侧像素比左侧像素黑,也就是右侧像素值(right)小于左侧像素值(left),所以right-left为负值因此为黑。这样也就能分辨出上向预测与左向预测。
三、归纳总结
通过此次实验进一步了解DPCM编码,并完成了DPCM左向8bit、4bit、2bit、1bit以及上向8bit的代码实现。同时,经过详细分析左向预测8bit、4bit、2bit、1bit和上向预测8bit的实验结果,明白了其之间的差异,并会通过结果区分左向预测与上向预测。并且熟悉巩固了C++语言的使用,为后续进一步学习数据压缩打下了基础。