一个比较老的技术,但还是学习了解一下。
矢量量化(Vector Quantization, VQ)
矢量量化是一种信号压缩方法,也是一种基于块编码规则的有损压缩方法。这项技术广泛地用在信号处理以及数据压缩等领域。事实上,在 JPEG 和 MPEG-4 等多媒体压缩格式里都有 VQ 这一步。
引言
Vector Quantization 这个名字听起来有些玄乎,其实它本身并没有这么高深。大家都知道,模拟信号是连续的值,而计算机只能处理离散的数字信号,在将模拟信号转换为数字信号的时候,我们可以用区间内的某一个值去代替着一个区间,比如,[0, 1) 上的所有值变为 0 ,[1, 2) 上的所有值变成 1 ,如此类推。其这就是一个 VQ 的过程。一个比较正式一点的定义是:VQ 是将一个向量空间中的点用其中的一个有限子集来进行编码的过程。
原理
即连续信息到数字信号的转化。
基本思想:
将若干个标量数据组构成一个矢量,然后在矢量空间给以整体量化,从而压缩了数据而不损失多少信息。
VQ实际上就是一种逼近:使用一个数字近似地表示它周围的数字;例如数学中取整⌊ x ⌋
举例
一维例子:
VQ实际上就是一种逼近。它的思想和“四舍五入”有异曲同工之妙,都是用一个和一个数最接近的整数来近似表示这个数。
这里,小于-2的数都近似为-3,在-2和0之间的数都近似为-1,在0和2之间的数都近似为1,大于2的数都近似为3。这样任意的一个数都会被近似为-3、-1、1或者3这四个数中的其中一个。而我们编码这四个数只需要两个二进制位就行了。所以这是1-dimensional,2-bit VQ,它的rate(量化速率?)为2bits/dimension。
二维例子:
上图,蓝色实线将这张图划分为16个区域,每个区域有个红星点;任意的坐标都会落到上面这张图中的某一特定区域。然后它就会被该区域的红星的点近似表示。
这些红星点就是量化矢量,表示图中的任意一个点都可以量化为这16个矢量中的其中一个。也就是这16个红星点可以描述这一整张图。
然后这16个值就可以用4位的二进制码来编码表示(2^4=16)。因此,这是个2-dimensional, 4-bit VQ,它的速率同样是2bits/dimension。
图像编码例子:
最简单的情况,考虑一个灰度图片,0 为黑色,1 为白色,每个像素的值为 [0, 1] 上的一个实数。现在要把它编码为 256 阶的灰阶图片,一个最简单的做法就是将每一个像素值 x 映射为一个整数 floor(x255) 。当然,原始的数据空间也并不以一定要是连续的。比如,你现在想要把压缩这个图片,每个像素只使用 4 bit (而不是原来的 8 bit)来存储,因此,要将原来的 [0, 255] 区间上的整数值用 [0, 15] 上的整数值来进行编码,一个简单的映射方案是 x15/255 。
不过这样的映射方案颇有些 Naive ,虽然能减少颜色数量起到压缩的效果,但是如果原来的颜色并不是均匀分布的,那么的出来的图片质量可能并不是很好。例如,如果一个 256 阶灰阶图片完全由 0 和 13 两种颜色组成,那么通过上面的映射就会得到一个全黑的图片,因为两个颜色全都被映射到 0 了。一个更好的做法是结合聚类来选取代表性的点。
实际做法就是:将每个像素点当作一个数据,跑一下 K-means ,得到 k 个 centroids ,然后用这些 centroids 的像素值来代替对应的 cluster 里的所有点的像素值。对于彩色图片来说,也可以用同样的方法来做,例如 RGB 三色的图片,每一个像素被当作是一个 3 维向量空间中的点。
VQ 2、VQ 10 和 VQ 100 三张图片分别显示聚类数目为 2 、10 和 100 时得到的结果,可以看到 VQ 100 已经和原图非常接近了。把原来的许多颜色值用 centroids 代替之后,总的颜色数量减少了,重复的颜色增加了,这种冗余正是压缩算法最喜欢的。考虑一种最简单的压缩办法:单独存储(比如 100 个)centroids 的颜色信息,然后每个像素点存储 centroid 的索引而不是颜色信息值,如果一个 RGB 颜色值需要 24 bits 来存放的话,每个(128 以内的)索引值只需要 7 bits 来存放,这样就起到了压缩的效果。
原始图像:
VQ 2:
VQ 10:
VQ 100:
代码:
直接使用了 SciPy 提供的 kmeans 和 vq 函数,图像读写用了 Python Image Library 。
#!/usr/bin/python
from scipy.cluster.vq import kmeans, vq
from numpy import array, reshape, zeros
from mltk import image
vqclst = [2, 10, 100, 256]
data = image.read('example.jpg')
(height, width, channel) = data.shape
data = reshape(data, (height*width, channel))
for k in vqclst:
print 'Generating vq-%d...' % k
(centroids, distor) = kmeans(data, k)
(code, distor) = vq(data, centroids)
print 'distor: %.6f' % distor.sum()
im_vq = centroids[code, :]
image.write('result-%d.jpg' % k, reshape(im_vq,
(height, width, channel)))
当然,Vector Quantization 并不一定要用 K-means 来做,各种能用的聚类方法都可以用,只是 K-means 通常是最简单的,而且通常都够用了。
数学公式描述
-
假定一个有M个矢量源(训练样本)的训练序列(训练集):T={x1, x2,…, xM};
-
假设每个源矢量是k维的:
xm=(xm1,xm2, …, xmk), m=1,2,…,M -
假设码矢的数目是N(也就是我们要把这个矢量空间划分为N个部分,或者说量化为N种值),码书(所有码矢的集合)表示为:C={c1, c2,…, cN};
-
每一个码矢是个k维向量:cn=(cn1, cn2, …, cnk),n=1,2,…,N;
-
与码矢cn对应的编码区域表示为Sn,然后将空间的划分表示为:
P={S1, S2,…,SN};
如果源矢量xm在Sn内,那么它的近似(用Q(xm)表示)就是cn,
Q(xm)=cn 。
假设我们采用均分误差失真度量,那么平均失真度表示如下(用欧式距离):
那么设计问题就可以简单的描述为:
给定T(训练集)和N(码矢数目),找到能使Dave(平均失真度)最小的C(码书)和P(空间划分)。
优化准则:
如果C和P是上面这个最小化问题的一个解,那么这个解需要满足以下两个条件:
(1)Nearest NeighborCondition 最近邻条件:
这个条件的意思是编码区域Sn应该包含所有与cn最接近的矢量(相比于与其他码矢的距离)。对于在边界(蓝色线)上面的矢量,需要采用一些决策方法(any tie-breaking procedure)。
(2)Centroid Condition质心条件:
这个条件要求码矢cn是编码区域Sn内所有的训练样本向量的平均向量。在实现中,需要保证每个编码区域至少要有一个训练样本向量,这样上式的分母才不为0。