python kmeans图像分割

原文:https://blog.csdn.net/google19890102/article/details/52911835

一、理论准备

1.1、图像分割

图像分割是图像处理中的一种方法,图像分割是指将一幅图像分解成若干互不相交区域的集合,其实质可以看成是一种像素的聚类过程。通常使用到的图像分割的方法可以分为:

  • 基于边缘的技术
  • 基于区域的技术

基于聚类算法的图像分割属于基于区域的技术。

1.2、K-Means算法

K-Means算法是基于距离相似性的聚类算法,通过比较样本之间的相似性,将形式的样本划分到同一个类别中,K-Means算法的基本过程为:

  • 初始化常数 ,随机初始化k个聚类中心
  • 重复计算以下过程,直到聚类中心不再改变
    • 计算每个样本与每个聚类中心之间的相似度,将样本划分到最相似的类别中
    • 计算划分到每个类别中的所有样本特征的均值,并将该均值作为每个类新的聚类中心
  • 输出最终的聚类中心以及每个样本所属的类别

经典K-means算法:

经典K-means算法
Step1: 从数据集中随机选取k个样本作为初始聚类中心` C = { c 1 , c 2 , ⋯   , c k } C=\{c_1,c_2,\cdots,c_k\} C={c1,c2,,ck}$
Step2: 针对数据集中每个样本$x_i$,计算它到k个聚类中心的距离并将其分到距离最小的聚类中心所对应的类中
Step3: 针对每个类别$c_i$,重新计算它的聚类中心$c_i=\frac{1}{\vert c_i \vert}\sum_{x\in c_i} x$(即属于该类的所有样本的质心)
Step4: 重复第2步和第3步直到聚类中心的位置不再变化

1.3、K-Means++算法

在K-Means算法中,需要随机初始化k个聚类中心,而K-Means算法对初始聚类中心的选取较为敏感,若选择的聚类中心不好,则得到的聚类结果会非常差,因此,对K-Means算法提出了很多的改进的方法,如K-Means++算法,在K-Means++算法中,希望初始化的k个聚类中心之间的距离尽可能的大,其具体过程为:

  • 步骤一:在数据集中随机选择一个样本点作为第一个初始化的聚类中心
  • 步骤二:选择出其余的聚类中心:
    • 计算样本中的每一个样本点与已经初始化的聚类中心之间的距离,并选择其中最短的距离(即与最近一个聚类中心的距离),用D(x)表示;
    • 这个值越大,表示被选取作为聚类中心的概率较大;
    • 最后,用轮盘法选出下一个聚类中心;
  • 步骤三:重复步骤二,知道选出 k 个聚类中心。
K-means++算法
Step1: 从数据集中随机选取一个样本作为初始聚类中心$c_1$
Step2: 首先计算每个样本与当前已有聚类中心之间的最短距离(即与最近的一个聚类中心的距离),用D(x)表示;接着计算每个样本被选为下一个聚类中心的概率` D ( x ) 2 ∑ x ∈ χ D ( x ) 2 \frac{D(x)^2}{\sum_{x\in \chi}D(x)^2} xχD(x)2D(x)2$. 最后,按照轮盘法选择出下一个聚类中心
Step3: 重复第2步直到选择出共k个聚类中心

下面结合一个简单的例子说明K-means++是如何选取初始聚类中心的。

数据集中共有8个样本,分布以及对应序号如下图所示:

python kmeans图像分割_第1张图片

假设经过图2的步骤一后6号点被选择为第一个初始聚类中心,
那在进行步骤二时每个样本的D(x)和被选择为第二个聚类中心的概率如下表所示:

