http://infoscience.epfl.ch/record/149300这是SLIC算法的官网,网站有和SLIC相关的资源。

SLIC主要运用K-means聚类算法进行超像素的处理,聚类算法中的距离度量不仅仅包括颜色空间的颜色距离还包括像素坐标的欧氏距离。所以K-means聚类的中心点由五维向量组成。其中包括,记录LAB颜色空间下的像素以及该像素点的XY坐标,由于XY坐标不能和颜色空间直接进行计算,所以添加了一个紧密度的参数。

算法的实现过程:

1 对于一个包含N个像素的图像而言,如果对这个图像聚类为K个超像素块,那么每个超像素的范围大小为N/K个。如果每个超像素区域长和宽都均匀分布的话,那么每个像素的跨度为STEP=sqrt(N/K)

2 利用上面的数据对K-means聚类中心点尽心初始化,不过在初始化之后还需要注意一点。为了保证选出来的中心点不在像素的边缘上所以需要对对求出的中心点的便于梯度进行一个对比排除。就是和周围的8个像素点尽心比较直到找到像素梯度最小的点作为中心点。初始化之后就进行k-means聚类操作。

3 聚类之后还需要有一个增强处理,以便于将一个区域围起来的独立的像素点给归并到某一类中。

下面是整个代码的执行过程:

  1. void SLIC::DoSuperpixelSegmentation_ForGivenNumberOfSuperpixels(
  2. const unsigned int*                             ubuff,
  3. const int                   width,
  4. const int                   height,
  5. int*&                       klabels,
  6. int&                        numlabels,
  7. const int&                  K,//required number of superpixels
  8. const double&                                   compactness)//weight given to spatial distance
  9. {
  10. const int superpixelsize = 0.5+double(width*height)/double(K);//每一个超像素的大小
  11. DoSuperpixelSegmentation_ForGivenSuperpixelSize(ubuff,width,height,klabels,numlabels,superpixelsize,compactness);
  12. }

函数的参数中,ubuff是图像的像素内存指针,width和height是图像的高度,klabels和ubuff大小一样,用于返回每个像素所代表的label——也就是聚合之后属于哪一个类。numlabels返回实际聚类之后得到的数目,而K则是用户希望聚类的数目,compactness则表示距离转化为颜色空间的比例。函数的起始求出向上取整的颜色空间区域的大小。

  1. void SLIC::DoSuperpixelSegmentation_ForGivenSuperpixelSize(
  2. const unsigned int*         ubuff,
  3. const int                   width,
  4. const int                   height,
  5. int*&                       klabels,
  6. int&                        numlabels,
  7. const int&                  superpixelsize,
  8. const double&               compactness)
  9. {
  10. const int STEP = sqrt(double(superpixelsize))+0.5;
  11. vector<double> kseedsl(0);
  12. vector<double> kseedsa(0);
  13. vector<double> kseedsb(0);
  14. vector<double> kseedsx(0);
  15. vector<double> kseedsy(0);
  16. m_width  = width;
  17. m_height = height;
  18. int sz = m_width*m_height;
  19. for( int s = 0; s < sz; s++ ) klabels[s] = -1;
  20. DoRGBtoLABConversion(ubuff, m_lvec, m_avec, m_bvec);
  21. bool perturbseeds(true);//perturb seeds is not absolutely necessary, one can set this flag to false
  22. vector<double> edgemag(0);
  23. if(perturbseeds) DetectLabEdges(m_lvec, m_avec, m_bvec, m_width, m_height, edgemag);
  24. GetLABXYSeeds_ForGivenStepSize(kseedsl,kseedsa,kseedsb,kseedsx,kseedsy,STEP,perturbseeds,edgemag);
  25. PerformSuperpixelSLIC(kseedsl,kseedsa, kseedsb, kseedsx, kseedsy, klabels, STEP, edgemag,compactness);
  26. numlabels = kseedsl.size();
  27. int* nlabels = new int[sz];
  28. EnforceLabelConnectivity(klabels,m_width, m_height, nlabels, numlabels, double(sz)/double(STEP*STEP));
  29. {for(int i = 0; i < sz; i++ ) klabels[i] = nlabels[i];}
  30. if(nlabels) delete [] nlabels;
  31. }

