分水岭算法如何解决过度分割问题,图像分割分水岭算法

首页 > 教育 > 作者:YD1662024-05-15 21:52:57

1. 分水岭分割方法

它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。

基本的步骤:

分水岭算法如何解决过度分割问题,图像分割分水岭算法(1)

例子1 粘连对象分离和计数。

例子代码:

#include<opencv2/opencv.hpp> #include<iostream> usingnamespacestd; usingnamespacecv; voidtest(){ MatsrcImg; srcImg=imread("pill_002.png"); if(srcImg.empty()) { cout<<"couldnotloadimage...\n"<<endl; } namedWindow("Originalimage",CV_WINDOW_AUTOSIZE); imshow("Originalimage",srcImg); MatgrayImg,binaryImg,shiftedImg; //做滤波,使图像更加平滑,保留边缘,类似于双边滤波 pyrMeanShiftFiltering(srcImg,shiftedImg,21,51); namedWindow("shifted",CV_WINDOW_AUTOSIZE); imshow("shifted",shiftedImg); cvtColor(shiftedImg,grayImg,COLOR_BGR2GRAY);//转为灰度图像 //二值化 threshold(grayImg,binaryImg,0,255,THRESH_BINARY|THRESH_OTSU); namedWindow("binary",CV_WINDOW_AUTOSIZE); imshow("binary",binaryImg); //距离变换 MatdistImg; distanceTransform(binaryImg,distImg,DistanceTypes::DIST_L2,3,CV_32F); //归一化,因为距离变换后得出来的值都比较小。 normalize(distImg,distImg,0,1,NORM_MINMAX); namedWindow("distance",CV_WINDOW_AUTOSIZE); imshow("distance",distImg); //这个二值化的作用是寻找局部最大。 threshold(distImg,distImg,0.4,1,THRESH_BINARY); namedWindow("distance_binary",CV_WINDOW_AUTOSIZE); imshow("distance_binary",distImg); //生成marker MatdistMaskImg; //distImg得到的是0-1之间的数,转化成8位单通道的。 distImg.convertTo(distMaskImg,CV_8U); vector<vector<Point>>contours; //找到marker的轮廓 findContours(distMaskImg,contours,RETR_EXTERNAL, CHAIN_APPROX_SIMPLE,Point(0,0)); //createmarker填充marker MatmarkersImg=Mat::zeros(srcImg.size(),CV_32SC1); for(inti=0;i<contours.size();i ) { drawContours(markersImg,contours,static_cast<int>(i), Scalar::all(static_cast<int>(i) 1),-1); } circle(markersImg,Point(5,5),3,Scalar(255),-1); //形态学操作-彩色图像,目的是去掉干扰,让结果更好。 Matkernel=getStructuringElement(MORPH_rect,Size(3,3),Point(-1,-1)); morphologyEx(srcImg,srcImg,MORPH_ERODE,kernel); //完成分水岭变换 watershed(srcImg,markersImg); Matmark=Mat::zeros(markersImg.size(),CV_8UC1); markersImg.convertTo(mark,CV_8UC1); bitwise_not(mark,mark,Mat()); namedWindow("watershed",CV_WINDOW_AUTOSIZE); imshow("watershed",mark); //下面的步骤可以不做,最好做出来让结果显示更美观。 //生成随机颜色 vector<Vec3b>colors; for(inti=0;i<contours.size();i ) { intr=theRNG().uniform(0,255); intg=theRNG().uniform(0,255); intb=theRNG().uniform(0,255); colors.push_back(Vec3b((uchar)b,(uchar)g,(uchar)r)); } //颜色填充和最终显示 MatdstImg=Mat::zeros(markersImg.size(),CV_8UC3); intindex=0; for(inti=0;i<markersImg.rows;i ) { for(intj=0;j<markersImg.cols;j ) { index=markersImg.at<int>(i,j); if(index>0&&index<=contours.size()) { dstImg.at<Vec3b>(i,j)=colors[index-1]; } else { dstImg.at<Vec3b>(i,j)=Vec3b(0,0,0); } } } cout<<"numberofobjects:"<<contours.size()<<endl; namedWindow("FinalResult",CV_WINDOW_AUTOSIZE); imshow("FinalResult",dstImg); } intmain(){ test(); waitKey(0); return0; }

分水岭算法如何解决过度分割问题,图像分割分水岭算法(2)

总结:有时候会导致碎片化,过度分割,因为二值化中如果有很多小的黑点或碎片,在分割的时候导致很多 mask ,即小山头太多了,这个时候我们要考虑怎么去合并它,可以通过联通区域的直方图,或者像素值均值相似程度等。

例子2:图像分割

