本文使用机器学习中的聚类方法来对图像进行分割,下面将会介绍两种方法实现:K-means、Fuzzy C-means
首先,输入为一张图片,图片由RGB三通道构成,我们可以通过对图像的每个像素点进行展开,再进一步对每个像素点进行聚类。
以该图像为例:
我们导入PIL库,引入Image包
img = Image.open('./2.png')
plt.imshow(img)
plt.show()
我们将图片转换成数组形式:
img = np.asarray(img)
m,n,d = img.shape
m,n,d
可以看见数组的维度信息为: (218, 252, 3)
接着,我们将数组展开:
data = img.reshape(m*n,d)
data.shape
将图片上的每个像素点都作为一行,得到了新的数组,其维度为:(218*252,3),即(54936, 3)
接下来,我们就可以使用K-means或者Fuzzy C-means来对其进行聚类:
其原理为:当前的二维数组的每一行有三个值,可以将其视为第 个像素的特征值,对应于一个特征向量,所以每个像素点都有一个特征向量,那么,这个特征向量就是由RGB三原色构成。从肉眼的角度看待,我们可以自然的认为颜色相接近的是同一类,这样,就可以使得聚类顺利的进行。
K-means代码:
def init_centroids(X, k):
m, n = X.shape
centroids = np.zeros((k, n))
idx = np.random.randint(0, m, k)
for i in range(k):
centroids[i, :] = X[idx[i], :]
return centroids
## 给定均值或者中心后,按照距离最小原则把样本归类。
## idx 存储了每个样本属于的类别
def find_closest_centroids(X, centroids):
m = X.shape[0]
k = centroids.shape[0]
idx = np.zeros(m)
for i in range(m):
min_dist = np.inf
for j in range(k):
dist = np.sum((X[i, :] - centroids[j, :])**2)
if dist < min_dist:
min_dist = dist
idx[i] = j
return idx
## 对样本进行划分后,要更新类别的均值
def compute_centroids(X, idx, k):
m, n = X.shape
centroids = np.zeros((k, n))
for i in range(k):
indices = np.where(idx == i) #indices为二维数组,所以下面axis=1,按行进行取平均 或者 先squeeze让indices变为一维,再axis=0取列平均
centroids[i, :] = (np.sum(X[indices, :], axis=1) /
len(indices[0])).ravel()
return centroids
## 执行k-means 算法的迭代过程
def run_k_means(X, initial_centroids, max_iters):
m, n = X.shape
k = initial_centroids.shape[0]
idx = np.zeros(m)
centroids = initial_centroids
for i in range(max_iters):
idx = find_closest_centroids(X, centroids)
centroids = compute_centroids(X, idx, k)
return idx, centroids
Fuzzy C-means代码:
def FCM(X, c, m, eps, max_its):
#x代表数据,c代表聚类数目,m是加权指标,eps是差别,max_its是最大迭代次数
#矩阵u初始化
num = X.shape[0]
u = np.random.random((num, c))
u = u/np.sum(u, axis=1)[:, np.newaxis] #[:, np.newaxis]将行向量转换为列向量 归一化u矩阵,使得每行加和等于1
it = 0
while it<max_its:
it+=1
um = u ** m
center = np.dot(um.T, X)/(np.sum(um.T, axis=1)[:, np.newaxis])
distance = np.zeros((num, c))
for i, x in enumerate(X):
for j, v in enumerate(center):
distance[i][j] = np.sum((x - v)**2)
new_u = np.zeros((len(X), c))
for i in range(num):
for j in range(c):
new_u[i][j] = 1. / np.sum((distance[i][j] / distance[i]) ** (2 / (m - 1)))
if np.sum(abs(new_u - u)) < eps:
break
u = new_u
return np.argmax(u, axis=1) #返回每一行隶属度最大的类
在运行前,我们需要先肉眼判断图片中大概有几个类,指定迭代的次数和其余的超参数,运行代码,得到每个像素的预测类别
使用K-means聚类
initial_centroids = init_centroids(data,classes)
idx, centroids = run_k_means(data, initial_centroids, 10)
使用FCM聚类
idx= FCM(data, 5, 2, 1e-15, 10)
那么,idx即为每个像素的预测类别,得到了这个之后,我们需要其以图像的形式展现出来。
我们可以轻松的想到,将不同类别染为不同的颜色,为此,我们定义出一个colors数组
color0 = np.array([20,20,20])
color1 = np.array([80,80,80])
color2 = np.array([150,150,150])
color3 = np.array([250,250,250])
color4 = np.array([200,200,200])
colors = np.array([color3,color1,color4,color2,color0])
根据idx的内容,将原data数组中对应位置的特征向量,替换为我们指定的颜色(特征向量)
idx_unique = np.unique(idx)
index = dict()
for i in idx_unique:
index[i] = np.where(idx==i)[0]
cluster1 = data[np.where(idx == 0)[0],:]#从X中取出隶属于当前类别的行,np.where(idx==0).shape =》(1,c) 后面加[0],则变为一个列表,长度为c
cluster2 = data[np.where(idx == 1)[0],:]
cluster3 = data[np.where(idx == 2)[0],:]
cluster4 = data[np.where(idx == 3)[0],:]
color0 = np.array([20,20,20])
color1 = np.array([80,80,80])
color2 = np.array([150,150,150])
color3 = np.array([250,250,250])
color4 = np.array([200,200,200])
colors = np.array([color3,color1,color4,color2,color0])
x = np.asarray(data,dtype=int) #将原数组复制出来,再进行下面相应的替换;使用原数组无法替换
#染色
for i in range(len(idx_unique)):
x[index[i]] = colors[i]
到这里,我们再次查看一下x数组的维度信息:
x.shape
(54936, 3)
维度与刚展开的时候相同,那么,我们现在需要将这个数组重新reshape为原始的三维数组:
result = np.asarray(x.reshape(m,n,d))
result.shape
(218, 252, 3)
#取特征向量的平均,转换为灰度图像,维度被压缩
result = np.average(result,axis=2)
result.shape
(218, 252)
这个时候,我们就可以使用Image.fromarray函数,将这种灰度图像展示出来:
res = Image.fromarray(result)
plt.imshow(res)
plt.show()
结果如图:
K-means运行结果:
Fuzzy C-means运行结果:
从运行结果可以看出,K-means和Fuzzy C-means可以很好的完成分割任务
两种方法的对比:
K-means:
k-means算法只有在簇的平均值被定义的情况下才能使用。
k-means算法的不足之处在于它要多次扫描数据库。
k-means算法只能找出球形的类,而不能发现任意形状的类。
初始质心的选择对聚类结果有较大的影响。
k-means算法对于噪声和孤立点数据是敏感的,少量的该类数据 能够对平均值产生极大的影响。
Fuzzy C-means:
fuzzy c-means在k-means的基础上,改善了k-means算法对于噪声和孤立点敏感。
问题与思考:
根据颜色进行分类,k-means和fuzzy c-means都是使用欧式距离进行类别划分,为什么图片中可以不规则形状的进行分类(帽子上的空心椭圆)?
答:在文章的开始,我们就先将图片中的每一个像素进行展开,人眼看见的它的现状虽然是不规则的,但是从像素角度来说,它打破了原始的形状的这个限制,从而实现跨区域的进行分类。其分类的依据是每个像素的特征向量,这个特征就是图片的RGB值,其颜色越相似,其特征就越接近。