计算机视觉——OpenCV 使用分水岭算法进行图像分割

news2024/12/25 4:59:27

分水岭算法

在这里插入图片描述

分水岭算法:模拟地理形态的图像分割

分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。

测地线距离:地形分析的核心

测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。

分水岭算法的执行步骤

  1. 梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。
  2. 起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。
  3. 水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。
  4. 大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。

避免过度分割的策略

分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:

  • 高斯平滑:通过高斯平滑减少噪声,合并小分区。
  • 基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。

OpenCV 实现 Watershed 算法

函数原型:

void watershed( InputArray image, InputOutputArray markers );

参数说明:

  1. image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。

  2. markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。

功能说明:

  • watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。
  • 算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。
  • 在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。

注意事项:

  • markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。
  • 分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。

C++ 代码实现

  1. 读取图像
if(argc < 2){
    std::cerr << "Errorn";
    std::cerr << "Provide Input Image:n<program> <inputimage>\n";
    return -1;
}
cv::Mat original_img = cv::imread(argv[1]);
if(original_img.empty()){
    std::cerr << "Errorn";
    std::cerr << "Cannot Read Imagen";
    return -1;
}

在这里插入图片描述

  1. 使用滤波器从图像中去除噪声
    Mean shift blur 是一种保留图像边缘的滤波算法,经常用于在图像 Watershed 分割之前消除噪声,这可以显著改善 Watershed 分割效果。
cv::Mat shifted;
cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
showImg("图像滤波", shifted);

在这里插入图片描述

  1. 将原始图像转换为灰度和二进制图像
cv::Mat gray_img;
cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
showImg("", gray_img);

在这里插入图片描述

cv::Mat bin_img;
cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
showImg("二值图像", bin_img);

在这里插入图片描述

  1. 查找图像的确定背景

在这一步中,要找到图像中我们确定是背景的区域。

void getBackground(const cv::Mat& source, cv::Mat& dst) {
    cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
}

在这里插入图片描述

  1. 查找图像的确定前景

为了找到图像的前景,使用距离变换算法

void getForeground(const cv::Mat& source, cv::Mat& dst) {
    cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
}

在这里插入图片描述

  1. 查找标记

在应用 Watershed 算法之前,需要标记。为此,我们将使用 OpenCV 提供的 findContour() 函数来在图像中找到标记。

void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {
    cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    // 绘制前景标记
    for (size_t i = 0, size = contours.size(); i < size; i++)
        drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
}

在这里插入图片描述

  1. 应用 Watershed 算法
cv::watershed(original_img, markers);
cv::Mat mark;
markers.convertTo(mark, CV_8U);
cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
showImg("MARKER", mark);

在这里插入图片描述

完整代码

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

void showImg(const std::string& windowName, const cv::Mat& img){
    cv::imshow(windowName, img);
}

void getBackground(const cv::Mat& source, cv::Mat& dst) {
    cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
}

void getForeground(const cv::Mat& source, cv::Mat& dst) {
    cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
}

void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {
    cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    // 绘制前景标记
    for (size_t i = 0, size = contours.size(); i < size; i++)
        drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
}

void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
    for (int i = 0; i < size ; ++i) {
        int b = cv::theRNG().uniform(0, 256);
        int g = cv::theRNG().uniform(0, 256);
        int r = cv::theRNG().uniform(0, 256);
        colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
    }
}

int main(int argc, char** argv) {
    if(argc < 2){
        std::cerr << "Errorn";
        std::cerr << "Provide Input Image:n n";
        return -1;
    }
    cv::Mat original_img = cv::imread(argv[1]);
    if(original_img.empty()){
        std::cerr << "Errorn";
        std::cerr << "Cannot Read Imagen";
        return -1;
    }
    cv::Mat shifted;
    cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
    showImg("Mean Shifted", shifted);
    cv::Mat gray_img;
    cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
    showImg("GrayIMg", gray_img);
    cv::Mat bin_img;
    cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    showImg("thres img", bin_img);
    cv::Mat sure_bg;
    getBackground(bin_img, sure_bg);
    showImg("Sure Background", sure_bg);
    cv::Mat sure_fg;
    getForeground(bin_img, sure_fg);
    showImg("Sure ForeGround", sure_fg);
    cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);
    std::vector<std::vector<cv::Point>> contours;
    findMarker(sure_bg, markers, contours);
    cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈
    
    cv::watershed(original_img, markers);
    cv::Mat mark;
    markers.convertTo(mark, CV_8U);
    cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
    showImg("MARKER", mark);
    // 在图像中突出显示标记 /
    std::vector<cv::Vec3b> colors;
    getRandomColor(colors, contours.size()); // 创建结果图像
    cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
    // 用随机颜色填充标记的对象
    for (int i = 0; i < markers.rows; i++)
    {
        for (int j = 0; j < markers.cols; j++)
        {
            int index = markers.at(i,j);
            if (index > 0 && index <= static_cast<int>(contours.size()))
                dst.at<cv::Vec3b>(i,j) = colors[index-1];
        }
    }
    showImg("Final Result", dst);
    cv::waitKey(0);
    return 0;
}

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

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

