【OpenCV】- 分水岭算法

article/2025/10/16 5:31:25

文章目录

      • 什么是图像分割
      • 分水岭算法
        • 1、实现分水岭算法:watershed()函数
        • 2、处理流程(视频)
        • 3、示例程序(书中)

什么是图像分割

  • 将图像中像素根据一定的规则分为若干个cluster集合,每个集合包含一类对象

如下,将两匹马从图像背景中抠出来

在这里插入图片描述

分水岭算法

解释:分水岭算法,是一种基于拓扑理论的数学形态学的分割方法。分割图像时可以从图像中获取有用的信息。

基本思想:把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以可以通过模拟侵入过程来说明:在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢侵入水中,随着侵入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。

分水岭的计算步骤:

  • 排序过程
  • 淹没过程

说明:首先对每个像素的灰度级进行从低到高的排序,然后在从低到高实现淹没的过程中,对每一个局部极小值在h阶高度的影响采用先进先出(FIFO)结构进行判断及标注。分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点,即为分水岭。

同理,分水岭算法首先计算灰度图像的梯度;可以很好的区分图像中的“山谷”或没有纹理的“盆地”(亮度值低的点)的形成是很有效的。然后开始从用户指定点(或算法得到的点)开始持续“灌注”盆地直到这些区域连成一片。

1、实现分水岭算法:watershed()函数

函数watershed实现的分水岭算法是基于标记的分割算法中的一种。在把图像传给函数之前,需要大致勾画标记出图像中的期望进行分割的区域,被标记为正指数。每一个区域都会标记为像素值1,2,3,4等。表示成为一个或多个连接组件。这些标记的值可以使用findContours()函数和drawContours()函数由二进制的掩码检索出来。这些标记就是即将绘制出来的分割区域的“种子”,而没有标记清楚的区域,被置为0。在函数输出中,每一个标记中的像素被设置为“种子”的值,而区域间的值被设置为-1。

void watershed(InputArray image,InputOutputArray markers)
  • 第一个参数:输入图像,即源图像。填Mat类的对象即可,且需为8位三通道的彩色图像

  • 第二个参数:函数调用后的运算结构存在这里,输入/输出32单通道图像的标记结果。即这个参数用于存放函数调用后的输出结果,需要和源图片有一样的尺寸和类型

