9.9watershed分水岭分割

news2024/11/15 9:28:12

实验原理

在计算机视觉中,分水岭算法(Watershed Algorithm)是一种基于形态学的分割方法,常用于图像分割。OpenCV 提供了 cv::watershed 函数来实现这一算法。分水岭算法的主要思想是将图像视为地形表面,其中像素强度值代表地形高度。算法试图找到图像中的“盆地”(区域)之间的“山脊线”,这些山脊线即为分割边界。

在计算机视觉和图像处理中,分水岭算法(Watershed Algorithm)是一种用于图像分割的重要技术。OpenCV提供了分水岭算法的实现,可以帮助开发者进行图像分割任务。分水岭算法的目标是将图像分割成多个区域或标记,每个区域对应图像中的一个对象。

分水岭算法的基本概念

分水岭算法的灵感来源于地理学中的分水岭概念:地形中的水流汇聚到最低点形成河流,而这些河流之间的高地称为分水岭。在图像分割中,图像中的“山峰”和“山谷”分别对应于图像的明亮区域和暗淡区域,而“分水岭”则对应于不同区域间的边界。

函数原型
在OpenCV中,分水岭算法主要是通过cv::watershed函数来实现的。该函数需要两个主要输入:一个是待分割的图像,另一个是一个标记(Markers)矩阵。

int watershed(InputArray _image, InputOutputArray _markers);

参数说明
_image: 输入图像,通常是灰度图像或彩色图像。
_markers: 输入/输出标记图像。
这是一个 32SC1 类型的单通道图像,其中每个像素值代表一个标记。
初始标记应该是已经确定的前景和背景区域。
输入输出标记矩阵,用于指示图像中的感兴趣区域(Foregroud)和背景区域(Background),以及可能的未知区域。

分水岭分割步骤
1.预处理:
对输入图像进行一些预处理,如灰度化、二值化、形态学操作等,如去噪、边缘检测等。
根据需要,可以转换为灰度图像。
2.标记初始化:

在预处理后的图像中生成标记,标记可以分为已知标记(确定的前景和背景区域)和未知标记(待分割的区域)
确定已知的前景和背景区域,并为它们分配唯一的标记值。
典型的做法是使用连通组件标记法(Connected Component Labeling)来找到这些区域。
对于未知区域,可以设置为零或其他特殊值表示未标记。
3.应用分水岭算法:
使用 cv::watershed 函数,传入图像和标记图像。
函数执行后,标记图像会被修改,其中包含了分割结果。
4.后处理:
可以根据需要对分割结果进行后处理,比如如去除小区域、填充孔洞,去除小的连通组件、平滑边界等。

示例代码1

下面是一个简单的示例代码,展示如何在OpenCV中使用分水岭算法进行图像分割:
#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
    // 读取图像
    cv::Mat src = cv::imread("path/to/image.jpg", cv::IMREAD_COLOR);
    if (src.empty())
    {
        std::cout << "Error opening image" << std::endl;
        return -1;
    }

    // 转换为灰度图像
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    // 二值化
    cv::Mat binary;
    cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU);

    // 形态学开运算去除噪声
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
    cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel, cv::Point(-1, -1), 2);

    // 确定前景区域
    cv::Mat sure_fg = binary.clone();

    // 寻找图像轮廓
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

    // 创建标记矩阵
    cv::Mat markers = cv::Mat::zeros(binary.size(), CV_32S);

    // 设置标记
    int marker = 1;
    for (size_t i = 0; i < contours.size(); ++i)
    {
        cv::drawContours(markers, contours, static_cast<int>(i), cv::Scalar(marker), -1);
        marker += 1;
    }

    // 执行分水岭算法
    cv::watershed(src, markers);

    // 将标记矩阵转换为可显示的格式
    cv::Mat dst;
    cv::convertScaleAbs(markers, dst);

    // 显示结果
    cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
    cv::imshow("Original Image", src);

    cv::namedWindow("Watershed Result", cv::WINDOW_NORMAL);
    cv::imshow("Watershed Result", dst);

    cv::waitKey(0);

    return 0;
}

代码解释
1. 读取图像:加载原始图像。
2. 转换为灰度图像:将彩色图像转换为灰度图像。
3. 二值化:使用Otsu方法进行自适应阈值分割,得到二值图像。
4. 形态学开运算:去除二值图像中的噪声。
5. 确定前景区域:复制二值图像作为确定的前景区域。
6. 寻找图像轮廓:使用cv::findContours函数找到图像中的轮廓。
7. 创建标记矩阵:初始化一个标记矩阵,用于存储标记信息。
8. 设置标记:为每个轮廓区域设置不同的标记值。
9. 执行分水岭算法:调用cv::watershed函数进行分水岭分割。
10. 显示结果:将标记矩阵转换为可视化的格式,并显示分割结果。

