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

news2024/11/25 23:35:52
  • 操作系统: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 removal
    cv::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 );  // 迭代次数为2

    cv::Mat sure_bg;
    // 执行膨胀操作
    cv::dilate(opening, sure_bg, kernel, cv::Point(-1,-1), 3); // 迭代次数为3

    cv::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 region
    sure_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 removal
    cv::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 );  // 迭代次数为2

    cv::Mat sure_bg;
    // 执行膨胀操作
    cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 );  // 迭代次数为3

    cv::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 region
    sure_fg.convertTo( sure_fg, CV_8U );

    cv::Mat unknown;

    // 执行矩阵相减操作
    cv::subtract( sure_bg, sure_fg, unknown );

    // Marker labelling
    cv::Mat markers;  // 将会存储标记结果

    // 执行连通组件标记
    int num_labels = cv::connectedComponents( sure_fg, markers );

    cv::Mat ones = cv::Mat::ones( markers.size(), markers.type() );

    // 将 markers 矩阵的所有元素值增加1
    cv::add( markers, ones, markers );


    // 创建一个与 markers 大小相同的掩码矩阵,其中 unknown 矩阵中值为255的位置为 true,其余位置为 false
    cv::Mat mask = unknown == 255;

    // 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0
    markers.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 removal
    cv::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 );  // 迭代次数为2

    cv::Mat sure_bg;
    // 执行膨胀操作
    cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 );  // 迭代次数为3

    cv::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 region
    sure_fg.convertTo( sure_fg, CV_8U );

    cv::Mat unknown;

    // 执行矩阵相减操作
    cv::subtract( sure_bg, sure_fg, unknown );

    // Marker labelling
    cv::Mat markers;  // 将会存储标记结果

    // 执行连通组件标记
    int num_labels = cv::connectedComponents( sure_fg, markers );

    cv::Mat ones = cv::Mat::ones( markers.size(), markers.type() );

    // 将 markers 矩阵的所有元素值增加1
    cv::add( markers, ones, markers );


    // 创建一个与 markers 大小相同的掩码矩阵,其中 unknown 矩阵中值为255的位置为 true,其余位置为 false
    cv::Mat mask = unknown == 255;

    // 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0
    markers.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.coloradmin.cn/o/1941548.html

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

相关文章

vue项目实战速查记录