相关文章

Pytorch 的神经网络 学习笔记

参照官方网址 Module — PyTorch 2.2 documentation 一. 介绍 1. torch.nn模块&#xff1a; torch.nn是PyTorch中专门用于构建神经网络的模块。它提供了构建深度学习模型所需的所有构建块&#xff0c;包括各种层类型&#xff08;如全连接层、卷积层、循环层等&#xff09;、…

Web3解密:理解去中心化应用的核心原理

引言 在当前数字化时代&#xff0c;去中心化技术和应用正在逐渐引起人们的关注和兴趣。Web3技术作为去中心化应用&#xff08;DApps&#xff09;的基础&#xff0c;为我们提供了一个全新的互联网体验。但是&#xff0c;对于许多人来说&#xff0c;这个复杂的概念仍然充满了神秘…

【源码】WBF多语言交易所/申购+自发币平台币+币币+杠杆+合约/附带安装教程/带VUE工程源码

【源码介绍】 WBF多语言交易所/申购自发币平台币币币杠杆合约/附带安装教程/带VUE工程源码 【源码说明】 带VUE工程源码最新申购&#xff0c;自发币平台币&#xff0c;币币&#xff0c;法币&#xff0c;杠杆&#xff0c;合约多语言交易所&#xff0c;附带pc和手机VUE&#x…

Grafana系列 | Grafana监控TDengine库数据 |Grafana自定义Dashboard

开始前可以去grafana官网看看dashboard文档 https://grafana.com/docs/grafana/latest/dashboards 本文主要是监控TDengine库数据 目录 一、TDengine介绍二、Grafana监控TDengine数据三、Grafana自定义Dashboard 监控TDengine库数据1、grafana 变量2、添加变量3、配置panel 一…

CasinoRoyale靶机练习实践报告

CasinoRoyale靶机练习实践报告 下载地址: https://drive.google.com/open?id1FYP246L63zShV00wOckAQ5F5XJ4HkZ0Lhttps://download.vulnhub.com/casinoroyale/CasinoRoyale.ovahttps://download.vulnhub.com/casinoroyale/CasinoRoyale.ova.torrent ( Magnet) 1 安装靶机 …

乘数而上,创邻科技入选2024数商典型应用场景“乘数榜”

4月18日&#xff0c;由浙江省科学技术协会指导的2024未来数商大会在杭州成功举办。本次大会以“场景突破 乘数而上”为主题&#xff0c;国际国内数商共聚未来科技城学术交流中心&#xff0c;聚焦数据要素市场的制度创新、数据治理、场景应用与生态构建等话题展开研讨。 大会现…

【算法刷题 | 贪心算法04】4.26(跳跃游戏、跳跃游戏||)

文章目录 6.跳跃游戏6.1题目6.2解法&#xff1a;贪心6.2.1贪心思路6.2.2代码实现 7.跳跃游戏||7.1题目7.2解法&#xff1a;贪心7.2.1贪心思路7.2.2代码实现 6.跳跃游戏 6.1题目 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你…

数据分析:宏基因组分析-从Raw data到profile的简单流程

简介 该宏基因组流程主要有四步&#xff0c;分别是1.检查raw data&#xff1b;2.获得高质量reads&#xff1b;3.合并PE数据&#xff1b;4. reads map到参考数据库得到profile。 初步想法&#xff1a; 先分别撰写每一步的基础脚本&#xff0c;如过滤&#xff0c;mapping等过程…

HarmonyOS实战开发-如何在鸿蒙开发中使用数据库