真正的函数实现是放在上面这个函数中的,首先定义的是聚类的中心点。然后对整个图像进行颜色空间的转换,将整个颜色空间由BGR转化为LAB,其中m_lvec,m_avec和m_bvec分表输出转换后的L,A和B颜色空间。然后根据用户的设定进行边缘检测,检测出来的边缘用于调整初始化时的中心点。经过上面的处理之后,就对需要聚类的中心点进行初始化。在初始化之后就可以执行聚类操作了,在聚类之后进行第三步增强处理,将一个区域范围内的没有被聚类的点和相邻的类进行一个聚类操作。

  1. void SLIC::DetectLabEdges(
  2. const double*               lvec,
  3. const double*               avec,
  4. const double*               bvec,
  5. const int&                  width,
  6. const int&                  height,
  7. vector<double>&               edges)
  8. {
  9. int sz = width*height;
  10. edges.resize(sz,0);
  11. for( int j = 1; j < height-1; j++ )
  12. {
  13. for( int k = 1; k < width-1; k++ )
  14. {
  15. int i = j*width+k;
  16. double dx = (lvec[i-1]-lvec[i+1])*(lvec[i-1]-lvec[i+1]) +
  17. (avec[i-1]-avec[i+1])*(avec[i-1]-avec[i+1]) +
  18. (bvec[i-1]-bvec[i+1])*(bvec[i-1]-bvec[i+1]);
  19. double dy = (lvec[i-width]-lvec[i+width])*(lvec[i-width]-lvec[i+width]) +
  20. (avec[i-width]-avec[i+width])*(avec[i-width]-avec[i+width]) +
  21. (bvec[i-width]-bvec[i+width])*(bvec[i-width]-bvec[i+width]);
  22. edges[i] = dx*dx + dy*dy;
  23. }
  24. }
  25. }

得到边缘的操作很简单,就是利用在X和Y方向上的求导然后得到斜线上的斜率,这些斜率全部存放到edegs里面输出。

  1. void SLIC::GetLABXYSeeds_ForGivenStepSize(
  2. vector<double>&               kseedsl,
  3. vector<double>&               kseedsa,
  4. vector<double>&               kseedsb,
  5. vector<double>&               kseedsx,
  6. vector<double>&               kseedsy,
  7. const int&                  STEP,
  8. const bool&                 perturbseeds,
  9. const vector<double>&       edgemag)
  10. {
  11. const bool hexgrid = false;
  12. int numseeds(0);
  13. int n(0);
  14. int xstrips = (0.5+double(m_width)/double(STEP));//向上取整
  15. int ystrips = (0.5+double(m_height)/double(STEP));
  16. int xerr = m_width  - STEP*xstrips;if(xerr < 0){xstrips--;xerr = m_width - STEP*xstrips;}
  17. int yerr = m_height - STEP*ystrips;if(yerr < 0){ystrips--;yerr = m_height- STEP*ystrips;}
  18. double xerrperstrip = double(xerr)/double(xstrips);
  19. double yerrperstrip = double(yerr)/double(ystrips);
  20. int xoff = STEP/2;
  21. int yoff = STEP/2;
  22. numseeds = xstrips*ystrips;//表明生成numseeds个超像素块
  23. kseedsl.resize(numseeds);
  24. kseedsa.resize(numseeds);
  25. kseedsb.resize(numseeds);
  26. kseedsx.resize(numseeds);
  27. kseedsy.resize(numseeds);
  28. for( int y = 0; y < ystrips; y++ )
  29. {
  30. int ye = y*yerrperstrip;
  31. for( int x = 0; x < xstrips; x++ )
  32. {
  33. int xe = x*xerrperstrip;
  34. int seedx = (x*STEP+xoff+xe);
  35. int seedy = (y*STEP+yoff+ye);
  36. int i = seedy*m_width + seedx;
  37. kseedsl[n] = m_lvec[i];
  38. kseedsa[n] = m_avec[i];
  39. kseedsb[n] = m_bvec[i];
  40. kseedsx[n] = seedx;
  41. kseedsy[n] = seedy;
  42. n++;
  43. }
  44. }//初始化聚类的中心点
  45. if(perturbseeds)
  46. {
  47. PerturbSeeds(kseedsl, kseedsa, kseedsb, kseedsx, kseedsy, edgemag);
  48. }
  49. }

