目标分割算法之分水岭算法

article/2025/10/16 5:37:36

分水岭算法

1.经典算法原理及实现

传统的目标分割算法主要分为两种
1.基于像素相似性:阈值分割、k-means分割
2.基于像素邻域关系:区域生长、分水岭、基于标记+分水岭

分水岭算法原理
如图中展现了凹凸不平的地貌,视觉上明显的位置有盆地及丘陵,用一维曲线讲对应波峰与波谷,向盆地注水,水会顺这地势先注入地势最低的波谷,然后随着水势升高再注入高一级的波谷,为了保证先注满第一个波谷,需要在右侧波峰出修建大坝,随后依次分水岭注满。
在图像中,地貌对应整个图像的背景,地势对应图像像素点大小。
在这里插入图片描述
分水岭算法整个过程:
1.把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值。
2.找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点。
3.水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类。
4.随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。
在这里插入图片描述
用上面的算法对图像进行分水岭运算,由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域。

其中的解决方法:

对图像进行高斯平滑操作,抹除很多小的最小值,这些小分区就会合并。
不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标记(mark)的分水岭算法。

opencv 函数

void watershed( InputArray image, InputOutputArray markers );

分割流程总结:
step1: 图像灰度化、滤波、Canny边缘检测
step2:查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。
step3:watershed分水岭运算
step4:绘制分割出来的区域,视觉控还可以使用随机颜色填充,或者跟原始图像融合以下,以得到更好的显示效果。