注意事项
•预处理:分水岭算法对图像质量非常敏感,因此预处理步骤非常重要,尤其是二值化和去噪。
•标记设置:标记矩阵的设置决定了分割的效果,需要合理选择标记区域。
•结果解释:分水岭算法的结果需要进一步解释,可能需要进行后处理以去除小区域或填补孔洞。

通过上述步骤和示例代码,您可以了解如何在OpenCV中使用分水岭算法进行图像分割。
根据具体的应用场景,您可能需要调整预处理和标记设置的策略。

运行结果1

示例代码2

下面是一个简单的示例,展示了如何使用 OpenCV 的 cv::watershed 函数来进行图像分割:

#include "pch.h"
#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
	cv::Mat img = cv::imread("033.jpeg");
	if (img.empty())
	{
		std::cout << "Error: Image not found." << std::endl;
		return -1;
	}

	// 转换为灰度图像
	cv::Mat gray;
	cvtColor(img, gray, cv::COLOR_BGR2GRAY);

	// 进行阈值处理以获取二值图像
	cv::Mat binary;
	threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU);

	// 进行形态学开运算去除噪声
	cv::Mat kernel = getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
	morphologyEx(binary, binary, cv::MORPH_OPEN, kernel, cv::Point(-1, -1));

	// 寻找轮廓
	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	findContours(binary, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);

	// 创建标记图像
	cv::Mat markers = cv::Mat::zeros(img.size(), CV_32S);

	// 初始化标记
	for (size_t i = 0; i < contours.size(); i++)
	{
		drawContours(markers, contours, static_cast<int>(i), cv::Scalar(i + 1), -1);
	}

	// 应用分水岭算法
	cv::watershed(img, markers);

	// 将标记图像转换为可视化格式
	cv::Mat vis;
	//markers.convertTo(vis, CV_8UC3, cv::Scalar(255, 255, 255) / 255);
	markers.convertTo(vis, CV_8UC3, 255);

	// 显示结果
	cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
	cv::imshow("Original Image", img);
	cv::namedWindow("Markers After Watershed", cv::WINDOW_NORMAL);
	cv::imshow("Markers After Watershed", vis);
	cv::waitKey(0);

	return 0;
}

在这个例子中,我们首先读取一个图像并将其转换为灰度图像。
接着,使用 Otsu 方法进行阈值处理,并通过形态学开运算去除噪声。
然后,我们找到图像中的轮廓,并将这些轮廓作为初始标记。最后,应用分水岭算法并显示分割结果。

总结
cv::watershed 是一个强大的工具,可用于图像分割。
它特别适用于那些需要精确分割对象边界的应用场景。
为了获得最佳效果,通常需要对输入图像进行预处理,并仔细选择初始标记。

运行结果2

示例代码3

#include "pch.h"
//#pragma comment(lib, "opencv_world450d.lib")  
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#include <iostream>
using namespace std;
using namespace cv;

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>

using namespace cv;
using namespace std;

Vec3b RandomColor(int value);  //生成随机颜色函数

