精华内容
下载资源
问答
  • Watershed函数

    千次阅读 2015-08-25 15:59:02
    Watershed 做分水岭图像分割 C++: void watershed(InputArray image, InputOutputArray markers) c语言形式:void cvWatershed( const CvArr* image, CvArr* markers ); image输入8比特3通道图像...

    Watershed

    做分水岭图像分割

    C++: void watershed(InputArray image, InputOutputArray markers)

    c语言形式:void cvWatershed( const CvArr* image, CvArr* markers );
    
    image
    输入8比特3通道图像。
    markers

    输入或输出的32比特单通道标记图像。

    markers即是输入矩阵也是输出矩阵,大小与image大小相同。使用该函数的时候,用户在markers矩阵中必须粗略指定两种以上区域,该区域为1个点以上的连通点集,并用不同的正整数(1,2,3…)标记

    函数cvWatershed实现在[Meyer92]描述的变量分水岭,基于非参数标记的分割算法中的一种。在把图像传给函数之前,用户需要用正指标大致勾画出图像标记的感兴趣区域。比如,每一个区域都表示成一个或者多个像素值1,2,3的互联部分。这些部分将作为将来图像区域的种子。标记中所有的其他像素,他们和勾画出的区域关系不明并且应由算法定义,应当被置0。这个函数的输出则是标记区域所有像素被置为某个种子部分的值,或者在区域边界则置-1。

    注:每两个相邻区域也不是必须有一个分水岭边界(-1像素)分开,例如在初始标记图像里有这样相切的部分。opencv例程文件夹里面有函数的视觉效果演示和用户例程

    #include<cv.h>
    #include<highgui.h>
    #include<iostream>
    
    #pragma comment(lib, "cv.lib")
    #pragma comment(lib, "cxcore.lib")
    #pragma comment(lib, "highgui.lib")
    
    using namespace  std;
    
    IplImage* marker_mask = 0;
    IplImage* markers = 0;
    IplImage* img0 = 0, *img = 0, *img_gray = 0, *wshed = 0;
    CvPoint prev_pt = {-1,-1};
    void on_mouse( int event, int x, int y, int flags, void* param )//opencv 会自动给函数传入合适的值
    {
    	if( !img )
    		return;
    	if( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) )
    		prev_pt = cvPoint(-1,-1);
    	else if( event == CV_EVENT_LBUTTONDOWN )
    		prev_pt = cvPoint(x,y);
    	else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON) )
    	{
    		CvPoint pt = cvPoint(x,y);
    		if( prev_pt.x < 0 )
    			prev_pt = pt;
    		cvLine( marker_mask, prev_pt, pt, cvScalarAll(255), 5, 8, 0 );//CvScalar 成员:double val[4] RGBA值A=alpha
    		cvLine( img, prev_pt, pt, cvScalarAll(255), 5, 8, 0 );
    		prev_pt = pt;
    		cvShowImage( "image", img);
    	}
    }
    
    int main( int argc, char** argv )
    {
    	char* filename = argc >= 2 ? argv[1] : (char*)"test.png";
    	CvMemStorage* storage = cvCreateMemStorage(0);
    	CvRNG rng = cvRNG(-1);
    	if( (img0 = cvLoadImage(filename,1)) == 0 )
    		return 0;
    	printf( "Hot keys: \n"
    		"\tESC - quit the program\n"
    		"\tr - restore the original image\n"
    		"\tw or SPACE - run watershed algorithm\n"
    		"\t\t(before running it, roughly mark the areas on the image)\n"
    		"\t  (before that, roughly outline several markers on the image)\n" );
    	cvNamedWindow( "image", 1 );
    	cvNamedWindow( "watershed transform", 1 );
    	img = cvCloneImage( img0 );
    	img_gray = cvCloneImage( img0 );
    	wshed = cvCloneImage( img0 );
    	marker_mask = cvCreateImage( cvGetSize(img), 8, 1 );
    	markers = cvCreateImage( cvGetSize(img), IPL_DEPTH_32S, 1 );
    	cvCvtColor( img, marker_mask, CV_BGR2GRAY );
    	cvCvtColor( marker_mask, img_gray, CV_GRAY2BGR );//这两句只用将RGB转成3通道的灰度图即R=G=B,用来显示用
    	cvZero( marker_mask );
    	cvZero( wshed );
    	cvShowImage( "image", img );
    	cvShowImage( "watershed transform", wshed );
    	cvSetMouseCallback( "image", on_mouse, 0 );
    	for(;;)
    	{
    		int c = cvWaitKey(0);
    		if( (char)c == 27 )
    			break;
    		if( (char)c == 'r' )
    		{
    			cvZero( marker_mask );
    			cvCopy( img0, img );//cvCopy()也可以这样用,不影响原img0图像,也随时更新
    			cvShowImage( "image", img );
    		}
    		if( (char)c == 'w' || (char)c == ' ' )
    		{
    			CvSeq* contours = 0;
    			CvMat* color_tab = 0;
    			int i, j, comp_count = 0;
    
    			//下面选将标记的图像取得其轮廓, 将每种轮廓用不同的整数表示
    			//不同的整数使用分水岭算法时,就成为不同的种子点
    			//算法本来就是以各个不同的种子点为中心扩张
    			cvClearMemStorage(storage);
    			cvFindContours( marker_mask, storage, &contours, sizeof(CvContour),
    				CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
    			cvZero( markers );
    			for( ; contours != 0; contours = contours->h_next, comp_count++ )
    			{
    				cvDrawContours(markers, contours, cvScalarAll(comp_count+1),
    					cvScalarAll(comp_count+1), -1, -1, 8, cvPoint(0,0) );
    			}
    			//cvShowImage("image",markers);
    			if( comp_count == 0 )
    				continue;
    			color_tab = cvCreateMat( 1, comp_count, CV_8UC3 );//创建随机颜色列表
    			for( i = 0; i < comp_count; i++ )	//不同的整数标记
    			{
    				uchar* ptr = color_tab->data.ptr + i*3;
    				ptr[0] = (uchar)(cvRandInt(&rng)%180 + 50);
    				ptr[1] = (uchar)(cvRandInt(&rng)%180 + 50);
    				ptr[2] = (uchar)(cvRandInt(&rng)%180 + 50);
    			}
    			{
    				double t = (double)cvGetTickCount();
    				cvWatershed( img0, markers );
    				cvSave("img0.xml",markers);
    				t = (double)cvGetTickCount() - t;
    				printf( "exec time = %gms\n", t/(cvGetTickFrequency()*1000.) );
    			}
    			// paint the watershed image
    			for( i = 0; i < markers->height; i++ )
    				for( j = 0; j < markers->width; j++ )
    				{
    					int idx = CV_IMAGE_ELEM( markers, int, i, j );//markers的数据类型为IPL_DEPTH_32S
    					uchar* dst = &CV_IMAGE_ELEM( wshed, uchar, i, j*3 );//BGR三个通道的数是一起的,故要j*3
    					if( idx == -1 ) //输出时若为-1,表示各个部分的边界
    						dst[0] = dst[1] = dst[2] = (uchar)255;
    					else if( idx <= 0 || idx > comp_count )  //异常情况
    						dst[0] = dst[1] = dst[2] = (uchar)0; // should not get here
    					else //正常情况
    					{
    						uchar* ptr = color_tab->data.ptr + (idx-1)*3;
    						dst[0] = ptr[0]; dst[1] = ptr[1]; dst[2] = ptr[2];
    					}
    				}
    				cvAddWeighted( wshed, 0.5, img_gray, 0.5, 0, wshed );//wshed.x.y=0.5*wshed.x.y+0.5*img_gray+0加权融合图像
    				cvShowImage( "watershed transform", wshed );
    				cvReleaseMat( &color_tab );
    		}
    	}
    	return 1;
    }


    展开全文
  • Matlab watershed函数

    2020-04-01 22:00:16
    L = watershed(A) 对输入矩阵 A 进行分水岭变换,并标记不同的分水岭区域,得到标签矩阵 L 。 A 可以是任意维度的矩阵。 L 中的元素是非负整数。 值为 0 的元素不属于一个独立的分水岭区域,这些像素被称为...

    L = watershed(A) 对输入矩阵 A 进行分水岭变换,并标记不同的分水岭区域,得到标签矩阵 L 。

    A 可以是任意维度的矩阵。

    L 中的元素是非负整数。

          值为 0 的元素不属于一个独立的分水岭区域,这些像素被称为“分水岭元素”;

          值为 1 的元素属于第一个分水岭区域;

          值为 2 的元素属于第二个分水岭区域;

          值为 3 的元素属于第三个分水岭区域;

          。。。

          以此类推。

    默认情况下,对于二维输入图像,该函数采用 8 连接邻域,

                          对于三维输入图像,该函数采用 26 连接邻域,

                         对于更高维的输入图像,邻域大小由 conndef(ndims(A), 'maximal') 给出。

     

    L =  watershed(A, CONN) 通过指定的邻域大小 CONN 对输入矩阵 A 进行分水岭变换,CONN可能有以下标量值:

    4        二维 4 连接邻域

    8        二维 8 连接邻域

    6        三维 6 连接邻域

    18      三维 18 连接邻域

    26      三维 26 连接邻域

    对于任意维度的矩阵,连接性可以用一种更一般的形式来定义,即通过一个只有 0 元素和 1 元素的 3×3×...×3 矩阵CONN来定义,其中,1 元素所在的位置就是CONN中心元素指定的邻域位置。这种情况下,CONN必须是关于中心对称的矩阵。

     

    【注】A 可以是任意维度的数值矩阵或逻辑矩阵,但它必须是非稀疏的。L 是无符号整型的

     

    例子:

    1、A 是二维的

    clear
    % (1)创建一幅包含两个重叠的圆形物体的二值图像
    center1 = -10;
    center2 = -center1;
    dist = sqrt(2*(2*center1)^2);
    radius = dist/2 * 1.4;
    lims = [floor(center1-1.2*radius) ceil(center2+1.2*radius)];
    [x,y] = meshgrid(lims(1):lims(2));
    bw1 = sqrt((x-center1).^2 + (y-center1).^2) <= radius;
    bw2 = sqrt((x-center2).^2 + (y-center2).^2) <= radius;
    bw = bw1 | bw2;
    figure, imshow(bw,'InitialMagnification','fit'), title('bw')
    
    % (2)对二值图像取反,并计算取反后的距离变换
    D = bwdist(~bw);
    figure, imshow(D,[],'InitialMagnification','fit')
    title('Distance transform of ~bw')
    
    % (3)对距离变换的结果取反,并强制非目标物体区域的像素值为无穷大
    D = -D;
    D(~bw) = Inf;
    
    % (4)计算分水岭变换,强制背景像素为零,并将生成的标签矩阵显示为RGB图像
    L = watershed(D); 
    L(~bw) = 0;
    rgb = label2rgb(L,'jet',[.5 .5 .5]);
    figure, imshow(rgb,'InitialMagnification','fit')
    title('Watershed transform of D')

     

     

     

     

    2、A 是三维的

    clear
    % (1)创建一幅包含两个重叠球体的三维二值图像
    center1 = -10;
    center2 = -center1;
    dist = sqrt(3*(2*center1)^2);
    radius = dist/2 * 1.4;
    lims = [floor(center1-1.2*radius) ceil(center2+1.2*radius)];
    [x,y,z] = meshgrid(lims(1):lims(2));
    bw1 = sqrt((x-center1).^2 + (y-center1).^2 + ...
               (z-center1).^2) <= radius;
    bw2 = sqrt((x-center2).^2 + (y-center2).^2 + ...
               (z-center2).^2) <= radius;
    bw = bw1 | bw2;
    figure, isosurface(x,y,z,bw,0.5), axis equal, title('BW')
    xlabel x, ylabel y, zlabel z
    xlim(lims), ylim(lims), zlim(lims)
    view(3), camlight, lighting gouraud
    
    
    % (2)计算距离变换
    D = bwdist(~bw);
    figure, isosurface(x,y,z,D,radius/2), axis equal
    title('Isosurface of distance transform')
    xlabel x, ylabel y, zlabel z
    xlim(lims), ylim(lims), zlim(lims)
    view(3), camlight, lighting gouraud
    
    % (3)对距离变换取反,强制非目标物体像素值为无穷大,然后计算分水岭变换
    D = -D;
    D(~bw) = Inf;
    L = watershed(D);
    L(~bw) = 0;
    figure
    isosurface(x,y,z,L==1,0.5)
    isosurface(x,y,z,L==2,0.5), axis equal
    title('Segmented objects')
    xlabel x, ylabel y, zlabel z
    xlim(lims), ylim(lims), zlim(lims)
    view(3), camlight, lighting gouraud

     

    展开全文
  • 为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中。先贴出加了注解的代码,以后补充对分水岭算法的解释。 #include...

    为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中。先贴出加了注解的代码,以后补充对分水岭算法的解释。

    
    #include "precomp.hpp"
    
    /*******************************************************                                    Watershed                                    **************************************************************************************/
    // 结点,用于存储原始图img中像素的偏移量和输出图mask中像素的偏移量
    typedef struct CvWSNode
    {
        struct CvWSNode* next;
        int mask_ofs;
        int img_ofs;
    }
    CvWSNode;
    
    // 队列,用于存储结点 CvWSNode
    typedef struct CvWSQueue
    {
        CvWSNode* first;
        CvWSNode* last;
    }
    CvWSQueue;
    
    // 分配空间
    static CvWSNode*
    icvAllocWSNodes( CvMemStorage* storage )
    {
        CvWSNode* n = 0;
    
        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;        // 加入到队列q中的点定义为 -2
        const int WSHED = -1;           // “分水岭”在mask中定义为 -1 
        const int NQ = 256;             // 队列的数量 256,其实是对应灰度的数量
        cv::Ptr<CvMemStorage> storage;
    
        CvMat sstub, *src;
        CvMat dstub, *dst;
        CvSize size;
        CvWSNode* free_node = 0, *node;
        CvWSQueue q[NQ];                // 长度为256的CvWSQueue数组,注意数组中每个元素都是一个队列,队列中每个元素是一个节点
        int active_queue;               // 指明当前处理的队列,q[active_queue]
        int i, j;
        int db, dg, dr;
        int* mask;                      // 指向标记图像的指针
        uchar* img;                     // 指向原始图像的指针
        int mstep, istep;               // mstep是mask对应的一行像素数(不是字节数),istep是img对应的一行像素数
        int subs_tab[513];
    
        // MAX(a,b) = b + MAX(a-b,0)    取最大值
        #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->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;       \
        }
    
        // 求出 ptr1 和 ptr2 指向的像素 r,g,b 差值的最大值
        #define c_diff(ptr1,ptr2,diff)      \
        {                                   \
            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 );
        dst = cvGetMat( dstarr, &dstub );
    
        // 对参数做检查,要求图像src的类型是8UC3,dst的类型是32SC1,src和dst size相同
        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);       // 获取图像的size
        storage = cvCreateMemStorage();
    
        // 步长 = 一行字节数 / sizeof(像素数据类型)
        istep = src->step;            // img是uchar型, sizeof(uchar) = 1,所以忽略除数  
        img = src->data.ptr;          // 获取 uchar类型指针
        mstep = dst->step / sizeof(mask[0]);    // mask是int32SC1)型,sizeof(mask[0]) = 4
        mask = dst->data.i;           // 获取 int类型指针
    
        memset( q, 0, NQ*sizeof(q[0]) );    // 初始化队列q
    
        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
        // 把图像四个边的像素画成分水岭
        // mask的首行和末行画成分水岭
        for( j = 0; j < size.width; j++ )
            mask[j] = mask[j + mstep*(size.height-1)] = WSHED;
    
        // initial phase: put all the neighbor pixels of each marker to the ordered queue -
        // determine the initial boundaries of the basins
        // 初始阶段:把每个标记的所有邻居像素放到有序队列中去,以确定聚水盆的初始边界
        // 即每个标记(种子,全为正值,1,2,3...)都是一个初始聚水盆,标记的周围一圈的邻居像素就是聚水盆的初始边界
        // 这里用的是一种逆向思维,不是找标记点,而是判断每一个点是否为标记点的邻居,若是,则该点也被扩充为与标记点同类型的标记点
        // 若是多个标记点的邻居,选择梯度最小的标记点的类型,作为该点的标记点类型
        for( i = 1; i < size.height-1; i++ )
        {
            img += istep; mask += mstep;            // 逐行扫描
            mask[0] = mask[size.width-1] = WSHED;   // 每一行的首列和末列画成分水岭,加上前面的首行和末行,mask被分水岭方框围起来
    
            for( j = 1; j < size.width-1; j++ )     // 逐列
            {
                int* m = mask + j;                  // mask的每个像素
                if( m[0] < 0 ) m[0] = 0;            // 该点若为负值,先置为零(初始状态下除了四边是分水岭(-1)其余点不应该存在负值?)
                if( m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0) ) // 若该点为非标记点(0),且四邻域存在标记点(>0)
                {
                    // 求出原图中该点到有标记点的四邻域中,梯度值最小(idx)方向的点,将该点和对应的最小梯度值放入q[idex]队列中
                    // 两个像素的r,g,b 三个通道中相差最大的值作为像素间的梯度值
                    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 );     // 将该点在img和mask中的坐标(一维表示)存储在q[idx]队列中
                    m[0] = IN_QUEUE;         // 在mask中标记该点已入队
                }
            }
        }
    
        // find the first non-empty queue
        // 定位到第一个非空的队列
        for( i = 0; i < NQ; i++ )
            if( q[i].first )
                break;
    
        // if there is no markers, exit immediately
        // 若i=256,说明数组q中所有队列为空
        if( i == NQ )
            return;
    
        active_queue = i;
        img = src->data.ptr;
        mask = dst->data.i;
    
        // recursively fill the basins
        // 递归地填满聚水盆
        for(;;)
        {
            int mofs, iofs;         // 将二维图像线性化后图像像素的坐标 mask_offset 和 img_offset 的缩写
            int lab = 0, t;
            int* m;
            uchar* ptr;
    
            // 如果这个灰度上的队列处理完了,就继续找下一个非空队列
            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 );     //q[active_queue]队列中取出一个结点数据
    
            // 找到这个结点记录的img和mask中的像素点,比较该点在mask中的邻居点
            // 邻居点中如果有标记点:该点与邻居点的标记类型不同,则该点为分水岭;该点与邻居点标记类型相同,则该点不变
            // 如果有非标记点:将非标记点扩充为标记点
            m = mask + mofs;
            ptr = img + iofs;
            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;            // 如果该像素点的标记类型和邻居像素标记类型都 > 0 且不同,则为分水岭
            }
            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;
            }
             // 因为标记点要么是初始种子点,要么是初始阶段延伸的种子点的邻接点
             // 该点一定存在一个邻接点是标记点,所以lab一定会赋值一次,不为 0
            assert( lab != 0 );  
            // 若lab > 0 ,则该点被周围的标记点扩充;若lab = -1(WSHED),则该点定义为分水岭,继续下一个循环      
            m[0] = lab;                 
            if( lab == WSHED )
                continue;
            // lab > 0 的情况,确定该点为标记点,且邻居点中存在未标记点的情况,将未标记点扩充为标记点
            if( m[-1] == 0 )
            {
                c_diff( ptr, ptr - 3, t );                  // 计算梯度t
                ws_push( t, mofs - 1, iofs - 3 );           //m[-1]这一未标记的点扩充为标记点,进队
                active_queue = ws_min( active_queue, t );   // 判断,若t < 当前处理的队列active_queue值,则下一次循环中处理q[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 );
    

    }转载自:http://blog.csdn.net/u011375993/article/details/46793655

    展开全文
  • OpenCV中的watershed函数实现的分水岭算法是基于“标记”的分割算法,用于解决传统的分水岭算法过度分割的问题。试想,一副图片中肯定有N多个“山谷”,它们中的很多是我们不想要的。 对于标记...
    #

    声明:如果有写的不对的地方欢迎指正!

    一、分水岭算法

    关于分水岭算法的具体原理我就不说了,网上搜一下很多。OpenCV中的watershed函数实现的分水岭算法是基于“标记”的分割算法,用于解决传统的分水岭算法过度分割的问题。试想,一副图片中肯定有N多个“山谷”,它们中的很多是我们不想要的。
    对于标记的原则,我总结是:你认为它们属于一个区域,就用标记将它们连接起来,对于另一个区域,再用另一个标记连接。就像这样:
    图片中我认为有三个区域,所以做了三个标记。看到这里,你就可以把文章结尾的代码和图片拷到你的工程中试一试效果了。

    二、代码分析

    要想watershed函数,我们先要做一些准备工作:

    1. 做标记

     做标记的原则在上面已经说过了,具体对应代码中on_Mouse函数里面的内容,这是一个鼠标事件回调函数。

    1. else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
    2. {
    3. Point pt(x, y);
    4. if (previousPoint.x < 0)
    5. previousPoint = pt;
    6. //绘制白色线条
    7. line(inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0);
    8. line(srcImage1, previousPoint, pt, Scalar::all(255), 5, 8, 0);
    9. previousPoint = pt;
    10. imshow(WINDOW_NAME1, srcImage1);
    11. }

    在鼠标左键点按并移动时画线,其中的maskImage是一个CV_8UC1类型的掩模,绘制完的结果就是黑色背景上有几条线(标记),srcImage用于实时显示我们做标记的结果。

    2. 寻找轮廓

    对我们做过标记的maskImage寻找轮廓,这部分代码写在if ((char)c == ‘1’)中,findContours函数这里不展开说明。
    1. vector<vector<Point>> contours;
    2. vector<Vec4i> hierarchy;
    3. findContours(maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);

    3. 绘制轮廓

    这里我们声明了一个CV_32S类型的Mat用于绘制轮廓,然后作为watershed的第二个参数传入。对于drawContours()函数的color参数,我们用的是Scalar::all(index + 1),也就是1,2,3这样的数,后面的代码我们会根据这些数绘制可以显示的图像。
    1. for (int index = 0; index < contours.size(); index++)
    2. drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);

    4. 分水岭算法分割

    下面就是调用OpenCV中的watershed函数进行分割
    watershed(srcImage_, maskWaterShed);
    注意它的两个参数:srcImage_是没做任何修改的原图,CV_8UC3类型;
    maskWaterShed声明为CV_32S类型(32位单通道),全部元素为0,然后作为drawContours的第一个参数传入
    (第3步),在上面绘制轮廓,最后作为watershed的参数。另外,参数maskWaterShed是InputOutputArray类型,
    作为输入,也作为输出保存函数调用的结果。
    经过watershed函数的处理,不同区域间的值被置为-1(边界)没有标记清楚的区域被置为0,其他每个区域
    的值保持不变:1,2,…,contours.size()。

    5. 绘制结果图像

    由于watershed的结果中只有-1,0,1,2这样的数,不能直接显示,所以我们还要做进一步的处理将结果显示出来
    1. Mat resImage = Mat(srcImage.size(), CV_8UC3);  // 声明一个最后要显示的图像
    2. for (int i = 0; i < maskImage.rows; i++)
    3. {
    4. for (int j = 0; j < maskImage.cols; j++)
    5. {// 根据经过watershed处理过的maskWaterShed来绘制每个区域的颜色
    6. int index = maskWaterShed.at<int>(i, j);  // 这里的maskWaterShed是经过watershed处理的
    7. if (index == -1)  // 区域间的值被置为-1(边界)
    8. resImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
    9. else if (index <= 0 || index > contours.size())  // 没有标记清楚的区域被置为0
    10. resImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
    11. else  // 其他每个区域的值保持不变:1,2,…,contours.size()
    12. resImage.at<Vec3b>(i, j) = colorTab[index - 1];  // 然后把这些区域绘制成不同颜色
    13. }
    14. }
    15. imshow(“resImage”, resImage);
    显示出来是这样

    我们用三个标记图片分成了三个区域,每个区域用不同的颜色表示,区域间用白色的线隔开。
    我们也可以用
    1. addWeighted(resImage, 0.3, srcImage_, 0.7, 0, resImage);
    2. imshow(“分水岭结果”, resImage);
    将它和原图做加权相加,结果是这样:
    或者将某个区域作为前景显示出来,另外两个区域作为背景显示为黑色,对应代码在if ((char)c == ‘0’)中,这里只贴出结果

    多次点按【0】键还可以显示不同前景。

    三、代码和原图

    1. #include <iostream>
    2. #include <opencv2\opencv.hpp>
    3. using namespace std;
    4. using namespace cv;
    5. Mat srcImage, srcImage_, maskImage;
    6. Mat maskWaterShed; // watershed()函数的参数
    7. Point clickPoint; // 鼠标点下去的位置
    8. void on_Mouse(int event, int x, int y, int flags, void*);
    9. void helpText();
    10. int main(int argc, char** argv)
    11. {
    12. /* 操作提示 */
    13. helpText();
    14. srcImage = imread(“fly.jpg”);
    15. srcImage_ = srcImage.clone(); // 程序中srcImage会被改变,所以这里做备份
    16. maskImage = Mat(srcImage.size(), CV_8UC1); // 掩模,在上面做标记,然后传给findContours
    17. maskImage = Scalar::all(0);
    18. int areaCount = 1; // 计数,在按【0】时绘制每个区域
    19. imshow(“在图像中做标记”, srcImage);
    20. setMouseCallback(“在图像中做标记”, on_Mouse, 0);
    21. while (true)
    22. {
    23. int c = waitKey(0);
    24. if ((char)c == 27) // 按【ESC】键退出
    25. break;
    26. if ((char)c == ‘2’) // 按【2】恢复原图
    27. {
    28. maskImage = Scalar::all(0);
    29. srcImage = srcImage_.clone();
    30. imshow(“在图像中做标记”, srcImage);
    31. }
    32. if ((char)c == ‘1’) // 按【1】处理图片
    33. {
    34. vector<vector<Point>> contours;
    35. vector<Vec4i> hierarchy;
    36. findContours(maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
    37. if (contours.size() == 0) // 如果没有做标记,即没有轮廓,则退出该if语句
    38. break;
    39. cout << contours.size() << “个轮廓” << endl;
    40. maskWaterShed = Mat(maskImage.size(), CV_32S);
    41. maskWaterShed = Scalar::all(0);
    42. /* 在maskWaterShed上绘制轮廓 */
    43. for (int index = 0; index < contours.size(); index++)
    44. drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);
    45. /* 如果imshow这个maskWaterShed,我们会发现它是一片黑,原因是在上面我们只给它赋了1,2,3这样的值,通过代码80行的处理我们才能清楚的看出结果 */
    46. watershed(srcImage_, maskWaterShed); // 注释一
    47. vector<Vec3b> colorTab; // 随机生成几种颜色
    48. for (int i = 0; i < contours.size(); i++)
    49. {
    50. int b = theRNG().uniform(0, 255);
    51. int g = theRNG().uniform(0, 255);
    52. int r = theRNG().uniform(0, 255);
    53. colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    54. }
    55. Mat resImage = Mat(srcImage.size(), CV_8UC3); // 声明一个最后要显示的图像
    56. for (int i = 0; i < maskImage.rows; i++)
    57. {
    58. for (int j = 0; j < maskImage.cols; j++)
    59. { // 根据经过watershed处理过的maskWaterShed来绘制每个区域的颜色
    60. int index = maskWaterShed.at<int>(i, j); // 这里的maskWaterShed是经过watershed处理的
    61. if (index == -1) // 区域间的值被置为-1(边界)
    62. resImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
    63. else if (index <= 0 || index > contours.size()) // 没有标记清楚的区域被置为0
    64. resImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
    65. else // 其他每个区域的值保持不变:1,2,…,contours.size()
    66. resImage.at<Vec3b>(i, j) = colorTab[index - 1]; // 然后把这些区域绘制成不同颜色
    67. }
    68. }
    69. imshow(“resImage”, resImage);
    70. addWeighted(resImage, 0.3, srcImage_, 0.7, 0, resImage);
    71. imshow(“分水岭结果”, resImage);
    72. }
    73. if ((char)c == ‘0’) // 多次点按【0】依次显示每个被分割的区域,需要先按【1】处理图像
    74. {
    75. Mat resImage = Mat(srcImage.size(), CV_8UC3); // 声明一个最后要显示的图像
    76. for (int i = 0; i < maskImage.rows; i++)
    77. {
    78. for (int j = 0; j < maskImage.cols; j++)
    79. {
    80. int index = maskWaterShed.at<int>(i, j);
    81. if (index == areaCount)
    82. resImage.at<Vec3b>(i, j) = srcImage_.at<Vec3b>(i, j);
    83. else
    84. resImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
    85. }
    86. }
    87. imshow(“分水岭结果”, resImage);
    88. areaCount++;
    89. if (areaCount == 4)
    90. areaCount = 1;
    91. }
    92. }
    93. return 0;
    94. }
    95. void on_Mouse(int event, int x, int y, int flags, void*)
    96. {
    97. // 如果鼠标不在窗口中则返回
    98. if (x < 0 || x >= srcImage.cols || y < 0 || y >= srcImage.rows)
    99. return;
    100. // 如果鼠标左键被按下,获取鼠标当前位置;当鼠标左键按下并且移动时,绘制白线;
    101. if (event == EVENT_LBUTTONDOWN)
    102. {
    103. clickPoint = Point(x, y);
    104. }
    105. else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
    106. {
    107. Point point(x, y);
    108. line(maskImage, clickPoint, point, Scalar::all(255), 5, 8, 0);
    109. line(srcImage, clickPoint, point, Scalar::all(255), 5, 8, 0);
    110. clickPoint = point;
    111. imshow(“在图像中做标记”, srcImage);
    112. }
    113. }
    114. void helpText()
    115. {
    116. cout << “先用鼠标在图片窗口中标记出大致的区域” << endl;
    117. cout << “如果想把图片分割为N个区域,就要做N个标记” << endl;
    118. cout << “键盘按键【1】 - 运行的分水岭分割算法” << endl;
    119. cout << “键盘按键【2】 - 恢复原始图片” << endl;
    120. cout << “键盘按键【0】 - 依次分割每个区域(必须先按【1】)” << endl;
    121. cout << “键盘按键【ESC】 - 退出程序” << endl << endl;
    122. }
    123. /* 注释一:watershed(srcImage_, maskWaterShed);
    124. * 注意它的两个参数
    125. * srcImage_是没做任何修改的原图,CV_8UC3类型
    126. * maskWaterShed声明为CV_32S类型(32位单通道),且全部元素为0
    127. * 然后作为drawContours的第一个参数传入,在上面绘制轮廓
    128. * 最后作为watershed的参数
    129. * 另外,watershed的第二个参数maskWaterShed是InputOutputArray类型
    130. * 即作为输入,也作为输出保存函数调用的结果
    131. */


            </div>
                </div> 欢迎使用Markdown编辑器写博客
    

    本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:

    • Markdown和扩展Markdown简洁的语法
    • 代码块高亮
    • 图片链接和图片上传
    • LaTex数学公式
    • UML序列图和流程图
    • 离线写博客
    • 导入导出Markdown文件
    • 丰富的快捷键

    快捷键

    • 加粗 Ctrl + B
    • 斜体 Ctrl + I
    • 引用 Ctrl + Q
    • 插入链接 Ctrl + L
    • 插入代码 Ctrl + K
    • 插入图片 Ctrl + G
    • 提升标题 Ctrl + H
    • 有序列表 Ctrl + O
    • 无序列表 Ctrl + U
    • 横线 Ctrl + R
    • 撤销 Ctrl + Z
    • 重做 Ctrl + Y

    Markdown及扩展

    Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]

    使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。

    本编辑器支持 Markdown Extra ,  扩展了很多好用的功能。具体请参考Github.

    表格

    Markdown Extra 表格语法:

    项目 价格
    Computer $1600
    Phone $12
    Pipe $1

    可以使用冒号来定义对齐方式:

    项目 价格 数量
    Computer 1600 元 5
    Phone 12 元 12
    Pipe 1 元 234

    定义列表

    Markdown Extra 定义列表语法:
    项目1
    项目2
    定义 A
    定义 B
    项目3
    定义 C

    定义 D

    定义D内容

    代码块

    代码块语法遵循标准markdown代码,例如:

    @requires_authorization
    def somefunc(param1='', param2=0):
        '''A docstring'''
        if param1 > param2: # interesting
            print 'Greater'
        return (param2 - param1 + 1) or None
    class SomeClass:
        pass
    >>> message = '''interpreter
    ... prompt'''

    脚注

    生成一个脚注1.

    目录

    [TOC]来生成目录:

    数学公式

    使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.

    • 行内公式,数学公式为:Γ(n)=(n1)!nN
    • 块级公式:

    x=b±b24ac2a

    更多LaTex语法请参考 这儿.

    UML 图:

    可以渲染序列图:

    Created with Raphaël 2.1.2张三张三李四李四嘿,小四儿, 写博客了没?李四愣了一下,说:忙得吐血,哪有时间写。

    或者流程图:

    Created with Raphaël 2.1.2开始我的操作确认?结束yesno
    • 关于 序列图 语法,参考 这儿,
    • 关于 流程图 语法,参考 这儿.

    离线写博客

    即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

    用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

    博客发表后,本地缓存将被删除。 

    用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

    注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

    浏览器兼容

    1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
    2. IE9以下不支持
    3. IE9,10,11存在以下问题
      1. 不支持离线功能
      2. IE9不支持文件导入导出
      3. IE10不支持拖拽文件导入


    1. 这里是 脚注内容.
    展开全文
  • 为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中。先贴出加了注解的代码,以后补充对分水岭算法的解释。#include ...
  • Matlab watershed函数学习(1)

    千次阅读 2018-06-22 19:58:23
    function L =watershed(varargin)%WATERSHED Watershedtransform.% L = WATERSHED(A) computes a label matrixidentifying the watershed% regions of the input matrix A. A can have any dimension. The e...
  • 数字信号处理中通常是取其有限的时间片段进行分析,而不是对无限长的信号进行...在FFT分析中为了减少或消除频谱能量泄漏及栅栏效应,可采用不同的截取函数对信号进行截短,截短函数称为窗函数,简称为窗。 泄漏与...
  • Matlab watershed函数学习(2)

    千次阅读 2018-06-26 20:15:17
    函数的代码 : cc =bwconncomp(imregionalmin(A, conn), conn); L =watershed_meyer(A,conn,cc); imregionalmin 是计算极小值区域 , 所谓极小值区域指的是具有相同像素值 t 的连通区域 , 且区域外边界的像素值均...
  • 之前写过一篇状态机的实用文章,很多朋友说有几个地方有点难度不易理解,今天给大家换种简单写法,使用函数指针的方法实现状态机。状态机简介有限状态机FSM是有限个状态及在这些状态之间的转移和动作等行为的数学...
  • 在日常开发中有很多场景我们都需要用到节流函数和防抖函数,比如:实现输入框的模糊查询因为需要轮询ajax,影响浏览器性能,所以需要用到节流函数;实现手机号、姓名之类的的验证,往往我们只需要验证一次,这个时候...
  • 可以用函数来实现cons、car、cdr(这里使用了高阶函数): ( 运行的结果是这样的: 这就是很简单的数据抽象。 代码上传到github:https://github.com/wx-jiang/zhihu-articles-code/blob/master/4.scm​github.com
  • 第七个参数为‘Callback’回调函数,这也是控件最重要的一个属性,往往通过回调函数来实现应有的功能,后面跟的参数必须是字符串,如本例中的函数为‘callBTest()’,建议此处直接写函数,因为低版本matlab不支持双...
  • opencv3实现分水岭算法-watershed函数

    千次阅读 2015-10-20 00:13:50
    watershed(g_srcImage, midImage); //显示分水岭算法后的图像 imshow("【分水岭算法结果图】", midImage); //遍历得到的掩码图像,用颜色值来替代相同的索引值 //在这之前需要先定义一个随机颜色 ...
  • OpenCV分水岭分割函数watershed()介绍

    千次阅读 2017-11-08 11:15:11
    OpenCV分水岭分割函数watershed()介绍 分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值...
  • 强大的内置数学函数和广泛的函数库。 用户定义函数形式的可扩展性。 语法兼容性 注释行可以以#字符和%字符为前缀; 支持各种基于C的运算符++,-,+ =,* =,/ =; 可以通过级联索引来引用元素而无需创建新变量,...
  • 函数watershed来实现分水岭算法 ,但必须配合轮廓检测才能实现分水岭分割。 具体的函数watershed使用方法请大家参考博文 : http://blog.csdn.net/dcrmg/article/details/52498440使用函数watershed进行分水岭...
  • 为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中。先贴出加了注解的代码,以后补充对分水岭算法的解释。
  • int distanceType: CV_DIST_USER =-1, CV_DIST_L1 =1, CV_DIST_L2 =2, CV_DIST_C =3, CV_DIST_L12 =4, CV_DIST_FAIR =5, CV_DIST_WELSCH =6, CV_DIST_HUBER =7 (1)watershed()分水岭算法函数 void watershed( ...
  • 函数说明: void watershed( InputArray image, InputOutputArray markers ); image:原图像 markers:包含了轮廓点的数据集合 void distanceTransform(InputArray src, OutputArray dst, int distanceType, int ...
  • 1. watershed  void watershed( InputArray image, InputOutputArray markers );...关键是第二个参数 markers:在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每...
  • watershed—基于标记的分水岭算法

    千次阅读 2019-07-01 17:38:53
    算法介绍 原始的分水岭算法对于存在噪声以及梯度不规则的图像极易造成过度分割(存在大量分割区域),解决该问题的一种方案是通过融入预处理步骤来限制允许存在的区域... Opencv函数介绍 void watershed( ...
  • 分水岭算法函数watershed()(待深入) //---------------------------------【头文件、命名空间包含部分】---------------------------- // 描述:包含程序所使用的头文件和命名空间 //-------------...
  • 函数:cv2.watershed(). 理论 任何灰度图像可以被看作是地形面(topographic surface),较大的灰度值表示峰和山,较小的灰度值区域表示山谷。我们首先使用不同的颜色(标签)填充孤立的山谷(局部最小值)。当填充...

空空如也

空空如也

1 2 3 4 5
收藏数 87
精华内容 34
关键字:

watershed函数