1. VQ(即Vector Quantization,矢量量化),其作用:将一个向量空间中的点,用其中一个有限子集中的点,进行编码;应用场景也很广泛,如图像压缩,声音压缩,CV领域特征降维等。
2. 数学含义:将矢量空间中的 k 维矢量映射到矢量的有限集合
- 即向量空间 R k R^k Rk中的所有x,都可以使用有限集合Y中的值表示:
Y = { y i : i = 1 , 2 , 3 , . . . , N } Y=\{y_i:i=1, 2,3,...,N\} Y={yi:i=1,2,3,...,N}
- 且每个 x 都有相应邻域中的 y i y_i yi与之对应
3. 考虑一个图像编码的例子,有一个简单的图像使用VQ编码,例如:
其中:左边是原图,中间是codebook,右边是vq编码后的图像
其中:红色点就是对上述原图使用k-means聚类后得到的centroids
4. 对图像进行vq编码就是:将原图像中每个像素点当作一个数据,使用 K-means聚类得到 k 个centroids(即codebook中的值) ,然后用这些 centroids 的像素值来代替对应原图像中所有点的像素值,代码如下:
import numpy as np
from scipy.cluster.vq import vq, kmeans
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def plot_scatter(data, codebook):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
colors = np.random.rand(data.shape[0]) # 散点颜色
sizes = np.random.randint(low=50, high=150, size=data.shape[0]) # 散点大小
ax.set_xlabel('R-X', c='r')
ax.set_ylabel('G-Y', c='g')
ax.set_zlabel('B-Z', c='b')
ax.scatter(data[:, 0], data[:, 1], data[:, 2], c=colors, s=sizes, alpha=1)
ax.scatter(codebook[:, 0], codebook[:, 1], codebook[:, 2], c='r', s=150)
plt.show()
return
def test_vq():
# # data
features = np.array([[121.6, 147.2, 108.8],
[96.9, 160.6, 140.8],
[51.2, 38.4, 108.8],
[204.9, 179.2, 172.8],
[40.5, 56.3, 98.6],
[168.2, 182.1, 192.3]])
# # discrete data
# features = np.array([[0., 0., 0.],
# [12., 12., 12.],
# [0., 0., 0.],
# [12., 12., 12.],
# [12., 12., 12.],
# [0., 0., 0.]])
# # random data.
# np.random.seed(seed=4321)
# features = np.random.uniform(0., 1.0, size=(10, 3)) * 255
# codebook.
code_book, dist = kmeans(features, 3, seed=4321)
code_book = np.around(code_book, 1)
# vq
code_index, distor = vq(features,code_book)
vq_data_from_codebook = code_book[code_index,:]
print(f'code book: \n', code_book)
print('-' * 128)
print(f'code index from codebook: {code_index}')
print('-' * 128)
print('vq data from codebook: \n{}'.format(vq_data_from_codebook))
print('-' * 128)
print('vq distance: \n', distor)
print('-' * 128)
print('validation distance: \n', np.linalg.norm(features - vq_data_from_codebook, axis=1))
print('-' * 128)
print('first row distance: \n', np.sqrt(np.sum(np.square(features[0] - vq_data_from_codebook[0]))))
# plot
plot_scatter(data=features, codebook=code_book)
if __name__ == "__main__":
##
test_vq()
注:代码对离散图像数据也是适合的
对于彩色图片来说,也可以用同样的方法来做,例如 RGB 彩色图像,每一个像素被当作是一个 3 维向量空间中的点, 使用scipy库中的k-means和vq,将centroids的个数分别为: 2,10,100,200;代码如下:
import os
from scipy.cluster.vq import kmeans, vq
import numpy as np
from PIL import Image
''' blog: https://www.cnblogs.com/Higgerw/p/15238513.html '''
def k_vq(data, k, iter=20):
print('Generating vq-%d...' % k)
(centroids, distor) = kmeans(data, k, iter=iter) # get discrete codebook.
(code_index, distor) = vq(data, centroids) # get discrete index from centroids.
print('distor: %.6f' % distor.sum())
return centroids, code_index
def main():
# get image.
# path = 'lenna.jpg'
path = 'xiaojiejie.jpg'
pic = Image.open(path)
data = np.array(pic)
height, width, channel = data.shape
data = np.float32(np.reshape(data, (height*width, channel)))
# save path
os.makedirs('output', exist_ok=True)
vqclst = [2, 10, 100, 200]
for k in vqclst:
codebook, code_index = k_vq(data, k)
im_vq = codebook[code_index, :]
new_data = np.uint8(np.reshape(im_vq, (height, width, channel))) # h x w x c
new_pic = Image.fromarray(new_data)
new_pic.save('output/result-%d.jpg' % k)
if __name__ == "__main__":
# ##
main()
运行代码的结果:
5. 那么重点来了,这里求解codebook是通过使用k-means聚类,k-means算法聚类一般通过欧氏距离求解属于同一簇的centroid的,而VQ-GAN中是先随机初始化一个N维的Embedding(也即是N维的codebook),然后计算Embedding与图像的featuremap的距离,通过卷积神经网络的反向传播算法更新Embedding中的值的到最后的codebook的,所以二者的原理是相似的。