int main(int argc, char* argv[])
{
	Mat image = imread("03.jpeg");    //载入RGB彩色图像
	if (image.empty())
	{
		cout << "图像读入为空" << endl;
	}

	namedWindow("Source Image", WINDOW_NORMAL);
	imshow("Source Image", image);

	//灰度化,滤波,Canny边缘检测
	Mat imageGray;
	cvtColor(image, imageGray, CV_RGB2GRAY);//灰度转换
	GaussianBlur(imageGray, imageGray, Size(5, 5), 2);   //高斯滤波
	namedWindow("Gray Image", WINDOW_NORMAL);
	imshow("Gray Image", imageGray);
	Canny(imageGray, imageGray, 80, 150);
	namedWindow("Canny Image", WINDOW_NORMAL);
	imshow("Canny Image", imageGray);

	//查找轮廓
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
	Mat imageContours = Mat::zeros(image.size(), CV_8UC1);  //轮廓	
	Mat marks(image.size(), CV_32S);   //Opencv分水岭第二个矩阵参数
	marks = Scalar::all(0);
	int index = 0;
	int compCount = 0;
	for (; index >= 0; index = hierarchy[index][0], compCount++)
	{
		//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点
		drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);
		drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);
	}

	//我们来看一下传入的矩阵marks里是什么东西
	Mat marksShows;
	convertScaleAbs(marks, marksShows);
	namedWindow("marksShow", WINDOW_NORMAL);
	imshow("marksShow", marksShows);
	namedWindow("轮廓", WINDOW_NORMAL);
	imshow("轮廓", imageContours);
	watershed(image, marks);

	//我们再来看一下分水岭算法之后的矩阵marks里是什么东西
	Mat afterWatershed;
	convertScaleAbs(marks, afterWatershed);
	namedWindow("After Watershed", WINDOW_NORMAL);
	imshow("After Watershed", afterWatershed);

	//对每一个区域进行颜色填充
	Mat PerspectiveImage = Mat::zeros(image.size(), CV_8UC3);
	for (int i = 0; i < marks.rows; i++)
	{
		for (int j = 0; j < marks.cols; j++)
		{
			int index = marks.at<int>(i, j);
			if (marks.at<int>(i, j) == -1)
			{
				PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
			}
			else
			{
				PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);
			}
		}
	}
	namedWindow("After ColorFill", WINDOW_NORMAL);
	imshow("After ColorFill", PerspectiveImage);

	//分割并填充颜色的结果跟原始图像融合
	Mat wshed;
	addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);
	namedWindow("AddWeighted Image", WINDOW_NORMAL);
	imshow("AddWeighted Image", wshed);

	waitKey();
}

Vec3b RandomColor(int value)  //生成随机颜色函数 
{
	value = value % 255;  //生成0~255的随机数
	RNG rng;
	int aa = rng.uniform(0, value);
	int bb = rng.uniform(0, value);
	int cc = rng.uniform(0, value);
	return Vec3b(aa, bb, cc);
}

运行结果3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2143209.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

水下目标检测数据集 urpc2021

项目背景&#xff1a; 水下目标检测在海洋科学研究、水下考古、海洋资源勘探等多个领域具有重要的应用价值。由于水下环境的复杂性和多变性&#xff0c;传统的人工检测方法存在诸多限制&#xff0c;自动化检测技术的需求日益增加。URPC2021数据集旨在为水下目标检测提供高质量…

校园社团|基于springBoot的校园社团信息管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信…

Android 微信,手机文件管理,通过自己软件打开

一、安卓微信关联文件打开&#xff0c;解锁便捷新体验 1.1 直接在微信中点击文件 在工作中&#xff0c;我们经常会通过微信接收各种文件&#xff0c;如文档、表格、PPT 等。安卓微信关联文件打开功能使得我们可以直接在微信中点击文件&#xff0c;快速跳转到相应的应用程序进…

反编译classes.dex安卓源码 文件-android反编译技术

一、安卓源码 通过解压我们得到dex文件 将dex转换为jar&#xff0c;就可以直接查看源码 二、阿雪技术观 拥抱开源与共享&#xff0c;见证科技进步奇迹&#xff0c;畅享人类幸福时光&#xff01; 让我们积极投身于技术共享的浪潮中&#xff0c;不仅仅是作为受益者&#xff0c…

认知小文3《打破桎梏,编程与人生的基本法则》

内容摘要&#xff1a; 面对挑战&#xff0c;编程起步艰难但必经磨砺。每周深耕Python&#xff0c;实战项目巩固技能。财务需努力与实战结合&#xff0c;构建坚实基础。规划先行&#xff0c;先进知识助力专家之路。认知升级阅读与多元资源&#xff0c;拓宽视野。价值积累靠专业证…

『功能项目』事件中心处理怪物死亡【55】

本章项目成果展示 我们打开上一篇54回调函数处理死亡的项目&#xff0c; 本章要做的事情是用事件中心处理怪物死亡后的逻辑 首先打开之前事件中心脚本&#xff08;不做更改&#xff0c;调用即可&#xff09;&#xff1a; using System.Collections.Generic; using UnityEngine…

WinForms 的支持跨域的测试程序

