OpenCV实战——基于分水岭算法的图像分割
OpenCV实战——基于分水岭算法的图像分割
- 0. 前言
- 1. 分水岭算法
- 2. 分水岭算法直观理解
- 3. 完整代码
- 相关链接
0. 前言
分水岭变换是一种流行的图像处理算法,用于快速将图像分割成同质区域。分水岭变换主要基于以下思想:当图像被视为拓扑浮雕时,均质区域对应于相对平坦且由陡峭的边缘界定的盆地。算法的原始版本倾向于过度分割图像,从而产生多个小区域,因此 OpenCV
中实现了该算法的改进版本,通过使用一组预定义的标记来指导图像分割区域的定义。
1. 分水岭算法
分水岭分割可以通过使用 cv::watershed
函数实现,函数的输入是一个 32
位有符号整数标记图像,其中每个非零像素表示一个标签。
通过标记图像中已知属于给定区域的一些像素,利用初始标记,分水岭算法可以确定其他像素所属的区域。
(1) 首先,将标记图像读取为灰度图像,然后将其转换为整数类型:
class WatershedSegmentater {private:cv::Mat markers;public:void setMarkers(const cv::Mat& markerImage) {// 转换数据类型markerImage.convertTo(markers, CV_32S);}cv::Mat process(const cv::Mat& image) {// 应用分水岭算法cv::watershed(image, markers);return markers;}
有多种获取标记的方式,例如,使用预处理步骤识别出属于感兴趣对象的某些像素,然后利用分水岭算法根据初始标记分割完整的对象。在本节中,我们将使用二值图像来识别相应原始图像中的动物。因此,从二值图像中,我们需要识别属于前景(动物)的像素和属于背景(主要是雪地)的像素,我们用标签 255
标记前景像素,用标签 128
标记背景像素,其他像素则标记为 0
。
(2) 初始二值图像包含过多属于图像各个部分的白色像素,为了只保留属于重要对象的像素,我们首先需要腐蚀该图像:
// 消除噪音
cv::Mat fg;
cv::erode(binary, fg, cv::Mat(), cv::Point(-1, -1), 4);
结果如下图所示:
(3) 图中仍然存在一些属于背景(雪地)的像素,我们通过对原始二值图像进行膨胀来选择几个属于背景的像素:
// 标记图像像素
cv::Mat bg;
cv::dilate(binary, bg, cv::Mat(), cv::Point(-1, -1), 4);
cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);
结果如下图所示,黑色像素对应于背景像素:
(4) 将这些图像组合起来形成标记图像:
cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0));
markers = fg+bg;
我们使用重载的 +
运算符来组合图像,得到用作分水岭算法的输入:
(5) 在这个输入图像中,白色区域属于前景对象,灰色区域是背景的一部分,黑色区域则属于未知标签,得到分割结果如下:
// 创建分水岭分割对象
WatershedSegmentater segmenter;
segmenter.setMarkers(markers);
segmenter.process(image);
更新标记图像,以便为黑色区域中的像素重新分配标签,而属于边界的像素的值为 -1
。结果标签图像如下:
图像中对象边缘的可视化结果如下图所示:
2. 分水岭算法直观理解
我们使用拓扑图进行类比,为了创建分水岭分割,我们从级别 0
开始注水,随着水位逐渐增加,就形成了集水盆地。这些盆地的大小也会逐渐增加,两个不同盆地的水最终会汇合,发生这种情况时,会创建一个分水岭,以将两个盆地分开。一旦水位达到最高水位,这些水域和分水岭就形成了分水岭分割。
在注水过程中最初会产生许多小盆地,当这些盆地进行合并时,会创建许多分水岭线,从而导致图像被过度分割。为了克服这个问题,已经提出了多种改进算法,在 OpenCV
调用 cv::watershed
函数时,注水过程从一组预定义的标记像素开始,根据分配给初始标记的值对盆地进行标记,当具有相同标签的两个盆地合并时,不会创建分水岭,从而防止过度分割,更新输入标记图像以获得最终的分水岭分割。用户可以输入带有任意数量的标签和未知标签的标记图像,标记图像的像素类型为为 32
位有符号整数,以便能够定义超过 255
个标签。cv::watershed
函数还允许返回与分水岭关联的像素(使用特殊值 -1
进行标记)。
为了便于显示结果,我们引入两种特殊的方法。第一个方法 getSegmentation()
通过阈值返回标签图像,分水岭值为 0
:
// 返回结果
cv::Mat getSegmentation() {cv::Mat tmp;markers.convertTo(tmp, CV_8U);return tmp;
}
第二种方法 getWatersheds()
返回的图像中,分水岭线使用值 0
进行标记,图像的其余部分像素值为 255
,可以使用 cv::convertTo
方法实现:
// 返回分水岭
cv::Mat getWatersheds() {cv::Mat tmp;markers.convertTo(tmp,CV_8U,255,255);return tmp;
}
在转换之前应用线性变换,可以将像素值 -1
转换为 0
(−1×255+255=0-1\times 255+255=0−1×255+255=0)。由于将有符号整数转换为无符号字符时需应用饱和操作,大于 255
的像素值将转换为 255
。
我们也可以通过许多不同的方式获得标记图像。例如,可以令用户以交互方式在图像中标记属于对象和背景的像素区域;或者,如果我们需要识别位于图像中心的物体,可以输入一个中心区域标有特定标签的图像,且图像背景标记带有另一个标签,可以按以下方式创建标记图像:
// 标记背景像素
cv::Mat imageMask(image.size(), CV_8U, cv::Scalar(0));
cv::rectangle(imageMask,cv::Point(5, 5),cv::Point(image.cols-5, image.rows-5),cv::Scalar(255),3);
// 标记前景像素
cv::rectangle(imageMask,cv::Point(image.cols/2-10, image.rows/2-10),cv::Point(image.cols/2+10, image.rows/2+10),cv::Scalar(1),10);
如果我们将此标记图像叠加在测试图像上,可以得到以下图像:
生成的分水岭图像如下图所示:
3. 完整代码
头文件 (watershedSegmentation.h
) 完整代码如下:
#if !defined WATERSHS
#define WATERSHS#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>class WatershedSegmentater {private:cv::Mat markers;public:void setMarkers(const cv::Mat& markerImage) {// 转换数据类型markerImage.convertTo(markers, CV_32S);}cv::Mat process(const cv::Mat& image) {// 应用分水岭算法cv::watershed(image, markers);return markers;}// 返回结果cv::Mat getSegmentation() {cv::Mat tmp;markers.convertTo(tmp, CV_8U);return tmp;}// 返回分水岭cv::Mat getWatersheds() {cv::Mat tmp;markers.convertTo(tmp,CV_8U,255,255);return tmp;}
};#endif
主文件 (segment.cpp
) 完整代码如下所示:
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "watershedSegmentation.h"int main() {// 读取输入图像cv::Mat image = cv::imread("1.png");if (!image.data) return 0;cv::namedWindow("Original Image");cv::imshow("Original Image",image);// 读取二值图像cv::Mat binary;binary = cv::imread("binary.png", 0);cv::namedWindow("Binary Image");cv::imshow("Binary Image", binary);// 消除噪音cv::Mat fg;cv::erode(binary, fg, cv::Mat(), cv::Point(-1, -1), 4);cv::namedWindow("Foreground Image");cv::imshow("Foreground Image", fg);// 标记图像像素cv::Mat bg;cv::dilate(binary, bg, cv::Mat(), cv::Point(-1, -1), 4);cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);cv::namedWindow("Background Image");cv::imshow("Background Image", bg);cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0));markers = fg+bg;cv::namedWindow("Markers");cv::imshow("Markers", markers);// 创建分水岭分割对象WatershedSegmentater segmenter;segmenter.setMarkers(markers);segmenter.process(image);cv::namedWindow("Segmentation");cv::imshow("Segmentation", segmenter.getSegmentation());cv::namedWindow("Watersheds");cv::imshow("Watersheds", segmenter.getWatersheds());// 打开另一张图像image = cv::imread("3.png");// 标记背景像素cv::Mat imageMask(image.size(), CV_8U, cv::Scalar(0));cv::rectangle(imageMask,cv::Point(5, 5),cv::Point(image.cols-5, image.rows-5),cv::Scalar(255),3);// 标记前景像素cv::rectangle(imageMask,cv::Point(image.cols/2-10, image.rows/2-10),cv::Point(image.cols/2+10, image.rows/2+10),cv::Scalar(1),10);segmenter.setMarkers(imageMask);segmenter.process(image);cv::rectangle(image,cv::Point(5, 5),cv::Point(image.cols-5, image.rows-5),cv::Scalar(255, 255, 255),3);cv::rectangle(image,cv::Point(image.cols/2-10, image.rows/2-10),cv::Point(image.cols/2+10, image.rows/2+10),cv::Scalar(1, 1, 1),10);cv::namedWindow("Image with marker");cv::imshow("Image with marker", image);cv::namedWindow("Watershed");cv::imshow("Watershed", segmenter.getWatersheds());cv::waitKey();return 0;
}
相关链接
OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
相关文章:

OpenCV实战——基于分水岭算法的图像分割
OpenCV实战——基于分水岭算法的图像分割0. 前言1. 分水岭算法2. 分水岭算法直观理解3. 完整代码相关链接0. 前言 分水岭变换是一种流行的图像处理算法,用于快速将图像分割成同质区域。分水岭变换主要基于以下思想:当图像被视为拓扑浮雕时,均…...

YOLOv8模型调试记录
前言 新年伊始,ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本,目前支持图像分类、物体检测和实例分割任务,在还没有开源时就收到了用户的广泛关注。 值得一提的是,在博主的印象中,YOLO系…...
算法刷题打卡第97天:删除字符串两端相同字符后的最短长度
删除字符串两端相同字符后的最短长度 难度:中等 给你一个只包含字符 a,b 和 c 的字符串 s ,你可以执行下面这个操作(5 个步骤)任意次: 选择字符串 s 一个 非空 的前缀,这个前缀的所有字符都相…...
WebGPU学习(3)---使用IndexBuffer(索引缓冲区)
现在让我们将 IndexBuffer 与 VertexBuffer 一起使用。演示示例 1.准备索引数据 我们用 Uint16Array 类型来准备索引数据。我们将矩形的4个点放到 VertexBuffer 中,然后根据三角形绘制顺序,组织成 0–1–2 和 0–2–3 的结构。 const quadIndexArray …...