2、处理流程(视频)

  • 将白色背景变成黑色 - 为后面的变换做准备

    src = imread("E://Pec//fenshui.jpg");imshow(WINDOW_NAME, src);//修改背景for(int row=0;row<src.rows;row++)for (int col = 0; col < src.cols; col++){if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)){src.at<Vec3b>(row, col)[0] = 0;src.at<Vec3b>(row, col)[1] = 0;src.at<Vec3b>(row, col)[2] = 0;}}//namedWindow("black backgroud", CV_WINDOW_AUTOSIZE);//imshow("black backgroud", src);
    
  • 使用filter2D与拉普拉斯算子实现图像对比度提高

    //锐化,清晰边缘Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);Mat imgLaplance;Mat sharpImage = src;filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);src.convertTo(sharpImage, CV_32F);Mat result = sharpImage - imgLaplance;//数据类型之间的转换result.convertTo(result, CV_8UC3);imgLaplance.convertTo(imgLaplance, CV_8UC3);//imshow("sharpImage", result);
    
  • 转换为二值图像

    //二值图像变换Mat binaryImag;//灰度转换cvtColor(src, result, CV_BGR2GRAY);threshold(result, binaryImag, 40, 255, THRESH_BINARY | THRESH_OTSU);//imshow("二值化图像", binaryImag);
    
  • 距离变换

    //二值距离变换Mat distImage;distanceTransform(binaryImag, distImage, DIST_L2, 3);
    
  • 对距离变换的结果进行归一化到[0~1]之间

    //0-归一化上限,1-归一化下限。归一化标量:映射到[a,b]范围normalize(distImage, distImage, 0, 1, NORM_MINMAX);
    
  • 使用阈值,再次二值化,得到标记

    threshold(distImage, distImage, .4, 1, THRESH_BINARY );//imshow("距离变换后-二值化", distImage);
    
  • 腐蚀得到每个Peak - erode

    	//二值腐蚀Mat kl = Mat::ones(3, 3, CV_8UC1);erode(distImage, distImage, kl , Point(-1, -1));imshow("腐蚀之后", distImage);
    
  • 发现轮廓 - drawContours

    //掩膜Mat dist_8u;distImage.convertTo(dist_8u, CV_8U);vector<vector<Point>>contours;findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));Mat markers = Mat::zeros(src.size(), CV_32SC1);//循环绘制轮廓for (size_t i= 0; i<contours.size(); i++)drawContours(markers, contours, static_cast<int>(i), Scalar(static_cast<int>(i)+1),-1);circle(markers, Point(3, 3), 3, Scalar(255, 255, 255), -1);//imshow("markers", markers*1000);
    
  • 分水岭变换

    //分水岭变换watershed(src, markers);//双层循环,将分水岭图像遍历存入mark中Mat mark(markers.size(), CV_8UC1);markers.convertTo(mark, CV_8UC1);//取反bitwise_not(mark, mark, Mat());imshow("分水岭变换", mark);
    
  • 对每个分割区域着色输出结果

    vector<Vec3b> colors;for (size_t i = 0; i <contours.size(); i++){int b = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int r = theRNG().uniform(0, 255);colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));}//双层循环,将分水岭图像遍历存入dst中Mat dst = Mat::zeros(markers.size(), CV_8UC3);for (int row = 0; row < markers.rows; row++)for (int col = 0; col < markers.cols; col++){int index = markers.at<int>(row, col);if (index > 0 && index <= static_cast<int>(contours.size()))dst.at<Vec3b>(row, col) = colors[index - 1];elsedst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);}//混合灰度图和分水岭效果图并显示最终的窗口//dst = dst * 0.5 + grayImage * 0.5;imshow("最后效果图",dst);
    

效果图:
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

3、示例程序(书中)

#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
#define WINDOW_NAME "【程序窗口1】"
Mat g_maskImage, g_srcImage;
Point prevPt(-1, -1);
static void ShowHelpText();
static void on_Mouse(int event, int x, int y, int flags, void *);
int main()
{system("color 1F");ShowHelpText();g_srcImage = imread("E://Pec//shan.jpg");imshow(WINDOW_NAME, g_srcImage);Mat srcImage, grayImage;g_srcImage.copyTo(srcImage);//从RBG和BGR颜色空间转换到灰度空间cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);//imshow("g_maskImage", g_maskImage);//从灰度空间转换到RGB和BGR颜色空间cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);//imshow("grayImage", grayImage);g_maskImage = Scalar::all(0);//设置鼠标回调函数setMouseCallback(WINDOW_NAME, on_Mouse, 0);while (1){int c = waitKey(0);if ((char)c == 27)break;//按键2按下时,恢复源图if ((char)c == 2){g_maskImage = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("image", g_srcImage);}//若检测到按键为1或者空格,则进行处理if ((char)c == '1' || (char)c == ' '){int i, j, compCount = 0;vector<vector<Point>>contours;vector<Vec4i>hierarchy;//寻找轮廓findContours(g_maskImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//若是轮廓为空if (contours.empty())continue;//复制掩膜Mat maskImage(g_maskImage.size(), CV_32S);maskImage = Scalar::all(0);//循环绘制轮廓for (int index = 0; index >= 0; index = hierarchy[index][0], compCount++)drawContours(maskImage, contours, index, Scalar::all(compCount + 1), -1, 8, hierarchy, INT_MAX);//compCOunt为0时if (compCount == 0)continue;//生成随机颜色vector<Vec3b> colorTab;for (i = 0; i < compCount; i++){int b = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int r = theRNG().uniform(0, 255);colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));				}//计算处理时间并输出到窗口double dTime = (double)getTickCount();watershed(srcImage, maskImage);dTime = (double)getTickCount() - dTime;printf("\t处理时间 =%gms\n", dTime*100. / getTickFrequency());//双层循环,将分水岭图像遍历存入watershedImage中Mat watershedImage(maskImage.size(), CV_8UC3);for(i=0;i<maskImage.rows;i++)for (j = 0; j < maskImage.cols; j++){int index = maskImage.at<int>(i, j);if (index == -1)watershedImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);else if (index <= 0 || index > compCount)watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);elsewatershedImage.at<Vec3b>(i, j) = colorTab[index-1];}//混合灰度图和分水岭效果图并显示最终的窗口watershedImage = watershedImage * 0.5 + grayImage * 0.5;imshow("watershed tandsform", watershedImage);}}return 0;
}
static void on_Mouse(int event, int x, int y, int flags, void *)
{//处理鼠标步骤窗口中的情况if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows)return;//处理鼠标相关信息if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON))prevPt = Point(-1, -1);else if (event == EVENT_LBUTTONDOWN){prevPt = Point(x, y);}//鼠标左键按下并移动,绘制出白色线条else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)){Point pt(x, y);if (prevPt.x < 0)prevPt = pt;line(g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0);prevPt = pt;imshow(WINDOW_NAME, g_srcImage);}
}
static void ShowHelpText()
{cout << "\n\t欢迎来到【分水岭算法】示例程序" << endl;cout << "\t\t请先用鼠标在窗口标记出大致的区域" << endl;cout << "\t\t然后按键【1】或者【空格】启动算法" << endl;cout << "\t\t按键操作如下:" << endl;cout << "\t\t\t按下按键【1】或者【空格】--运行分水岭分割算法" << endl;cout << "\t\t\t按下按键【2】--恢复原始图片" << endl;cout << "\t\t\t按下按键【ESC】--退出程序" << endl;
}

