• OpenCV开发笔记(五十九):红胖子8分钟带你深入了解分水岭算法(图文并茂+浅显易懂+程序源码)


     

    前言

      红胖子,来也!
      做识别,有时候需求要识别物体,物体在背景上比较杂,但是其边缘与背景图相差大,这个时候可以使用分水岭算法突出两边的颜色对比度,从而更好的分割。

     

    Demo

      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      

     

    分水岭算法

    概述

      分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。简单来说就是根据图像相邻的像素差值,分成不同区域,将各区域染成不同颜色,其适合使用者已经可以标记已知对象或背景中的一部分。
     &emp;分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
      分水岭的计算过程是一个迭代标注过程。

    原理

      分水岭比较经典的计算方法是L. Vincent提出的。在该算法中,分水岭计算分两个步骤,一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高排序,然后在从低到高实现淹没过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。
      分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点,即为分水岭。显然,分水岭表示的是输入图像极大值点。因此,为得到图像的边缘信息,通常把梯度图像作为输入图像,即
    g(x,y)=grad(f(x,y))={[f(x,y)-f(x-1,y)]2[f(x,y)-f(x,y-1)]2}0.5
    式中,f(x,y)表示原始图像,grad{.}表示梯度运算。
      分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证的。

    分水岭函数原型

    void watershed(InputArray image, InputOutputArray markers )
    
    • 参数一:lnputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为8位三通道的彩色图像。
    • 参数二:InputOutputArray类型的markers,在执行分水岭函数watershed之前,必须对该参数进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。
     

    Demo涉及到的相关知识

     

    Demo源码

    void OpenCVManager::testWatersheed()
    {
        QString fileName1 =
                "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/5.jpg";
        int width = 400;
        int height = 300;
    
        cv::Mat srcMat = cv::imread(fileName1.toStdString());
        cv::resize(srcMat, srcMat, cv::Size(width, height));
    
        cv::String windowName = _windowTitle.toStdString();
        cvui::init(windowName);
    
        cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
                                    srcMat.type());
    
        int threshold1 = 200;
        int threshold2 = 100;
        while(true)
        {
            windowMat = cv::Scalar(0, 0, 0);
    
            cv::Mat mat;
    
            cv::Mat tempMat;
            // 原图先copy到左边
            mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));
            cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);
    
            {
                // 灰度图
                cv::Mat grayMat;
                cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
                // copy
                mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
                                cv::Range(srcMat.cols * 1, srcMat.cols * 2));
                cv::Mat grayMat2;
                cv::cvtColor(grayMat, grayMat2, cv::COLOR_GRAY2BGR);
                cv::addWeighted(mat, 0.0f, grayMat2, 1.0f, 0.0f, mat);
    
                // 均值滤波
                cv::blur(grayMat, tempMat, cv::Size(3, 3));
    
                cvui::printf(windowMat, width * 1 + 20, height * 1 + 20, "threshold1");
                cvui::trackbar(windowMat, width * 1 + 20, height * 1 + 40, 200, &threshold1, 0, 255);
                cvui::printf(windowMat, width * 1 + 20, height * 1 + 100, "threshold2");
                cvui::trackbar(windowMat, width * 1 + 20, height * 1 + 120, 200, &threshold2, 0, 255);
    
                // canny边缘检测
                cv::Canny(tempMat, tempMat, threshold1, threshold2);
                // copy
                mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::cvtColor(tempMat, grayMat2, cv::COLOR_GRAY2BGR);
                cv::addWeighted(mat, 0.0f, grayMat2, 1.0f, 0.0f, mat);
    
                // 查找轮廓
                std::vector<std::vector<cv::Point>> contours;
                std::vector<cv::Vec4i> hierarchy;
                cv::findContours(tempMat, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
    
                // 绘制轮廓
                cv::Mat maskers = cv::Mat::zeros(grayMat.size(), CV_32SC1);
                maskers = cv::Scalar::all(0);
                cv::Mat tMat = srcMat.clone();
                tMat = cv::Scalar(0, 0, 0);
                for(int index = 0; index < contours.size(); index++)
                {
                    cv::drawContours(maskers, contours, index, cv::Scalar::all(index+1));
                    cv::drawContours(tMat, contours, index, cv::Scalar(0, 0, 255));
                }
                // copy
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, tMat, 1.0f, 0.0f, mat);
    
                // 分水岭
                cv::watershed(srcMat, maskers);
                cv::Mat watershedImage(maskers.size(), CV_8UC3) ;
                for(int i = 0 ; i < maskers.rows ; i++ )
                {
                    for(int j = 0 ; j < maskers.cols; j++)
                    {
                        int index = maskers.at<int>(i, j);
                        if(index == -1)
                        {
                            watershedImage.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 255, 255);
                        }else if( index <= 0 || index > contours.size() )
                        {
                            watershedImage.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 0, 0);
                        }else
                        {
                            watershedImage.at<cv::Vec3b>(i, j) = cv::Vec3b((index - 5 > 0 ? 0 : index % 5) * 50,
                                                                           (index - 5 > 0 ? index - 5 : 0) % 5 * 50,
                                                                           (index - 10 > 0 ? index - 10 : 0) % 5 * 50);
                        }
                        // 混合灰皮图和 分水岭效果 图 并显 示最终的窗 口
                    }
                }
                // copy
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 1, srcMat.cols * 2));
                cv::addWeighted(mat, 0.0f, watershedImage, 1.0f, 0.0f, mat);
    
            }
            // 更新
            cvui::update();
            // 显示
            cv::imshow(windowName, windowMat);
            // esc键退出
            if(cv::waitKey(25) == 27)
            {
                break;
            }
        }
    }
    
     

    工程模板:对应版本号v1.53.0

      对应版本号v1.53.0

     
     
  • 相关阅读:
    转:Swagger2自动生成接口文档和Mock模拟数据
    InfluxDB
    springboot application.properties文件加载顺序
    maven surefire插件与testng
    spring-boot项目学习路径
    collection 与stream与lambd表达式的结合使用
    转:Java中Lambda表达式的使用
    RPC之Thrift 介绍及java实例
    win10中shift+右键,在此处打开cmd窗口
    激活xmind的方法
  • 原文地址:https://www.cnblogs.com/qq21497936/p/12953036.html
Copyright © 2020-2023  润新知