Opencv学习之分水岭算法

Opencv学习之分水岭算法
分水岭算法可以将图像中的边缘转化成“山脉”,将均匀区域转化为“山谷”,这样有助于分割目标。
分水岭算法是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中的每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明:在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响区域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
分水岭的计算过程是一个迭代标注过程。分水岭计算分成两个步骤:一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高的排序,然后在从低到高实现淹没的过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点即为分水岭。显然,分水岭表示的是输入图像的极大值点。
简而言之,分水岭算法首先计算灰度图的梯度,这对图像中的“山谷”或没有纹理的“盆地”(亮度值低的点)的形成是很有效的,也对“山头”或图像中有主导线段的“山脉”(山脊对应的边缘)的形成有效。然后开始从用户指定点(或者算法得到点)开始持续“灌注”盆地直到这些区域连成一片。基于这样产生的标记就可以把区域合并到0一起,合并后的区域又通过聚集的方式进行分割,好像图像被“填充”起来一样。

实现分水岭算法–watershed函数

函数watershed实现的分水岭算法是基于标记的分割算法中的一种。在把图像传给函数之前,需要大致勾画标记出图像中的期望进行分割的区域,它们被标记为正指数,所以,每一个区域都会被标记为像素值1、2、3等,表示成为一个或者多个连接组件,这些标记的值可以使用findContours函数和drawContours函数由二进制的掩码检索出来。这些标记就是即将绘制出来的分割区域的“种子”,而没有标记清楚的区域,被置为0,在函数的输出中,每一个标记中的像素被设置为“种子”的值,而区域间的值被设置为-1。
void watershed(inputArray,intputOutputArray markers)
*第一个参数,输入图像,需为8位三通道的彩色图像。
*第二个参数,函数调用后的运算结果存在这里,输入/输入32位单通道图像的标记结果。

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

using namespace cv;
using namespace std;
//宏定义
#define WINDOW_NAME "image[procedure window]"

//全局变量声明
Mat g_srcImage,g_maskImage;
Point prevPt(-1,-1);

//全局函数声明
static void on_Mouse(int event,int x,int y,int flags,void*);

//主函数
int main()
{
    //载入源图像
    g_srcImage=imread("/Users/new/Desktop/1.jpg");
    if(!g_srcImage.data){printf("读取源图像srcImage错误~!\n");return false;}

    //显示源图像
    imshow(WINDOW_NAME,g_srcImage);
    Mat srcImage,grayImage;
    g_srcImage.copyTo(srcImage);
    //灰度化
    cvtColor(srcImage, g_maskImage, COLOR_BGR2GRAY);
    //imshow("image[mask]",g_maskImage);
    cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);
    //imshow("image[gray]",grayImage);
    //掩膜图像初始化为0
    g_maskImage=Scalar::all(0);

    //设置鼠标回调函数
    setMouseCallback(WINDOW_NAME, on_Mouse,0);

    //轮询按键处理
    while(1)
    {
        //获取键值
        int c=waitKey(0);
        //若按键为ESC时,退出
        if((char)c == 27)
            break;
        //若按键为2时,恢复原图
        if((char)c=='2')
        {
            g_maskImage=Scalar::all(0);
            srcImage.copyTo(g_srcImage);
            imshow("image",g_srcImage);
        }
        //若按键为1,则进行处理
        if((char)c=='1')
        {
            //定义一些参数
            int i,j,compCount=0;
            vector<vector<Point>>contours;
            vector<Vec4i> hierarchy;
            //寻找轮廓
            findContours(g_maskImage, contours, hierarchy, CV_RETR_CCOMP, CHAIN_APPROX_SIMPLE);
            //轮廓为空时的处理
            if(contours.empty())
                continue;
            //复制掩膜
            Mat maskImage(g_maskImage.size(),CV_32S);
            maskImage=Scalar::all(0);

            //循环绘制轮廓
            for(int index=0;index>=0;index=hierarchy[index][0],++compCount)
                drawContours(maskImage, contours, index, Scalar::all(compCount+1),-1,8,hierarchy,INT_MAX);
                //compCount为零时的处理
                if(compCount==0)
                    continue;

                //生成随机颜色
                vector<Vec3b>colorTab;
                for(int i=0;i<compCount;++i)
                {
                    int b=theRNG().uniform(0, 255);
                    int g=theRNG().uniform(0, 255);
                    int r=theRNG().uniform(0, 255);

                    colorTab.push_back(Vec3b((uchar)b,(uchar)g,(uchar)r));
                }
                //计算处理时间并输出到窗口中
                double dTime=(double)getTickCount();
                //进行分水岭算法
                watershed(srcImage, maskImage);
                dTime=(double)getTickCount()-dTime;
                printf("\t 处理时间=%gms\n",dTime*1000./getTickFrequency());
                //双层循环,将分水岭图像遍历存入watershedImage中
                Mat watershedImage(maskImage.size(),CV_8UC3);
                for(i=0;i<maskImage.rows;++i)
                    for(j=0;j<maskImage.cols;++j)
                    {
                        int index=maskImage.at<int>(i,j);
                        if(index==-1)
                            watershedImage.at<Vec3b>(i,j)=Vec3b(255,255,255);//图像变白色
                        else if(index<=0||index>compCount)
                            watershedImage.at<Vec3b>(i,j)=Vec3b(0,0,0);//图像变黑色
                        else
                            watershedImage.at<Vec3b>(i,j)=colorTab[index-1];
                    }
                //混合灰度图和分水岭效果图并显示最终的窗口
                watershedImage=watershedImage*0.5+grayImage*0.5;
                imshow("image[watershed]",watershedImage);
        }
    }
        return 0;
}