首先,函数根据传递进来的跨度求出在宽度一定的图像中可以有多少个区域,然后再求出在高度一定的图像中有多少个区域,总共区域就是这个两个数的乘积。接下来求两个偏移,第一个是全局的偏移,这个偏移会随着整个图像的中块的位置而累积,还有一个偏移是指本身需要在块的中心点开始聚类。根据上面的解释,后面的一个两层循环就很好理解了。经过上面的初始化就得到了初始的聚类中心点,然后进行一个调整,使得所有的像素都不会在像素的边缘点上。这个操作通过在一个3*3的小区域内求出最小的梯度点更新初始的像素点中心。因为聚类必须具备的一个很好地性质就是不能讲图像中的一个目标给一分为二。

  1. void SLIC::PerturbSeeds(
  2. vector<double>&               kseedsl,
  3. vector<double>&               kseedsa,
  4. vector<double>&               kseedsb,
  5. vector<double>&               kseedsx,
  6. vector<double>&               kseedsy,
  7. const vector<double>&                   edges)
  8. {
  9. const int dx8[8] = {-1, -1,  0,  1, 1, 1, 0, -1};
  10. const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1,  1};
  11. int numseeds = kseedsl.size();
  12. for( int n = 0; n < numseeds; n++ )
  13. {
  14. int ox = kseedsx[n];//original x
  15. int oy = kseedsy[n];//original y
  16. int oind = oy*m_width + ox;
  17. int storeind = oind;
  18. for( int i = 0; i < 8; i++ )
  19. {
  20. int nx = ox+dx8[i];//new x
  21. int ny = oy+dy8[i];//new y
  22. if( nx >= 0 && nx < m_width && ny >= 0 && ny < m_height)
  23. {
  24. int nind = ny*m_width + nx;
  25. if( edges[nind] < edges[storeind])
  26. {
  27. storeind = nind;
  28. }
  29. }
  30. }
  31. if(storeind != oind)
  32. {
  33. kseedsx[n] = storeind%m_width;
  34. kseedsy[n] = storeind/m_width;
  35. kseedsl[n] = m_lvec[storeind];
  36. kseedsa[n] = m_avec[storeind];
  37. kseedsb[n] = m_bvec[storeind];
  38. }
  39. }
  40. }