WinForms 的支持跨域的测试程序 using System; using System.Diagnostics; using System.IO; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Forms;namespace SimpleHttpServer_cors {public par…

工作流activiti笔记(六)已办列表

待办列表可以用 taskService.createTaskQuery() 但是已办列表就比较麻烦了。为什么呢&#xff1f;直接查询act_hi_procinst是不行的&#xff0c;已办要查询每个环节是否有当前登录工号处理过的记录&#xff0c;那这个记录是在act_hi_taskinst里的。 方式一&#xff1a;left j…

金葫芦STM32L431上手流程

目录 教材书名和开发板 金葫芦STM32L431上手流程 IDE软件安装流程 IDE软件使用流程 第一步 第二步 第三步 第四步 第五步 第六步 教材书名和开发板 教材&#xff1a;《嵌入式技术基础与实践&#xff08;第6版&#xff09;》&#xff08;王宜怀主编&#xff09; 开发…

计算机人工智能前沿进展-大语言模型方向-2024-09-12

计算机人工智能前沿进展-大语言模型方向-2024-09-12 1. PharmaBench: Enhancing ADMET benchmarks with large language models Z Niu, X Xiao, W Wu, Q Cai, Y Jiang, W Jin, M Wang… - Scientific Data, 2024 大语言模型在药物发现中的应用&#xff1a;PharmaBench 文章由…

力扣题解815

大家好&#xff0c;欢迎来到无限大的频道。祝大家中秋节快乐​。 今日继续给大家带来力扣题解。 题目描述&#xff08;困难&#xff09;​&#xff1a; 公交路线 给你一个数组 routes &#xff0c;表示一系列公交线路&#xff0c;其中每个 routes[i] 表示一条公交线路&…

红黑树的插入(NGINX源码)

下载并查看NGINX源码 访问NGINX下载页面&#xff0c;找到所需版本 https://nginx.org/en/download.html 使用wget下载源码包&#xff0c;替换版本号为所需版本 wget http://nginx.org/download/nginx-1.24.0.tar.gz解压源码包 tar -xzvf nginx-1.24.0.tar.gz进入解压后的目…

Java的输入输出

秋招笔试很多都是要自己写输出输出的&#xff0c;所以对常见的整理一下&#xff0c;后续也会持续更新的~~~ 目录 1.java中的Scanner类 1.1next()方法和nextLine()方法的区别 1. next() 方法 示例 2. nextLine() 方法 示例 主要区别 使用场景 2.print类 3.常用的转换…

音频左右声道数据传输_2024年9月6日

如下为音频数据传输标准I2S总线的基本时序图 I2S slave将I2S master发送来的左右声道的串行数据DATA转变为16bit的并行数据 WS为左右声道选择信号&#xff0c;WS高代表左声道&#xff0c;WS低代表右声道; WS为高和为低都持续18个周期&#xff0c;前面16个周期用来传输数据。 I2…

npm安装时候报错certificate has expired

打开了一个很久没用的电脑&#xff0c;npm和node都装好了&#xff0c;安装包的时候一直报错 request to https://registry.npm.taobao.org/create-react-app failed, reason: certificate has expired而且先报错rollbackFailedOptional 然而npm没什么问题&#xff0c;是ssl过…

【数据结构与算法 | 灵神题单 | 自底向上DFS篇】力扣965, 2331, 100, 1379

1. 力扣965&#xff1a;单值二叉树 1.1 题目&#xff1a; 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;[1,1,1,1,1,n…

UE5学习笔记22-武器瞄准和武器自动开火

0、一些疑问的记录 1.UUserWidget类和AHUD类的区别。两者都是关于界面显示的类。 实践&#xff1a; 想让界面和用户有交互使用UUserWidget&#xff0c;如果不要交互只是显示使用AHUD类&#xff0c;例如使用UUserWidget类制作开始界面&#xff0c;游戏开始&#xff0c;游戏设置&…

TensorRT-LLM——优化大型语言模型推理以实现最大性能的综合指南

引言 随着对大型语言模型 (LLM) 的需求不断增长&#xff0c;确保快速、高效和可扩展的推理变得比以往任何时候都更加重要。NVIDIA 的 TensorRT-LLM 通过提供一套专为 LLM 推理设计的强大工具和优化&#xff0c;TensorRT-LLM 可以应对这一挑战。TensorRT-LLM 提供了一系列令人印…

AD的入门操作

锦囊 1、打开AD后&#xff0c;一般默认打开上一个工程&#xff0c;这个时候如果想要打开新的工程&#xff0c;那就必须要创建一个项目&#xff0c;然后再在项目中添加原理图库和PCB库。 2、大多数情况下&#xff0c;直接使用库&#xff0c;不用自己再画原理图和封装库。 3、…

LeetCode[中等] 49.字母异位词分组

给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 思路&#xff1a; new Dictionary<string, List<string>>() 存储数据&#xff0c;key为排序之后的字符…