Java代码加密混淆工具有哪些?
在Java中,代码加密混淆工具可以帮助开发者将源代码进行加密和混淆处理,以增加代码的安全性和保护知识产权。以下是一些流行的Java代码加密混淆工具: 第一款:ProGuard:ProGuard ProGuard:ProGuard…...
华为OD机试 - 高效的任务规划(Python) | 机试题+算法思路+考点+代码解析 【2023】
高效的任务规划 题目 你有 n 台机器编号为1-n,每台都需要完成一项工作, 机器经过配置后都能独立完成一项工作。 假设第i台机器你需要花 Bi 分钟进行设置, 然后开始运行,Ji分钟后完成任务。 现在,你需要选择布置工作的顺序,使得用最短的时间完成所有工作。 注意,不能同…...

ChatGPT写程序如何?
前言ChatGPT最近挺火的,据说还能写程序,感到有些惊讶。于是在使用ChatGPT有一周左右后,分享一下用它写程序的效果如何。1、对于矩阵,把减法操作转换加法?感觉不错的,能清晰介绍原理,然后写示例程…...

编译链接实战(9)elf符号表
文章目录符号的概念符号表探索前面介绍了elf文件的两种视图,以及两种视图的各自几个组成部分:elf文件有两种视图,链接视图和执行视图。在链接视图里,elf文件被划分成了elf 头、节头表、若干的节(section)&a…...
React合成事件的原理是什么
事件介绍 什么是事件? 事件是在编程时系统内发生的动作或者发生的事情,而开发者可以某种方式对事件做出回应,而这里有几个先决条件 事件对象 给事件对象注册事件,当事件被触发后需要做什么 事件触发 举个例子 在机场等待检票…...