上面是一个对初始中心点的更新,很简单,就是根据之前计算出来的梯度值来进行更新。

  1. void SLIC::PerformSuperpixelSLIC(
  2. vector<double>&               kseedsl,
  3. vector<double>&               kseedsa,
  4. vector<double>&               kseedsb,
  5. vector<double>&               kseedsx,
  6. vector<double>&               kseedsy,
  7. int*&                   klabels,
  8. const int&              STEP,
  9. const vector<double>&                   edgemag,
  10. const double&               M)
  11. {
  12. int sz = m_width*m_height;
  13. const int numk = kseedsl.size();
  14. int offset = STEP;
  15. vector<double> clustersize(numk, 0);
  16. vector<double> inv(numk, 0);//to store 1/clustersize[k] values
  17. vector<double> sigmal(numk, 0);
  18. vector<double> sigmaa(numk, 0);
  19. vector<double> sigmab(numk, 0);
  20. vector<double> sigmax(numk, 0);
  21. vector<double> sigmay(numk, 0);
  22. vector<double> distvec(sz, DBL_MAX);
  23. double invwt = 1.0/((STEP/M)*(STEP/M));
  24. int x1, y1, x2, y2;
  25. double l, a, b;
  26. double dist;
  27. double distxy;
  28. for( int itr = 0; itr < 10; itr++ )
  29. {
  30. distvec.assign(sz, DBL_MAX);
  31. for( int n = 0; n < numk; n++ )
  32. {
  33. y1 = max(0.0,           kseedsy[n]-offset);
  34. y2 = min((double)m_height,  kseedsy[n]+offset);
  35. x1 = max(0.0,           kseedsx[n]-offset);
  36. x2 = min((double)m_width,   kseedsx[n]+offset);
  37. for( int y = y1; y < y2; y++ )
  38. {
  39. for( int x = x1; x < x2; x++ )
  40. {
  41. int i = y*m_width + x;
  42. l = m_lvec[i];
  43. a = m_avec[i];
  44. b = m_bvec[i];
  45. dist =(l-kseedsl[n])*(l-kseedsl[n])+(a-kseedsa[n])*(a-kseedsa[n])+(b-kseedsb[n])*(b-kseedsb[n]);
  46. distxy=(x-kseedsx[n])*(x-kseedsx[n])+(y -kseedsy[n])*(y - kseedsy[n]);
  47. dist += distxy*invwt;
  48. if( dist < distvec[i] )
  49. {
  50. distvec[i] = dist;//label的距离
  51. klabels[i]  = n;//表明属于哪个label
  52. }
  53. }
  54. }
  55. }
  56. sigmal.assign(numk, 0);
  57. sigmaa.assign(numk, 0);
  58. sigmab.assign(numk, 0);
  59. sigmax.assign(numk, 0);
  60. sigmay.assign(numk, 0);
  61. clustersize.assign(numk, 0);
  62. int ind(0);
  63. for( int r = 0; r < m_height; r++ )
  64. {
  65. for( int c = 0; c < m_width; c++ )
  66. {
  67. sigmal[klabels[ind]] += m_lvec[ind];//统计当前的l颜色通道的数据
  68. sigmaa[klabels[ind]] += m_avec[ind];
  69. sigmab[klabels[ind]] += m_bvec[ind];
  70. sigmax[klabels[ind]] += c;
  71. sigmay[klabels[ind]] += r;
  72. clustersize[klabels[ind]] += 1.0;//相应的label中的计数增加1
  73. ind++;
  74. }
  75. }
  76. for( int k = 0; k < numk; k++ )
  77. {
  78. if( clustersize[k] <= 0 ) clustersize[k] = 1;
  79. inv[k] = 1.0/clustersize[k];//computing inverse now to multiply, than divide later
  80. }
  81. for( int k = 0; k < numk; k++ )
  82. {
  83. kseedsl[k] = sigmal[k]*inv[k];//更新中心点
  84. kseedsa[k] = sigmaa[k]*inv[k];
  85. kseedsb[k] = sigmab[k]*inv[k];
  86. kseedsx[k] = sigmax[k]*inv[k];
  87. kseedsy[k] = sigmay[k]*inv[k];
  88. }
  89. }
  90. }

