-
分水岭分割
2016-08-27 11:16:06分水岭图像分割 -
分水岭分割图片
2018-05-19 22:24:36一般分水岭分割,从结果可以看出存在过分割问题 -
分水岭分割算法
2018-04-19 10:54:20用MATLAB对图片进行分水岭分割算法 -
IDL分水岭分割
2018-02-27 19:47:34利用IDL实现分水岭分割,供参考,数据可自己准备,功能较简单 -
分水岭分割方法
2018-11-07 17:07:48分水岭分割方法分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
分水岭的计算过程是一个迭代标注过程。分水岭比较经典的计算方法是L. Vincent提出的。在该算法中,分水岭计算分两个步骤,一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高排序,然后在从低到高实现淹没过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。
分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点,即为分水岭。显然,分水岭表示的是输入图像极大值点。因此,为得到图像的边缘信息,通常把梯度图像作为输入图像,即
g(x,y)=grad(f(x,y))={[f(x,y)-f(x-1,y)]2[f(x,y)-f(x,y-1)]2}0.5
式中,f(x,y)表示原始图像,grad{.}表示梯度运算。
分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时应当看出,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证的。另外,分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。
为消除分水岭算法产生的过度分割,通常可以采用两种处理方法,一是利用先验知识去除无关边缘信息。二是修改梯度函数使得集水盆只响应想要探测的目标。
为降低分水岭算法产生的过度分割,通常要对梯度函数进行修改,一个简单的方法是对梯度图像进行阈值处理,以消除灰度的微小变化产生的过度分割。即
g(x,y)=max(grad(f(x,y)),gθ)
式中,gθ表示阈值。
分水岭分割技术是一种很优秀的且得到了广泛应用的分割技术,从本质上讲,它属于一种基于区域增长的分割方法,但它得到的确是目标的边界,且是连续、闭合、但像素宽的边界。在很多领域,这种分割技术都得到了广泛的应用,但分水岭分割却有一个致命的弱点,那就是容易产生过分割,对于噪声和细密纹理非常敏感,使其常常产生严重的过分割结果。所以,针对这个问题,很多人提出了很多种改进的分水岭分割技术。综合来讲,大概也就三类。其一,分割预处理。既在应用分水岭分割之前对图像进行一些预处理,诸如除噪,求梯度图像,形态学重建,标记前景背景等等,一个目的,减少小的积水盆,从而减少过分割区域的数量。其二,分割后处理。既在应用分水岭分割之后对结果图像进行合并处理。如果初始分割产生过多小区域,合并处理会具有很大的运算量,所以后处理的时间复杂度经常较高,还有合并准则的确定,也是一件比较麻烦的事情,通常有基于相邻区域的平均灰度信息和边界强度信息的合并准则,不同的合并准则会得到不同的分割结果。其三,就是既有预处理又有后处理的分割技术了,根据具体应用领域的要求,如果只是采取预处理或后处理还不能得到满意的效果,那就把他们都用上试试了。
分水岭分割算法源代码:
//Image Watershed Segmentation
//This is the implementation of the algorithm based on immersion model.
// ===========================================================================
// ===== Module: Watershed.cpp
// ===== -------------------------------------------------------------- ======
// ===== Version 01 Date: 04/21/2003
// ===== -------------------------------------------------------------- ======
// ===========================================================================
// ===== Written by Foxhole@smth.org
// ===== e-mail: gong200@china.com
// ===========================================================================
// Permission to use, copy, or modify this software and its documentation
// for educational and research purposes only is hereby granted without
// fee, provided that this copyright notice appear on all copies and
// related documentation. For any other uses of this software, in original
// or modified form, including but not limited to distribution in whole
// or in part, specific prior permission must be obtained from
// the author(s).
//
// THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
// EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
// WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//
// IN NO EVENT SHALL RUTGERS UNIVERSITY BE LIABLE FOR ANY SPECIAL,
// INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY
// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY
// THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE
// OR PERFORMANCE OF THIS SOFTWARE.
// ===========================================================================#include <queue>
#include <vector>
#include <windows.h>
/*====================================================================
函数名 : Watershed
功能 : 用标记-分水岭算法对输入图像进行分割
算法实现 : 无
输入参数说明 : OriginalImage --输入图像(灰度图,0~255)
SeedImage --标记图像(二值图,0-非标记,1-标记)
LabelImage --输出图像(1-第一个分割区域,2-第二个分割区域,...)
row --图像行数
col --图像列数
返回值说明 : 无
====================================================================*/
void Watershed(const int **OriginalImage, char** SeedImage, int **LabelImage, int row, int col)
{
using namespace std;//标记区域标识号,从1开始
int Num=0;
int i,j;//保存每个队列种子个数的数组
vector<int*> SeedCounts;
//临时种子队列
queue<POINT> que;
//保存所有标记区域种子队列的数组
vector<queue<POINT>* > qu;
int* array;
queue<POINT> *uu;
POINT temp;for(i=0;i<row;i++)
for(j=0;j<col;j++)
LabelImage[j]=0;
int m,n,k=0;
int up,down,right,left,upleft,upright,downleft,downright;//预处理,提取区分每个标记区域,并初始化每个标记的种子队列
//种子是指标记区域边缘的点,他们可以在水位上升时向外淹没(或者说生长)
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
{
//如果找到一个标记区域
if(SeedImage[j]==1)
{
//区域的标识号加一
Num++;
//分配数组并初始化为零
array=new int[256];
ZeroMemory(array,256*sizeof(int));
//
SeedCounts.push_back(array);
//分配本标记的优先队列
uu=new queue<POINT>[256];
//加入到队列数组中
qu.push_back(uu);
//当前点放入本标记区域的临时种子队列中
temp.x=i;
temp.y=j;
que.push(temp);
//当前点标记为已处理
LabelImage[j]=Num;
SeedImage[j]=127;
//让种子队列中的种子进行生长直到所有的种子都生长完毕
while(!que.empty())
{
up=down=right=left=0;
upleft=upright=downleft=downright=0;
//队列中取出一个种子
temp=que.front();
m=temp.x;
n=temp.y;
que.pop();if(m>0)
{
//上方若为可生长点则加为新种子
if(SeedImage[m-1][n]==1)
{
temp.x=m-1;
temp.y=n;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m-1][n]=Num;
SeedImage[m-1][n]=127;
}else//否则上方为不可生长
{
up=1;
}
}
if(m>0&&n>0)
{
if(SeedImage[m-1][n-1]==1)//左上方若为可生长点则加为新种子
{
temp.x=m-1;
temp.y=n-1;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m-1][n-1]=Num;
SeedImage[m-1][n-1]=127;
}else//否则左上方为不可生长
{
upleft=1;
}
}if(m<row-1)
{
if(SeedImage[m+1][n]==1)//下方若为可生长点则加为新种子
{
temp.x=m+1;
temp.y=n;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m+1][n]=Num;
SeedImage[m+1][n]=127;
}else//否则下方为不可生长
{
down=1;
}
}
if(m<(row-1)&&n<(col-1))
{
if(SeedImage[m+1][n+1]==1)//下方若为可生长点则加为新种子
{
temp.x=m+1;
temp.y=n+1;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m+1][n+1]=Num;
SeedImage[m+1][n+1]=127;
}else//否则下方为不可生长
{
downright=1;
}
}
if(n<col-1)
{
if(SeedImage[m][n+1]==1)//右方若为可生长点则加为新种子
{
temp.x=m;
temp.y=n+1;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m][n+1]=Num;
SeedImage[m][n+1]=127;
}else//否则右方为不可生长
{
right=1;
}
}
if(m>0&&n<(col-1))
{
if(SeedImage[m-1][n+1]==1)//右上方若为可生长点则加为新种子
{
temp.x=m-1;
temp.y=n+1;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m-1][n+1]=Num;
SeedImage[m-1][n+1]=127;
}else//否则右上方为不可生长
{
upright=1;
}
}if(n>0)
{
if(SeedImage[m][n-1]==1)//左方若为可生长点则加为新种子
{
temp.x=m;
temp.y=n-1;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m][n-1]=Num;
SeedImage[m][n-1]=127;
}else//否则左方为不可生长
{
left=1;
}
}
if(m<(row-1)&&n>0)
{
if(SeedImage[m+1][n-1]==1)//左下方若为可生长点则加为新种子
{
temp.x=m+1;
temp.y=n-1;
que.push(temp);
//新种子点标记为已淹没区域
LabelImage[m+1][n-1]=Num;
SeedImage[m+1][n-1]=127;
}else//否则左方为不可生长
{
downleft=1;
}
}//上下左右只要有一点不可生长,那么本点为初始种子队列中的一个
if(up||down||right||left||
upleft||downleft||upright||downright)
{
temp.x=m;
temp.y=n;
qu[Num-1][OriginalImage[m][n]].push(temp);
SeedCounts[Num-1][OriginalImage[m][n]]++;
}}//while结束
}
}
}bool actives;//在某一水位处,所有标记的种子生长完的标志
int WaterLevel;//淹没过程开始,水位从零开始上升
for(WaterLevel=0;WaterLevel<256;WaterLevel++)
{
actives=true;
while(actives)
{
actives=false;
//依次处理每个标记区域
for(i=0;i<Num;i++)
{
if(!qu[WaterLevel].empty())
{
actives=true;
while(SeedCounts[WaterLevel]>0)
{
SeedCounts[WaterLevel]--;
temp=qu[WaterLevel].front();
qu[WaterLevel].pop();
m = temp.x;
n = temp.y;//当前种子的坐标
if(m>0)
{
if(!LabelImage[m-1][n])//上方若未处理
{
temp.x=m-1;
temp.y=n;
LabelImage[m-1][n]=i+1;//上方点标记为已淹没区域if(OriginalImage[m-1][n]<=WaterLevel)//上方若为可生长点则加入当前队列
{
qu[WaterLevel].push(temp);
}
else//否则加入OriginalImage[m-1][n]级队列
{
qu[OriginalImage[m-1][n]].push(temp);
SeedCounts[OriginalImage[m-1][n]]++;
}
}
}if(m<row-1)
{
if(!LabelImage[m+1][n])//下方若未处理
{
temp.x=m+1;
temp.y=n;
LabelImage[m+1][n]=i+1;//下方点标记为已淹没区域if(OriginalImage[m+1][n]<=WaterLevel)//下方若为可生长点则加入当前队列
{
qu[WaterLevel].push(temp);
}
else//否则加入OriginalImage[m+1][n]级队列
{
qu[OriginalImage[m+1][n]].push(temp);
SeedCounts[OriginalImage[m+1][n]]++;
}
}
}
if(n<col-1)
{
if(!LabelImage[m][n+1])//右边若未处理
{
temp.x=m;
temp.y=n+1;
LabelImage[m][n+1]=i+1;//右边点标记为已淹没区域if(OriginalImage[m][n+1]<=WaterLevel)//右边若为可生长点则加入当前队列
{
qu[WaterLevel].push(temp);
}
else//否则加入OriginalImage[m][n+1]级队列
{
qu[OriginalImage[m][n+1]].push(temp);
SeedCounts[OriginalImage[m][n+1]]++;
}
}
}if(n>0)
{
if(!LabelImage[m][n-1])//左边若未处理
{
temp.x=m;
temp.y=n-1;
LabelImage[m][n-1]=i+1;//左边点标记为已淹没区域if(OriginalImage[m][n-1]<=WaterLevel)//左边若为可生长点则加入当前队列
{
qu[WaterLevel].push(temp);
}
else//否则加入OriginalImage[m][n-1]级队列
{
qu[OriginalImage[m][n-1]].push(temp);
SeedCounts[OriginalImage[m][n-1]]++;
}
}
}
}//while循环结束
SeedCounts[WaterLevel]=qu[WaterLevel].size();
}//if结束
}//for循环结束
}//while循环结束
}//for循环结束
while(!qu.empty())
{
uu=qu.back();
delete[] uu;
qu.pop_back();
}
while(!SeedCounts.empty())
{
array=SeedCounts.back();
delete[] array;
SeedCounts.pop_back();
}
}
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wazdxm1980/archive/2007/07/04/1678275.aspx给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
-
源码图像分割-分水岭分割.m
2019-08-13 11:53:38源码图像分割-分水岭分割.m 上传一些关于图像分割的源码,文件名对应算法名,希望对大家有点用处。 -
自适应梯度重建分水岭分割算法
2021-03-18 14:34:23自适应梯度重建分水岭分割算法 -
分水岭分割法
2014-04-02 09:22:34图像处理领域较为流行的分割算法,分水岭分割算法的代码实现! -
matlab分水岭分割算法不用内置函数_图像分割实战-分水岭分割方法和GrabCut 算法
2020-11-28 11:56:001. 分水岭分割方法它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法...1. 分水岭分割方法
它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。
基本的步骤:
例子1 粘连对象分离和计数。
例子代码:
#include#includeusing namespace std;using namespace cv;void test(){ Mat srcImg; srcImg = imread("pill_002.png"); if (srcImg.empty()) { cout <>contours; //找到 marker 的轮廓 findContours(distMaskImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //create marker 填充 marker Mat markersImg = Mat::zeros(srcImg.size(), CV_32SC1); for (int i = 0; i (i), Scalar::all(static_cast(i)+1), -1); } circle(markersImg, Point(5, 5), 3, Scalar(255), -1); //形态学操作 - 彩色图像,目的是去掉干扰,让结果更好。 Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(srcImg, srcImg, MORPH_ERODE, kernel); //完成分水岭变换 watershed(srcImg, markersImg); Mat mark = Mat::zeros(markersImg.size(), CV_8UC1); markersImg.convertTo(mark, CV_8UC1); bitwise_not(mark, mark, Mat()); namedWindow("watershed", CV_WINDOW_AUTOSIZE); imshow("watershed", mark); //下面的步骤可以不做,最好做出来让结果显示更美观。 //生成随机颜色 vectorcolors; for (int i = 0; i (i, j); if (index > 0 && index <= contours.size()) { dstImg.at(i, j) = colors[index - 1]; } else { dstImg.at(i, j) = Vec3b(0, 0, 0); } } } cout <
总结:有时候会导致碎片化,过度分割,因为二值化中如果有很多小的黑点或碎片,在分割的时候导致很多 mask ,即小山头太多了,这个时候我们要考虑怎么去合并它,可以通过联通区域的直方图,或者像素值均值相似程度等。
例子2:图像分割
#include#includeusing namespace std;using namespace cv;//执行分水岭算法函数Mat watershedCluster(Mat &srcImg, int &numSegments);//结果显示函数void DisplaySegments(Mat &markersImg, int numSegments);void test(){ Mat srcImg; srcImg = imread("toux.jpg"); if (srcImg.empty()) { cout <>contours; vectorhireachy; findContours(distImg, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE); if (contours.empty()) { return Mat(); } Mat markersImg(distImg.size(), CV_32S); markersImg = Scalar::all(0); for (int i = 0; i colors; for (int i = 0; i (i, j); if (index > 0 && index <= numSegments) { dstImg.at(i, j) = colors[index - 1]; } else { dstImg.at(i, j) = Vec3b(255, 255, 255); } } } cout <
效果图:
2. GrabCut 算法分割图像
GrabCut 算法的原理前面有介绍过,这里就不在介绍了,具体可以看下文章末尾往期推荐中阅读。下面例子实现图像中对象的抠图。
基本步骤:
例子代码:
#include#includeusing namespace std;using namespace cv;int numRun = 0; //算法迭代次数bool init = false;Rect rect;Mat srcImg, MaskImg, bgModel, fgModel;//鼠标回调函数void onMouse(int event, int x, int y, int flags, void* param);void showImg(); //显示画的图片void setRoiMask(); //选择 ROI 的函数void runGrabCut(); //执行算法函数static void ShowHelpText(); //提示用户操作函数void test(){ srcImg = imread("toux.jpg"); if (srcImg.empty()) { cout < 1 && rect.height > 1) { showImg(); } break; default: break; }}void showImg(){ Mat result, binMask; binMask.create(MaskImg.size(), CV_8UC1); binMask = MaskImg & 1; if (init) { srcImg.copyTo(result,binMask); } else { srcImg.copyTo(result); } rectangle(result, rect, Scalar(0, 0, 255), 2, 8); namedWindow("Original image", CV_WINDOW_AUTOSIZE); imshow("Original image", result);}void setRoiMask(){ //GC_BGD = 0 明确属于背景的像素 //GC_FGD = 1 明确属于前景的像素 //GC_PR_BGD = 2 可能属于背景的像素 //GC_PR_FGD = 3 可能属于前景的像素 MaskImg.setTo(GC_BGD); //为了避免选择越界 rect.x = max(0, rect.x); rect.y = max(0, rect.y); rect.width = min(rect.width, srcImg.cols - rect.x); rect.height = min(rect.height, srcImg.rows - rect.y); //把我们选取的那一块设为前景 MaskImg(rect).setTo(Scalar(GC_PR_FGD));}void runGrabCut(){ if (rect.width
效果图:
-
分水岭分割方法提取目标轮廓
2018-03-21 15:56:36详细的分水岭分割方法提取目标轮廓,有详细的注释。包括了三种对比分析,直接使用分水岭,使用梯度图像分水岭分割,使用形态学重建分水岭分割 -
matlab 分水岭分割算法
2017-07-28 09:57:10matlab分水岭分割算法实例,内涵有实测数据,亲测可用! -
图像分水岭分割
2013-03-15 15:42:37matlab图像分水岭分割,图像形态学经典示例。 -
基于距离变换和分水岭分割的图像分割代码
2018-05-28 18:17:59基于距离变换和分水岭分割的图像分割代码,VS2013, opencv2.4.9, -
python版本的GrabCut前景分割和分水岭分割
2018-06-09 10:53:19python版本的GrabCut前景分割和分水岭分割,同时也有深度估计 -
opencv分水岭分割算法
2014-03-27 09:33:27opencv下实现的图像分水岭分割算法的源代码。 -
分水岭分割算法程序
2011-10-23 00:05:56分水岭分割算法程序,试验过,效果很好!分水岭分割算法程序,试验过,效果很好! -
一种新的SAR图像分水岭分割方法
2021-03-03 07:13:19一种新的SAR图像分水岭分割方法 -
图像处理中标记分水岭分割算法.pdf
2020-11-19 21:31:47图像处理中的标记分水岭分割算法 如果图像中的目标物体是连接在一起的则分割起来会更困难分 水岭分割算法经常用于处理这类问题通常会取得比较好的效果分 水岭分割算法把图像看成一幅 地形图其中亮度比较强的区域 ... -
Watershed分水岭分割算法
2013-03-22 11:07:41Watershed分水岭分割算法,学习一下吧哈哈哈 -
论文研究-一种采用模板预分割的分水岭分割算法 .pdf
2019-08-15 13:27:54一种采用模板预分割的分水岭分割算法,姜雪峰,王毅,本文提出了一种改进的分水岭分割方法。先利用灰度直方图的阈值自动提取方法,通过用模板对图像进行预分割,得到具有一定意义的边 -
opencv图像分割之分水岭分割
2020-02-06 13:44:44分水岭分割 watershed图像自动分割的实现步骤: 图像灰度化、滤波、Canny边缘检测 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。 watershed分水岭运算(实质是...分水岭分割
watershed图像自动分割的实现步骤:
- 图像灰度化、滤波、Canny边缘检测
- 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。
- watershed分水岭运算(实质是将markers的轮廓线的信息,转化为按轮廓区分的块状信息)
- 绘制分割出来的区域,视觉控还可以使用随机颜色填充,或者跟原始图像融合以下,以得到更好的显示效果。
相关api
函数api
void watershed( InputArray image, InputOutputArray markers );
参数介绍
- image:必须是一个8bit,3通道彩色图像矩阵序列
- markers:在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。
接下来执行分水岭会发生什么呢?算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。
均值漂移函数api
void pyrMeanShiftFiltering( InputArray src, OutputArray dst, double sp, double sr, int maxLevel=1, TermCriteria termcrit=TermCriteria( TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) );
参数介绍
- src,输入图像,8位,三通道的彩色图像,并不要求必须是RGB格式,HSV、YUV等Opencv中的彩色图像格式均可;
- dst,输出图像,跟输入src有同样的大小和数据格式;
- sp,定义的漂移物理空间半径大小;
- sr,定义的漂移色彩空间半径大小;
- maxLevel,定义金字塔的最大层数;
- termcrit,定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合;
函数介绍
meanShfit
均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。
可以利用均值偏移算法的这个特性,实现彩色图像分割,Opencv中对应的函数是pyrMeanShiftFiltering
。这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在Opencv中它的后缀是滤波“Filter”,而不是分割“segment”。先列一下这个函数,再说一下它“分割”彩色图像的实现过程。
pyrMeanShiftFiltering函数的执行过程是这样的:-
迭代空间构建:
以输入图像上src上任一点P0为圆心,建立物理空间上半径为sp,色彩空间上半径为sr的球形空间,物理空间上坐标2个—x、y,色彩空间上坐标3个—R、G、B(或HSV),构成一个5维的空间球体。其中物理空间的范围x和y是图像的长和宽,色彩空间的范围R、G、B分别是0~255。 -
求取迭代空间的向量并移动迭代空间球体后重新计算向量,直至收敛:
在1中构建的球形空间中,求得所有点相对于中心点的色彩向量之和后,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得的向量和的终点就是该空间球体的中心点Pn,迭代结束。 -
更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,如此完成一个点的色彩均值漂移。
-
对输入图像src上其他点,依次执行步骤1,、2、3,遍历完所有点位后,整个均值偏移色彩滤波完成,这里忽略对金字塔的讨论。
在这个过程中,关键参数是sp和sr的设置,二者设置的值越大,对图像色彩的平滑效果越明显,同时函数耗时也越多
代码
#include <opencv2/opencv.hpp> #include <iostream> #include <math.h> using namespace std; using namespace cv; using namespace cv::ml; #define PIC_PATH "/work/opencv_pic/" #define PIC_NAME "man_face.jpg" int main(void) { Mat src; //获取完整的图片路径及名称 string pic = string(PIC_PATH)+string(PIC_NAME); //打印图片路径 cout << "pic path is :"<<pic<<endl; //读取图片 src = imread(pic); //判断图片是否存在 if(src.empty()) { cout<<"pic is not exist!!!!"<<endl; return -1; } //显示图片 namedWindow("src pic",WINDOW_AUTOSIZE); imshow("src pic",src); Mat gray_src,binaryimg; //转化为灰度图 cvtColor(src,gray_src,COLOR_BGR2GRAY); //二值化 threshold(gray_src,binaryimg,0,255,THRESH_BINARY | THRESH_OTSU); imshow("binary",binaryimg); //形态学操作 消除噪点 防止小的尖峰影响分水岭检测 Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3)); morphologyEx(binaryimg,binaryimg,MORPH_OPEN,kernel); //距离变换 寻找水坝基础 Mat dist; distanceTransform(binaryimg,dist,DIST_L2,3,CV_32F); normalize(dist,dist,0,1,NORM_MINMAX); imshow("dist",dist); //绘制轮廓 threshold(dist,dist,0.1,1,THRESH_BINARY); //水坝定位 imshow("distimg",dist); //准备marks roi的轮廓信息 也就是分水岭的水坝 dist.convertTo(dist,CV_8UC1); vector<vector<Point>> contours; vector<Vec4i> hireachy; findContours(dist,contours,hireachy,RETR_TREE,CHAIN_APPROX_SIMPLE); if(contours.empty()) return -1; //创建maker Mat markers = Mat::zeros(src.size(),CV_32S); markers = Scalar::all(0); for(size_t i=0;i<contours.size();i++) { drawContours(markers,contours,i,Scalar(i+1),-1,8,hireachy,INTER_MAX); //绘制轮廓 每条轮廓颜色一定一定要区分开 } circle(markers,Point(5,5),3,Scalar(255),-1); //mark做一个小标记 int index = 0; //打印轮廓数据 有值的均为轮廓线 for(int row=0;row<markers.rows;row++) for(int col=0;col<markers.cols;col++) { index = markers.at<int>(row,col); cout << index <<","; } //进行分水岭变换 watershed(src,markers); int num_segments = contours.size(); //获取分割区块数 vector<Vec3b> colors; //准备着色候选 for(int i=0;i<num_segments;i++) { int r = theRNG().uniform(0,255); int g = theRNG().uniform(0,255); int b = theRNG().uniform(0,255); colors.push_back(Vec3b((uchar)b,(uchar)g,(uchar)r)); } //颜色填充最终显示 Mat dst = Mat::zeros(markers.size(),CV_8UC3); for(int row=0;row<markers.rows;row++) for(int col=0;col<markers.cols;col++) { index = markers.at<int>(row,col); //打印区域数据 我们可以看到markers的数据已经变为块状数据 值为-1的为块状的边界线 cout << index <<","; if(index>0 && index <=num_segments) { dst.at<Vec3b>(row,col) = colors[index-1]; }else { dst.at<Vec3b>(row,col) = Vec3b(0,0,0); } } imshow("分水岭分割演示",dst); waitKey(0); destroyAllWindows(); return 0; }
效果
分水岭运算之前 的markers数据
可以看到数据是每条不同颜色的轮廓线
分水岭运算之后的markers数据
数据是以-1为边界的,块状索引数据,按这些数据可以对各个块进行着色处理
-
基于分水岭分割进行肺癌诊断.zip
2019-08-09 20:57:53基于标记的分水岭分割算法能有效控制过分割现象的发生,该算法的标记包括内部标记和外部标记。其基本现象是通过引入标记来修正梯度图像,使得局部最小值仅出现在标记的位置,并设置阈值h来对像素值进行过滤,删除... -
segmentation_picture(分水岭分割算法)
2017-11-13 17:49:16参考网上资源写的一个课程作业(分水岭分割算法),初步实现分水岭分割算法处理图片,配置环境是:win8.1+vs2015+opencv3.2.0。