一、实验原理
1. 预测编码
在数字图像中,如果不是随机的噪声,那么每个像素与其周围的像素都会存在着一定的关联,像素值很大程度上依赖于其邻域中其它像素的值。也就是预测误差(在这个实验中用当前像素值与前一个像素值的差来表示)应该非常接近,通常比单个的像素值要小。因此如果只存储预测误差,由预测误差也可以重构出原图像,而且这样可以降低图像中的冗余信息,实现图像的压缩。
如果用前面几个样值的线性组合来预测当前的样值,称为线性预测,只用前一个样值进行预测,就称为 DPCM 。
2. DPCM 编解码框图
从框图中可以看出,由于需要更新预测值,编码器中已经内嵌了一个解码器。
二、实验流程及代码分析
从框图可以看出,本实验的编码流程如下
实验用图像均为 BMP 格式,使用实验二中的方法读入 BMP 图像并提取出亮度通道 Y 。在下面的代码中, Y 通道的值已经存在了 yBuf 中。另外在每一行中,由于第一个像素的前面没有像素,因此假设它的预测值为平均灰度值 128 。
//命令行参数:输入图像名、误差图像名、重建图像名、量化比特数
unsigned char g(int x) //防止超过 0 到 255 的动态范围
{
if (x < 0) return 0;
if (x > 255) return 255;
return x;
}
...
int Qbits = atoi(argv[4]); // Qbits 为量化比特数
...
unsigned char* diffBuf = (unsigned char*)malloc(width*height); //量化预测误差存储区
unsigned char* reBuf = (unsigned char*)malloc(width*height); //重建图像存储区
int ek; //原始的预测误差值,范围从 -255 到 +255
int scale = 512 / (1<<Qbits); //误差值的压缩比率
for (i = 0; i < height; i++) //行循环
{
//对每行第一个像素的处理
ek = yBuf[width*i] - 128; //假设第一个像素的预测值为128
diffBuf[width*i] = (ek + 255) / scale; //量化后的预测误差
reBuf[width*i] = g((diffBuf[width*i] - 255 / scale)*scale + 128); //根据预测误差再重建当前电平值
for (j = 1; j < width; j++) //列循环
{
//后面的像素与第一个像素处理方法一样
ek = yBuf[j + width*i] - reBuf[j - 1 + width*i];
diffBuf[j + width*i] = (ek + 255) / scale;
reBuf[j + width*i] = g((diffBuf[j + width*i] - 255 / scale)*scale + reBuf[j - 1 + width*i]);
}
}
代码很短,外层循环为图像中每一行的循环,每行起始像素的预测值设为 128,单独对其进行预测并重建。首先得到差值 ek,像素灰度值范围从 0 到 255,那么两个像素灰度值之差 ek 的范围从 -255 到 255。由于要存储误差图像,那么先把负灰度值变成正的,也就是 ek + 255,在 0 到 510 这个区间内再做量化,区间长度可以看成 9 bit,512 级。
要将预测误差作为一幅图像显示以观察预测结果,那么最基本的要做 8 bit量化,将误差范围压缩在 0 到 255 之间,也就是误差值除以 2 下取整。 7 bit 量化,误差值要除以 4 下取整。因此定义了变量 scale 用于对误差值进行量化,量化预测误差存储到 diffBuf 中。
最后要根据量化值重建当前像素值以用于下一个像素的预测。 误差已经量化了,因此要得到原本的误差要先乘以比率 scale 再减去 255 ,就得到了真实的量化误差。但是对比一下这样两种代码
y[i] = diff * scale - 255 + y[i - 1];
y[i] = (diff - 255/scale) * scale + y[i - 1];
对于量化比特数的不同,误差移动的范围也不同。在对误差的量化,也就是 (ek + 255) / scale 中,如果是 4 bit 量化,scale = 32,那么 ek = 0 对应 7,重建时 7×32-255 = -31,对应上面的第一种写法,重建结果不对。这种问题的原因在于端点值 255 也被量化了,由于取整的存在,重建时的正确表达式应为 (7-255/32)×32 = 0,也就是第二种写法。
最后还要解决一个问题,就是这样做可能会超出动态范围。考虑图像不使用差分编码,直接量化:
int scale = 256 / (1 << (Qbits - 1));
reBuf[j + width*i] = yBuf[j + width*i] / scale*scale;
如果对误差图像进行量化,也就是要对 512 级灰度进行量化,而对原图像的直接量化是对 256 级灰度量化,因此如果要得到相同的灰度级数,直接量化的量化比特数要比误差的量化比特数少 1。表 1 为一个量化比特数为 3 的示例,也就是直接量化为 2 bit 量化,共有 4 级灰度。
原始图像 | 直接量化图像 | 误差量化后重建图像 | 预测误差 |
---|---|---|---|
三、实验结果
1. 两种方法编码效率的对比
差分预测编码可降低图像中的冗余信息,那么对量化后的误差图像进行实验三中的 Huffman 编码可以得到比直接进行 Huffman 编码更高的压缩效率。表 2 中为几种图像的量化误差分布情况,表 3 为两种压缩方法的对比。
原始图像 | 量化误差图像 | 重建图像 | 误差频率分布 |
---|---|---|---|
原始图像 | 原始图像大小(KB) | 直接压缩后的大小(KB) | 压缩率 | 预测误差图像大小(KB) | 预测误差压缩后的大小(KB) | 压缩率 |
---|---|---|---|---|---|---|
1 | 66 | 13 | 5.07 | 64 | 9 | 7.11 |
2 | 98 | 90 | 1.09 | 96 | 47 | 2.04 |
3 | 66 | 64 | 1.03 | 64 | 34 | 1.88 |
4 | 66 | 60 | 1.10 | 64 | 63 | 1.01 |
5 | 66 | 64 | 1.03 | 64 | 64 | 1.00 |
2. 不同量化比特数的对比
上表中都是对误差进行 8 bit 量化,下面的表 4 进行了不同量化比特数之间的对比,图 1 为原始图像。
量化比特数 | 量化误差图像 | 重建图像 | 误差频率分布 |
---|---|---|---|
8 | |||
6 | |||
4 | |||
3 | |||
2 |
p[j + width*i] = diffBuf[j + width*i] * scale / 2;
比如 4 bit 量化的误差图像,量化误差全部变换成正值后最大值为 16,那么把最大值乘以之前定义的缩放比率 scale(512 / 2^n),这里是 32 ,再除以 2 之后就变到了 0 ~ 255 区间内。量化比特数越少,误差 0 对应的灰度值就越小,4 bit 量化时误差 0 对应量化值 7,在误差图像中为 7 × 16 = 112,而 8 bit量化时误差 0 对应量化值 127,在误差图像中也为 127,因此表格中从上到下误差图像会越来越暗。