上面的函数实现k-means聚类操作,首先给定中心点然后计算最短距离,然后根据计算得到的最短距离更新中心点。首先定义两个数据来辅助聚类操作,一个用于计数一个类中存在多少个像素点,另一个用于得到前者的倒数,用于进行归一化处理。整个聚类循环包含10次,这个是可以进行修改的。循环起始位置先将整个距离初始化为最长距离,然后再计算当中更新最短的距离。首先根据中心点的起始范围计算聚类的起始位置,跨度为2STEP*2STEP。然后计算颜色空间和像素位置的欧氏距离,其中位置距离需要进行一个调整,而不是直接作为欧氏距离使用。根据新计算出来的距离和类别对坐标和颜色进行更新,然后进行下一轮聚类操作。

  1. void SLIC::EnforceLabelConnectivity(
  2. const int*                  labels,
  3. const int                   width,
  4. const int                   height,
  5. int*&                       nlabels,//new labels
  6. int&                        numlabels,
  7. const int&                  K) //the number of superpixels desired by the user
  8. {
  9. const int dx4[4] = {-1,  0,  1,  0};//相应的像素的四个方向,左右上下
  10. const int dy4[4] = { 0, -1,  0,  1};
  11. const int sz = width*height;
  12. const int SUPSZ = sz/K;//超像素大小
  13. for( int i = 0; i < sz; i++ ) nlabels[i] = -1;
  14. int label(0);
  15. int* xvec = new int[sz];
  16. int* yvec = new int[sz];
  17. int oindex(0);
  18. int adjlabel(0);//adjacent label
  19. for( int j = 0; j < height; j++ )
  20. {
  21. for( int k = 0; k < width; k++ )
  22. {
  23. if( 0 > nlabels[oindex] )//找出相应的位置的label
  24. {
  25. nlabels[oindex] = label;
  26. xvec[0] = k;
  27. yvec[0] = j;
  28. for( int n = 0; n < 4; n++ )
  29. {
  30. int x = xvec[0] + dx4[n];
  31. int y = yvec[0] + dy4[n];
  32. if( (x >= 0 && x < width) && (y >= 0 && y < height) )
  33. {
  34. int nindex = y*width + x;
  35. if(nlabels[nindex] >= 0) adjlabel = nlabels[nindex];
  36. }
  37. }
  38. int count(1);
  39. for( int c = 0; c < count; c++ )
  40. {
  41. for( int n = 0; n < 4; n++ )
  42. {
  43. int x = xvec[c] + dx4[n];
  44. int y = yvec[c] + dy4[n];
  45. if( (x >= 0 && x < width) && (y >= 0 && y < height) )
  46. {
  47. int nindex = y*width + x;
  48. if(0>nlabels[nindex]&&labels[oindex]==labels[nindex] )
  49. {
  50. xvec[count] = x;
  51. yvec[count] = y;
  52. nlabels[nindex] = label;
  53. count++;
  54. }
  55. }
  56. }
  57. }
  58. if(count <= SUPSZ >> 2)//搜索范围为以某一个像素为中心点的2倍范围
  59. {
  60. for( int c = 0; c < count; c++ )
  61. {
  62. int ind = yvec[c]*width+xvec[c];
  63. nlabels[ind] = adjlabel;//将相应的label设置为附近的标签
  64. }
  65. label--;
  66. }
  67. label++;
  68. }
  69. oindex++;
  70. }
  71. }
  72. numlabels = label;
  73. if(xvec) delete [] xvec;
  74. if(yvec) delete [] yvec;
  75. }

循环遍历每一个标签,使得每一个标签都有类别——当label大于0的时候,则表明存在标签否则不存在标签。其中,根据k和j作为搜索的起点,搜索依据是这个像素点附近的标签,如果这个像素点附近存在属于某一个类别的标签,则首先将其保存下来以便于后面的合并处理。否则根据这个起点开始搜索,搜索的依据是第一这个像素点还没有被搜索到,第二这个点的像素标签和其实的标签一样。更新标签之后,下一步就是看看是否处理了足够的标签,因为以一个像素四个方位的像素点进行处理,所以区域不能小于等于超像素值的1/4,如果小于这个值表明聚类还不够彻底,所以需要将标签递减一位表示下面的处理还是从当前的像素开始,经过这样的处理之后整个像素就被聚类了。

完整的代码可以在我资源页下载。

源地址:http://blog.csdn.net/dayenglish/article/details/39616937