鸿蒙中的数据库基于SQLite组件&#xff0c;用来处理关系比较复杂的数据&#xff0c;本文将以WORKER表为例&#xff0c;为大家演示在鸿蒙开发中对数据库的增删改查操作。 1、首先导入数据库模块&#xff1a; import relationalStore from ohos.data.relationalStore;2、配置数…

Linux平台Unity下RTMP|RTSP低延迟播放器技术实现

技术背景 国产操作系统对于确保信息安全、促进技术创新、满足特定需求以及推动经济发展等方面都具有重要意义&#xff0c;多以Linux为基础二次开发。2014年4月8日起&#xff0c;美国微软公司停止了对Windows XP SP3操作系统提供支持&#xff0c;这引起了社会和广大用户的广泛关…

从零入门区块链和比特币(第二期)

欢迎来到我的区块链与比特币入门指南&#xff01;如果你对区块链和比特币感兴趣&#xff0c;但不知道从何开始&#xff0c;那么你来对地方了。本博客将为你提供一个简明扼要的介绍&#xff0c;帮助你了解这个领域的基础知识&#xff0c;并引导你进一步探索这个激动人心的领域。…

四:物联网ARM开发

一&#xff1a;ARM体系结构概述 1&#xff1a;控制外设led灯还有一些按键这些就要用到gpio&#xff0c;采集传感器的数据需要adc进行转化数据格式&#xff0c;特殊的外设和传感器是通过特殊的协议接口去进行连接的比如一些轴传感器和主控器的连接是通过spi&#xff0c;IIC 控制…

Python+Django网站指纹信息侦测探查

程序示例精选 PythonDjango网站指纹信息侦测探查 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango网站指纹信息侦测探查》编写代码&#xff0c;代码整洁&#xff0c;规则&#…

Docker与Linux容器:“探索容器化技术的奥秘”

目录 一、Docker概述 二、容器技术的起源&#xff1a; 三、Linux容器 四、Docker的出现 五、Docker容器特点&#xff1a; 六、Docker三大概念&#xff1a; 容器&#xff1a; 镜像&#xff1a; 仓库&#xff1a; 七、Docker容器常用命令 一、Docker概述 在云原生时代&…

玩机进阶教程------高通刷机 纯adb脚本操作刷写分区 备份分区的一些简单操作步骤解析

目前来说大多数刷机平台都是使用官方提供的工具。但一般这类工具刷写校验较多。例如小米刷机平台miflash和高通qpst平台。都对于电脑系统刷写环境有一定的要求。而且平台刷写校验md5等等。虽然可以通过修改脚本去除类似校验。但还是有必要了解一些纯adb脚本来刷写9008固件的方法…

智能变频三模正弦波控制器

智能变频三模正弦波控制器 前言一、图片介绍总结 前言 不敢动&#xff0c;完全不敢动。多做笔记&#xff0c;完全了解之后再说吧 一、图片介绍 轮毂电机 主角登场 淘宝关于这款控制器的介绍 当然不同的型号功能不同 学习线插上就会转,可以使用继电器控制通断。 电门…

[论文阅读] 3D感知相关论文简单摘要

Adaptive Fusion of Single-View and Multi-View Depth for Autonomous Driving 提出了一个单、多视图融合深度估计系统&#xff0c;它自适应地集成了高置信度的单视图和多视图结果 动态选择两个分支之间的高置信度区域执行融合 提出了一个双分支网络&#xff0c;即一个以单…

本地生活服务平台哪家强,怎么申请成为服务商?

当下&#xff0c;本地生活服务已经成为了多家互联网大厂布局的重要板块&#xff0c;在巨大的市场需求和强大的资本加持下&#xff0c;不少人都看到了本地生活服务平台广阔的前景和收益空间。在此背景下&#xff0c;许多普通人都跃跃欲试&#xff0c;想要成为本地生活服务商&…

Spark AQE 导致的 Driver OOM问题

背景 最近在做Spark 3.1 升级 Spark 3.5的过程中&#xff0c;遇到了一批SQL在运行的过程中 Driver OOM的情况&#xff0c;排查到是AQE开启导致的问题&#xff0c;再次分析记录一下&#xff0c;顺便了解一下Spark中指标的事件处理情况 结论 SQLAppStatusListener 类在内存中存…

怎么用PHP语言实现远程控制电器

怎么用PHP语言实现远程控制电器呢&#xff1f; 本文描述了使用PHP语言调用HTTP接口&#xff0c;实现控制电器&#xff0c;通过控制电器的电源线路来实现电器控制。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能WiFi通断器AC3统…