SLIC superpixel实现分析
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 聚类之后还需要有一个增强处理,以便于将一个区域围起来的独立的像素点给归并到某一类中。
下面是整个代码的执行过程:
- void SLIC::DoSuperpixelSegmentation_ForGivenNumberOfSuperpixels(
- const unsigned int* ubuff,
- const int width,
- const int height,
- int*& klabels,
- int& numlabels,
- const int& K,//required number of superpixels
- const double& compactness)//weight given to spatial distance
- {
- const int superpixelsize = 0.5+double(width*height)/double(K);//每一个超像素的大小
- DoSuperpixelSegmentation_ForGivenSuperpixelSize(ubuff,width,height,klabels,numlabels,superpixelsize,compactness);
- }
函数的参数中,ubuff是图像的像素内存指针,width和height是图像的高度,klabels和ubuff大小一样,用于返回每个像素所代表的label——也就是聚合之后属于哪一个类。numlabels返回实际聚类之后得到的数目,而K则是用户希望聚类的数目,compactness则表示距离转化为颜色空间的比例。函数的起始求出向上取整的颜色空间区域的大小。
- void SLIC::DoSuperpixelSegmentation_ForGivenSuperpixelSize(
- const unsigned int* ubuff,
- const int width,
- const int height,
- int*& klabels,
- int& numlabels,
- const int& superpixelsize,
- const double& compactness)
- {
- const int STEP = sqrt(double(superpixelsize))+0.5;
- vector<double> kseedsl(0);
- vector<double> kseedsa(0);
- vector<double> kseedsb(0);
- vector<double> kseedsx(0);
- vector<double> kseedsy(0);
- m_width = width;
- m_height = height;
- int sz = m_width*m_height;
- for( int s = 0; s < sz; s++ ) klabels[s] = -1;
- DoRGBtoLABConversion(ubuff, m_lvec, m_avec, m_bvec);
- bool perturbseeds(true);//perturb seeds is not absolutely necessary, one can set this flag to false
- vector<double> edgemag(0);
- if(perturbseeds) DetectLabEdges(m_lvec, m_avec, m_bvec, m_width, m_height, edgemag);
- GetLABXYSeeds_ForGivenStepSize(kseedsl,kseedsa,kseedsb,kseedsx,kseedsy,STEP,perturbseeds,edgemag);
- PerformSuperpixelSLIC(kseedsl,kseedsa, kseedsb, kseedsx, kseedsy, klabels, STEP, edgemag,compactness);
- numlabels = kseedsl.size();
- int* nlabels = new int[sz];
- EnforceLabelConnectivity(klabels,m_width, m_height, nlabels, numlabels, double(sz)/double(STEP*STEP));
- {for(int i = 0; i < sz; i++ ) klabels[i] = nlabels[i];}
- if(nlabels) delete [] nlabels;
- }
真正的函数实现是放在上面这个函数中的,首先定义的是聚类的中心点。然后对整个图像进行颜色空间的转换,将整个颜色空间由BGR转化为LAB,其中m_lvec,m_avec和m_bvec分表输出转换后的L,A和B颜色空间。然后根据用户的设定进行边缘检测,检测出来的边缘用于调整初始化时的中心点。经过上面的处理之后,就对需要聚类的中心点进行初始化。在初始化之后就可以执行聚类操作了,在聚类之后进行第三步增强处理,将一个区域范围内的没有被聚类的点和相邻的类进行一个聚类操作。
- void SLIC::DetectLabEdges(
- const double* lvec,
- const double* avec,
- const double* bvec,
- const int& width,
- const int& height,
- vector<double>& edges)
- {
- int sz = width*height;
- edges.resize(sz,0);
- for( int j = 1; j < height-1; j++ )
- {
- for( int k = 1; k < width-1; k++ )
- {
- int i = j*width+k;
- double dx = (lvec[i-1]-lvec[i+1])*(lvec[i-1]-lvec[i+1]) +
- (avec[i-1]-avec[i+1])*(avec[i-1]-avec[i+1]) +
- (bvec[i-1]-bvec[i+1])*(bvec[i-1]-bvec[i+1]);
- double dy = (lvec[i-width]-lvec[i+width])*(lvec[i-width]-lvec[i+width]) +
- (avec[i-width]-avec[i+width])*(avec[i-width]-avec[i+width]) +
- (bvec[i-width]-bvec[i+width])*(bvec[i-width]-bvec[i+width]);
- edges[i] = dx*dx + dy*dy;
- }
- }
- }
得到边缘的操作很简单,就是利用在X和Y方向上的求导然后得到斜线上的斜率,这些斜率全部存放到edegs里面输出。
- void SLIC::GetLABXYSeeds_ForGivenStepSize(
- vector<double>& kseedsl,
- vector<double>& kseedsa,
- vector<double>& kseedsb,
- vector<double>& kseedsx,
- vector<double>& kseedsy,
- const int& STEP,
- const bool& perturbseeds,
- const vector<double>& edgemag)
- {
- const bool hexgrid = false;
- int numseeds(0);
- int n(0);
- int xstrips = (0.5+double(m_width)/double(STEP));//向上取整
- int ystrips = (0.5+double(m_height)/double(STEP));
- int xerr = m_width - STEP*xstrips;if(xerr < 0){xstrips--;xerr = m_width - STEP*xstrips;}
- int yerr = m_height - STEP*ystrips;if(yerr < 0){ystrips--;yerr = m_height- STEP*ystrips;}
- double xerrperstrip = double(xerr)/double(xstrips);
- double yerrperstrip = double(yerr)/double(ystrips);
- int xoff = STEP/2;
- int yoff = STEP/2;
- numseeds = xstrips*ystrips;//表明生成numseeds个超像素块
- kseedsl.resize(numseeds);
- kseedsa.resize(numseeds);
- kseedsb.resize(numseeds);
- kseedsx.resize(numseeds);
- kseedsy.resize(numseeds);
- for( int y = 0; y < ystrips; y++ )
- {
- int ye = y*yerrperstrip;
- for( int x = 0; x < xstrips; x++ )
- {
- int xe = x*xerrperstrip;
- int seedx = (x*STEP+xoff+xe);
- int seedy = (y*STEP+yoff+ye);
- int i = seedy*m_width + seedx;
- kseedsl[n] = m_lvec[i];
- kseedsa[n] = m_avec[i];
- kseedsb[n] = m_bvec[i];
- kseedsx[n] = seedx;
- kseedsy[n] = seedy;
- n++;
- }
- }//初始化聚类的中心点
- if(perturbseeds)
- {
- PerturbSeeds(kseedsl, kseedsa, kseedsb, kseedsx, kseedsy, edgemag);
- }
- }
首先,函数根据传递进来的跨度求出在宽度一定的图像中可以有多少个区域,然后再求出在高度一定的图像中有多少个区域,总共区域就是这个两个数的乘积。接下来求两个偏移,第一个是全局的偏移,这个偏移会随着整个图像的中块的位置而累积,还有一个偏移是指本身需要在块的中心点开始聚类。根据上面的解释,后面的一个两层循环就很好理解了。经过上面的初始化就得到了初始的聚类中心点,然后进行一个调整,使得所有的像素都不会在像素的边缘点上。这个操作通过在一个3*3的小区域内求出最小的梯度点更新初始的像素点中心。因为聚类必须具备的一个很好地性质就是不能讲图像中的一个目标给一分为二。
- void SLIC::PerturbSeeds(
- vector<double>& kseedsl,
- vector<double>& kseedsa,
- vector<double>& kseedsb,
- vector<double>& kseedsx,
- vector<double>& kseedsy,
- const vector<double>& edges)
- {
- const int dx8[8] = {-1, -1, 0, 1, 1, 1, 0, -1};
- const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1};
- int numseeds = kseedsl.size();
- for( int n = 0; n < numseeds; n++ )
- {
- int ox = kseedsx[n];//original x
- int oy = kseedsy[n];//original y
- int oind = oy*m_width + ox;
- int storeind = oind;
- for( int i = 0; i < 8; i++ )
- {
- int nx = ox+dx8[i];//new x
- int ny = oy+dy8[i];//new y
- if( nx >= 0 && nx < m_width && ny >= 0 && ny < m_height)
- {
- int nind = ny*m_width + nx;
- if( edges[nind] < edges[storeind])
- {
- storeind = nind;
- }
- }
- }
- if(storeind != oind)
- {
- kseedsx[n] = storeind%m_width;
- kseedsy[n] = storeind/m_width;
- kseedsl[n] = m_lvec[storeind];
- kseedsa[n] = m_avec[storeind];
- kseedsb[n] = m_bvec[storeind];
- }
- }
- }
上面是一个对初始中心点的更新,很简单,就是根据之前计算出来的梯度值来进行更新。
- void SLIC::PerformSuperpixelSLIC(
- vector<double>& kseedsl,
- vector<double>& kseedsa,
- vector<double>& kseedsb,
- vector<double>& kseedsx,
- vector<double>& kseedsy,
- int*& klabels,
- const int& STEP,
- const vector<double>& edgemag,
- const double& M)
- {
- int sz = m_width*m_height;
- const int numk = kseedsl.size();
- int offset = STEP;
- vector<double> clustersize(numk, 0);
- vector<double> inv(numk, 0);//to store 1/clustersize[k] values
- vector<double> sigmal(numk, 0);
- vector<double> sigmaa(numk, 0);
- vector<double> sigmab(numk, 0);
- vector<double> sigmax(numk, 0);
- vector<double> sigmay(numk, 0);
- vector<double> distvec(sz, DBL_MAX);
- double invwt = 1.0/((STEP/M)*(STEP/M));
- int x1, y1, x2, y2;
- double l, a, b;
- double dist;
- double distxy;
- for( int itr = 0; itr < 10; itr++ )
- {
- distvec.assign(sz, DBL_MAX);
- for( int n = 0; n < numk; n++ )
- {
- y1 = max(0.0, kseedsy[n]-offset);
- y2 = min((double)m_height, kseedsy[n]+offset);
- x1 = max(0.0, kseedsx[n]-offset);
- x2 = min((double)m_width, kseedsx[n]+offset);
- for( int y = y1; y < y2; y++ )
- {
- for( int x = x1; x < x2; x++ )
- {
- int i = y*m_width + x;
- l = m_lvec[i];
- a = m_avec[i];
- b = m_bvec[i];
- dist =(l-kseedsl[n])*(l-kseedsl[n])+(a-kseedsa[n])*(a-kseedsa[n])+(b-kseedsb[n])*(b-kseedsb[n]);
- distxy=(x-kseedsx[n])*(x-kseedsx[n])+(y -kseedsy[n])*(y - kseedsy[n]);
- dist += distxy*invwt;
- if( dist < distvec[i] )
- {
- distvec[i] = dist;//label的距离
- klabels[i] = n;//表明属于哪个label
- }
- }
- }
- }
- sigmal.assign(numk, 0);
- sigmaa.assign(numk, 0);
- sigmab.assign(numk, 0);
- sigmax.assign(numk, 0);
- sigmay.assign(numk, 0);
- clustersize.assign(numk, 0);
- int ind(0);
- for( int r = 0; r < m_height; r++ )
- {
- for( int c = 0; c < m_width; c++ )
- {
- sigmal[klabels[ind]] += m_lvec[ind];//统计当前的l颜色通道的数据
- sigmaa[klabels[ind]] += m_avec[ind];
- sigmab[klabels[ind]] += m_bvec[ind];
- sigmax[klabels[ind]] += c;
- sigmay[klabels[ind]] += r;
- clustersize[klabels[ind]] += 1.0;//相应的label中的计数增加1
- ind++;
- }
- }
- for( int k = 0; k < numk; k++ )
- {
- if( clustersize[k] <= 0 ) clustersize[k] = 1;
- inv[k] = 1.0/clustersize[k];//computing inverse now to multiply, than divide later
- }
- for( int k = 0; k < numk; k++ )
- {
- kseedsl[k] = sigmal[k]*inv[k];//更新中心点
- kseedsa[k] = sigmaa[k]*inv[k];
- kseedsb[k] = sigmab[k]*inv[k];
- kseedsx[k] = sigmax[k]*inv[k];
- kseedsy[k] = sigmay[k]*inv[k];
- }
- }
- }
上面的函数实现k-means聚类操作,首先给定中心点然后计算最短距离,然后根据计算得到的最短距离更新中心点。首先定义两个数据来辅助聚类操作,一个用于计数一个类中存在多少个像素点,另一个用于得到前者的倒数,用于进行归一化处理。整个聚类循环包含10次,这个是可以进行修改的。循环起始位置先将整个距离初始化为最长距离,然后再计算当中更新最短的距离。首先根据中心点的起始范围计算聚类的起始位置,跨度为2STEP*2STEP。然后计算颜色空间和像素位置的欧氏距离,其中位置距离需要进行一个调整,而不是直接作为欧氏距离使用。根据新计算出来的距离和类别对坐标和颜色进行更新,然后进行下一轮聚类操作。
- void SLIC::EnforceLabelConnectivity(
- const int* labels,
- const int width,
- const int height,
- int*& nlabels,//new labels
- int& numlabels,
- const int& K) //the number of superpixels desired by the user
- {
- const int dx4[4] = {-1, 0, 1, 0};//相应的像素的四个方向,左右上下
- const int dy4[4] = { 0, -1, 0, 1};
- const int sz = width*height;
- const int SUPSZ = sz/K;//超像素大小
- for( int i = 0; i < sz; i++ ) nlabels[i] = -1;
- int label(0);
- int* xvec = new int[sz];
- int* yvec = new int[sz];
- int oindex(0);
- int adjlabel(0);//adjacent label
- for( int j = 0; j < height; j++ )
- {
- for( int k = 0; k < width; k++ )
- {
- if( 0 > nlabels[oindex] )//找出相应的位置的label
- {
- nlabels[oindex] = label;
- xvec[0] = k;
- yvec[0] = j;
- for( int n = 0; n < 4; n++ )
- {
- int x = xvec[0] + dx4[n];
- int y = yvec[0] + dy4[n];
- if( (x >= 0 && x < width) && (y >= 0 && y < height) )
- {
- int nindex = y*width + x;
- if(nlabels[nindex] >= 0) adjlabel = nlabels[nindex];
- }
- }
- int count(1);
- for( int c = 0; c < count; c++ )
- {
- for( int n = 0; n < 4; n++ )
- {
- int x = xvec[c] + dx4[n];
- int y = yvec[c] + dy4[n];
- if( (x >= 0 && x < width) && (y >= 0 && y < height) )
- {
- int nindex = y*width + x;
- if(0>nlabels[nindex]&&labels[oindex]==labels[nindex] )
- {
- xvec[count] = x;
- yvec[count] = y;
- nlabels[nindex] = label;
- count++;
- }
- }
- }
- }
- if(count <= SUPSZ >> 2)//搜索范围为以某一个像素为中心点的2倍范围
- {
- for( int c = 0; c < count; c++ )
- {
- int ind = yvec[c]*width+xvec[c];
- nlabels[ind] = adjlabel;//将相应的label设置为附近的标签
- }
- label--;
- }
- label++;
- }
- oindex++;
- }
- }
- numlabels = label;
- if(xvec) delete [] xvec;
- if(yvec) delete [] yvec;
- }
循环遍历每一个标签,使得每一个标签都有类别——当label大于0的时候,则表明存在标签否则不存在标签。其中,根据k和j作为搜索的起点,搜索依据是这个像素点附近的标签,如果这个像素点附近存在属于某一个类别的标签,则首先将其保存下来以便于后面的合并处理。否则根据这个起点开始搜索,搜索的依据是第一这个像素点还没有被搜索到,第二这个点的像素标签和其实的标签一样。更新标签之后,下一步就是看看是否处理了足够的标签,因为以一个像素四个方位的像素点进行处理,所以区域不能小于等于超像素值的1/4,如果小于这个值表明聚类还不够彻底,所以需要将标签递减一位表示下面的处理还是从当前的像素开始,经过这样的处理之后整个像素就被聚类了。
完整的代码可以在我资源页下载。
源地址:http://blog.csdn.net/dayenglish/article/details/39616937
SLIC superpixel实现分析的更多相关文章
- SLIC superpixel算法
标题 SLIC superpixel算法 作者 YangZheng 联系方式 263693992 SLIC算法是simple linear iterative cluster的简称,该算法用来生成超像 ...
- SuperPixel
目录 SLIC Superpixel algorithm 距离函数的选择 代码 Gonzalez R. C. and Woods R. E. Digital Image Processing (For ...
- 超像素经典算法SLIC的代码的深度优化和分析。
现在这个社会发展的太快,到处都充斥着各种各样的资源,各种开源的平台,如github,codeproject,pudn等等,加上一些大型的官方的开源软件,基本上能找到各个类型的代码.很多初创业的老板可能 ...
- 【深度聚类】Superpixel Sampling Networks
Superpixel Sampling Networks 原始文档:https://www.yuque.com/lart/papers/ssn 本文的思想很简单,传统的超像素算法是一种有效的低/中级的 ...
- Superpixel Based RGB-D Image Segmentation Using Markov Random Field——阅读笔记
1.基本信息 题目:使用马尔科夫场实现基于超像素的RGB-D图像分割: 作者所属:Ferdowsi University of Mashhad(Iron) 发表:2015 International ...
- 机器学习:simple linear iterative clustering (SLIC) 算法
图像分割是图像处理,计算机视觉领域里非常基础,非常重要的一个应用.今天介绍一种高效的分割算法,即 simple linear iterative clustering (SLIC) 算法,顾名思义,这 ...
- 跑superpixel的程序
知乎上对superpixel的讲解还不错:https://www.zhihu.com/question/27623988 superpixel的算法有很多,opencv中也包含了很多,我找了一个比较经 ...
- 《SLIC Superpixels》阅读笔记
原始链接:http://blog.csdn.net/jkhere/article/details/16819285 或许有改动,请参考原文! SLIC 超像素(SLICSuperpixels) Rad ...
- alias导致virtualenv异常的分析和解法
title: alias导致virtualenv异常的分析和解法 toc: true comments: true date: 2016-06-27 23:40:56 tags: [OS X, ZSH ...
随机推荐
- cmake总结
无论生活还是工作上,做过的事,需要总结下. 接触cmake,一般都会看一本书 <<cmake 实践>>. 这是cmake的入门书.我就不多说了. 下面说一下我对部分cmake命 ...
- codeforces 316F3 Suns and Rays
题目在此 找出中有多少个太阳以及每个太阳的散发线段. 算法 原图: 将图"缩小",如果一个白点的四周有黑点,那么把这个白点变成黑点: 将图"放大",即上述&qu ...
- 京东金融集团BD部门招聘 BD经理
新标签页http://74.55.154.136/ 互联网招聘_cnBeta.COM 北京 / 全职 / 20k-30k / 经验3-5年 / 本科及以上 / 1天前发布 职位诱惑 : 五险一金 职位 ...
- 九度OnlineJudge之1017:还是畅通工程
题目描述: 某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离.省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可 ...
- VB.net总结
.NET视频差点儿相同用时一周结束,总体感觉就是"走耳不走脑".刚開始看视频理解起来有一点困难,台湾口音以及台湾与大陆计算机术语的差异,让我把前几集相当于直接忽略过了(建议打算看这 ...
- 写程序取自己进程的AEP
测试程序功能 打印出自己进程的程序入口点地址. 结合OD载入程序,看到的入口点确实是0x004014f0, 说明程序入口点找到了 测试程序 /// @file exam_1_1.c #include ...
- js实现表格的选中一行-------Day58
最開始想很多其它的用js来动态操作表格,是由于在应用了easyUI之后,发现直接写一个<table id="tt"></table>,这就够了,界面里面就剩 ...
- php数据库操作类
config.db.php <?php $db_config["hostname"] = "localhost"; //服务器地址 $db_config[ ...
- PHP开发-上传文件
<?php /****************************************************************************** 参数说明: $max_ ...
- 【翻译】十大要避免的Ext JS开发方法
原文地址:http://www.sencha.com/blog/top-10-ext-js-development-practices-to-avoid/ 作者:Sean Lanktree Sean ...