OpenCV分水岭算法watershed函数的使用

  • 操作系统:ubuntu22.04
  • OpenCV版本:OpenCV4.9
  • IDE:Visual Studio Code
  • 编程语言:C++11

描述

我们将学会使用基于标记的分水岭算法来进行图像分割。我们将看到:watershed()函数的用法。
        任何灰度图像都可以被视为一个地形表面,其中高强度对应着山峰和丘陵,而低强度则对应着山谷。你可以想象,从每个孤立的山谷(局部最小值)开始,用不同颜色的水(标记)来填充。随着水位上升,依据附近的山峰(梯度),来自不同山谷的水,显然带有不同的颜色,将会开始融合。为了避免这种情况发生,你必须在水开始汇合的地方建立起屏障。你持续进行填充水和构建屏障的工作,直到所有的山峰都被水覆盖。此时,你所建立的这些屏障就构成了分割的结果。这就是分水岭算法背后的理念。你可以在CMM网页上关于分水岭的页面,通过观看一些动画来更直观地理解这个概念。

        但是,这种方法会因为图像中的噪声或其他不规则性而导致过度分割的结果。因此,OpenCV实现了一种基于标记的分水岭算法,其中你指明了哪些山谷点应该被合并,哪些不应该。这是一种交互式的图像分割方式。我们所做的就是给已知的对象赋予不同的标记。将我们确信属于前景或对象的区域标记为一种颜色(或强度),将我们确信属于背景或非对象的区域标记为另一种颜色,最后,对于那些我们不确定的区域,我们将其标记为0。这就是我们的标记。接着,应用分水岭算法。随后,我们的标记将被更新为我们给予的标签,而对象的边界将拥有一个值为-1的特殊标记。

代码

假设我们有一张硬币的图像,其中硬币彼此接触。即使你对图像进行了阈值处理,硬币的边缘仍然会粘连在一起,原图如下:
在这里插入图片描述
我们开始着手于对硬币数量进行一个大致的估算。为此,我们可以使用大津的二值化方法(Otsu’s binarization)。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV+cv::THRESH_OTSU );cv::imshow( "Original Image", img );cv::imshow( "Gray Image", imgGray );cv::imshow( "binary Image", binary );cv::waitKey( 0 );return 0;
}

运行结果:
在这里插入图片描述
现在我们需要去除图像中的任何细小的白色噪声。为此,我们可以使用形态学开运算。为了消除物体上的任何微小孔洞,我们可以使用形态学闭运算。因此,我们现在可以确信,靠近物体中心的区域是前景,而远离物体的区域则是背景。唯一不确定的区域是硬币的边界区域。

所以我们需要提取那些我们确信是硬币的区域。腐蚀操作可以移除边界像素。因此,剩下的区域,我们可以确信那就是硬币。这在物体彼此不接触的情况下是可行的。但由于它们相互接触,另一个好的选择是找到距离变换并应用一个适当的阈值。接下来我们需要找出那些我们确信不是硬币的区域。为此,我们对结果进行膨胀处理。膨胀操作会使物体边界扩展到背景。这样一来,我们就可以确保结果中处于背景中的任何区域确实是背景,因为边界区域已经被去除了。请参见下图。
在这里插入图片描述

剩余的区域是我们无法确定是硬币还是背景的部分。这些不确定区域通常位于硬币边界处,也就是前景与背景相遇的地方(甚至可能是两个不同硬币相遇的区域)。我们称这部分区域为边界区域。边界区域可以通过从确定的背景区域(sure_bg)中减去确定的前景区域(sure_fg)得到。


#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU );// noise removalcv::Mat kernel = cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 );  // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate(opening, sure_bg, kernel, cv::Point(-1,-1), 3); // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform(opening, dist_transform, cv::DIST_L2, 3);cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc(dist_transform, nullptr, &maxVal);// 设置阈值double thresholdValue = 0.7 * maxVal;cv::threshold(dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY);//  Finding unknown regionsure_fg.convertTo(sure_fg, CV_8U);cv::Mat unknown;// 执行矩阵相减操作cv::subtract(sure_bg, sure_fg, unknown);// cv::imshow( "原始图", img );// cv::imshow( "灰度图", imgGray );// cv::imshow( "二值化后的图", binary );cv::imshow( "sure_fg", sure_fg );cv::imshow( "dist_transform", dist_transform );cv::waitKey( 0 );return 0;
}