Arduino-交通灯
LED交通灯实验实验器件:■ 红色LED灯:1 个■ 黄色LED灯:1 个■ 绿色LED灯:1 个■ 220欧电阻:3 个■ 面包板:1 个■ 多彩杜邦线:若干实验连线1.将3个发光二极管插入面包板,2.用杜邦线…...

【论文笔记】Manhattan-SDF == ZJU == CVPR‘2022 Oral
Neural 3D Scene Reconstruction with the Manhattan-world Assumption 本文工作:基于曼哈顿世界假设,重建室内场景三维模型。 1.1 曼哈顿世界假设 参考阅读文献:Structure-SLAM: Low-Drift Monocular SLAM in Indoor EnvironmentsIEEE IR…...

好消息!Ellab(易来博)官方微信公众号开通了!携虹科提供专业验证和监测解决方案
自1949年以来,丹麦Ellab一直通过全球范围内的验证和监测解决方案,协助全球生命科学和食品公司优化和改进其流程的质量。Ellab全面的无线数据记录仪,热电偶系统,无线环境监测系统,校准设备,软件解决方案等等…...

想要去字节跳动面试Android岗,给你这些面试知识点
关于面试字节跳动,我总结一些面试点,希望可以帮到更多的小伙伴,由于篇幅问题这里没有把全部的面试知识点问题都放上来!!目录:1.网络2.Java 基础&容器&同步&设计模式3.Java 虚拟机&内存结构…...
Java的Lambda表达式的使用
Lambda表达式是Java 8中引入的一个重要特性,它是一种简洁而强大的语法结构,可以用于替代传统的匿名内部类。 Lambda表达式的语法结构如下: (parameters) -> expression或者 (parameters) -> { statements; }其中,paramet…...

Spring MVC 源码 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
HandlerMapping 组件HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)handler 处理器是 Objec…...

超店有数,为什么商家要使用tiktok达人进行营销推广呢?
近几年互联网发展萌生出更多的短视频平台,而tittok这个平台在海外也越来越火爆。与此同时,很多商家也开始用tiktok进行营销推广。商家使用较多的方式就是达人营销,这种方法很常见且转化效果不错。那为什么现在这么多商家喜欢用tiktok达人进行…...

【分享】订阅万里牛集简云连接器同步企业采购审批至万里牛系统
方案场景 面临着数字化转型的到来,不少公司希望实现业务自动化需求,公司内部将钉钉作为办公系统,万里牛作为ERP系统,两个系统之前的数据都储存在各自的后台,导致数据割裂,数据互不相通,人工手动…...

C++类和对象_02----对象模型和this指针
目录C对象模型和this指针1、成员变量和成员函数分开存储1.1、空类大小1.2、非空类大小1.3、结论2、this指针概念2.1、解决名称冲突2.2、在类的非静态成员函数中返回对象本身,可使用return *this2.3、拷贝构造函数返回值为引用的时候,可进行链式编程3、空…...
瑞芯微RK3568开发:烧录过程
进入rk3568这款芯片的烧录模式共有3种方式,先讲需要准备的环境要求。 一、软硬件环境 1、配套sdk版本的驱动DriverAssitant_vx.x.x和RKDevTool_Release_vx.x,版本不对应可能无法烧录,建议直接在sdk压缩包里获取; 2、如果正确安…...

【数据结构】——树和二叉树的概念
目录 1.树概念及结构 1.1树的概念 1.2 树的相关性质 1.3 树的表示 1.4 树在实际中的运用(表示文件系统的目录树结构) 2.二叉树概念及结构 2.1二叉树概念 2.2 特殊的二叉树 2.3 二叉树的性质 1.树概念及结构 1.1树的概念 树是一种非线性的数据结构…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...