#include<opencv2/opencv.hpp> #include<iostream> usingnamespacestd; usingnamespacecv; //执行分水岭算法函数 MatwatershedCluster(Mat&srcImg,int&numSegments); //结果显示函数 voidDisplaySegments(Mat&markersImg,intnumSegments); voidtest(){ MatsrcImg; srcImg=imread("toux.jpg"); if(srcImg.empty()) { cout<<"couldnotloadimage...\n"<<endl; } namedWindow("Originalimage",CV_WINDOW_AUTOSIZE); imshow("Originalimage",srcImg); intnumSegments; Matmarkers=watershedCluster(srcImg,numSegments); DisplaySegments(markers,numSegments); } MatwatershedCluster(Mat&srcImg,int&numSegments){ //二值化 MatgrayImg,binaryImg; cvtColor(srcImg,grayImg,COLOR_BGR2GRAY); threshold(grayImg,binaryImg,0,255,THRESH_BINARY|THRESH_OTSU); //形态学和距离变换 Matkernel=getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1)); morphologyEx(binaryImg,binaryImg,MORPH_OPEN,kernel,Point(-1,-1)); MatdistImg; distanceTransform(binaryImg,distImg,DistanceTypes::DIST_L2,3,CV_32F); normalize(distImg,distImg,0.0,1.0,NORM_MINMAX); //开始生成标记 threshold(distImg,distImg,0.1,1.0,THRESH_BINARY); normalize(distImg,distImg,0,255,NORM_MINMAX); distImg.convertTo(distImg,CV_8UC1);//CV_32F转成CV_8UC1 //标记开始 vector<vector<Point>>contours; vector<Vec4i>hireachy; findContours(distImg,contours,hireachy,RETR_CCOMP,CHAIN_APPROX_SIMPLE); if(contours.empty()) { returnMat(); } MatmarkersImg(distImg.size(),CV_32S); markersImg=Scalar::all(0); for(inti=0;i<contours.size();i ) { drawContours(markersImg,contours,i,Scalar(i 1),-1,8,hireachy,INT_MAX); } circle(markersImg,Point(5,5),3,Scalar(255),-1); //分水岭变换 watershed(srcImg,markersImg); numSegments=contours.size(); returnmarkersImg; } voidDisplaySegments(Mat&markersImg,intnumSegments){ //生成随机颜色 vector<Vec3b>colors; for(inti=0;i<numSegments;i ) { intr=theRNG().uniform(0,255); intg=theRNG().uniform(0,255); intb=theRNG().uniform(0,255); colors.push_back(Vec3b((uchar)b,(uchar)g,(uchar)r)); } //颜色填充和最终显示 MatdstImg=Mat::zeros(markersImg.size(),CV_8UC3); intindex=0; for(inti=0;i<markersImg.rows;i ) { for(intj=0;j<markersImg.cols;j ) { index=markersImg.at<int>(i,j); if(index>0&&index<=numSegments) { dstImg.at<Vec3b>(i,j)=colors[index-1]; } else { dstImg.at<Vec3b>(i,j)=Vec3b(255,255,255); } } } cout<<"numberofobjects:"<<numSegments<<endl; namedWindow("FinalResult",CV_WINDOW_AUTOSIZE); imshow("FinalResult",dstImg); } intmain(){ test(); waitKey(0); return0; }

效果图:

分水岭算法如何解决过度分割问题,图像分割分水岭算法(3)

2. GrabCut 算法分割图像

GrabCut 算法的原理前面有介绍过,这里就不在介绍了,具体可以看下文章末尾往期推荐中阅读。下面例子实现图像中对象的抠图。

基本步骤:

分水岭算法如何解决过度分割问题,图像分割分水岭算法(4)

例子代码:

#include<opencv2/opencv.hpp> #include<iostream> usingnamespacestd; usingnamespacecv; intnumRun=0;//算法迭代次数 boolinit=false; Rectrect; MatsrcImg,MaskImg,bgModel,fgModel; //鼠标回调函数 voidonMouse(intevent,intx,inty,intflags,void*param); voidshowImg();//显示画的图片 voidsetRoiMask();//选择ROI的函数 voidrunGrabCut();//执行算法函数 staticvoidShowHelpText();//提示用户操作函数 voidtest(){ srcImg=imread("toux.jpg"); if(srcImg.empty()) { cout<<"couldnotloadimage...\n"<<endl; } namedWindow("Originalimage",CV_WINDOW_AUTOSIZE); imshow("Originalimage",srcImg); //初始化mask,单通道8位 MaskImg.create(srcImg.size(),CV_8UC1); //在不知道它是前景还是背景的情况下,把它全部设为背景。 MaskImg.setTo(Scalar::all(GC_BGD));//结果不是0就是1GC_BGD为0 setMouseCallback("Originalimage",onMouse,0); while(true) { charc=(char)waitKey(0); if(c=='n')//按下n建开始执行算法 { runGrabCut(); numRun ; showImg(); cout<<"currentiteativetimes:"<<numRun<<endl; } if(c==27) { break; } } } voidonMouse(intevent,intx,inty,intflags,void*param){ switch(event) { caseEVENT_LBUTTONDOWN: rect.x=x; rect.y=y; rect.width=1; rect.height=1; break; caseEVENT_MOUSEMOVE: if(flags&EVENT_FLAG_LBUTTON) { rect=Rect(Point(rect.x,rect.y),Point(x,y)); showImg(); } break; caseEVENT_LBUTTONUP: if(rect.width>1&&rect.height>1) { showImg(); } break; default: break; } } voidshowImg(){ Matresult,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("Originalimage",CV_WINDOW_AUTOSIZE); imshow("Originalimage",result); } voidsetRoiMask(){ //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)); } voidrunGrabCut(){ if(rect.width<2||rect.height<2) { return; } if(init) { grabCut(srcImg,MaskImg,rect,bgModel,fgModel,1); } else { grabCut(srcImg,MaskImg,rect,bgModel,fgModel,1,GC_INIT_WITH_RECT); init=true; } } staticvoidShowHelpText(){ cout<<"请先用鼠标在图片窗口中标记出属于前景的区域"<<endl; cout<<"然后再按按键【n】启动算法"<<endl; cout<<"按键【ESC】-退出程序"<<endl; } intmain(){ ShowHelpText(); test(); waitKey(0); return0; }

效果图:

分水岭算法如何解决过度分割问题,图像分割分水岭算法(5)

分水岭算法如何解决过度分割问题,图像分割分水岭算法(6)

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.