在阈值处理后的图像中,如下图,我们可以看到一些硬币区域,我们确信这些区域属于硬币,并且它们现在是分离的。在某些情况下,你可能只对前景分割感兴趣,而不关心相互接触的物体是否分离。在这种情况下,你不需要使用距离变换,仅仅使用腐蚀操作就足够了。腐蚀操作其实只是另一种提取确定前景区域的方法,仅此而已。
在这里插入图片描述
现在我们已经确定了哪些区域属于硬币,哪些属于背景。因此,我们可以创建一个标记(marker)图像,它与原始图像具有相同的尺寸,但数据类型为int32。在这个标记图像中,我们将确定的区域(无论是前景还是背景)标记为不同的正整数,而不确定的区域则保持为零。

在OpenCV中,我们可以使用cv::connectedComponentsWithStats函数来实现这一目的。该函数会将图像的背景标记为0,其他对象则从1开始分配不同的整数标签。然而,正如你所提到的,如果背景被标记为0,那么在Watershed算法中,它将被视为未知区域。为了避免这种情况,我们应该将未知区域,即由unknown定义的区域,标记为0,而将背景标记为一个不同的整数。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU );// noise removalcv::Mat kernel = cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 );  // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 );  // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform( opening, dist_transform, cv::DIST_L2, 3 );cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc( dist_transform, nullptr, &maxVal );// 设置阈值double thresholdValue = 0.7 * maxVal;cv::threshold( dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY );//  Finding unknown regionsure_fg.convertTo( sure_fg, CV_8U );cv::Mat unknown;// 执行矩阵相减操作cv::subtract( sure_bg, sure_fg, unknown );// Marker labellingcv::Mat markers;  // 将会存储标记结果// 执行连通组件标记int num_labels = cv::connectedComponents( sure_fg, markers );cv::Mat ones = cv::Mat::ones( markers.size(), markers.type() );// 将 markers 矩阵的所有元素值增加1cv::add( markers, ones, markers );// 创建一个与 markers 大小相同的掩码矩阵,其中 unknown 矩阵中值为255的位置为 true,其余位置为 falsecv::Mat mask = unknown == 255;// 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0markers.setTo( 0, mask );// 创建一个与原图像大小相同的输出图像cv::Mat colorImage;// 将灰度图像转换为具有Jet色彩映射的彩色图像cv::applyColorMap(mask, colorImage, cv::COLORMAP_JET);//  Add one to all labels so that sure background is not 0, but 1// cv::imshow( "原始图", img );// cv::imshow( "灰度图", imgGray );// cv::imshow( "二值化后的图", binary );cv::imshow( "sure_fg", sure_fg );cv::imshow( "dist_transform", dist_transform );cv::imshow( "mask", colorImage );cv::waitKey( 0 );return 0;
}

在应用了JET色彩映射的结果中,红色区域代表了未知区域,这是在硬币分割过程中尚未确定为硬币或背景的部分。确定的硬币区域则被赋予了不同的色彩值。而确定为背景的区域则以较浅的蓝色显示,与未知区域的红色色形成对比。
在这里插入图片描述

现在我们的标记图像已经准备好了,下一步就是应用Watershed算法。一旦应用了Watershed算法,标记图像将会被修改。在硬币和背景之间的边界区域将会被标记为-1,这是OpenCV中Watershed算法的一个特性,它用-1来表示分割出的边界区域。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU );// noise removalcv::Mat kernel = cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 );  // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 );  // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform( opening, dist_transform, cv::DIST_L2, 3 );cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc( dist_transform, nullptr, &maxVal );// 设置阈值double thresholdValue = 0.7 * maxVal;cv::threshold( dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY );//  Finding unknown regionsure_fg.convertTo( sure_fg, CV_8U );cv::Mat unknown;// 执行矩阵相减操作cv::subtract( sure_bg, sure_fg, unknown );// Marker labellingcv::Mat markers;  // 将会存储标记结果// 执行连通组件标记int num_labels = cv::connectedComponents( sure_fg, markers );cv::Mat ones = cv::Mat::ones( markers.size(), markers.type() );// 将 markers 矩阵的所有元素值增加1cv::add( markers, ones, markers );// 创建一个与 markers 大小相同的掩码矩阵,其中 unknown 矩阵中值为255的位置为 true,其余位置为 falsecv::Mat mask = unknown == 255;// 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0markers.setTo( 0, mask );// 创建一个与原图像大小相同的输出图像cv::Mat colorImage;// 将灰度图像转换为具有Jet色彩映射的彩色图像cv::applyColorMap(mask, colorImage, cv::COLORMAP_JET);cv::imshow( "原始图", img );cv::watershed(img, markers);mask = markers == -1;img.setTo(cv::Scalar(255, 0, 0), mask);cv::imshow( "watershed", img );cv::waitKey( 0 );return 0;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

