SLIC超像素分割

在计算机视觉领域里,图像分割(Segmentation)是指将数字图像分割成一些列像素集合的过程。这些集合也被成为超像素。因为分割是基于一定标准进行像素划分,归类的,所以形成的超像素中的像素一般具有位置相邻且颜色、亮度、纹理等特征相似等特点。这些小区域大多保留了进一步进行图像处理的有效信息,且一般不会破坏图像中物体的边界信息。因此超像素分割可以作为一些算法的前处理。接下来介绍一种常用的超像素分割算法SLIC(simple linear iterativeclustering)即简单的线性迭代聚类。
SLIC算法流程是:

  1. 将彩色图像转化为LAB颜色空间。
  2. 接着,由LAB颜色空间向量和图像本身的位置(row, col) 向量构建新的5维特征向量(即 new_vector[l, a, b, row, col])。
  3. 然后,根据针对5维向量构建的距离标准来聚类。

聚类算法流程如下:

  1. 初始化种子点(聚类中心):按照设定的超像素个数,在图像内均匀的分配种子点。假设图片总共有 N 个像素点,预分割为 K 个相同尺寸的超像素,那么每个超像素的大小为N/ K ,则相邻种子点的距离(步长)近似为S=\sqrt{N/K}

  2. 在种子点的n*n邻域内重新选择种子点(一般取n=3)。具体方法为:计算该邻域内所有像素点的梯度值,将种子点移到该邻域内梯度最小的地方。这样做的目的是为了避免种子点落在梯度较大的轮廓边界上,以免影响后续聚类效果。

  3. 在每个种子点周围的邻域内为每个像素点分配类标签(即属于哪个聚类中心)。和标准的k-means在整张图中搜索不同,SLIC的搜索范围限制为2S*2S,可以加速算法收敛。在此注意一点:期望的超像素尺寸为S*S,但是搜索的范围是2S*2S

  4. 距离度量。包括颜色距离和空间距离。对于每个搜索到的像素点,分别计算它和该种子点的距离。距离计算方法如下:

距离计算公式

其中,dc代表颜色距离,ds代表空间距离,Ns是类内最大空间距离,定义为Ns=S=\sqrt{N/K},适用于每个聚类。最大的颜色距离Nc既随图片不同而不同,也随聚类不同而不同,一般取值范围是取值范围[1,40]。D'就是最后5维向量的距离,是由颜色距离和空间距离构成的2维向量的欧氏距离。

由于每个像素点都会被多个种子点搜索到,所以每个像素点都会有一个与周围种子点的距离,取最小值对应的种子点作为该像素点的聚类中心。

  1. 迭代优化。理论上上述步骤不断迭代直到误差收敛(可以理解为每个像素点聚类中心不再发生变化为止),实践发现10次迭代对绝大部分图片都可以得到较理想效果,所以一般迭代次数取10。

下面是简易的python实现:

import math
from skimage import io, color
import numpy as np

class Cluster(object):

    cluster_index = 1

    def __init__(self, row, col, l=0, a=0, b=0):
        self.row = row
        self.col = col
        self.l = l
        self.a = a
        self.b = b

        self.pixels = []
        self.no = self.cluster_index
        Cluster.cluster_index += 1

    def update(self, row, col, l, a, b):
        self.row = row
        self.col = col
        self.l = l
        self.a = a
        self.b = b