SLIC superpixel实现分析的更多相关文章

  1. SLIC superpixel算法

    标题 SLIC superpixel算法 作者 YangZheng 联系方式 263693992 SLIC算法是simple linear iterative cluster的简称,该算法用来生成超像 ...

  2. SuperPixel

    目录 SLIC Superpixel algorithm 距离函数的选择 代码 Gonzalez R. C. and Woods R. E. Digital Image Processing (For ...

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

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

  4. 【深度聚类】Superpixel Sampling Networks

    Superpixel Sampling Networks 原始文档:https://www.yuque.com/lart/papers/ssn 本文的思想很简单,传统的超像素算法是一种有效的低/中级的 ...

  5. Superpixel Based RGB-D Image Segmentation Using Markov Random Field——阅读笔记

    1.基本信息 题目:使用马尔科夫场实现基于超像素的RGB-D图像分割: 作者所属:Ferdowsi University of Mashhad(Iron) 发表:2015 International ...

  6. 机器学习:simple linear iterative clustering (SLIC) 算法

    图像分割是图像处理,计算机视觉领域里非常基础,非常重要的一个应用.今天介绍一种高效的分割算法,即 simple linear iterative clustering (SLIC) 算法,顾名思义,这 ...

  7. 跑superpixel的程序

    知乎上对superpixel的讲解还不错:https://www.zhihu.com/question/27623988 superpixel的算法有很多,opencv中也包含了很多,我找了一个比较经 ...

  8. 《SLIC Superpixels》阅读笔记

    原始链接:http://blog.csdn.net/jkhere/article/details/16819285 或许有改动,请参考原文! SLIC 超像素(SLICSuperpixels) Rad ...

  9. alias导致virtualenv异常的分析和解法

    title: alias导致virtualenv异常的分析和解法 toc: true comments: true date: 2016-06-27 23:40:56 tags: [OS X, ZSH ...

随机推荐

  1. C语言数据结构----栈的定义及实现

    本节主要说的是数据结构中的栈的基本定义和实现的方式,其中实现的方式采用的是复用顺序表和单向链表的方式. 一.栈的基本定义 1.栈是一种特殊的线性表,只能从固定的方向进出,而且栈进出的基本原则是:先进栈 ...

  2. MVC:Controller向View传值方式总结

    Controller向View传值方式总结 总结发现ASP.NET MVC中Controller向View传值的方式共有6种,分别是: ViewBag ViewData TempData 向普通Vie ...

  3. linux: /usr/bin/ld: cannot find -lloc

    /usr/bin/ld: cannot find -lloc ld链接库的时候没发现loc这个库-lloc本事不是文件名字,要去找这个库就搜索libloc, loc, 不能搜索lloc. /usr1/ ...

  4. php的var关键字

    public和var的作用差不多 因为 var定义的变量如果没有加protected 或 private则默认为public php4 中一般是用 varphp5 中就一般是用 public了 现在基 ...

  5. SharePoint 2013 &quot;通知我&quot;简单的功能

    简单的功能 "通知我"内部列表或文档库中的主要项目.加入/删除/修改等操作,用户的E- mail通知设定功能:设置列表或文档库通知的能力,有可能设置通知为一个单一的项目.这是Sha ...

  6. 使用MVC模式开发一简单的销售额查询系统

    与上一篇比较,只改变了index.jsp文件中form的提交路径 <form action="ShowServlet" method="post"> ...

  7. 鼠标进入与离开的消息(使用CM_MOUSEENTER来判断是否进入控件)

    unit Unit1; interface uses  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs ...

  8. HTTP协议和web工作原理

    本章学完之后能干什么? 要把 知识点学好,那就需要把它相关的周边知识点了解全面 HTTP协议是web学习的核心!!! 学东东切忌只学配置,不学原理:只学会框架有什么用,要会自己写框架!! web学习直 ...

  9. java WEB Response重定向和缓存控制

    package cn.com; import java.io.IOException; import javax.servlet.ServletException; import javax.serv ...

  10. css3 animation动画事件

    当使用css3时,会遇到利用@keyframes来定义动画事件,利用以下3个事件,能够捕捉当前元素的动画: AnimationEnd //动画结束时 AnimationStart  //动画開始 An ...