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.mzph.cn/bicheng/49051.shtml

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

相关文章

【数据结构_C语言】归并排序—文件类型

文章目录 1.排序定义2. 代码实现 1.排序定义 内排序&#xff1a;数据量相对少一些&#xff0c;可以放到内存中排序。 外排序&#xff1a;数据量较大&#xff0c;内存中放不下&#xff0c;数据放到磁盘文件中&#xff0c;需要排序。 归并排序&#xff1a; 2. 代码实现 void…

Flask 框架 redirect() url_for()

url_for url_for 函数根据传入的端点名称&#xff08;即路由函数名&#xff09;生成对应的 URL。 1. url_for() url_for 函数根据传入的端点名称&#xff08;即路由函数名&#xff09;生成对应的 URL。 它接受一个或多个参数&#xff0c;其中第一个参数是路由的名称&#x…

挖掘基于边缘无线协同感知的低功耗物联网 (LPIOT) 的巨大潜力

关键词&#xff1a;边缘无线协同感知、低功耗物联网(LPIOT)、无线混合组网、用电监测、用电计量、多角色、计量插座、无线场景感知、多角色运用、后台边缘层&#xff0c;网络边缘层&#xff0c;场景能效管理&#xff0c;场景能耗计算 在数字化和智能化日益加速的今天&#xff…

甄选范文“论面向方面的编程技术及其应”,软考高级论文,系统架构设计师论文

论文真题 针对应用开发所面临的规模不断扩大、复杂度不断提升的问题,面向方面的编程(Aspect Oriented Programming,AOP)技术提供了一种有效的程序开发方法。为了理解和完成一个复杂的程序,通常要把程序进行功能划分和封装。一般系统中的某些通用功能,如安全性、持续性、日…

C++ —— STL简介

1. 什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的 组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架 2.STL的版本 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本…

【Java】中的List集合

目录 一、什么是List集合二、List的常用方法List的初始化元素操作1.添加元素2.删除元素3.修改元素4.查询元素 三、List集合的遍历1.for循环遍历2.增强for循环3.迭代器遍历 一、什么是List集合 List集合是最常用的一种数据结构之一。它具有动态扩容、元素添加、删除和查询等基础…

【Linux学习】常用基本指令

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a;Linux学习 目录 &#x1f308;前言&#x1f525;XShell的一些使用查看Linux主机IP使用XShell登录主机XShell下的复制粘贴 &#x1f525;Linux下常用基本指令ls指令pwd指令cd指定touch指令…

MSP430单片机快速上手CSS开发环境(24电赛省赛)

前言&#xff1a;3天学完MSP430单片机&#xff0c;为了对应电赛&#xff0c;同时写下这篇笔记&#xff0c;用来熟悉对应开发环境&#xff0c;看的懂对应代码。 #MSP430开发环境安装 MSP单片机是TI&#xff08;德州仪器&#xff09;半导体厂家产的&#xff0c;一款16位单片机…

【机器学习】机器学习解决的问题特点、机器学习学的是什么、怎么学、如何构建高效机器学习模型的策略、机器学习的分类以及机器学习、模式识别、数据挖掘和人工智能的区别

引言 机器学习是人工智能的一个重要分支&#xff0c;主要解决的是如何通过算法让机器从数据中自动学习规律和知识&#xff0c;以完成特定任务或解决特定问题。 文章目录 引言一、机器学习解决的是什么样的问题1.2 数据驱动的预测问题1.3 数据理解与挖掘1.4 优化与决策问题1.5 异…

如何开启或者关闭 Windows 安全登录?

什么是安全登录 什么是 Windows 安全登录呢&#xff1f;安全登录是 Windows 附加的一个组件&#xff0c;它可以在用户需要登录的之前先将登录界面隐藏&#xff0c;只有当用户按下 CtrlAltDelete 之后才出现登录屏幕&#xff0c;这样可以防止那些模拟登录界面的程序获取密码信息…

华为云技术精髓笔记(四)-CES基础入门实战

华为云技术精髓笔记(四) CES基础入门实战 一、监控ECS性能 1、 远程登录ECS 步骤一 双击实验桌面的“Xfce终端”打开Terminal&#xff0c;输入以下命令登录云服务器。注意&#xff1a;请使用云服务器的公网IP替换命令中的【EIP】。 LANGen_us.UTF-8 ssh rootEIP说明&#x…

ARM功耗管理之功耗和安全

安全之安全(security)博客目录导读 思考&#xff1a;功耗与安全&#xff1f;超频攻击&#xff1f;欠压攻击&#xff1f;低功耗流程中的安全&#xff1f; 睡眠唤醒流程中&#xff0c;安全相关寄存器的备份恢复 举例&#xff1a;比如某DMA通道&#xff0c;芯片逻辑默认为安全通…

centos/Ubuntu安装Nginx服务器

安装方式 使用系统自带的软件包管理器快速安装&#xff08;如centos的yum&#xff09;到官网下载压缩包安装&#xff08;https://nginx.org/en/download.html&#xff09;docker容器实例 下面是昨天以第二种方式安装的命令小记&#xff01; centos # 下载&#xff08;https…

压缩视频在线免费 怎么免费压缩视频大小 哪个软件可以免费压缩视频

在数字媒体时代&#xff0c;视频文件的体积越来越大&#xff0c;这就需要我们找到高效的方式来压缩视频&#xff0c;以节省存储空间和提升分享速度。本文将为您介绍几款免费的视频压缩软件&#xff0c;帮助您轻松应对视频文件管理难题。 方法一、 安装并打开一款的视频软件。 …

Git之repo sync -c与repo sync -dc用法区别四十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

idea springBoot启动时覆盖apollo配置中心的参数

vm options -Dorder.stat.corn“0/1 * * * * ?” 只有vm options, -D参数才能覆盖apollo参数 program arguments –key01val01 --key02val02 environment varibales envFAT;key02val02;key03val03

【专题】百度萝卜快跑体验:Robotaxi发展现状与展望报告合集PDF分享(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p37054 百度“萝卜快跑”近期因事故与抵制引发关注&#xff0c;武汉部署超300辆全无人驾驶车。体验显示其安全但策略保守&#xff0c;行驶效率低于人类司机&#xff0c;价格亲民。阅读原文&#xff0c;获取专题报告合集全文&#xf…

Elastic 及阿里云 AI 搜索 Tech Day 将于 7 月 27 日在上海举办

活动主题 面向开发者的 AI 搜索相关技术分享&#xff0c;如 RAG、多模态搜索、向量检索等。 活动介绍 参加 Elastic 原厂与阿里云联合举办的 Generative AI 技术交流分享日。借助 The Elastic Search AI Platform&#xff0c; 使用开放且灵活的企业解决方案&#xff0c;以前所…

Flink笔记整理(三)

Flink笔记整理&#xff08;三&#xff09; 文章目录 Flink笔记整理&#xff08;三&#xff09;五、DataStream API5.1Environment5.2 Source5.3 Transformation5.4 Sink 总结 五、DataStream API DataStream API是Flink的核心层API&#xff0c;一个Flink程序&#xff0c;其实本…

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…