1.图片下载到本地 2.本地静态文件访问 3.元素大小相同,相互覆盖 1.图片下载到本地 实现原理:创建a标签,利用a标签下载属性. download(){const link document.createElement(a);link.href "图片地址";link.setAttribute(download, name);document.body.ap…

Docker核心技术:Docker原理之Namespace

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;Docker原理之Namespace&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 Docker核心技术&#xff1…

【Qt】 FFmpeg+Qt windows 32位或者64位环境搭建

简介 目前Ffmpeg官网&#xff08;64位连接&#xff09;下载的均为64位编译的&#xff0c;这要求我们采用的Qt creator也采用64位编译器。但是仍存在部分用户采用32位编译器&#xff0c;所以这部分用户需下载32 Ffmpeg&#xff08;32位连接&#xff09;。 根据使用的编译器位数…

ArkTS语言---基础知识

ArkTS是一种为构建高性能应用而设计的编程语言。ArkTS在继承TypeScript语法的基础上进行了优化&#xff0c;以提供更高的性能和开发效率。目前流行的编程语言TypeScript是在JavaScript基础上通过添加类型定义扩展而来的&#xff0c;而ArkTS则是TypeScript的进一步扩展。TypeScr…

【面经】C++八股文(地平线C++一面)

一、C11的新特性都有哪些&#xff1f; 1.1 自动类型推断 (auto) auto 关键字允许编译器自动推断变量的类型&#xff0c;从而简化代码的书写。 auto num 5; // int auto pi 3.14; // double auto str "Hello"; // const char*1.2 范围 for 循环 范围…

tomcat部署java项目 出现404访问不到

今天使用tomcat做项目部署,部署SSM项目把项目上传到tomcat下面的webapps后一直访问不到项目 一直报404访问不到资源&#xff0c;但是访问tomcat又可以&#xff0c;浏览器能出现tomcat的标致&#xff0c;查看logs里面的运行日志发现报错&#xff1a;org.apache.catalina.core.St…

PYTHON学习笔记(四、pyhton数据结构--列表)

&#xff08;1&#xff09;list列表 列表的含义是指&#xff1a;&#xff08;1&#xff09;一系列的按特定顺序排列的元素组成。&#xff08;2&#xff09;python中内置的可变序列。&#xff08;3&#xff09;在python中使用[]定义列表&#xff0c;元素与元素之间使用英文的逗…

Vue中渲染函数

why? 在绝大多数情况下&#xff0c;Vue 推荐使用模板语法来创建应用。然而在某些使用场景下&#xff0c;我们真的需要用到 JavaScript 完全的编程能力。这时渲染函数就派上用场了。 例如&#xff1a;下方要在多个模型上方设置对话框&#xff0c;如果使用Vue模板语法相对较困难…

c#中的From窗体

Windows Forms&#xff08;简称WinForms&#xff09;是.NET Framework中用于构建Windows桌面应用程序的一个组件。Form 类是WinForms中最基本的窗口类型&#xff0c;提供了一个容器&#xff0c;可以容纳控件&#xff08;如按钮、文本框、标签等&#xff09;。 以下是Form的一些…

数据结构之判断平衡二叉树详解与示例(C,C++)

文章目录 AVL树定义节点定义计算高度获取平衡因子判断是否为平衡二叉树完整示例代码结论 在计算机科学中&#xff0c;二叉树是一种非常重要的数据结构。它们被广泛用于多种算法中&#xff0c;如排序、查找等。然而&#xff0c;普通的二叉树在极端情况下可能退化成链表&#xff…

autoware.universe源码略读(3.17)--perception:occupancy_grid_map_outlier_filter

autoware.universe源码略读3.17--perception:occupancy_grid_map_outlier_filter Overview&#xff08;Class&#xff09;RadiusSearch2dfilter&#xff08;Class Constructor&#xff09;RadiusSearch2dfilter::RadiusSearch2dfilter&#xff08;mFunc&#xff09;RadiusSearc…

Transformer系列总结

文章目录 1、Transformer基本原理介绍1.Transformer 结构2.嵌入表示层3. 注意力层3.1 输⼊矩阵3.2 查询矩阵和键矩阵3.3 Q和K的转置的点击除以键向量维度的平⽅根3.4 应⽤softmax函数3.5 注意力矩阵通过分数矩阵乘以值矩阵得出3.6 注意⼒矩阵 4. 前馈层5. 残差连接与层归一化6.…

虚幻引擎,体积雾、体积光、镜头泛光

1、体积雾 这里介绍的是用于地面的体积雾效果&#xff0c;效果如图1-1&#xff1a; 图1-1 首先&#xff0c;需要场景中存在指数级高度雾并开启体积雾&#xff08;如图1-2&#xff09;。然后创建材质&#xff0c;材质域选择“体积”&#xff0c;混合模式选择“Additive”。材质节…

二叉树的构造问题 | LeetCode刷题笔记 | 每日练习 | 深度优先遍历| 广度优先遍历 | Java

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本篇分享的是与构造二叉树&#x1f384;有关的问题&#xff0c;有关二叉树的基础知识可以点击此处跳转学习&#x1f448;&#xff0c;构造二叉树的就是…

嵌入式人工智能(18-基于树莓派4B的继电器JQC-3FF-S-Z)

1、继电器 继电器是一种电控开关设备&#xff0c;由一个电磁系统和一个控制电路组成。当控制电路给予电磁系统足够的电流或电压时&#xff0c;电磁系统会产生磁场&#xff0c;使其内部的触点发生动作。这个动作可以使电流或电压在主电路中开关或转换&#xff0c;起到控制电路的…

【EarthMarker】区域级和点级遥感图像理解的视觉提示学习框架

摘要 自然图像区域视觉提示使用户可以通过各种视觉标记&#xff0c;如框、点和其他形状&#xff0c;和AI进行交互。但是&#xff0c;自然图像和RS图像之间存在显著差异&#xff0c;现有的视觉提示模型在RS场景中面临着挑战。此外&#xff0c;RS MLLMs主要关注于解释图像级RS数…

中文诗歌生成

用transformer在诗歌集上训练出的模型 import os os.environ["KERAS_BACKEND"] "tensorflow" # param ["tensorflow", "jax", "torch"] os.environ[TF_CPP_MIN_LOG_LEVEL] 2 os.environ[HF_ENDPOINT] https://hf-mirro…

拥抱AI时代:解锁Prompt技术的无限潜力与深远影响

拥抱AI时代&#xff1a;解锁Prompt技术的无限潜力与深远影响 引言 在人工智能的浩瀚星空中&#xff0c;自然语言处理&#xff08;NLP&#xff09;无疑是最耀眼的星辰之一。随着技术的不断演进&#xff0c;NLP已经从最初的简单问答系统发展成为能够生成复杂文本、理解人类情感与…

JavaScript之WebAPIs-BOM

目录 BOM操作浏览器一、Window对象1.1 BOM&#xff08;浏览器对象模型&#xff09;1.2 定时器-延时函数1.3 js执行机制1.4 location对象1.5 navigator对象1.6 history对象 二、本地存储三、补充数组中的map方法数组中的join方法数组中的forEach方法(重点)数组中的filter方法(重…

Linux_线程的同步与互斥

目录 1、互斥相关概念 2、代码体现互斥重要性 3、互斥锁 3.1 初始化锁 3.2 申请、释放锁 3.3 加锁的思想 3.4 实现加锁 3.5 锁的原子性 4、线程安全 4.1 可重入函数 4.2 死锁 5、线程同步 5.1 条件变量初始化 5.2 条件变量等待队列 5.3 唤醒等待队列…