//回调函数定义
void on_Mouse(int event,int x,int y,int flags,void*)
{
    //处理鼠标不在窗口中的情况
    if(x<0||x>=g_srcImage.cols||y<0||y>=g_srcImage.rows)
        return;

    //处理鼠标左键相关消息
    if(event==EVENT_LBUTTONUP||!(flags & EVENT_FLAG_LBUTTON))//按下左键
        prevPt=Point(-1,-1);
    else if(event==EVENT_LBUTTONDOWN)//松开左键
        prevPt=Point(x,y);//鼠标所指的位置

    //鼠标左键按下并移动,绘制出白色线条
    else if(event==EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
    {
        Point pt(x,y);
        if(prevPt.x<0)//如果指出去了,返回
            prevPt=pt;
        line(g_maskImage, prevPt, pt, Scalar::all(255),2,8,0);//画白线
        line(g_srcImage,prevPt,pt,Scalar::all(255),2,8,0);//画白线
        prevPt=pt;
        imshow(WINDOW_NAME, g_srcImage);

    }
}

这里写图片描述
这里写图片描述
这里写图片描述

Opencv技巧

(1)计算算法运行时间:

//计算处理时间并输出到窗口中
                double dTime=(double)getTickCount();
                //进行分水岭算法
                watershed(srcImage, maskImage);
                dTime=(double)getTickCount()-dTime;
                printf("\t 处理时间=%gms\n",dTime*1000./getTickFrequency());

(2)改变图像某点像素值:Mat类中的at方法对于获取图像矩阵某点的RGB值或者改变某点的值很方便,

     对于单通道的图像:image.at<uchar>(i, j)
     对于RGB通道的图像:image.at<Vec3b>(i, j)[0]  
                     image.at<Vec3b>(i, j)[1]  
                     image.at<Vec3b>(i, j)[2]

(3)Point(-1,-1)解析:由于卷积过程,图像矩阵要进行填充,Point(-1,-1)即代表卷积开始的位置,这决定了不填充时的结果A处于填充后结果B的位置的那个部分,从(-1,-1)开始卷积的结果是A处于B的正中间那块位置。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
分水岭算法是一种在图像处理中常用的图像分割算法,它基于图像中的灰度和梯度信息,将图像分割成不同的区域。该算法的目标是将图像中的前景和背景分开,同时保留物体的边缘和细节。 在OpenCV中,可以使用函数cv2.watershed()实现分水岭算法。该函数接受一个灰度图像和标记图像作为输入,并将分割结果存储在标记图像中。标记图像中的像素值表示不同的区域,-1表示边界。 分水岭算法的处理流程如下: 1. 将原始图像转换为灰度图像。 2. 对灰度图像进行预处理,例如平滑和二值化,以便更好地识别前景和背景。 3. 创建一个空的标记图像,初始化为0。 4. 根据预处理后的图像,确定前景和背景的种子点。 5. 使用cv2.connectedComponents()函数将种子点标记为不同的区域,并将其存储在标记图像中。 6. 调用cv2.watershed()函数进行分水岭算法处理,将分割结果存储在标记图像中。 7. 可选步骤:根据需要进一步处理分割结果,例如绘制边界或提取特定区域的像素值。 请注意,分水岭算法需要一些预处理步骤和参数调整,以确保得到满意的分割结果。因此,在实际使用时,可能需要根据具体情况进行调整和优化。 参考资料: opencv-python——通过cv2.distanceTransform()函数将距离转换成热力图 OpenCV学习三十五:distanceTransform 距离变换函数 OPENCV自学记录(6)——连通域处理函数CV2.CONNECTEDCOMPONENTSWITHSTATS()和CV2.CONNECTEDCOMPONENTS() OpenCV3学习(7.2)——图像分割之二(分水岭算法watershed) opencv进阶学习笔记14:分水岭算法 实现图像分割图像分割之分水岭算法python opencv入门 分水岭算法(29)<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [OpenCV-分水岭算法](https://blog.csdn.net/qq_43951094/article/details/120801731)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【OpenCV】- 分水岭算法](https://blog.csdn.net/qq_44859533/article/details/126436001)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值