序号
$D(x)$$| 2 2 2\sqrt{2} 22 $ 13 \sqrt{13} 13 5 \sqrt{5} 5 10 \sqrt{10} 10 1 0 2 \sqrt{2} 2 1
$D(x)^2$ 8 13 5 10 1 0 2 1
` P ( x ) P(x) P(x)$ 0.2 0.325 0.125 0.25 0.025 0 0.05 0.025
Sum 0.2 0.525 0.65 0.9 0.925 0.925 0.975 1

其中的P(x)就是每个样本被选为下一个聚类中心的概率。
最后一行的Sum是概率P(x)的累加和,用于轮盘法选择出第二个聚类中心。

方法是随机产生出一个0~1之间的随机数,判断它属于哪个区间,那么该区间对应的序号就是被选择出来的第二个聚类中心了。

例如1号点的区间为[0,0.2),2号点的区间为[0.2, 0.525)。

从上表可以直观的看到第二个初始聚类中心是1号,2号,3号,4号中的一个的概率为0.9。而这4个点正好是离第一个初始聚类中心6号点较远的四个点。

这也验证了K-means的改进思想:即离当前已有聚类中心较远的点有更大的概率被选为下一个聚类中心。

可以看到,该例的K值取2是比较合适的。当K值大于2时,每个样本会有多个距离,需要取最小的那个距离作为D(x)。

python实现

# coding: utf-8
import math
import random
from sklearn import datasets

def euler_distance(point1: list, point2: list) -> float:
    """
    计算两点之间的欧拉距离,支持多维
    """
    distance = 0.0
    for a, b in zip(point1, point2):
        distance += math.pow(a - b, 2)
    return math.sqrt(distance)

def get_closest_dist(point, centroids):
    min_dist = math.inf  # 初始设为无穷大
    for i, centroid in enumerate(centroids):
        dist = euler_distance(centroid, point)
        if dist < min_dist:
            min_dist = dist
    return min_dist

def kpp_centers(data_set: list, k: int) -> list:
    """
    从数据集中返回 k 个对象可作为质心
    """
    cluster_centers = []
    cluster_centers.append(random.choice(data_set))
    d = [0 for _ in range(len(data_set))]
    for _ in range(1, k):
        total = 0.0
        for i, point in enumerate(data_set):
            d[i] = get_closest_dist(point, cluster_centers) # 与最近一个聚类中心的距离
            total += d[i]
        total *= random.random()
        for i, di in enumerate(d): # 轮盘法选出下一个聚类中心;
            total -= di
            if total > 0:
                continue
            cluster_centers.append(data_set[i])
            break
    return cluster_centers

if __name__ == "__main__":
    iris = datasets.load_iris()
    print(kpp_centers(iris.data, 4))

二、实践准备

  • 使用到的模块Numpy,PIL(python image library)

import PIL.Image as image

  • 打开图片

首先是以二进制文件的形式打开文件,再利用Image模块的open方法导入图片.

fp=open(file_path,'rb')  
im=image.open(fp)
  • 常用操作
im.format,im.size,im.mode
结果为:
JPEG,(1600,1067),RGB
r,g,b=im.split() #通道分离
im.getpixel((4,4)) #取得像素点的值
im.putpixel(x,y,color) #改变单个像素点的值
im=im.convert("L") #图像类型转换
image.new(mode,size,color) #生成新的图像
im.save('save.gif','GIF')

图片如下:

python kmeans图像分割_第2张图片

三、利用K-Means++算法进行图像分割

3.1、利用K-Means聚类

在利用K-Means++算法进行图像分割时,将图像中的每一个像素点作为一个样本,对RGB图像来说,每个样本包括三维:(151, 169, 205),通过归一化,将每个通道的值压缩到[0,1]区间上。数据的导入和处理如下面程序所示:

import numpy as np
import PIL.Image as image
def load_data(file_path):
    '''导入数据
    input:  file_path(string):文件的存储位置
    output: data(mat):数据
    '''
    f = open(file_path, "rb")  # 以二进制的方式打开图像文件
    data = []
    im = image.open(f)  # 导入图片
    m, n = im.size  # 得到图片的大小
    print(m, n)
    for i in range(m):
        for j in range(n):
            tmp = []
            x, y, z = im.getpixel((i, j))
            tmp.append(x / 256.0)
            tmp.append(y / 256.0)
            tmp.append(z / 256.0)
            data.append(tmp)
    f.close()
    return np.mat(data)
data = load_data("c:/users/administrator/desktop/1/qc.jpg")
data.shape
#结果为:(370400, 3)

最终保存成矩阵的形式,矩阵的行为样本的个数,列为每一个通道的数值(RGB)。

K-Means++程序的实现如下面程序所示:

def distance(vecA, vecB):
    '''计算vecA与vecB之间的欧式距离的平方
    input:  vecA(mat)A点坐标
        vecB(mat)B点坐标
    output: dist[0, 0](float)A点与B点距离的平方
    '''
    dist = (vecA - vecB) * (vecA - vecB).T
    return dist[0, 0]
def randCent(data, k):
    '''随机初始化聚类中心
    input:  data(mat):训练数据
        k(int):类别个数
    output: centroids(mat):聚类中心
    '''
    n = np.shape(data)[1]  # 属性的个数
    centroids = np.mat(np.zeros((k, n)))  # 初始化k个聚类中心
    for j in range(n):  # 初始化聚类中心每一维的坐标
        minJ = np.min(data[:, j])
        rangeJ = np.max(data[:, j]) - minJ
        # 在最大值和最小值之间随机初始化
        centroids[:, j] = minJ * np.mat(np.ones((k , 1))) + np.random.rand(k, 1) * rangeJ
    return centroids
centroids=randCent(data,4)
centroids

结果为:

matrix([[0.60464688, 0.91807467, 0.46543835],
        [0.05775657, 0.92319872, 0.49939161],
        [0.20882795, 0.66650272, 0.83838715],
        [0.81367366, 0.13199625, 0.51818457]])

kmeans函数代码如下:

def kmeans(data, k, centroids):
    '''根据KMeans算法求解聚类中心
    input:  data(mat):训练数据
        k(int):类别个数
        centroids(mat):随机初始化的聚类中心
    output: centroids(mat):训练完成的聚类中心
        subCenter(mat):每一个样本所属的类别
    '''
    m, n = np.shape(data)  # m:样本的个数,n:特征的维度
    subCenter = np.mat(np.zeros((m, 2)))  # 初始化每一个样本所属的类别
    change = True  # 判断是否需要重新计算聚类中心
    while change == True:
        change = False  # 重置
        for i in range(m):
            minDist = np.inf  # 设置样本与聚类中心之间的最小的距离,初始值为争取穷
            minIndex = 0  # 所属的类别
            for j in range(k):
                # 计算i和每个聚类中心之间的距离
                dist = distance(data[i, ], centroids[j, ])
                if dist < minDist:
                    minDist = dist
                    minIndex = j
            # 判断是否需要改变
            if subCenter[i, 0] != minIndex:  # 需要改变
                change = True
                subCenter[i, ] = np.mat([minIndex, minDist])
        # 重新计算聚类中心
        for j in range(k):
            sum_all = np.mat(np.zeros((1, n)))
            r = 0  # 每个类别中的样本的个数
            for i in range(m):
                if subCenter[i, 0] == j:  # 计算第j个类别
                    sum_all += data[i, ]
                    r += 1
            for z in range(n):
                try:
                    centroids[j, z] = sum_all[0, z] / r
                    print(r)
                except:
                    print(" r is zero")   
    return subCenter
subCenter=kmeans(data, 4, centroids)

3.2、利用聚类结果保存和生成新的图片

首先,保存图片如下:

def save_result(file_name, source):
    '''保存source中的结果到file_name文件中
    input:  file_name(string):文件名
        source(mat):需要保存的数据
    output: 
    '''
    m, n = np.shape(source)
    f = open(file_name, "w")
    for i in range(m):
        tmp = []
        for j in range(n):
            tmp.append(str(source[i, j]))
        f.write("\t".join(tmp) + "\n")
    f.close()
save_result("sub_pp", subCenter)

上述的过程中,对每一个像素点进行了聚类,最终利用聚类中心点的RGB值替换原图中每一个像素点的值,便得到了最终的分割后的图片,代码如下所示:

import PIL.Image as image

f_center = open("d:/0402/center_pp_fj1_7")

center = []
for line in f_center.readlines():
    lines = line.strip().split("\t")
    tmp = []
    for x in lines:
        tmp.append(int(float(x) * 256))
    center.append(tuple(tmp))
print(center)
f_center.close()

fp = open("d:/0402/fj1.jpg", "rb")
im = image.open(fp)
# 新建一个图片
m, n = im.size
pic_new = image.new("RGB", (m, n)) 

f_sub = open("d:/0402/sub_pp_fj1_7")
i = 0
for line in f_sub.readlines():
    index = float((line.strip().split("\t"))[0])
    index_n = int(index)
    pic_new.putpixel((int(i/n),(i % n)),center[index_n])
    i = i + 1
f_sub.close()

pic_new.save("d:/0402/fj1_7.jpg", "JPEG")    

对于上述的圣托里尼的图片,取不同的k值,得到如下的一些结果:

  • 原图

  • k=4
    python kmeans图像分割_第3张图片

  • k=7
    python kmeans图像分割_第4张图片

  • k=10
    python kmeans图像分割_第5张图片
    再来看一个例子。

  • 原图
    python kmeans图像分割_第6张图片

  • k=3
    python kmeans图像分割_第7张图片

  • k=5
    python kmeans图像分割_第8张图片

  • k=10
    python kmeans图像分割_第9张图片

你可能感兴趣的:(python)