我的编程空间,编程开发者的网络收藏夹
学习永远不晚

OpenCV实战记录之基于分水岭算法的图像分割

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

OpenCV实战记录之基于分水岭算法的图像分割

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实战记录之基于分水岭算法的图像分割的文章就介绍到这了,更多相关OpenCV分水岭算法的图像分割内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

OpenCV实战记录之基于分水岭算法的图像分割

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

OpenCV实战记录之基于分水岭算法的图像分割

在机器视觉中,有时需要对产品进行检测和计数,其难点无非是对于产品的图像分割,这篇文章主要给大家介绍了关于OpenCV实战记录之基于分水岭算法的图像分割的相关资料,需要的朋友可以参考下
2023-02-22

OpenCV基于分水岭算法的图像分割怎么实现

本文小编为大家详细介绍“OpenCV基于分水岭算法的图像分割怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“OpenCV基于分水岭算法的图像分割怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1.
2023-07-05

C++中怎么实现OpenCV图像分割与分水岭算法

小编给大家分享一下C++中怎么实现OpenCV图像分割与分水岭算法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!分水岭算法是一种图像区域分割法,在分割的过程中,它
2023-06-15

Python基于均值漂移算法和分水岭算法实现图像分割

图像分割是将图像分成若干具有独特性质的区域并提取感兴趣目标的技术和过程。这篇文章将详细讲解基于均值漂移算法和分水岭算法的图像分割,需要的可以参考一下
2023-01-11

OpenCV-Python怎么使用分水岭算法实现图像分割与提取功能

小编给大家分享一下OpenCV-Python怎么使用分水岭算法实现图像分割与提取功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!随着当今世界的发展,计算机视觉技
2023-06-15

Otsu阈值算法实战——基于Python实现图像背景分割

本文将通过一个具体的实例来展示Otsu阈值算法在图像背景分割中的应用。

Python基于纹理背景和聚类算法实现图像分割详解

这篇文章将详细讲解Python图和基于纹理背景的图像分割和聚类算法实现图像分割效果,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
2023-01-03

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录