图像处理之基于标记的分水岭算法(C++)

文章目录
图像处理之基于标记的分水岭算法(C++)
前言
一、基于标记点的分水岭算法应用
1.实现步骤:
2.代码实现
总结
前言
传统分水岭算法存在过分割的不足,OpenCV提供了一种改进的分水岭算法,使用一系列预定义标记来引导图像分割的定义方式。使用OpenCV的分水岭算法cv::wathershed,需要输入一个标记图像,图像的像素值为32位有符号正数(CV_32S类型),每个非零像素代表一个标签。**它的原理是对图像中部分像素做标记,表明它的所属区域是已知的。分水岭算法可以根据这个初始标签确定其他像素所属的区域。**传统的基于梯度的分水岭算法和改进后基于标记的分水岭算法示意图如下图所示。

从上图可以看出,传统基于梯度的分水岭算法由于局部最小值过多造成分割后的分水岭较多。而基于标记的分水岭算法,水淹过程从预先定义好的标记图像(像素)开始,较好的克服了过度分割的不足。本质上讲,基于标记点的改进算法是利用先验知识来帮助分割的一种方法。因此,改进算法的关键在于如何获得准确的标记图像,即如何将前景物体与背景准确的标记出来。

一、基于标记点的分水岭算法应用
1.实现步骤:
封装分水岭算法
获取标记图像(在标记图中前景设置为255,背景像素设置为128,未知像素设置为0)
将原图和标记图输入分水岭算法
显示结果
2.代码实现
#include <iostream>
#include <opencv.hpp>

class WatershedSegmenter {

private:

    cv::Mat markers;        // 标记图

public:

    /*
    * @param const cv::Mat& markerImage 传入的标记图
    * @brief 将传入的标记图转换成CV_32S的标记图
    */
    void setMarkers(const cv::Mat& markerImage) {

        // Convert to image of ints
        markerImage.convertTo(markers, CV_32S);
    }


    /*
    * @param const cv::Mat& image 原图
    * @brief 对原图和标记图执行基于标记的分水岭分割
    */
    cv::Mat process(const cv::Mat& image) {

        // Apply watershed
        cv::watershed(image, markers);

        return markers;
    }

    // Return result in the form of an image
    /*
    * @brief 得到分割结果的标签
    */
    cv::Mat getSegmentation() {

        cv::Mat tmp;
        // all segment with label higher than 255
        // will be assigned value 255
        markers.convertTo(tmp, CV_8U);

        return tmp;
    }

    // Return watershed in the form of an image以图像的形式返回分水岭
    cv::Mat getWatersheds() {

        cv::Mat tmp;
        //在变换前,把每个像素p转换为255p+255(在conertTo中实现)
        markers.convertTo(tmp, CV_8U, 255, 255);

        return tmp;
    }
};

int main()
{
    // 读取图片
    std::string filepath = "F://work_study//algorithm_demo//watershedSeg.jpg";
    cv::Mat src = cv::imread(filepath);
    if (src.empty())
    {
        return -1;
    }
    imshow("src", src);

    // 高斯滤波平滑图像
    cv::Mat gaussImg;
    // 彩色图转换为灰度图
    cv::cvtColor(src, gaussImg, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gaussImg, gaussImg,cv::Size(7,7),1.0);
    imshow("gaussImg", gaussImg);

    // 形态学梯度
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
    cv::morphologyEx(gaussImg, gaussImg, cv::MORPH_GRADIENT,kernel);
    imshow("morphologyEx", gaussImg);

    // OTSU大津法阈值
    cv::threshold(gaussImg, gaussImg, 0, 255, cv::THRESH_OTSU | cv::THRESH_BINARY);
    imshow("threshold", gaussImg);

    // 形态学操作,生成确定的背景区域
    cv::Mat kernel_bg = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
     开运算(先腐蚀后膨胀)消除噪声点
    //cv::morphologyEx(gaussImg, gaussImg, cv::MORPH_OPEN, kernel1,cv::Point(-1,-1),2);
    // 生成确定的背景区域
    cv::Mat backgroundImg;
    cv::dilate(gaussImg, backgroundImg, kernel_bg);
    imshow("backgroundImg", backgroundImg);


    // 距离变换,生成确定的前景区域
    cv::Mat distanceImg,foregroundImg;
    cv::distanceTransform(gaussImg, distanceImg, cv::DIST_L1,5);
    double min, max;
    cv::minMaxLoc(distanceImg, &min, &max);
    cv::threshold(distanceImg, distanceImg, 0.1 * max, 255, cv::THRESH_BINARY);
    cv::normalize(distanceImg, foregroundImg, 0, 255, cv::NORM_MINMAX, CV_8U);
    imshow("foregroundImg", foregroundImg);

    // 去除连通域中的背景部分
    cv::Mat unknownImg;
    cv::subtract(backgroundImg, foregroundImg, unknownImg);    //待定区域,减去前景与背景的重合区域
    imshow("unknownImg", unknownImg);

    cv::Mat makers(gaussImg.size(),CV_8U,cv::Scalar(128));
    for (int i = 0; i < makers.rows; i++)
    {
        for (int j = 0; j < makers.cols; j++)
        {
            if (foregroundImg.at<uchar>(i, j) == 255)
            {
                makers.at<uchar>(i, j) = 255;
            }
            else if (unknownImg.at<uchar>(i, j) == 255)
            {
                makers.at<uchar>(i, j) = 0;
            }
        }
    }
    imshow("makers", makers);

    // 分水岭算法分割图像
    WatershedSegmenter seg;                    // 实例化一个分水岭分割对象
    seg.setMarkers(makers);                    // 设置算法的标记图像,使得水淹过程从这组预定义好的标记像素开始
    seg.process(src);                        // 传入待分割的原图(要求为CV_8UC3)
    cv::Mat resultImg;
    resultImg = seg.getSegmentation();        // 将修改后的标记图makers转换为可显示的8位灰度图并返回分割结果(白色为前景,灰色为背景,0为边缘)

    cv::threshold(resultImg, resultImg, 250, 1, cv::THRESH_BINARY);
    cv::cvtColor(resultImg, resultImg, cv::COLOR_GRAY2BGR);
    resultImg = src.mul(resultImg);
    cv::cvtColor(resultImg, resultImg, cv::COLOR_BGR2GRAY);
    std::cout << resultImg.type() << std::endl;
    for(int i=0;i<resultImg.rows;i++)
        for (int j = 0; j < resultImg.cols; j++)
        {
            if (resultImg.at<uchar>(i, j) == 0)
            {
                resultImg.at<uchar>(i, j) = 255;
            }
        }
    imshow("resultImg", resultImg);
    cv::waitKey(0);
    return 0;

}

总结
本文主要介绍了一种基于标记的分水岭算法的应用,关键在于如何巧妙地设计“标记图”,欢迎讨论交流。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_45045175/article/details/139236721

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值