SILC算法详解

一、原理介绍

SLIC算法是simple linear iterative cluster的简称,该算法用来生成超像素(superpixel)

算法步骤:

  • 已知一副图像大小M*N,可以从RGB空间转换为LAB空间,LAB颜色空间表现的颜色更全面
  • 假如预定义参数K,K为预生成的超像素数量,即预计将M*N大小的图像(像素数目即为M*N)分隔为K个超像素块,每个超像素块范围大小包含[(M*N)/K]个像素
  • 假设每个超像素区域长和宽都均匀分布的话,那么每个超像素块的长和宽均可定义为S,S=sqrt(M*N/K)
  • 遍历操作,将每个像素块的中心点的坐标(x,y)及其lab的值保存起来,加入到事先定义好的集合中
  • 每个像素块的中心点默认是(S/2,S/2)进行获取的,有可能落在噪音点或者像素边缘(所谓像素边缘,即指像素突变处,比如从黑色过渡到白色的交界处),这里,利用差分方式进行梯度计算,调整中心点:

算法中,使用中心点的8领域像素点,计算获得最小梯度值的像素点,并将其作为新的中心点,差分计算梯度的公式:

  1. Gradient(x,y)=dx(i,j) + dy(i,j);
  1. dx(i,j) = I(i+1,j) - I(i,j); 
  1. dy(i,j) = I(i,j+1) - I(i,j);
  2.  
  3. 遍历现中心点的8领域像素点,将其中计算得到最小Gradient值的像素点作为新的中心点
  • 调整完中心点后即需要进行像素点的聚类操作
  1. 通过聚类的方式迭代计算新的聚类中心;
  1. 首先,需要借助K-means聚类算法,将像素点进行归类,通过变换的欧氏聚距离公式进行,公式如下(同时参考像素值和坐标值提取相似度):

  1. 通过两个参数mS来协调两种距离的比例分配。参数S即是上面第③步计算得出的每个像素块的长度值,而参数MLAB空间的距离可能最大值,其可取的范围建议为[1,40]
  1. 为了节省时间,只遍历每个超像素块中心点周边的2S*2S区域内的像素点,计算该区域内每个像素点距离哪一个超像素块的中心点最近,并将其划分到其中;完成一次迭代后,重新计算每个超像素块的中心点坐标,并重新进行迭代(注:衡量效率和效果后一般选择迭代10次)
  1. 二、代码实现
  1. import math
  2. from skimage import io, color
  3. import numpy as np
  4.  
  5. class Cluster(object):
  6.  
  7. cluster_index = 1
  8.  
  9. def __init__(self, row, col, l=0, a=0, b=0):
  10. self.update(row, col, l, a, b)
  11. self.pixels = []
  12. self.no = self.cluster_index
  13. Cluster.cluster_index += 1
  14.  
  15. def update(self, row, col, l, a, b):
  16. self.row = row
  17. self.col = col
  18. self.l = l
  19. self.a = a
  20. self.b = b
  21.  
  22. class SLICProcessor(object):
  23. @staticmethod
  24. def open_image(path):
  25. rgb = io.imread(path)
  26. lab_arr = color.rgb2lab(rgb)
  27. return lab_arr
  28.  
  29. @staticmethod
  30. def save_lab_image(path, lab_arr):
  31. rgb_arr = color.lab2rgb(lab_arr)
  32. io.imsave(path, rgb_arr)
  33.  
  34. def make_cluster(self, row, col):
  35. row=int(row)
  36. col=int(col)
  37. return Cluster(row, col,
  38. self.data[row][col][0],
  39. self.data[row][col][1],
  40. self.data[row][col][2])
  41.  
  42. def __init__(self, filename, K, M):
  43. self.K = K
  44. self.M = M
  45.  
  46. self.data = self.open_image(filename)
  47. self.rows = self.data.shape[0]
  48. self.cols = self.data.shape[1]
  49. self.N = self.rows * self.cols
  50. self.S = int(math.sqrt(self.N / self.K))
  51.  
  52. self.clusters = []
  53. self.label = {}
  54. self.dis = np.full((self.rows, self.cols), np.inf)
  55.  
  56. def init_clusters(self):
  57. row = self.S / 2
  58. col = self.S / 2
  59. while row < self.rows:
  60. while col < self.cols:
  61. self.clusters.append(self.make_cluster(row, col))
  62. col+= self.S
  63. col = self.S / 2
  64. row += self.S
  65.  
  66. def get_gradient(self, row, col):
  67. if col + 1 >= self.cols:
  68. col = self.cols - 2
  69. if row + 1 >= self.rows:
  70. row = self.rows - 2
  71.  
  72. gradient = (self.data[row + 1][col][0] +self.data[row][col+1][0]-2*self.data[row][col][0])+ \
  73. (self.data[row + 1][col][1] +self.data[row][col+1][1]-2*self.data[row][col][1]) + \
  74. (self.data[row + 1][col][2] +self.data[row][col+1][2]-2*self.data[row][col][2])
  75.  
  76. return gradient
  77.  
  78. def move_clusters(self):
  79. for cluster in self.clusters:
  80. cluster_gradient = self.get_gradient(cluster.row, cluster.col)
  81. for dh in range(-1, 2):
  82. for dw in range(-1, 2):
  83. _row = cluster.row + dh
  84. _col = cluster.col + dw
  85. new_gradient = self.get_gradient(_row, _col)
  86. if new_gradient < cluster_gradient:
  87. cluster.update(_row, _col, self.data[_row][_col][0], self.data[_row][_col][1], self.data[_row][_col][2])
  88. cluster_gradient = new_gradient
  89.  
  90. def assignment(self):
  91. for cluster in self.clusters:
  92. for h in range(cluster.row - 2 * self.S, cluster.row + 2 * self.S):
  93. if h < 0 or h >= self.rows: continue
  94. for w in range(cluster.col - 2 * self.S, cluster.col + 2 * self.S):
  95. if w < 0 or w >= self.cols: continue
  96. L, A, B = self.data[h][w]
  97. Dc = math.sqrt(
  98. math.pow(L - cluster.l, 2) +
  99. math.pow(A - cluster.a, 2) +
  100. math.pow(B - cluster.b, 2))
  101. Ds = math.sqrt(
  102. math.pow(h - cluster.row, 2) +
  103. math.pow(w - cluster.col, 2))
  104. D = math.sqrt(math.pow(Dc / self.M, 2) + math.pow(Ds / self.S, 2))
  105. if D < self.dis[h][w]:
  106. if (h, w) not in self.label:
  107. self.label[(h, w)] = cluster
  108. cluster.pixels.append((h, w))
  109. else:
  110. self.label[(h, w)].pixels.remove((h, w))
  111. self.label[(h, w)] = cluster
  112. cluster.pixels.append((h, w))
  113. self.dis[h][w] = D
  114.  
  115. def update_cluster(self):
  116. for cluster in self.clusters:
  117. sum_h = sum_w = number = 0
  118. for p in cluster.pixels:
  119. sum_h += p[0]
  120. sum_w += p[1]
  121. number += 1
  122. _h =int( sum_h / number)
  123. _w =int( sum_w / number)
  124. cluster.update(_h, _w, self.data[_h][_w][0], self.data[_h][_w][1], self.data[_h][_w][2])
  125.  
  126. def save_current_image(self, name):
  127. image_arr = np.copy(self.data)
  128. for cluster in self.clusters:
  129. for p in cluster.pixels:
  130. image_arr[p[0]][p[1]][0] = cluster.l
  131. image_arr[p[0]][p[1]][1] = cluster.a
  132. image_arr[p[0]][p[1]][2] = cluster.b
  133. image_arr[cluster.row][cluster.col][0] = 0
  134. image_arr[cluster.row][cluster.col][1] = 0
  135. image_arr[cluster.row][cluster.col][2] = 0
  136. self.save_lab_image(name, image_arr)
  137.  
  138. def iterates(self):
  139. self.init_clusters()
  140. self.move_clusters()
  141. #考虑到效率和效果,折中选择迭代10次
  142. for i in range(10):
  143. self.assignment()
  144. self.update_cluster()
  145. self.save_current_image("output.jpg")
  146.  
  147. if __name__ == '__main__':
  148. p = SLICProcessor('beauty.jpg', 200, 40)
  149. p.iterates()
  1.  
  1. 三、运行效果截图

    (原图)
  2.  

  3. (效果图)