199.二叉树的右视图(BFS)

给定一个二叉树的根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3: 输入: [] 输出: [] 解题…

centos/Ubuntu安装Java/Maven

上图就是今天在Linux环境下安装好Java和Maven后&#xff0c;打包Spring Boot项目的截图&#xff01; 安装Java centos # 安装 yum install -y java-1.8.0-openjdk*# 查看版本检测是否成功安装 java -versionUbuntu # 更新软件包 sudo apt-get update# 安装 sudo apt-get in…

4.Java Web开发模式(javaBean+servlet+MVC)

Java Web开发模式 一、Java Web开发模式 1.javaBean简介 JavaBeans是Java中一种特殊的类&#xff0c;可以将多个对象封装到一个对象&#xff08;bean&#xff09;中。特点是可序列化&#xff0c;提供无参构造器&#xff0c;提供getter方法和setter方法访问对象的属性。名称中…

Java之 jvm

jvm之管理内存 程序计数器&#xff1a;当前线程所执行的字节码的行号指示器。程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域&#xff0c;它的生命周期随着线程的创建而创建&#xff0c;随着线程的结束而死亡。Java虚拟机栈 方法调用 一个方法调用都会有对应的栈帧…

set(集合),multiset容器及pair队组的创建

1.set的基本概念&#xff1a;所有元素再插入时自动按升序排序&#xff0c;set/multiset属于关联式容器&#xff0c;底层结构是用二叉树实现的 set与multiset区别&#xff1a; set中不允许容器中有重复的元素 multiset允许容器中有重复的元素 2.set的构造函数 3.set的大小和…

MT6701磁编码IC在自动化插件流水线中的应用

艾毕胜马达控制平台专家 MT6701磁编码IC作为现代工业自动化领域的重要组成部分&#xff0c;其在自动化插件流水线中的应用日益广泛。本文将从MT6701磁编码IC的特性、工作原理、在自动化插件流水线中的具体应用、以及未来的发展趋势等方面&#xff0c;详细探讨其在工业自动化领…

Apache POI-Excel入门与实战

目录 一、了解Apache POI 1.1 什么是Apache POI 1.2 为什么要使用ApaChe POI 1.3 Apache POI应用场景 1.4 Apache POI 依赖 二、Apache POI-Excel 入门案例 2.1 写入Excel文件 2.2 读取文件 四、Apache POI实战 4.1 创建一个获取天气的API 4.2高德天气请求API与响应…

volatile,最轻量的同步机制

目录 一、volatile 二、如何使用&#xff1f; 三、volatile关键字能代替synchronized关键字吗&#xff1f; 四、总结&#xff1a; 还是老样子&#xff0c;先来看一段代码&#xff1a; 我们先由我们自己的常规思路分析一下代码&#xff1a;子线程中&#xff0c;一直循环&…

小技巧:如何在已知PDF密码情况下去掉PDF的密码保护

第一步&#xff0c;用Edge打开你的pdf&#xff0c;输入密码进去 第二步&#xff0c;点击打印 第三步&#xff0c;选择导出PDF&#xff0c;选择彩印 第四步&#xff0c;选择导出位置&#xff0c;导出成功后打开发现没有密码限制了&#xff01;

物联网mqtt网关搭建背后的技术原理

前言 物联网是现在比较热门的软件领域&#xff0c;众多物联网厂商都有自己的物联网平台&#xff0c;而物联网平台其中一个核心的模块就是Mqtt网关。这篇文章的目的是手把手教大家写书写一个mqtt网关&#xff0c;后端存储支持Kafka/Pulsar&#xff0c;支持mqtt 连接、断链、发送…