在这里插入图片描述


http://chatgpt.dhexx.cn/article/89fjsEeH.shtml

相关文章

OpenCV分水岭算法详解

原理分析 分水岭算法主要用于图像分段&#xff0c;通常是把一副彩色图像灰度化&#xff0c;然后再求梯度图&#xff0c;最后在梯度图的基础上进行分水岭算法&#xff0c;求得分段图像的边缘线。 下面左边的灰度图&#xff0c;可以描述为右边的地形图&#xff0c;地形的高度是由…

分水岭算法 matlab实现

背景 做图像分割的时候用到了&#xff0c;就学习了一下 大概思想 把图像中的像素大小理解成山地的海拔&#xff0c;向山地灌水&#xff0c;海拔低的地方会积水&#xff0c;这些地方称之为谷底。随着水位上升&#xff0c;不同谷底的水会相遇&#xff0c;相遇的地方就是分水岭。…

分水岭算法c语言,Opencv分水岭算法学习

分水岭算法可以将图像中的边缘转化成“山脉”&#xff0c;将均匀区域转化为“山谷”&#xff0c;这样有助于分割目标。 分水岭算法是一种基于拓扑理论的数学形态学的分割方法&#xff0c;其基本思想是把图像看作是测地学上的拓扑地貌&#xff0c;图像中的每一点像素的灰度值表示…

分水岭算法

引言&#xff1a;它是基于拓扑理论的形态学处理方法。将一张图像假想成为一张地貌特征图。 原理理解&#xff1a;灰度图被看作拓扑平面&#xff0c;灰度高看成山峰&#xff0c;灰度低看成山谷。从山谷开始注水&#xff0c;随着水位升高水流会相遇汇合。为了防止汇合&#xff0…

Opencv分水岭算法——watershed自动图像分割用法

分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征。 其他图像分割方法,如阈值,边缘检测等都不会考虑像素在…

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

分水岭算法 1.经典算法原理及实现 传统的目标分割算法主要分为两种 1.基于像素相似性&#xff1a;阈值分割、k-means分割 2.基于像素邻域关系&#xff1a;区域生长、分水岭、基于标记分水岭 分水岭算法原理 如图中展现了凹凸不平的地貌&#xff0c;视觉上明显的位置有盆地及…

分水岭算法的理解和应用

分水岭算法 主要思想 图像的灰度空间很像地球表面的整个地理结构&#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…