代码参考了https://github.com/laixintao/slic-python-implementation,且做了改进

作为一枚技术小白,写这篇笔记的时候参考了很多博客论文,在这里表示感谢,转载请注明出处......

SILC超像素分割算法详解(附Python代码)的更多相关文章

  1. OpenCV3三种超像素分割算法源码以及效果

    OpenCV3中超像素分割算法SEEDS,SLIC, LSC算法在Contrib包里,需要使用Cmake编译使用.为了方便起见,我将三种算法的源码文件从contrib包里拎了出来,可以直接使用,顺便比 ...

  2. 机器学习经典算法详解及Python实现--基于SMO的SVM分类器

    原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector  ...

  3. 对数损失函数logloss详解和python代码

    python机器学习-乳腺癌细胞挖掘(博主亲自录制视频)https://study.163.com/course/introduction.htm?courseId=1005269003&ut ...

  4. 洛谷P3366【模板】最小生成树-克鲁斯卡尔Kruskal算法详解附赠习题

    链接 题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz 输入输出格式 输入格式: 第一行包含两个整数N.M,表示该图共有N个结点和M条无向边.(N<=5000,M&l ...

  5. sip鉴权认证算法详解及python加密

    1. 认证和加密    认证(Authorization)的作用在于表明自己是谁,即向别人证明自己是谁.而相关的概念是MD5,用于认证安全.注意MD5仅仅是个hash函数而已,并不是用于加密.因为ha ...

  6. AdaBoost算法详解与python实现

    1. 概述 1.1 集成学习 目前存在各种各样的机器学习算法,例如SVM.决策树.感知机等等.但是实际应用中,或者说在打比赛时,成绩较好的队伍几乎都用了集成学习(ensemble learning)的 ...

  7. 排序算法详解(java代码实现)

    ​ 排序算法大致分为内部排序和外部排序两种 内部排序:待排序的记录全部放到内存中进行排序,时间复杂度也就等于比较的次数 外部排序:数据量很大,内存无法容纳,需要对外存进行访问再排序,把若干段数据一次读 ...

  8. 斐波那契堆(Fibonacci heap)原理详解(附java代码实现)

    前言 斐波那契堆(Fibonacci heap)是计算机科学中最小堆有序树的集合.它和二项式堆有类似的性质,但比二项式堆有更好的均摊时间.堆的名字来源于斐波那契数,它常用于分析运行时间. 堆结构介绍 ...

  9. 超像素经典算法SLIC的代码的深度优化和分析。

    现在这个社会发展的太快,到处都充斥着各种各样的资源,各种开源的平台,如github,codeproject,pudn等等,加上一些大型的官方的开源软件,基本上能找到各个类型的代码.很多初创业的老板可能 ...

随机推荐

  1. 2018 CCPC网络赛 1010 hdu 6447 ( 树状数组优化dp)

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=6447 思路:很容易推得dp转移公式:dp[i][j] = max(dp[i][j-1],dp[i-1][j ...

  2. MT【216】韦达定理

    设$n$为正整数,$a_1,a_2,\cdots,a_n;b_1,b_2,\cdots,b_n;A,B$都是正数, 满足$a_i\le b_i,a_i\le A,i=1,2,\cdots,n$ 且$\ ...

  3. 自学Linux Shell3.6-文件查看命令file cat more less tail head

    点击返回 自学Linux命令行与Shell脚本之路 3.6-文件查看命令file cat more less tail head 1.参看文件类型file 该命令用来识别文件类型,也可用来辨别一些文件 ...

  4. formelf.exe的用法

    @2018-9-17 在windows下的cmd.exe程序下生成某个文件的 .txt版本 fromelf --text -o name.txt target.x

  5. bzoj4817/luogu3703 树点涂色 (LCT+dfs序+线段树)

    我们发现,这个染色的操作他就很像LCT中access的操作(为什么??),然后就自然而然地想到,其实一个某条路径上的颜色数量,就是我们做一个只有access操作的LCT,这条路径经过的splay的数量 ...

  6. P1198 最大数 线段树水题

    这道题模拟一下可以过,但是我们发现线段树也可以安全水过...... 写的线段树只需要滋磁单点修改,区间求max即可 我一开始犯了一个很SB的错误:每次插入修改了t,然后疯狂爆0到怀疑人生... 而且我 ...

  7. c#中用lua脚本执行redis命令

    直接贴出代码,实现执行lua脚本的方法,用到的第三方类库是 StackExchange.Redis(nuget上有) 注:下面的代码是简化后的,实际使用要修改, using System; using ...

  8. NO.9: 令operator=返回一个reference to *this

    1.令赋值操作返回一个reference to *this(除非你有个标新立异的理由,那就随大众- - )

  9. poj 1904(强连通分量+完美匹配)

    传送门:Problem 1904 https://www.cnblogs.com/violet-acmer/p/9739990.html 参考资料: [1]:http://www.cnblogs.co ...

  10. [译]Golang中的优雅重启

    原文 Graceful Restart in Golang 作者 grisha 声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加. ...