前言
这次只是对之前两次编码实验的一个小小的补充,没有很多的内容。而且临近期末了,我也不是很想去写一些很复杂的优化策略了,只用最最简单的差值预测来进行编码优化。经测试赫夫曼编码的压缩效率提高了很多,LZW依旧取决于图像,但是不会出现反向压缩的问题了。
编码优化
上次在进行LZW编解码时我提到过有反向压缩的问题,给出了一种很麻烦的解决方法,就是用变长的码表来逐一试,最终试出效果最好的那个。然而你要是全自动的试也就算了,bitset
还只能手动编译,因此这种方法注定只能是权宜之计。
不出所料,这周的课程就讲了如何优化编码的效果。不过和我预想的计算可以计算最佳码表长度不一样,我们的优化是通过降低信息熵实现的。
1、无损预测编码
信息熵实际上就是衡量信息量多少的一个量化标准,根据最优编码理论,信息熵是图像压缩的下限,也就是说不论你用什么样的方法,都不可能在不损失信息的情况下,将图片的平均码长缩短到小于信息熵的程度。
信息熵H和平均码长L的计算公式如下:
H ( X ) = − ∑ i = 1 k p ( x i ) l o g p ( x i ) H(X)=-\sum_{i=1}^{k}p(x_i)logp(x_i) H(X)=−i=1∑kp(xi)logp(xi)
L = ∑ i = 1 k p ( s i ) l i L=\sum_{i=1}^{k}p(s_i)l_i L=i=1∑kp(si)li
x i x_i xi表示像素值, s i s_i si表示编码, l i l_i li是码长,对于灰度图像而言就是对0-255的像素值的对应项求和。
但是往往我们的要求就是这么高,既不想损失信息,又想要尽可能地压缩,那咋办嘛?聪明的前人想到了很多种方法,它们可以暂时的将图像的信息熵减小,解码之后再复原就好了。预测编码就是其中的一个大类,而我实现的,是最不需要计算量的一种,其原理图如下:
f
(
i
)
f(i)
f(i)就是原始的图像数据,我们将首位设为0,然后将原始图像的像素值错位的赋值给后续位置。最后用原始数据减去错位后的数据,就可以得到一个信息熵减小的数据
e
(
i
)
e(i)
e(i)了。原始图片的信息熵较大,是因为像素值的范围、起伏都比较大,我们通过这样的错位相减,可以使图像整体的像素分布更加集中(你可以用实验二中的方法绘制直方图,效果很明显)。
而且这样变化之后还是可以复原的,复原过程如下图:
这个减小信息熵过程,叫做预测器处理,我们的算法就是预测器。用不同的预测器会有不同的效果,上面演示的属于差值预测,还有另一大类的方法叫做线性预测,一般会按照预测值的数量划分为一维、二维预测等,不再赘述了。
2、有损预测编码
既然有无损压缩这样的方法,那对应的也就有有损压缩算法。不是每个人都是列文虎克写轮眼,对于类似表情包这样的图片,我们只需要看缩略图就明白对面是什么意思了,不需要放大再放大。因此与其追求两全,不如省点流量,把图像尽可能的压缩,这就是有损预测的初衷。
有损预测为什么会有损呢?是因为它在预测器的基础上添加了一个量化器。当然这个量化也分为标量量化和矢量量化,这里只讨论标量量化。
我给你说量化器干的事,你就知道损失在哪了。编码的时候,量化器对所有像素值进行除以10后取整操作。解码的时候,量化器对所有像素值进行乘以10的操作。。。
当然人家也是有公式的,我上面只是举了一个简单的例子,公式如下:
f ′ ( x , y ) = r o u n d ( ∑ i = 1 m a i F ′ ( x , y − i ) ) f'(x,y)=round(\sum_{i=1}^ma_iF'(x,y-i)) f′(x,y)=round(i=1∑maiF′(x,y−i))
最简单的有损预测编码方法是Delta调制,其公式如下:
f ′ ( n ) = a f ′ ( n − 1 ) f'(n)=af'(n-1) f′(n)=af′(n−1)
e ( n ) = f ( n ) − f ′ ( n ) e(n)=f(n)-f'(n) e(n)=f(n)−f′(n)
e ′ ( n ) = { + δ , 当 e n > 0 − δ , 其 他 情 况 e'(n)= \left\{ \begin{aligned} +\delta,当e_n>0\\ -\delta,其他情况 \end{aligned} \right. e′(n)={+δ,当en>0−δ,其他情况
最简单当然效果也比较差了,现在一般不会再使用了。
代码实现
没什么可讲的,按照上面的原理图一一对应就好了,只给出了预测和还原的函数,在调用其他编码算法之前使用预测函数,在用其他编码还原出预测图像后再还原即可。
预测编码:
void img_Predict(BMPFILE &src, BMPFILE &des) //对图像进行线性预测
{
img_Clone(src, des);
int i, j;
for (i = 0; i < des.imagew; i++) //f'(i)=f(i-1)
for (j = 0; j < des.imageh; j++)
{
if (i == 0 && j == 0)
{
des.pDataAt(i)[j] = 0;
}
else
{
if (j != 0)
{
des.pDataAt(i)[j] = src.pDataAt(i)[j - 1];
}
if (j == 0)
{
des.pDataAt(i)[j] = src.pDataAt(i - 1)[src.imageh - 1];
}
}
}
for (i = 0; i < des.imagew; i++) //e(i)=f(i)-f'(i)
for (j = 0; j < des.imageh; j++)
{
des.pDataAt(i)[j] = src.pDataAt(i)[j] - des.pDataAt(i)[j];
}
des.SaveBMPFILE("predict.bmp");
}
还原:
void img_Depredict(BMPFILE &src, BMPFILE &des) //将经过预测编码的图像还原为原始图片
{ //src相当于e(i)
img_Clone(src, des);
int i, j;
for (i = 0; i < des.imagew; i++) //f'(i)=e(i-1)+f'(i-1)
for (j = 0; j < des.imageh; j++)
{
if (i == 0 && j == 0)
{
des.pDataAt(i)[j] = 0;
}
else
{
if (j != 0)
{
des.pDataAt(i)[j] = src.pDataAt(i)[j - 1] + des.pDataAt(i)[j - 1];
}
if (j == 0)
{
des.pDataAt(i)[j] = src.pDataAt(i - 1)[src.imageh - 1] + des.pDataAt(i - 1)[des.imageh - 1];
}
}
}
for (i = 0; i < des.imagew; i++) //f'(i)=e(i-1)+f'(i-1)
for (j = 0; j < des.imageh; j++)
{
des.pDataAt(i)[j] += src.pDataAt(i)[j];
}
des.SaveBMPFILE("Depredict.bmp");
}
结语
我已经把这个优化整合在了赫夫曼编解码函数里,等LZW的实验检查之后我也会将其整合一下。
本次内容的完整代码及其他相关函数代码均可见我的gitee。