/**
*定义一个使用分水岭算法的辅助类
*/
class WatershedSegmenter{
{
private:Mat markers;
public:void setMarkers(Mat&markerImage){markerImage.convertTo(markers,CV_32S);}Mat process(Mat&image){watershed(image,markers);markers.convertTo(markers,CV_8U);return markes;;}
};//接受一个参数,显示结果
void watershedSegment (Mat img){Mat gray(img.rows, img.cols,CV_8UC1);cvtColor(img, gray, CV_BGR2GRAY);	//转换为8-bit,3通道的灰度图Mat binary = Mat::zeros(gray.rows, gray.cols, CV_8UC1);adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 5, 10);		//将灰度图转换为二值图Mat markers = Mat::zeros(gray.rows, gray.cols, CV_8UC1);//使用findContour()函数找出图像的轮廓vector<vector<Point> > contours;vector<Vec4i> hierarchy;findContours(binary, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);//将contours结果放入到markers中,便于访问int idx = 0;for( ; idx >= 0; idx = hierarchy[idx][0]){Scalar color(rand()&255, rand()&255, rand()&255);drawContours(markers, contours, idx, color, CV_FILLED, 8, hierarchy);}//调用分水岭算法分割图像WatershedSegmenter segmenter;segmenter.setMarkers(markers);cv::Mat result = segmenter.process(img);//显示分割结果namedWindow("segmentation_result", 0);imshow("segmentation_result", result);
}

2.分水岭算法C++实现

实现效果:
opencv结果
在这里插入图片描述
c++实现效果
在这里插入图片描述

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<vector>
#include<iostream>
#include<queue>
#include<fstream>
cv::Mat marker_mask;
cv::Mat g_markers;
cv::Mat img0, img, img_gray,wshed;
cv::Point_<int> prev_pt(-1,-1);
using std::vector;
using std::queue;
static void my_watershed(cv::Mat img,cv::Mat& markers,int comp_count);
static void mouse_event(int event,int x, int y,int flags, void*)
{if(img.rows==0)return;if(event==CV_EVENT_LBUTTONUP||!(flags&CV_EVENT_FLAG_LBUTTON))prev_pt=cv::Point_<int>(-1,-1);else if(event==CV_EVENT_LBUTTONDOWN)prev_pt=cv::Point2i(x,y);else if(event==CV_EVENT_MOUSEMOVE&&(flags&CV_EVENT_FLAG_LBUTTON)){cv::Point2i pt(x,y);if(prev_pt.x<0)prev_pt=pt;cv::line(marker_mask,prev_pt,pt,cv::Scalar(255,255,255),1,8,0);cv::line(img,prev_pt,pt,cv::Scalar(255,255,255),1,8,0);prev_pt=pt;cv::imshow("image",img);}
}
int main()
{img0=cv::imread("Lenna.png",1);img=img0.clone();CvRNG rng = cvRNG(-1); img_gray=img0.clone();wshed=img0.clone();marker_mask=cv::Mat(cv::Size(img0.cols,img0.rows),8,1);g_markers=cv::Mat(cv::Size(img0.cols,img0.rows),CV_32S,1);cv::cvtColor(img,marker_mask,CV_BGR2GRAY);cv::cvtColor(marker_mask,img_gray,CV_GRAY2BGR);for(int i=0;i<marker_mask.rows;i++)for(int j=0;j<marker_mask.cols;j++)marker_mask.at<unsigned char>(i,j)=0;for(int i=0;i<g_markers.rows;i++)for(int j=0;j<g_markers.cols;j++)g_markers.at<int>(i,j)=0;cv::imshow("image",img);cv::imshow("watershed transform",wshed);cv::setMouseCallback("image",mouse_event,0);for(;;){int c=cv::waitKey(0);if((char)c==27)break;if((char)c=='r'){for(int i=0;i<marker_mask.rows;i++)for(int j=0;j<marker_mask.cols;j++)marker_mask.at<unsigned char>(i,j)=0;img0.copyTo(img);cv::imshow("image",img);}if((char)c=='w'||(char)c==' '){vector<vector<cv::Point>> contours;CvMat* color_tab=0;int comp_count=0;cv::findContours(marker_mask,contours,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE,cv::Point(0,0));for(int i=0;i<g_markers.rows;i++)for(int j=0;j<g_markers.cols;j++)g_markers.at<int>(i,j)=0;vector<vector<cv::Point> >::iterator iter=contours.begin();for(int i=0;i<(int)contours.size();i++){cv::drawContours(g_markers,contours,i,cv::Scalar::all(comp_count+1),1,8,vector<cv::Vec4i>());comp_count++;}if(comp_count==0)continue;color_tab=cvCreateMat(1,comp_count,CV_8UC3);for(int i=0;i<comp_count;i++){uchar* ptr=color_tab->data.ptr+i*3;ptr[0]=(uchar)(cvRandInt(&rng)%180+50);ptr[1]=(uchar)(cvRandInt(&rng)%180+50);ptr[2]=(uchar)(cvRandInt(&rng)%180+50);}cv::Mat temp=g_markers.clone();double t=(double)cvGetTickCount();//my_watershed(img0,g_markers,comp_count);cv::watershed(img0,g_markers);t=(double)cvGetTickCount()-t;std::cout<<"exec time= "<<t/(cvGetTickFrequency()*1000.)<<std::endl;for(int i=0;i<g_markers.rows;i++)for(int j=0;j<g_markers.cols;j++){int idx=g_markers.at<int>(i,j);uchar* dst=&wshed.at<uchar>(i,j*3);if(idx==-1)dst[0]=dst[1]=dst[2]=(uchar)255;else if(idx<=0||idx>comp_count)dst[0]=dst[1]=dst[2]=(uchar)8;else{uchar* ptr=color_tab->data.ptr+(idx-1)*3;dst[0]=ptr[0];dst[1]=ptr[1];dst[2]=ptr[2];}}cv::addWeighted(wshed,0.5,img_gray,0.5,0,wshed);cv::imshow("watershed transform",wshed);cvReleaseMat(&color_tab);}}return 0;
}
static void my_watershed(cv::Mat img0,cv::Mat& markers,int comp_count)
{cv::Mat gray=cv::Mat(cv::Size(img0.rows,img0.cols),8,1);cv::cvtColor(img0,gray,CV_BGR2GRAY);cv::Mat imge=cv::Mat(cv::Size(img0.rows,img0.cols),8,1);cv::Sobel(gray,imge,CV_8U,1,1);vector<queue<cv::Point2i>*>Labeleddata;//图像中各连通区域的点queue<cv::Point2i>* pque;//某连通区域已包含的点queue<cv::Point2i> quetem; //用于提取某连通区域中输入种子点中的初始种子点vector<int*> SeedCounts;int* Array;cv:: Point2i temp;int row=imge.rows,col=imge.cols;cv::Mat marker_saved=markers.clone();bool up,down,right,left,uplef,uprig,downlef,downrig;int m,n;for(int i=0;i<comp_count;i++){Array=new int[256];SeedCounts.push_back(Array);//统计某waterlevel的各个连通区域中种子点的个数pque=new queue<cv::Point2i>[256]; Labeleddata.push_back(pque);//存储该连通区域中种子生长所得的点		}for(int i=0;i<row;i++)for(int j=0;j<col;j++){if(markers.at<int>(i,j)>0){temp.x=i;temp.y=j;quetem.push(temp);int num=markers.at<int>(i,j);markers.at<int>(i,j)=-1;//该点已处理,其他种子点生长时将绕过该点while(!quetem.empty()){up=down=right=left=uplef=uprig=downlef=downrig=false;temp=quetem.front(); //提取出一个点,在该点的八连通区域内寻找可生长点m=temp.x;n=temp.y;quetem.pop();if(m-1>=0)//若上方可生长则添加为新种子{if(markers.at<int>(m-1,n)==num){temp.x=m-1;temp.y=n;quetem.push(temp);markers.at<int>(m-1,n)=-1;}else{up=true;}}if(m-1>=0&&n-1>=0){if(markers.at<int>(m-1,n-1)==num){temp.x=m-1;temp.y=n-1;quetem.push(temp);markers.at<int>(m-1,n-1)=-1;}else{uplef=true;}}if(m+1<=row-1){if(markers.at<int>(m+1,n)==num){temp.x=m+1;temp.y=n;quetem.push(temp);markers.at<int>(m+1,n)=-1;}else{down=true;}}if(m+1<=row-1&&n+1<=col-1){if(markers.at<int>(m+1,n+1)==num){temp.x=m+1;temp.y=n+1;quetem.push(temp);markers.at<int>(m+1,n+1)=-1;}else{downrig=true;}}if(n+1<=col-1){if(markers.at<int>(m,n+1)==num){temp.x=m;temp.y=n+1;quetem.push(temp);markers.at<int>(m,n+1)=-1;}else{right=true;}}if(m-1>=0&&n+1<=col-1){if(markers.at<int>(m-1,n+1)==num){temp.x=m-1;temp.y=n+1;quetem.push(temp);markers.at<int>(m-1,n+1)=-1;}else{uprig=true;}}if(n-1>=0){if(markers.at<int>(m,n-1)==num){temp.x=m;temp.y=n-1;quetem.push(temp);markers.at<int>(m,n-1)=-1;}else{left=true;}}if(m+1<=row-1&&n-1>=0){if(markers.at<int>(m+1,n-1)==num){temp.x=m+1;temp.y=n-1;quetem.push(temp);markers.at<int>(m+1,n-1)=-1;}else{downlef=true;}}//八连通区域中有未标记点,则该点属于初始种子点if(up||down||right||left||uplef||downlef||uprig||downrig){temp.x=m;temp.y=n;Labeleddata[comp_count-1][imge.at<uchar>(m,n)].push(temp);SeedCounts[comp_count-1][imge.at<uchar>(m,n)]++;}}}}bool active;int waterlevel;for(waterlevel=0;waterlevel<180;waterlevel++){active=true;while(active) //当1-count_com个连通区域都无可生长点时结束循环{active=false;for(int i=0;i<comp_count;i++)//将区域i中将waterlevel梯度以下的点用于区域增长{//区域增长,经过多次迭代,直至该区域,该waterlevel无可生长点。if(!Labeleddata[i][waterlevel].empty()){active=true;	//SeedCount中保留了前一轮生长后各区域,各waterlevel的种子点个数,//本轮生长结束后,将根据Labeleddata中的元素个数更新while(SeedCounts[i][waterlevel]>0) {SeedCounts[i][waterlevel]--;temp=Labeleddata[i][waterlevel].front();Labeleddata[i][waterlevel].pop();m=temp.x;n=temp.y;int num=marker_saved.at<int>(m,n);if(m-1>=0){if(!marker_saved.at<int>(m-1,n))//上方点未处理过{temp.x=m-1;temp.y=n;marker_saved.at<int>(m-1,n)=num;if(imge.at<uchar>(m-1,n)<=waterlevel)Labeleddata[i][waterlevel].push(temp);else{//本次生长不处理,可能在waterlevel变化到某值时再用于生长Labeleddata[i][imge.at<uchar>(m-1,n)].push(temp); SeedCounts[i][imge.at<uchar>(m-1,n)]++;}}}if(m+1<=row-1){if(!marker_saved.at<int>(m+1,n)){temp.x=m+1;temp.y=n;marker_saved.at<int>(m+1,n)=num;if(imge.at<uchar>(m+1,n)<=waterlevel)Labeleddata[i][waterlevel].push(temp);else{Labeleddata[i][imge.at<uchar>(m+1,n)].push(temp);SeedCounts[i][imge.at<uchar>(m+1,n)]++;}}}if(n+1<=col-1){if(!marker_saved.at<int>(m,n+1)){temp.x=m;temp.y=n+1;marker_saved.at<int>(m,n+1)=num;if(imge.at<uchar>(m,n+1)<=waterlevel)Labeleddata[i][waterlevel].push(temp);else{Labeleddata[i][imge.at<uchar>(m,n+1)].push(temp);SeedCounts[i][imge.at<uchar>(m,n+1)]++;}}}if(n-1>=0){if(!marker_saved.at<int>(m,n-1)){temp.x=m;temp.y=n-1;marker_saved.at<int>(m,n-1)=num;if(imge.at<uchar>(m,n-1)<=waterlevel)Labeleddata[i][waterlevel].push(temp);else{Labeleddata[i][imge.at<uchar>(m,n-1)].push(temp);SeedCounts[i][imge.at<uchar>(m,n-1)]++;}}}}SeedCounts[i][waterlevel]=Labeleddata[i][waterlevel].size();}}}}markers=marker_saved.clone();
}

参考:

https://blog.csdn.net/qingyafan/article/details/44260817
https://blog.csdn.net/twowind/article/details/8988282


http://chatgpt.dhexx.cn/article/BvwS8vpW.shtml

相关文章

分水岭算法的理解和应用

分水岭算法 主要思想 图像的灰度空间很像地球表面的整个地理结构&#xff0c;每个像素的灰度值代表高度。分水岭就是灰度值较大的像素连成的线。二值化阈值可以理解为水平面&#xff0c;比灰度二值化阈值小的像素区域会被淹没。随着水位线的升高&#xff0c;被淹没的区域越来越…

分水岭算法及其实现

&#xff11; - 算法描述 1.1 分水岭算法的原理   分水岭的概念是以三维方式来形象化一幅图像为基础的&#xff1a;两个空间坐标再加上强度。在这种“地形学”解释中&#xff0c;考虑三种类型的点&#xff1a;&#xff08;a&#xff09;局部最小值点&#xff0c;该点对应一个…

传统图像分割——分水岭算法(watershed)

传统图像分割——分水岭算法&#xff08;watershed&#xff09; 文章目录 传统图像分割——分水岭算法&#xff08;watershed&#xff09;前言一、什么是分水岭算法&#xff1f;二、经典的分水岭求解算法1.定义2.算法流程 总结 前言 本篇文章主要梳理分水岭算法的原理&#xf…

图像分割 - 分水岭算法

目录 1. 介绍 2. 分水岭算法的实现 距离变换 连接连通分量 3. 代码 1. 介绍 图像是由x&#xff0c;y表示的&#xff0c;如果将灰度值也考虑进去的话&#xff0c;那么一幅图像需要一个三维的空间去表示。 这样就可以把x&#xff0c;y轴比作大地&#xff0c;将灰度值的z轴…

【OpenCv】图像分割——分水岭算法

文章目录 1 原理2 算法改进3 API4 实例 1 原理 分水岭分割方法&#xff0c;是一种基于拓扑理论的数学形态学的分割方法&#xff0c;其基本思想是把图像看作是测地学上的拓扑地貌&#xff0c;图像中每一点像素的灰度值表示该点的海拔高度&#xff0c;每一个局部极小值及其影响区…

MFC图像处理CImage类常用操作

原文作者&#xff1a;aircraft 原文地址&#xff1a;https://www.cnblogs.com/DOMLX/p/9598974.html MFC图像处理CImage类常用操作 CImage类头文件为#include<atlimage.h> CImage类读取图片CImage.Load("src.bmp"); CImage类保存图片CImage.Save("dst…

使用CImage进行图像处理

MFC和ATL共享的新类CImage为图像处理提供了许多相应的处理方法 CImage类 我们知道&#xff0c;Visual C的CBitmap类和静态图片控件的功能是比较弱的&#xff0c;它只能显示出在资源中的图标、位图、光标以及图元文件的内容&#xff0c;而不像VB中的Image控件可 以显示出绝大多数…

用CImage类来显示PNG、JPG等图片

系统环境&#xff1a;Windows 7 软件环境&#xff1a;Visual Studio 2008 SP1 本次目的&#xff1a;实现VC单文档、对话框程序显示图片效果 CImage 是VC.NET中定义的一种MFC/ATL共享类&#xff0c;也是ATL的一种工具类&#xff0c;它提供增强型的&#xff08;DDB和DIB&#xff…

CImage类(外部图像文件(BMP、GIF、JPEG等)

CImage类 我们知道&#xff0c;Visual C的CBitmap类和静态图片控件的功能是比较弱的&#xff0c;它只能显示出在资源中的图标、位图、光标以及图元文件的内容&#xff0c;而不像VB中的Image控件可 以显示出绝大多数的外部图像文件(BMP、GIF、JPEG等)。因此&#xff0c;想要在对…

CImage的一般使用方法和技巧

Visual C的CBitmap类的功能是比较弱的,它只能显示出在资源中的图标、位图、光标以及图元文件的内容&#xff0c;而不像VB中的Image控件可以显示出绝大多数的外部图像文件(BMP、GIF、JPEG等)。如果想要在对话框或其他窗口中显示外部图像文件则只能借助于第三方提供的控件或代码,…

图像处理(C++ CImage class)学习笔记

基础篇 A. 图像三原色及灰度值 A1. 彩色图像的三原色 图像三原色 — R&#xff1a;红色red — G&#xff1a;绿色green — B&#xff1a;蓝色blue三原色的取值范围&#xff1a;0&#xff08;无&#xff09;~255&#xff08;满&#xff09; — 红色&#xff1a;R255 G0 B0 —…

Cimage

本系列文章由zhmxy555编写&#xff0c;转载请注明出处。 http://blog.csdn.net/zhmxy555/article/details/7422922 作者&#xff1a;毛星云 邮箱&#xff1a; happylifemxyqq.com 欢迎邮件交流编程心得 我们知道&#xff0c;Visual C中的CBitmap类的功能简直太弱小了&am…

【无标题】c++ MFC图像处理CImage类常用操作代码

原文作者&#xff1a;aircraft 原文地址&#xff1a;https://www.cnblogs.com/DOMLX/p/9598974.html 我看了一下发现关于c下的CImage图像处理类 的图像处理相关的介绍真的是比较少&#xff0c;因为我要做大二的数据结构的课程设计&#xff0c;要用纯c语言去实现&#xff08;老…

C++,CImage类的建立方法(可以打开图像和保存)

建立CImage类&#xff08;以vs2015为例&#xff09; 一&#xff0c; 新建一个MFC项目&#xff1a;名字为 image3 二&#xff0c; 单个文档&#xff0c;MFC标准&#xff0c;然后完成。 三&#xff0c;打开应用程序的 stdafx.h 文件添加 CImage 类的包含文件&#xff1a; #incl…

VB.net 进程通信中FindWindow、FindWindowEX、SendMessage函数的理解

目录 一、代码背景 二、主要工具 三、函数解析 1、FindWindow&#xff1a; 2、 FindWindowEx&#xff1a; 3、SendMessage&#xff1a; 四、具体代码示例&#xff1a; 1、第一部分功能&#xff1a; A、接收端&#xff1a; B、发送端 C、运行测试 2.第二部分功能&…

C#-FindWindow的用法

C# FindWindow用法 函数功能&#xff1a;该函数获得一个顶层窗口的句柄&#xff0c;该窗口的类名和窗口名与给定的字符串相匹配。 这个函数不查找子窗口。在查找时不区分大小写。 函数型&#xff1a;HWND FindWindow&#xff08;LPCTSTR IpClassName&#xff0c;LPCTSTR IpWi…

vb.net中FindWindow方法的使用

问题描述 遇到的问题是&#xff0c;需要判断MsgBox是否已经弹出&#xff0c;如果已经弹出就不要重复弹出了。 解决方案&#xff1a; 利用FindWindow方法判断MsgBox是否已经出现 MsgBox的本质就是一个窗体&#xff0c;有标题和内容&#xff0c;可以使用FindWindow这个API函数去…

C# FindWindow的用法

找了一大堆C#怎么用FindWindowAPI函数不多说,请看步骤. 创建好WinForm窗口,如果不会创建的话,在图下面有. 项目名字和位置这个自己设置,下面那个框架,目前现在出5.0了,为了演示实例,就4.6吧. 创建完成后,把自己窗口设置一下

FindWindow ,GetWindowThreadProcessId , OpenProcess 和ReadProcessMemory

文章目录 FindWindow函数功能&#xff1a;函数声明&#xff1a;第一个参数第二个参数返回值注意&#xff1a;GetWindowThreadProcessId函数功能函数声明第一个参数&#xff1a;第二个参数&#xff1a;返回值代码实现OpenProcess函数功能&#xff1a;函数声明&#xff1a;第一个…

【CV系列】主动轮廓模型snake及其应用

DATE: 2019.5.30 前言 主动轮廓模型(Active Contour Model)&#xff0c;又被称为Snake&#xff0c;是由Andrew Blake教授提出的一种目标轮廓描述方法&#xff0c;主要应用于基于形状的目标分割。该模型的优越之处在于它对于范围广泛的一系列视觉问题给出了统一的解决方法,在最…