class SLICProcessor(object):
    @staticmethod
    def open_image(path):
        rgb = io.imread(path)
        lab_arr = color.rgb2lab(rgb)
        return lab_arr

    @staticmethod
    def save_lab_image(path, lab_arr):
        rgb_arr = color.lab2rgb(lab_arr)
        io.imsave(path, rgb_arr)

    def make_cluster(self, row, col):
        row=int(row)
        col=int(col)
        return Cluster(row, col,
                       self.data[row][col][0],
                       self.data[row][col][1],
                       self.data[row][col][2])

    # K是超像素的个数,M是用于计算lab空间颜色向量距离的参数
    def __init__(self, filename, K, M):
        self.K = K
        self.M = M

        self.data = self.open_image(filename)
        self.rows = self.data.shape[0]
        self.cols = self.data.shape[1]
        self.N = self.rows * self.cols
        self.S = int(math.sqrt(self.N / self.K))

        self.clusters = []
        self.label = {}
        self.dis = np.full((self.rows, self.cols), np.inf)

    def init_clusters(self):
        row = self.S / 2
        col = self.S / 2
        while row < self.rows:
            while col < self.cols:
                self.clusters.append(self.make_cluster(row, col))
                col+= self.S
            col = self.S / 2
            row += self.S

    def get_gradient(self, row, col):
        if col + 1 >= self.cols:
            col = self.cols - 2
        if row + 1 >= self.rows:
            row = self.rows - 2

        gradient = (self.data[row + 1][col][0] + self.data[row][col+1][0] - 2 * self.data[row][col][0]) + \
                   (self.data[row + 1][col][1] + self.data[row][col+1][1] - 2 * self.data[row][col][1]) + \
                   (self.data[row + 1][col][2] + self.data[row][col+1][2] - 2 * self.data[row][col][2])

        return gradient

    def move_clusters(self):
        for cluster in self.clusters:
            cluster_gradient = self.get_gradient(cluster.row, cluster.col)
            new_row = cluster.row
            new_col = cluster.col

            for dh in range(-1, 2):
                for dw in range(-1, 2):
                    _row = cluster.row + dh
                    _col = cluster.col + dw
                    new_gradient = self.get_gradient(_row, _col)
                    if new_gradient < cluster_gradient:
                        cluster_gradient = new_gradient
                        new_row = _row
                        new_col = _col

            cluster.update(new_row, new_col, self.data[new_row][new_col][0], self.data[new_row][new_col][1], self.data[new_row][new_col][2])

    def assignment(self):
        for cluster in self.clusters:
            for h in range(cluster.row - 2 * self.S, cluster.row + 2 * self.S):
                if h < 0 or h >= self.rows: continue
                for w in range(cluster.col - 2 * self.S, cluster.col + 2 * self.S):
                    if w < 0 or w >= self.cols: continue
                    L, A, B = self.data[h][w]
                    Dc = math.sqrt(
                        math.pow(L - cluster.l, 2) +
                        math.pow(A - cluster.a, 2) +
                        math.pow(B - cluster.b, 2))
                    Ds = math.sqrt(
                        math.pow(h - cluster.row, 2) +
                        math.pow(w - cluster.col, 2))
                    D = math.sqrt(math.pow(Dc / self.M, 2) + math.pow(Ds / self.S, 2))
                    if D < self.dis[h][w]:
                        if (h, w) not in self.label:
                            self.label[(h, w)] = cluster
                            cluster.pixels.append((h, w))
                        else:
                            self.label[(h, w)].pixels.remove((h, w))
                            self.label[(h, w)] = cluster
                            cluster.pixels.append((h, w))
                        self.dis[h][w] = D

    def update_cluster(self):
        for cluster in self.clusters:
            sum_h = sum_w = number = 0
            for p in cluster.pixels:
                sum_h += p[0]
                sum_w += p[1]
                number += 1
                _h =int(sum_h / number)
                _w =int(sum_w / number)
                cluster.update(_h, _w, self.data[_h][_w][0], self.data[_h][_w][1], self.data[_h][_w][2])

    def save_current_image(self, name):
        image_arr = np.copy(self.data)
        for cluster in self.clusters:
            for p in cluster.pixels:
                image_arr[p[0]][p[1]][0] = cluster.l
                image_arr[p[0]][p[1]][1] = cluster.a
                image_arr[p[0]][p[1]][2] = cluster.b
            # 迭代后的聚合点 -- 如果要显示请反注释下面的代码
            # image_arr[cluster.row][cluster.col][0] = 0
            # image_arr[cluster.row][cluster.col][1] = 0
            # image_arr[cluster.row][cluster.col][2] = 0
        self.save_lab_image(name, image_arr)

    def iterates(self, output_path):
        self.init_clusters()
        self.move_clusters()
        #考虑到效率和效果,折中选择迭代10次
        for i in range(10):
            self.assignment()
            self.update_cluster()
        self.save_current_image(output_path)



if __name__ == '__main__':
    p = SLICProcessor("path of the image you want to process", 200, 40)
    p.iterates("path of the image you want to save")

效果如下:


输入图

输出图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354