DevExpress中文教程 - 如何在.NET MAUI应用中实现Material Design 3?

DevExpress .NET MAUI多平台应用UI组件库提供了用于Android和iOS移动开发的高性能UI组件&#xff0c;该组件库包括数据网格、图表、调度程序、数据编辑器、CollectionView和选项卡组件等。 获取DevExpress v24.1正式版下载 Material Design是一个由Google开发的跨平台指南系统…

计算机毕业设计hadoop+spark+hive物流大数据分析平台 仓储数据分析 物流预测系统 物流信息爬虫 物流大数据 机器学习 深度学习 知识图谱 大数据

Hadoop物流可视化系统的开题报告 一、课题名称 Hadoop物流可视化系统设计与实现 二、研究背景与意义 随着信息技术的飞速发展&#xff0c;物流行业产生了海量的数据&#xff0c;这些数据包含了订单信息、运输路线、仓储状态等丰富的信息和价值。然而&#xff0c;传统的数据…

谷粒商城实战笔记-44-前端基础-Vue-整合ElementUI快速开发/设置模板代码

文章目录 一&#xff0c;安装导入ElementUI1&#xff0c;安装 element-ui2&#xff0c;导入 element-ui 二&#xff0c;ElementUI 实战1&#xff0c;将 App.vue 改为 element-ui 中的后台布局2&#xff0c;开发导航栏2.1 开发MyTable组件2.2 注册路由2.3 改造App.vue2.4 新增左…

AV1技术学习:Coding Structure

一、SuperBlock SuperBlock 是AV1编解码器可以处理的最大编码块。SuperBlock 大小可以是128 128 或64 64&#xff0c;由 sequence header 控制。SuperBlock 可以进一步划分为更小的编码块&#xff0c;每个编码块都有自己的预测和变换模式。SuperBlock 编码只依赖于它的上侧和…

【jmeter边界值提取器】

【目的】 从响应头中取token的内容 【方案】 使用后置处理器-边界值提取器 【组件路径】HTTP请求->右键添加->后置处理器->边界提取器 用途&#xff1a;边界提取器(Boundary Extractor)是放在Sample请求之后执行的&#xff0c;用于获取左右边界中间的所有字符&#xf…

Anaconda下安装配置Jupyter

Anaconda下安装配置Jupyter 1、安装 conda activate my_env #激活虚拟环境 pip install jupyter #安装 jupyter notebook --generate-config #生成配置文件提示配置文件的位置&#xff1a; Writing default config to: /root/.jupyter/jupyter_notebook_config.py检查版本&am…

指针!!C语言(第二篇)

目录 一. 数组名的理解 二. 一维数组传参的本质 三. 冒泡排序法 四. 二级指针与指针数组 五. 字符指针变量与数组指针 一. 数组名的理解 在我们对指针有了初步的理解之外&#xff0c;今天我们来掌握一些新的知识就是数组与指针&#xff0c;第一个对数组名的了解&#xff…

基于R语言复杂数据回归与混合效应模型【多水平/分层/嵌套】技术与代码

回归分析是科学研究特别是生态学领域科学研究和数据分析十分重要的统计工具&#xff0c;可以回答众多科学问题&#xff0c;如环境因素对物种、种群、群落及生态系统或气候变化的影响&#xff1b;物种属性和系统发育对物种分布&#xff08;多度&#xff09;的影响等。纵观涉及数…

excel批量新建多个同类型的表格

背景引入 比如&#xff0c;一个企业有多个部门&#xff0c;现在需要按照某一个excel表模板收集各个部门的信息&#xff0c;需要创建数十个同类型表格&#xff0c;且标题要包含部门名称。 1.修改模板表格标题 在一个文件夹下面放入需要发放给各个部门的表格&#xff0c;将标题…

初识godot游戏引擎并安装

简介 Godot是一款自由开源、由社区驱动的2D和3D游戏引擎。游戏开发虽复杂&#xff0c;却蕴含一定的通用规律&#xff0c;正是为了简化这些通用化的工作&#xff0c;游戏引擎应运而生。Godot引擎作为一款功能丰富的跨平台游戏引擎&#xff0c;通过统一的界面支持创建2D和3D游戏。…