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. boost::share_ptr用法

    boost中提供了几种智能指针方法:scoped_ptr shared_ptr intrusive_ptr weak_ptr,而标准库中提供的智能指针为auto_ptr. 这其中,我最喜欢,使用最多的 ...

  2. struts2之高危远程代码执行漏洞,可造成服务器被入侵,下载最新版本进行修复

          Struts2 被发现存在新的高危远程代码执行漏洞,可造成服务器被入侵,只要是Struts2版本 低于 2.3.14.3 全部存在此漏洞.目前官方已经发布了最新的版本进行修复.请将stru ...

  3. Windows的TCP协议参数

    注册表编辑器:regedit 表项:HKEY_LOCAL_MACHINE\SYSTEM\CurentControlSet\Services\Tcpip\Parameters 窗口扩大因子 & ...

  4. CuSparse 第一章

    (部分翻译) 第一章 介绍 1. 命名惯例 CUSPARSE 包含了一系列处理稀疏矩阵的基本的线性代数子程式.是cuda函数库的一部分,从C,C++中调用. 该库例程可以分为四类: 第一层:在稠密向量 ...

  5. Swift 与 Objective-C混合编程

    在Swift项目中想要同一时候加入Objective-C的库支持或者须要同一时候用Objective-C编程 在加入新的文件时选择Objective-C系统就会自己主动生成一个xx-Bridging- ...

  6. 4部门明确软件IC产业企业所得税优惠政策

    中国证券网讯 据财政部5月9日消息,财政部.国家税务总局.发展改革委.工业和信息化部联合发布关于软件和集成电路产业企业所得税优惠政策有关问题的通知.该通知自2015年1月1日起执行. 通知指出,按照& ...

  7. log4net结构

    log4net是.Net下一个非常优秀的开源日志记录组件.log4net记录日志的功能非常强大.它可以将日志分不同的等级,以不同的格式,输出到不同的媒介.其大致分为如下这些模块. Appenders模 ...

  8. 基于visual Studio2013解决C语言竞赛题之1022最大数最小数

         题目 解决代码及点评 /************************************************************************/ ...

  9. ul不加宽高

    ul可以不加宽高,但是不能用margin(上下左右), 可以用margin(左右),否则里面的内容如果是要左右浮动的话,就会掉下来

  10. CSS中怎么让DIV居中(转载)

    CSS 如何使DIV层水平居中 今天用CSS碰到个很棘手的问题,DIV本身没有定义自己居中的属性, 网上很多的方法都是介绍用上级的text-align: center然后嵌套一层DIV来解决问题. 可 ...