本章将介绍聚类方法,用图像进行聚类,寻找相似的图像组。聚类可用于识别、划分图像数据集,组织与导航。此外,会对聚类后的图像进行相似性的可视化。
K-means是一种将输入数据划分为K个簇的简单聚类算法,反复提炼初始评估的类中心,步骤如下:
K-means聚类算法的意义在于试图使类内总方差最小:
V = ∑ i = 1 k ∑ x j ∈ c i ( x i − μ i ) 2 V = \sum _{i=1} ^{k} \sum_{x_j \in c_i} (x_i-\mu_i)^2 V=i=1∑kxj∈ci∑(xi−μi)2
注:
SciPy包中有直接实现K-means的方法,下面的代码时实现过程:
生成二维正态分布数据(两类),用k=2进行聚类
from numpy import *
from scipy.cluster.vq import *
class1 = 1.5*random.randn(100,2)
class2 = random.randn(100,2)+array([5,5])
features = vstack((class1,class2))
centroids,variance = kmeans(features,2)
但返回的方差并不是我们真正需要的,用SciPy包中的矢量量化函数对每个数据点进行归类。
code是分类号,K=2,所以被分为1和0,distance是点离类中心的距离。
code,distance = vq(features,centroids)
将其可视化,我们画出数据点和最终的聚类中心。
from pylab import *
figure(figsize=(10,10))
ndx = where(code==0)[0]
plot(features[ndx,0],features[ndx,1],'*')
ndx = where(code==1)[0]
plot(features[ndx,0],features[ndx,1],'r.')
plot(centroids[:,0],centroids[:,1],'go')
axis('off')
show()
用K-means对字体图像进行聚类,获取66幅字体数据集,并且利用之前计算过的pca主成分进行投影,用投影系数作为每幅图像的向量描述符,用下面的方法进行聚类。
import imtools
import pickle
from scipy.cluster.vq import *
from PIL import Image
from numpy import *
from pylab import *
# 获取 selected-fontimages 文件下图像文件名,并保存在列表中
imlist = imtools.get_imlist('selectedfontimages')
imnbr = len(imlist)
# 载入模型文件
with open('font_pca_modes.pkl','rb') as f:
immean = pickle.load(f)
V = pickle.load(f)
# 创建矩阵,存储所有拉成一组形式后的图像
immatrix = array([array(Image.open(im)).flatten()
for im in imlist],'f')
immean=immean.flatten()
# 投影系数
projected = array([dot(V[:40],immatrix[i]-immean) for i in range(imnbr)])
# 进行 k-means 聚类,作归一化处理
projected = whiten(projected)
centroids,distortion = kmeans(projected,4)
# 绘制聚类簇
for k in range(4):
ind = where(code==k)[0]
figure()
gray()
for i in range(minimum(len(ind),40)):
subplot(4,10,i+1)
imshow(immatrix[ind[i]].reshape((25,25)))
axis('off')
show()
第一行是第一类,第二行是第二类,第三行是第三类,之后是第四类。
为了观察上面是如何利用主成分进行聚类的,在一对主成分方向的坐标上可视化这些图像,将图像投影到两个主成分上。
projected = array([dot(V[[0,2]],immatrix[i]-immean) for i in range(imnbr)])
V[0,2]分别是第一个和第三个主成分。
使用PIL中的ImageDraw模块进行可视化
from PIL import Image, ImageDraw
# 高和宽
h,w = 1200,1200
# 创建一幅白色背景图
img = Image.new('RGB',(w,h),(255,255,255))
draw = ImageDraw.Draw(img)
# 绘制坐标轴
draw.line((0,h/2,w,h/2),fill=(255,0,0))
draw.line((w/2,0,w/2,h),fill=(255,0,0))
# 缩放以适应坐标系
scale = abs(projected).max(0)
scaled = floor(array([ (p / scale) * (w/2-20,h/2-20) + (w/2,h/2) for p in projected]))
# 粘贴每幅图像的缩略图到白色背景图片
for i in range(imnbr):
nodeim = Image.open(imlist[i])
nodeim.thumbnail((25,25))
ns = nodeim.size
img.paste(nodeim,(int(scaled[i][0]-ns[0]//2),int(scaled[i][1]-
ns[1]//2),int(scaled[i][0]+ns[0]//2+1),int(scaled[i][1]+ns[1]//2+1)))
成对主成分上投影的字体图像,第一个和第三个主成分,二维投影后相似的字体图像距离较近。
来看一个对单幅图像中的像素而非全部图像进行聚类的例子。将图像区域或像素合并成有意义的部分称为图像分割。现在,我们仅会在 RGB 三通道的像素值上运用 K-means 进行聚类。
用一个步长为 steps 的方形网格在图像中滑动,每滑一次对网格中图像区域像素求平均值,将其作为新生成的低分辨率图像对应位置
处的像素值,并用 K-means 进行聚类:
from scipy.cluster.vq import *
from pylab import *
from PIL import Image
steps = 50 # 图像被划分成 steps×steps 的区域
im = array(Image.open('pic/empire.jpg'))
dx = im.shape[0] // steps
dy = im.shape[1] // steps
# 计算每个区域的颜色特征
features = []
for x in range(steps):
for y in range(steps):
R = mean(im[x*dx:(x+1)*dx,y*dy:(y+1)*dy,0])
G = mean(im[x*dx:(x+1)*dx,y*dy:(y+1)*dy,1])
B = mean(im[x*dx:(x+1)*dx,y*dy:(y+1)*dy,2])
features.append([R,G,B])
features = array(features,'f') # 变为数组
# 聚类
centroids,variance = kmeans(features,3)
code,distance = vq(features,centroids)
# 用聚类标记创建图像
codeim = code.reshape(steps,steps)
figure()
subplot(121)
imshow(im)
axis('off')
subplot(122)
imshow(codeim)
axis('off')
show()
50 × 50 , K = 3 50\times50, K=3 50×50,K=3
100 × 100 , K = 3 100\times100,K=3 100×100,K=3
谱聚类和K-means方法截然不同,对于n个元素(n幅图像),相似矩阵是一个 n × n n\times n n×n的矩阵,矩阵的两个元素表示两两之间的相似性分数,谱聚类是由于相似性矩阵的建谱矩阵而得名,对该谱矩阵进行特征分解得到的特征向量可以用于降维,然后聚类。
谱聚类的优点之一是仅需要输入相似性矩阵,采用任意度量方法构建该相似性矩阵,特征或描述子没有类别限制,只要有一个相似性或距离的概念即可。
给定 n × n n\times n n×n的相似矩阵S, s i j s_{ij} sij为相似性分数(矩阵的元素),可以创建矩阵,称为拉普拉斯矩阵:
L = I − D − 0.5 S D − 0.5 L=I-D^{-0.5}SD^{-0.5} L=I−D−0.5SD−0.5
I是单位矩阵,D是对角矩阵,对角元素是S对于应行元素之和, d i = ∑ j s i j d_i=\sum _{j} s_{ij} di=∑jsij,我们要求相似性分数较小且大等于0。
计算L(拉普拉斯矩阵)的特征向量,并使用K个最大特征值对于的k个特征向量,构建出特征向量集,各列是由之前的k个特征向量构成,每一行可看作一个新的特征向量,长度为k(尺度k*k),而新的特征向量可以用K-means方法进行聚类,形成最终的聚类簇。
import imtools
import pickle
from scipy.cluster.vq import *
from PIL import Image
from numpy import *
from pylab import *
import math
from scipy import linalg
# 获取 selected-fontimages 文件下图像文件名,并保存在列表中
imlist = imtools.get_imlist('selectedfontimages')
imnbr = len(imlist)
# 载入模型文件
with open('font_pca_modes.pkl','rb') as f:
immean = pickle.load(f)
V = pickle.load(f)
# 创建矩阵,存储所有拉成一组形式后的图像
immatrix = array([array(Image.open(im)).flatten()
for im in imlist],'f')
immean=immean.flatten()
# 投影到前40个主成分上
immean = immean.flatten()
projected = array([dot(V[:40], immatrix[i] - immean) for i in range(imnbr)])
n = len(projected)
# 欧式距离
S = array([[ sqrt(sum((projected[i]-projected[j])**2))
for i in range(n) ] for j in range(n)], 'f')
#拉普拉斯矩阵
# 应行元素之和
rowsum = sum(S,axis = 1)
D = diag(1/sqrt(rowsum))
I = identity(n)
L = I-dot(D,dot(S,D))
# 计算特征向量
U,sigma,V = linalg.svd(L)
k = 5 # 前5个特征向量
features = array(V[:5].T)
# 聚类
features = whiten(features)
centroids,distortion = kmeans(features,k)
code,distance = vq(features,centroids)
# 绘制聚类簇
for c in range(k):
ind = where(code==c)[0]
figure()
for i in range(minimum(len(ind),39)):
im = Image.open(imlist[ind[i]])
subplot(4,10,i+1)
imshow(array(im))
axis('equal')
axis('off')
show()