OpenCV 学习笔记(Watershed)

OpenCV 学习笔记(Watershed)

Watershed,中文一般翻译为分水岭算法。分水岭算法是一种图像区域分割算法,它把位置接近,灰度值也接近的像素点连接起来形成一个封闭的区域。关于分水岭算法的具体原理可以参考下面的网址:

wiki 百科 Watershed

这里只介绍在 OpenCV中的如何使用 Watershed 算法。Opencv 中 watershed函数原型如下:

void watershed( InputArray image, InputOutputArray markers );

image 是 3 通道彩色(CV_8UC3)图像,markers 是单通道(CV_32S) 图像。为什么 markers 要求是CV_32S 呢,我觉得应该是图像的区域数可能会很大,CV_8U 不够用,索性就搞个 CV_32S,这个类型是绝对够用的。

这里重点说说markers 的作用,传统的 watershed 算法是不需要markers 的。但是经常会把图像分割成太多的小区域。markers 的作用就是我们预先把一些区域标注好,这些标注了的区域称之为种子点。watershed 算法会把这些标记的区域慢慢膨胀填充整个图像。

如何预先的标记这些区域呢?这就是个关键的问题了。我们可以交互式的人为的标记。也可以通过一些其他的算法来确定一部分区域。下面的例子就会讲解如何用 connectedComponents 函数来自动的标记区域。

首先我们来看看我们要标注的图像是什么样的:
在这里插入图片描述这幅图像中有很多个小球。我们就是要标记出这些小球来。首先这幅图像中有些噪声,我们先进行滤波操作。这个噪声类似于椒盐噪声,所以用 medianBlur 效果会比较好。之后我们再用 GaussianBlur 进一步平滑图像。之后就可以做二值化了。做完二值化后再把图像腐蚀一下以确保小球之间不会相连。

cv::Mat image = cv::imread("zeiss.jpg");
cv::cvtColor(image(cv::Rect(0, 0, 1024, 700)), image, CV_BGRA2BGR);
cv::Mat imageBlur;
cv::medianBlur(image, imageBlur, 7);
cv::GaussianBlur(imageBlur, imageBlur, cv::Size(5, 5), 0);
cv::Mat grayMat, grayMatBW;
cv::cvtColor(imageBlur, grayMat, CV_BGR2GRAY);
cv::imshow("zeiss",  grayMat);
cv::threshold( grayMat, grayMatBW, 0, 255 ,cv::THRESH_OTSU );
cv::Mat foreground;
cv::erode(grayMatBW, foreground, cv::Mat(), cv::Point(-1, -1), 4);
cv::imshow("foreground",  foreground);

在这里插入图片描述
下面把每个小球都标记成不同的数字。这个可以使用 connectedComponents 函数。标记完小球后还要标记背景区域。方法类似。最后把小球和背景区域叠加到一张图中。

cv::Mat foreground;
cv::erode(grayMatBW, foreground, cv::Mat(), cv::Point(-1, -1), 4);
cv::imshow("foreground",  foreground);
cv::connectedComponents(foreground, foreground, 4, CV_32S);
foreground.convertTo(foreground, CV_8U);

cv::Mat background;
cv::dilate(grayMatBW, background, cv::Mat(), cv::Point(-1, -1), 4);
cv::threshold(background, background, 1, 128, cv::THRESH_BINARY_INV);
bg.convertTo(bg, CV_32S);

cv::Mat marker = background + foreground;
marker.convertTo(marker, CV_32S);

至此,我们就准备好marker 了, 下面调用 watershed 函数。

cv::watershed(image, marker);
cv::Mat segment;
marker.convertTo(segment, CV_8U);
std::vector<cv::Vec3b> colormap = buildRandomColormap(255);
cv::Mat outcolor = imageFromColormap(segment, colormap);
cv::imshow("out", outcolor);

segment 就是我们分隔出的各个区域,取值为 128 的是背景区域。其余的各个小球。为了更好的显示,我们还写了两个辅助函数。

std::vector<cv::Vec3b> buildRandomColormap(int N)
{
cv::RNG rng;
    std::vector<cv::Vec3b> colormap;
    for(int i = 0; i < N; i++)
    {
        colormap.push_back( cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)) );
    }
    return colormap;
}

cv::Mat imageFromColormap(const cv::Mat &grayImage, const std::vector<cv::Vec3b> &colormap)
{
    cv::Mat out(grayImage.size(), CV_8UC3);
    out = cv::Scalar(0, 0, 0);
    int N = colormap.size();
    //cv::Mat_<cv::Vec3b> img(out);
    for(int row = 0; row < grayImage.rows; row ++)
    {
        cv::Vec3b * pDest = out.ptr<cv::Vec3b>(row);
        const uchar *pSrc = grayImage.ptr<const uchar>(row);
        for(int col = 0; col < grayImage.cols; col ++)
        {
            if(pSrc[col] >= N)
            {
                pDest[col] = cv::Vec3b(pSrc[col], pSrc[col], pSrc[col]);
            }
            else
            {
                pDest[col] = colormap[pSrc[col]];
            }
        }
    }
    return out;
}

最终的输出效果如下图。可以看到各个区域都填充了不同的颜色。但是小球的边界找的不是特别的理想。这也是 watershed 算法的缺点吧。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值