精华内容
下载资源
问答
  • opencv watershed demo: http://download.csdn.net/detail/keen_zuxwang/9852585 分水岭算法 1、一种基于拓扑理论的数学形态学的图像分割方法。分水岭算法容易导致图像的过度分割。opencv中,使用预定义的一组...

    opencv watershed

    demo: http://download.csdn.net/detail/keen_zuxwang/9852585

    分水岭算法
    1、一种基于拓扑理论的数学形态学的图像分割方法。分水岭算法容易导致图像的过度分割。opencv中,使用预定义的一组标记来引导对图像的分割,防止过度切割。
    2、一种图像区域分割法,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征。

    //! segments the image using watershed algorithm
    CV_EXPORTS_W void watershed( InputArray image, InputOutputArray markers );

    功能:标识出不同区域间的值被置为-1(边界), 没有标记清楚的区域被置为0,其他每个区域的值保持不变
    image
    输入图像,须为CV_8UC3类型
    markers
    声明为CV_32S类型(32位单通道),既作为输入,也作为输出保存函数调用的结果。

    watershed函数
    1、watershed第二个参数markers必须包含了种子点信息,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。
    2、分水岭算法会根据markers传入的轮廓作为种子(注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。

    JNI:

    JNIEXPORT jlong JNICALL Java_com_example_grabcut_MainActivity_doWatershed(JNIEnv *env, jclass clz, jlong imageGray)
    {
          Mat img0 = Mat(*(Mat*)imageGray);
        Mat img(img0.size(),CV_8UC3); //输入图像须为CV_8UC3类型
          cvtColor(img0, img, CV_BGRA2BGR);
    
        Mat gray;
        cvtColor(img, gray, CV_BGR2GRAY);//1、转换为灰度图
    
        equalizeHist(gray, gray);    //2、直方图均衡化  CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
        blur(gray, gray, Size(5,5)); //3、均值滤波
    
        Mat binary;
        adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 5, 10);//4、将灰度图转换为二值图
        Mat markers(gray.size(), CV_32S, Scalar::all(0));
    
        vector< vector<Point> > contours;
        vector<Vec4i> hierarchy;
        findContours(binary, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); //5、findContour()函数找出图像的轮廓
    
        int idx = 0;
        int compIdx = 0;
        for( ; idx >= 0; idx = hierarchy[idx][0],compIdx++){
            //Scalar color(rand()&255, rand()&255, rand()&255);
            //drawContours(markers, contours, idx, color, CV_FILLED, 8, hierarchy); 
            drawContours(markers, contours, idx, Scalar::all(compIdx+1), CV_FILLED, 8, hierarchy);//6、markers上绘制contours
        }
    
        Mat marks0;
        convertScaleAbs(markers,marks0);
    
        //调用分水岭算法分割图像
        watershed(img, markers); //7、img须为CV_8UC3类型,markers须为CV_32S类型
    
        Mat marks1;
        convertScaleAbs(markers,marks1);
    
        //Mat marker;
        //markers.convertTo(marker, CV_8U);
        //marks1.convertTo(marker,CV_8U);//CV_8UC3 输出测试使用
    
        Mat fillMat = Mat::zeros(gray.size(),CV_8UC3); // 8、填充绘制
        for(int i=0;i<markers.rows;i++)  {
            for(int j=0;j<markers.cols;j++)  {
                int index=markers.at<int>(i,j);
                if(markers.at<int>(i,j)==-1)  {
                    fillMat.at<Vec3b>(i,j)= Vec3b(255,255,255);
                }else  {
                    fillMat.at<Vec3b>(i,j)= RandomColor(index);
                }
            }
        }
    
        //Mat mix;
        //addWeighted(img,0.5,fillMat,0.5,0,mix); //输出测试使用
    
        Mat *hist = new Mat(fillMat); // 9、回传Java端显示
        return (jlong) hist;
    }

    这里写图片描述

    展开全文
  • 分水岭算法 参考博客: (1)迈克老狼2012https://www.cnblogs.com/mikewolf2002/p/3304118.html (2)-牧野-http://blog.csdn.net/dcrmg/article/details/52498440 分水岭算法是一种图像区域分割法,在分割的...

    分水岭算法 

    参考博客:

    (1)迈克老狼2012   https://www.cnblogs.com/mikewolf2002/p/3304118.html

    (2)-牧野-              http://blog.csdn.net/dcrmg/article/details/52498440


    分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近(求梯度)的像素点互相连接起来构成一个封闭的轮廓。分水岭算法常用的操作步骤:彩色图像灰度化,然后再求梯度图,最后在梯度图的基础上进行分水岭算法,求得分段图像的边缘线。
    下面左边的灰度图,可以描述为右边的地形图,地形的高度是由灰度图的灰度值决定,灰度为0对应地形图的地面,灰度值最大的像素对应地形图的最高点。

    我们可以自己编程实现灰度图的地形图显示

    对灰度图的地形学解释,我们我们考虑三类点:

    1. 局部最小值点,该点对应一个盆地的最低点,当我们在盆地里滴一滴水的时候,由于重力作用,水最终会汇聚到该点。注意:可能存在一个最小值面,该平面内的都是最小值点。

    2. 盆地的其它位置点,该位置滴的水滴会汇聚到局部最小点。

    3. 盆地的边缘点,是该盆地和其它盆地交接点,在该点滴一滴水,会等概率的流向任何一个盆地。

    假设我们在盆地的最小值点,打一个洞,然后往盆地里面注水,并阻止两个盆地的水汇集,我们会在两个盆地的水汇集的时刻,在交接的边缘线上(也即分水岭线),建一个坝,来阻止两个盆地的水汇集成一片水域。这样图像就被分成2个像素集,一个是注水盆地像素集,一个是分水岭线像素集。

          下面的gif图很好的演示了分水岭算法的效果:

    在真实图像中,由于噪声点或者其它干扰因素的存在,使用分水岭算法常常存在过度分割的现象,这是因为很多很小的局部极值点的存在,比如下面的图像,这样的分割效果是毫无用处的。

      为了解决过度分割的问题,可以使用基于标记(mark)图像的分水岭算法,就是通过先验知识,来指导分水岭算法,以便获得更好的图像分段效果。通常的mark图像,都是在某个区域定义了一些灰度层级,在这个区域的洪水淹没过程中,水平面都是从定义的高度开始的,这样可以避免一些很小的噪声极值区域的分割。


    下面的gif图很好的演示了基于mark的分水岭算法过程:

    上面的过度分段图像,我们通过指定mark区域,可以得到很好的分段效果:

    Opencv 中 watershed函数原型:

    watershed()
    void cv::watershed	(	InputArray 	image,
    InputOutputArray 	markers 
    )		
    Python:
    markers	=	cv.watershed(	image, markers	)
    

    #include <opencv2/imgproc.hpp>

    使用分水岭算法进行基于标记的图像分割。

    该函数实现了分水岭算法的一个变种,基于非参数标记的分割算法,

    在将图像传递给函数之前,必须使用正(>)索引粗略地在图像标记中勾画出所需的区域。因此,每个区域都被表示为一个或多个具有像素值1、2、3等的连接组件。这样的标记可以使用findContours 和 drawContours从二进制掩码中检索(参见watershed.cpp演示)。这些标记是未来图像区域的“种子”。标记中的所有其他像素,其与轮廓区域的关系未知,应由算法定义,应设置为0。在函数输出中,标记中的每个像素被设置为“种子”组件的值,或者在区域之间的边界被设置为-1。

    简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。
     

    Note:任意两个相邻连接的分量不一定被分水岭边界(-1的像素)隔开;例如,它们可以在传递给函数的初始标记图像中相互接触。

    Parameters

    image 输入8位3通道图像。
    markers 输入/输出标记的32位单通道图像(map)。它应该有相同的大小作为图像。

    现在我们看看OpenCV中如何使用分水岭算法。

    首先我们打开一副图像: 

     // 打开另一幅图像
      cv::Mat    image= cv::imread("../tower.jpg");
        if (!image.data)
            {
            cout<<"不能打开图像!"<<endl;
            return 0;
            }

    接下来,我们要创建mark图像。mark图像格式是有符号整数,其中没有被mark的部分用0表示,其它不同区域的mark标记,我们用非零值表示,通常为1-255,但也可以为其它值,比如大于255的值,不同mark区域甚至可以用同样的值,这个值大小对最后分割可能没有影响(也可能影响),最好不同mark区域还是用不同的值表示,这样能够确保结果正确,之所以用有符号整数,是因为opencv在分水岭算法内部,要用-1,-2等来标记注水区域,最终在mark图像中生成的分水岭线就是用-1表示

     

        我们通常会创建uchar格式的灰度图,指定mark区域,然后转化为有符号整数的图像格式。

        首先对整个背景区域我们创建一个mark域,是下图中白色框框住的部分,其灰度值为255,第二个选择mark域为塔,就是黑色框框住的一块区域,其灰度值为64,最后就是树mark域,蓝色框的部分,其灰度值为128。在分水岭算法时候,会分别对这个3个区域来进行注水操作,如果两个注水盆地被一个mark域覆盖,则它们之间不会有分水岭线产生。

        对于mark图像,opencv分水岭算法在初始化时候,会把最外圈的值置为-1,作为整个图像的边界,所以我们第一个mark区域,选择倒数第2外圈,因为设置到最外圈,最后还是会被冲掉。

    注意:mark图像是32bit的有符号整数,所以在使用分水岭算法前,我们先对mark图像做一个转化。算法执行完后,再转化为0-255的灰度图。

      imageMask.convertTo(imageMask,CV_32S);
        // 设置marker和处理图像 
      cv::watershed(image,imageMask);
    
      cv::Mat mark1;
      imageMask.convertTo(mark1,CV_8U);
      cv::namedWindow("marker");
    
       cv::imshow("marker",mark1);

      此时imageMask图像从无符号整数转化为uchar后,如下图所示,第一个mask区域注水,将会使得整个图像为白色,之后分别在第二个,第三个区域的盆地注水,会产生相应的注水图,注水的区域的值即为mark的值,128和64, 分水岭线则为0,注:在转化前分水岭线的值为-1,转化后成为0

    我们使用一个转化函数把分水岭线转化为黑色,其它的部分都白黑色,转化函数的公式为:

     最后显示分水岭线,得到下图:(注:在转化前,分水岭线的值为-1)

    展开全文
  • OpenCV watershed 源码分析

    千次阅读 2013-06-02 17:05:38
    ¨参考文献: ...算法流程: 优先级队列及一维算法示例: 核心数据结构: 源码注释: typedef struct CvWSNode //像素节点 { struct CvWSNode* next; int mask_ofs; //该节点对于mask中的
    ¨考文献:

    Meyer, F.Color Image Segmentation, ICIP92,1992

    基本思想:

    算法流程:

    (1)首先得到mark标记图,标记区域的值为1,2...L,未标记区域为0,分水岭区域为-1(图像边界预先标记为分水岭)

    (2)将mark区域的四邻域加入到优先级队列,优先级为0,1,...255 ,取决于两像素点rgb距离中最大的值(也可以称为棋盘距离吧)

    while(!queue.empty())

       从优先级最高的队列开始,出队

           如果,其4邻域不存在其他label,那么4邻域就统统归它所有,同样对于俘获的像素也安装其与该出队的元素之间的距离作为优先级加入队列

           如果,其周围已经有其它的label了,那么画地为界,该像素充当分水岭,标记为-1



    优先级队列及一维算法示例:

    核心数据结构:

    源码注释:

    typedef struct CvWSNode	//像素节点
    {
        struct CvWSNode* next;
        int mask_ofs;	//该节点对于mask中的偏移
        int img_ofs;	//该节点对于原图像中的偏移
    }
    CvWSNode;
    
    typedef struct CvWSQueue	//同等级像素队列
    {
        CvWSNode* first;
        CvWSNode* last;
    }
    CvWSQueue;
    
    static CvWSNode*
    icvAllocWSNodes( CvMemStorage* storage )
    {
        CvWSNode* n = 0;
    	//改内存块的总大小,减去内存块链接需要的空间,/一个节点的大小,得到的就是能存放节点的个数
    	//减去1,可能是为了安全起见
        int i, count = (storage->block_size - sizeof(CvMemBlock))/sizeof(*n) - 1;
    	//向该内存块要空间,搞成线性链表
        n = (CvWSNode*)cvMemStorageAlloc( storage, count*sizeof(*n) );
        for( i = 0; i < count-1; i++ )
            n[i].next = n + i + 1;
        n[count-1].next = 0;
    
        return n;
    }
    
    
    CV_IMPL void
    cvWatershed( const CvArr* srcarr, CvArr* dstarr )
    {
        const int IN_QUEUE = -2;		//进入队列标记
        const int WSHED = -1;	//分水岭标记
        const int NQ = 256;		//队列个数
        cv::Ptr<CvMemStorage> storage;	//内存块
    
        CvMat sstub, *src;
        CvMat dstub, *dst;
        CvSize size;
        CvWSNode* free_node = 0, *node;	//内存块的空闲内存指针,指向第一个未被占用的节点
        CvWSQueue q[NQ];	//可以看成数组实现的特殊队列
        int active_queue;	//当前要分类的队列,其实链表更准确
        int i, j;
        int db, dg, dr;	//d:距离  r,g,b
        int* mask;
        uchar* img;
        int mstep, istep;	//mask步长,image步长
        int subs_tab[513];
    
        // MAX(a,b) = b + MAX(a-b,0),要先看看subs_tab的赋值才能理解
        #define ws_max(a,b) ((b) + subs_tab[(a)-(b)+NQ])		//我去,比较大小搞这么复杂
        // MIN(a,b) = a - MAX(a-b,0)
        #define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ])
    
        #define ws_push(idx,mofs,iofs)  \
        {                               \
            if( !free_node )            \
    			//一次性分配很多空间,得到一个节点链表,要用的时候从里面取节点
                free_node = icvAllocWSNodes( storage );\	
            node = free_node;           \
            free_node = free_node->next;\	//取一个节点给node
            node->next = 0;             \
            node->mask_ofs = mofs;      \
            node->img_ofs = iofs;       \
            if( q[idx].last )           \
                q[idx].last->next=node; \	//跟在比人屁股后面去吧
            else                        \
                q[idx].first = node;    \	//嘿嘿,你是第一个哦
            q[idx].last = node;         \	
        }
    
        #define ws_pop(idx,mofs,iofs)   \
        {                               \
            node = q[idx].first;        \
            q[idx].first = node->next;  \
            if( !node->next )           \
                q[idx].last = 0;        \
            node->next = free_node;     \
            free_node = node;           \
            mofs = node->mask_ofs;      \
            iofs = node->img_ofs;       \
        }
    
        #define c_diff(ptr1,ptr2,diff)      \	//计算两像素之差,结果为r,g,b距离最大者
        {                                   \
            db = abs((ptr1)[0] - (ptr2)[0]);\
            dg = abs((ptr1)[1] - (ptr2)[1]);\
            dr = abs((ptr1)[2] - (ptr2)[2]);\
            diff = ws_max(db,dg);           \
            diff = ws_max(diff,dr);         \
            assert( 0 <= diff && diff <= 255 ); \
        }
    
        src = cvGetMat( srcarr, &sstub );	//取Mat 信息头,sstub在输入参数为Mat的情况下没有用到
        dst = cvGetMat( dstarr, &dstub );
    
        if( CV_MAT_TYPE(src->type) != CV_8UC3 )
            CV_Error( CV_StsUnsupportedFormat, "Only 8-bit, 3-channel input images are supported" );
    
        if( CV_MAT_TYPE(dst->type) != CV_32SC1 )
            CV_Error( CV_StsUnsupportedFormat,
                "Only 32-bit, 1-channel output images are supported" );
    
        if( !CV_ARE_SIZES_EQ( src, dst ))
            CV_Error( CV_StsUnmatchedSizes, "The input and output images must have the same size" );
    
        size = cvGetMatSize(src);
        storage = cvCreateMemStorage();	//默认要64K的空间
    
        istep = src->step;	// image step
        img = src->data.ptr;	//pointer to 8-bit unsigned elements !!!
        mstep = dst->step / sizeof(mask[0]);		//mask step
    	//mstep的作用是方便移到某一行,比如 mask+mstep,是要移到下一行,但对于mask指针来说其对象为 32S
    	//所以直接加 每一行的字节数就相当于跳到 mask+4*mstep
        mask = dst->data.i;	//pointer to 32-bit signed elements  !!!
    
        memset( q, 0, NQ*sizeof(q[0]) );
    
        for( i = 0; i < 256; i++ )
            subs_tab[i] = 0;
        for( i = 256; i <= 512; i++ )
            subs_tab[i] = i - 256;
    
        // draw a pixel-wide border of dummy "watershed" (i.e. boundary) pixels
    	//上下两行标记为分水岭
        for( j = 0; j < size.width; j++ )
            mask[j] = mask[j + mstep*(size.height-1)] = WSHED;
    
    	/*-----------------初始化:将标记区域4邻域的点加入优先级队列q-----------------------------*/
        // initial phase: put all the neighbor pixels of each marker to the ordered queue -
        // determine the initial boundaries of the basins
    	//除边框外,把标记 4 邻域的像素加入队列,q有256个优先级,对应距离0-255,所以具体加入哪个
    	//队列就看距离最小idx了
        for( i = 1; i < size.height-1; i++ )
        {
            img += istep; mask += mstep;
            mask[0] = mask[size.width-1] = WSHED;	//左右两端标为分水岭
    
            for( j = 1; j < size.width-1; j++ )
            {
                int* m = mask + j;
                if( m[0] < 0 ) m[0] = 0;	//此处归零是为了防止意外情况		
    			//mask中值为0的为未分类的像素	
    			//还没有归宿,但是其4邻域有标记区,那就入队,等待分配区域标号
                if( m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0) )
                {
                    uchar* ptr = img + j*3;	//三通道
                    int idx = 256, t;
    				//计算该点到其四邻域中最近的距离
                    if( m[-1] > 0 )	//左
                        c_diff( ptr, ptr - 3, idx );
                    if( m[1] > 0 )	//右
                    {
                        c_diff( ptr, ptr + 3, t );
                        idx = ws_min( idx, t );
                    }
                    if( m[-mstep] > 0 )	//上
                    {
                        c_diff( ptr, ptr - istep, t );
                        idx = ws_min( idx, t );
                    }
                    if( m[mstep] > 0 )	//下
                    {
                        c_diff( ptr, ptr + istep, t );
                        idx = ws_min( idx, t );
                    }
                    assert( 0 <= idx && idx <= 255 );
                    ws_push( idx, i*mstep + j, i*istep + j*3 );	//入队
                    m[0] = IN_QUEUE;
                }
            }
        }
    
        // find the first non-empty queue
    	//比如 q[0]表示所有标记区附近与标记区距离为0的点,自然很可能没有这样的点
        for( i = 0; i < NQ; i++ )
            if( q[i].first )
                break;
    
        // if there is no markers, exit immediately
        if( i == NQ )
            return;
    
        active_queue = i;
        img = src->data.ptr;
        mask = dst->data.i;
    
        // recursively fill the basins
        for(;;)
        {
            int mofs, iofs;
            int lab = 0, t;
            int* m;
            uchar* ptr;
    		/*-----------------划分:如果其4邻域只有一个集水区那就归入其中,如果有多个那就标记为分水岭-----------------------------*/
            if( q[active_queue].first == 0 )	//当前层级处理完或无像素入队,换下一层
            {
                for( i = active_queue+1; i < NQ; i++ )
                    if( q[i].first )	//换这层
                        break;
                if( i == NQ )	//退出大循环
                    break;
                active_queue = i;	//下面给这一层的像素分配标号
            }
    		//取一个点
            ws_pop( active_queue, mofs, iofs );	
    
            m = mask + mofs;	//mask的值
            ptr = img + iofs;	//img的值
    		//4邻域只有一个区域,就滚到那里面去,要是处在多个集水区,那你就做大坝吧
            t = m[-1];
            if( t > 0 ) lab = t;
            t = m[1];
            if( t > 0 )
            {
                if( lab == 0 ) lab = t;
                else if( t != lab ) lab = WSHED;
            }
            t = m[-mstep];
            if( t > 0 )
            {
                if( lab == 0 ) lab = t;
                else if( t != lab ) lab = WSHED;
            }
            t = m[mstep];
            if( t > 0 )
            {
                if( lab == 0 ) lab = t;
                else if( t != lab ) lab = WSHED;
            }
            assert( lab != 0 );
            m[0] = lab;
            if( lab == WSHED )
                continue;	//为啥不接着找呢?
    	    /*-----------------扩散:将标记区域4邻域的点中尚未分类的点加入优先级队列q-----------------------------*/
            if( m[-1] == 0 )		//左
            {
                c_diff( ptr, ptr - 3, t );
                ws_push( t, mofs - 1, iofs - 3 );
                active_queue = ws_min( active_queue, t );	//有更近的点,下一轮优先
                m[-1] = IN_QUEUE;	//标记为入队
            }
            if( m[1] == 0 )	//右
            {
                c_diff( ptr, ptr + 3, t );
                ws_push( t, mofs + 1, iofs + 3 );
                active_queue = ws_min( active_queue, t );
                m[1] = IN_QUEUE;
            }
            if( m[-mstep] == 0 )	//上
            {
                c_diff( ptr, ptr - istep, t );
                ws_push( t, mofs - mstep, iofs - istep );
                active_queue = ws_min( active_queue, t );
                m[-mstep] = IN_QUEUE;
            }
            if( m[mstep] == 0 )	//下
            {
                c_diff( ptr, ptr + istep, t );
                ws_push( t, mofs + mstep, iofs + istep );
                active_queue = ws_min( active_queue, t );
                m[mstep] = IN_QUEUE;
            }
        }
    }
    
    
    void cv::watershed( InputArray _src, InputOutputArray markers )
    {
        Mat src = _src.getMat();
        CvMat c_src = _src.getMat(), c_markers = markers.getMat();
        cvWatershed( &c_src, &c_markers );
    }


     

     

     

    展开全文
  • Opencv分水岭算法——watershed自动图像分割用法

    万次阅读 多人点赞 2016-09-11 00:40:26
    分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要...

    分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征

    其他图像分割方法,如阈值,边缘检测等都不会考虑像素在空间关系上的相似性和封闭性这一概念,彼此像素间互相独立,没有统一性。分水岭算法较其他分割方法更具有思想性,更符合人眼对图像的印象。


    其他关于分水岭“聚水盆地”、“水坝”、“分水线”等概念不准备赘述,只探讨一下Opencv中分水岭算法的实现方法watershed——这个“简单”到只有两个参数的函数是如何工作的。


    Opencv 中 watershed函数原型:

    void watershed( InputArray image, InputOutputArray markers );

    第一个参数 image,必须是一个8bit 3通道彩色图像矩阵序列,第一个参数没什么要说的。关键是第二个参数 markers,Opencv官方文档的说明如下:

    Before passing the image to the function, you have to roughly outline the desired regions in the image markers with positive (>0) indices. So, every region is represented as one or more connected components with the pixel values 1, 2, 3, and so on. Such markers can be retrieved from a binary mask using findContours() and drawContours(). The markers are “seeds” of the future image regions. All the other pixels in markers , whose relation to the outlined regions is not known and should be defined by the algorithm, should be set to 0’s. In the function output, each pixel in markers is set to a value of the “seed” components or to -1 at boundaries between the regions.


    就不一句一句翻译了,大意说的是在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。

    接下来执行分水岭会发生什么呢?算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。

    简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。


    下边通过图示来看一下watershed函数的第二个参数markers在算法执行前后发生了什么变化。对于一个原图:




    经过灰度化、滤波、Canny边缘检测、findContours轮廓查找、轮廓绘制等步骤后终于得到了符合Opencv要求的merkers,我们把merkers转换成8bit单通道灰度图看看它里边到底是什么内容:


    这个是分水岭运算前的merkers:


    这个是findContours检测到的轮廓:



    看效果,基本上跟图像的轮廓是一样的,也是简单的勾勒出了物体的外形。但如果仔细观察就能发现,图像上不同线条的灰度值是不同的,底部略暗,越往上灰度越高。由于这幅图像边缘比较少,对比不是很明显,再来看一幅轮廓数量较多的图效果:


    这个是分水岭运算前的merkers:


    这个是findContours检测到的轮廓:



    从这两幅图对比可以很明显看到,从图像底部往上,线条的灰度值是越来越高的,并且merkers图像底部部分线条的灰度值由于太低,已经观察不到了相互连接在一起的线条灰度值是一样的,这些线条和不同的灰度值又能说明什么呢?

    答案是:每一个线条代表了一个种子,线条的不同灰度值其实代表了对不同注水种子的编号,有多少不同灰度值的线条,就有多少个种子,图像最后分割后就有多少个区域。



    再来看一下执行完分水岭方法之后merkers里边的内容发生了什么变化:




    可以看到,执行完watershed之后,merkers里边被分割出来的区域已经非常明显了,空间上临近并且灰度值上相近的区域被划分为一个区域,灰度值是一样,不同区域间被划分开,这其实就是分水岭对图像的分割效果了。


    总的概括一下watershed图像自动分割的实现步骤:

    1. 图像灰度化、滤波、Canny边缘检测

    2. 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。

    3. watershed分水岭运算

    4. 绘制分割出来的区域,视觉控还可以使用随机颜色填充,或者跟原始图像融合以下,以得到更好的显示效果。


    以下是Opencv分水岭算法watershed实现的完整过程:


    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/highgui/highgui.hpp"
    
    #include <iostream>
    
    using namespace cv;
    using namespace std;
    
    Vec3b RandomColor(int value);  //生成随机颜色函数
    
    int main( int argc, char* argv[] )
    {
    	Mat image=imread(argv[1]);    //载入RGB彩色图像
    	imshow("Source Image",image);
    
    	//灰度化,滤波,Canny边缘检测
    	Mat imageGray;
    	cvtColor(image,imageGray,CV_RGB2GRAY);//灰度转换
    	GaussianBlur(imageGray,imageGray,Size(5,5),2);   //高斯滤波
    	imshow("Gray Image",imageGray); 
    	Canny(imageGray,imageGray,80,150);  
    	imshow("Canny Image",imageGray);
    
    	//查找轮廓
    	vector<vector<Point>> contours;  
    	vector<Vec4i> hierarchy;  
    	findContours(imageGray,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());  
    	Mat imageContours=Mat::zeros(image.size(),CV_8UC1);  //轮廓	
    	Mat marks(image.size(),CV_32S);   //Opencv分水岭第二个矩阵参数
    	marks=Scalar::all(0);
    	int index = 0;
    	int compCount = 0;
    	for( ; index >= 0; index = hierarchy[index][0], compCount++ ) 
    	{
    		//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点
    		drawContours(marks, contours, index, Scalar::all(compCount+1), 1, 8, hierarchy);
    		drawContours(imageContours,contours,index,Scalar(255),1,8,hierarchy);  
    	}
    
    	//我们来看一下传入的矩阵marks里是什么东西
    	Mat marksShows;
    	convertScaleAbs(marks,marksShows);
    	imshow("marksShow",marksShows);
    	imshow("轮廓",imageContours);
    	watershed(image,marks);
    
    	//我们再来看一下分水岭算法之后的矩阵marks里是什么东西
    	Mat afterWatershed;
    	convertScaleAbs(marks,afterWatershed);
    	imshow("After Watershed",afterWatershed);
    
    	//对每一个区域进行颜色填充
    	Mat PerspectiveImage=Mat::zeros(image.size(),CV_8UC3);
    	for(int i=0;i<marks.rows;i++)
    	{
    		for(int j=0;j<marks.cols;j++)
    		{
    			int index=marks.at<int>(i,j);
    			if(marks.at<int>(i,j)==-1)
    			{
    				PerspectiveImage.at<Vec3b>(i,j)=Vec3b(255,255,255);
    			}			 
    			else
    			{
    				PerspectiveImage.at<Vec3b>(i,j) =RandomColor(index);
    			}
    		}
    	}
    	imshow("After ColorFill",PerspectiveImage);
    
    	//分割并填充颜色的结果跟原始图像融合
    	Mat wshed;
    	addWeighted(image,0.4,PerspectiveImage,0.6,0,wshed);
    	imshow("AddWeighted Image",wshed);
    
    	waitKey();
    }
    
    Vec3b RandomColor(int value)    <span style="line-height: 20.8px; font-family: sans-serif;">//生成随机颜色函数</span>
    {
    	value=value%255;  //生成0~255的随机数
    	RNG rng;
    	int aa=rng.uniform(0,value);
    	int bb=rng.uniform(0,value);
    	int cc=rng.uniform(0,value);
    	return Vec3b(aa,bb,cc);
    }

    第一幅图像分割效果:



    按比例跟原始图像融合:



    第二幅图像原始图:



    分割效果:



    按比例跟原始图像融合:




    展开全文
  • 我们将学习使用分水岭算法实现基于标记的图像分割 我们将看到:cv.watershed() 理论 任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个孤立的山谷...
  • 目标在本章中,我们将学习使用分水岭算法实现基于标记的图像分割我们将看到:cv.watershed()理论任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个...
  • opencv 分水岭算法

    千次阅读 2016-11-06 01:07:43
    分水岭在地理学上就是指一个山脊,水通常会沿着山脊的两边流向不同的“汇水盆”。分水岭算法是一种用于图像分割的经典...那我们先来看一下opencv当中,分水岭算法watershed的实现。 opencvwatershed是在Meyer, F.Col
  • 目标在本章中,我们将学习使用分水岭算法实现基于标记的图像分割我们将看到:cv.watershed()理论任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个...
  • 目标在本章中,我们将学习使用分水岭算法实现基于标记的图像分割我们将看到:cv.watershed()理论任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个...
  • 目标在本章中,我们将学习使用分水岭算法实现基于标记的图像分割我们将看到:cv.watershed()理论任何灰度图像都可以看作是一个地形表面,其中高强度...
  • 为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中。先贴出加了注解的代码,以后补充对分水岭算法的解释。
  • 为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中。先贴出加了注解的代码,以后补充对分水岭算法的解释。 #include...
  • 我们将学习使用分水岭算法实现基于标记的图像分割 - 我们将看到:cv.watershed() 理论 任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个孤立的山谷...
  • 本次我们来看图像分割,同样也是OpenCV中较为重要的一个部分。图像分割是按照一定的原则,将一幅图像分为若干个互不相交的小局域的过程,它是图像处理中最为基础的研究领域之一。目前有很多图像分割方法,其中分水岭...
  • 如何利用opencv分水岭图像分割算法threshold来分割图像区域? 函数说明: void watershed( InputArray image, InputOutputArray markers ); image:原图像 markers:包含了轮廓点的数据集合 void distanceTransform...
  • 下面三张图就是push和pop过程演示(不知道为何图片插进来就自动旋转了,尴尬,如果人懂,望一定告知解决方法,谢谢)下面是opencv中分水岭算法源码:void cv::watershed( InputArray _src, InputOut...
  • Python+Opencv分水岭算法

    千次阅读 2019-07-20 23:25:54
    目录一、分水岭算法Watershed)简介二、分水岭算法实现步骤三、阈值和轮廓检测硬币分割代码实现与分析四、分水岭硬币分割代码实现五、代码效果展示与分析参考资料注意事项 一、分水岭算法Watershed)简介   ...
  • Opencv 分水岭算法 void watershed( InputArray image, InputOutputArray markers ); 官方文档中这样表述分水岭算法种子的选取 /* Before passing the image to the function, you have to roughly outline the...
  • 基于距离的分水岭分割流程:输入图像——>灰度转换(如果有噪声,在这之前要先消去噪声)——>二值图像——>距离变换 ——>寻找种子——>...def watershed_demo(): print(src.sh...
  • Opencv 分水岭算法用于图像分割

    千次阅读 2017-05-26 11:36:06
    • 使用分水岭算法基于掩模的图像分割 • 学习函数: cv2.watershed()

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 179
精华内容 71
关键字:

opencvwatershed算法