SLIC算法分割超像素原理及Python实现

超像素(SuperPixel),就是把原本多个像素点,组合成一个大的像素。比如,原本的图片有二十多万个像素,用超像素处理之后,就只有几千个像素了。后面做直方图等处理就会方便许多。经常作为图像处理的预处理步骤。

在超像素算法方面,SLIC Superpixels Compared to State-of-the-art Superpixel Methods这篇论文非常经典。论文中从算法效率,内存使用以及直观性比较了现有的几种超像素处理方法,并提出了一种更加实用,速度更快的算法——SLIC(simple linear iterative clustering),名字叫做简单的线性迭代聚类。其实是从k-means算法演化的,算法复杂度是O(n),只与图像的像素点数有关。

这个算法突破性的地方有二:

  1. 限制聚类时搜索的区域(2Sx2S),这样将k-means算法的复杂度降为常数。整个算法的复杂度为线性。
  2. 计算距离时考虑LAB颜色和XY距离,5维。这样就把颜色和距离都考虑进去了。通过M可以调整颜色和距离的比重,灵活性强,超像素更加规则。

SLIC算法原理

整个算法的输入只有一个,即超像素的个数K。

图片原有N个像素,要分割成K个像素,那么每个像素的大小是N/K。超像素之间的距离(即规则情况下超像素的边长)就是S=√N/K。

我们的目标是使代价函数(cost function)最小。具体到本算法中,就是每个像素到所属的中心点的距离之和最小。

首先,将K个超像素种子(也叫做聚类,即超像素的中心),均匀撒到图像的像素点上。

一次迭代的第一步,对每个超像素的中心,2S范围内的所有像素点,判断他们是否属于这个超像素。这样之后,就缩短了像素点到超像素中心的距离。

一次迭代的第二步,对每个超像素,将它的超像素中心移动到这个超像素的中点上。这样也缩短了像素点到超像素中心的距离。

一般来说,迭代10是聚类效果和计算成本折中的次数。

SLIC算法步骤

  1. 撒种子。将K个超像素中心分布到图像的像素点上。
  2. 微调种子的位置。以K为中心的3×3范围内,移动超像素中心到这9个点中梯度最小的点上。这样是为了避免超像素点落到噪点或者边界上。
  3. 初始化数据。取一个数组label保存每一个像素点属于哪个超像素。dis数组保存像素点到它属于的那个超像素中心的距离。
  4. 对每一个超像素中心x,它2S范围内的点:如果点到超像素中心x的距离(5维)小于这个点到它原来属于的超像素中心的距离,那么说明这个点属于超像素x。更新dis,更新label。
  5. 对每一个超像素中心,重新计算它的位置。
  6. 重复4 5 两步。

伪代码(来自论文)

Python实现SLIC

最新版本的代码请看这里:https://github.com/laixintao/slic-python-implementation

效果如下:

Lenna图像在M=30,K=500时第一次迭代产生的超像素图。

Lenna图像在M=30,K=500时第10次迭代产生的超像素图。

SLIC算法分割超像素原理及Python实现”已经有35条评论

  1. 你好, 非常感谢你的代码,我发现了几个小问题:
    1. self.cluster_index += 1 因为对于每个对象cluster_index 都等于 1, 所以这样的话每个self.cluster_index都会等于2, 起不到index的作用, 建议改成Cluster.cluster_index += 1
    2. gradient = self.data[w + 1][h + 1][0] – self.data[w][h][0] + \ 的w和h和别处是反的

    • 谢谢指出!第一个我觉得你说的对,源代码也是写到了class的属性里面,应该是我写错了但是代码能work就没注意到。第2个我不太确定,得明天调试一下。写着写着调来调去的我也不知道自己写的什么了……

      这个代码其实写的比较乱,这篇论文读着好玩的,就试了一下。代码可能存在别的漏洞,要小心使用哦~

  2. 你好:) 我又发现了一个问题, 在update_cluster函数里面, 你是取了h和w的平均值, 然后取图中对应位置的l,a,b; 这样得到的结果和slic官网上c++代码得到的结果不太一样, 我看了一下发现他们对l,a,b也取了平均

    • l,a,b平均值这个做法应该是论文里面的,我上面的代码只参考了论文,别处的实现可能带有优化。不过不好意思,我现在回去看这个代码也看不懂了…… 要解释的话只有从头复习一遍论文…… 帮不上你了。你可以调试对比一下。

      • 我明白了,如果把SLIC看成是clustering, 那论文里面就是在[x, y, l, a, b] 5维空间的clustering,5维向量取平均得到的新的中心不一定是图上的点; 你当时可能考虑新的中心应该是图上实际存在的点

        • 隐约记得是这样的,我记得我没有做按照自己想法的处理,都是照着论文上来的。你看论文上有没有推荐这样处理不在图像上的点?这个算法好像供自己调节的只有两个参数,写出来的结果应该不会差太多。

  3. 请问一下,我换一个图片就报错了:IndexError: index 3026 is out of bounds for axis 0 with size 3024 这是什么情况?

    • 图片的宽高是用库从文件里面读出来的:

      你的文件格式是png吗? 你手动调试一下,设置一下宽高试一下。

  4. 我运行你的代码,会显示错误,only integers, slices (:), ellipsis (...), numpy.newaxis (None) and integer or boolean arrays are valid indices,请问是为什么呢

  5. 楼主,您好,谢谢您的代码,希望您能提供一下图片显示的代码,我自己添加的只出现一种颜色,谢谢

    • 不知道图片显示的代码指的是啥,save_current_image 这个函数就可以吧? 你可以先跑github上的代码,然后自己改。你这么说我也听不明白你哪里出问题了。

  6. 您好,请问出现这个错误是什么原因呢?应该如何解决?

  7. 博主你好~
    我在用opencv-python进行读写图片的时候遇到输出效果很差的问题,修改的代码如下:

    你感觉可能是哪里出现了问题呢? 谢谢

      • 感谢博主~
        可是我用skimage也缩放了,效果很好
        有没有可能是在转换到LAB通道和转换回RGB的时候,有不同?

          • reply: resize没有影响。以下是我另一个测试,在RGB->LAB的过程中,cv2的lab结果在0-255之间,但是skimage的结果却有诸如1.00000000e+02、-2.45493786e-03 的数字。您认为这样会对分割结果造成怎么的影响?

            import cv2
            from skimage import io, transform, color
            import numpy as np
            if __name__ == '__main__':
            filename = 'data/train/RGB-PanSharpen_AOI_2_Vegas_img1.tif'
            img_c = cv2.imread(filename)
            img_s = io.imread(filename)
            # img_c, img_s: same values
            lab_c = cv2.cvtColor(img_c, cv2.COLOR_BGR2LAB)
            lab_s = color.rgb2lab(img_s)

            print(lab_c, '\n----\n', lab_s)

          • 具体两个库的实现我也不是很清楚,不过按照我的理解 LAB 和 RGB 的转换应该只是色彩的表示方式的转换吧,像素点应该还是那么多像素点,效果应该是看起来颜色不一样了,但是图像的分辨率应该是完全一样的。

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注