图像分割 3 opencv分水岭算法源码研究

                                                                                     分水岭算法实现

  1.输入原图像和掩模图像,掩模图像为手动标记的线,每个线对应的在掩模图像中的灰度不一样。(在maskshows图像中可以看出。)

  2.统计原图像中除边框外,每个像素与其周围上下左右四个邻居的差别,差别的定义是,两个三通道图像BGR三个通道相减的绝对值的最小值。得出四个邻居差别最小值,根据这个最值放入相应队列中。比如与左邻居在红色通道差别最小为10,则放入10这个队列中。队列最多有0—255  也就是256个

  3.队列中每个元素包含,像素点在掩模图像和原图像的位置。遍历整个原图像将除边框外每个像素点情况放入对应队列中。

  4.从差别最小的队列依次取出,如果某个像素点在掩模图像值大于0,说明这个像素点是标记点。如果小于等于0,且其四个邻居只有一个大于0,此像素点在掩模图像的值等于邻居在掩模图像中的值。如果此像素点四个邻居中有两个大于0,而且不相同,那么此像素点在掩模图像中的值记为-1,就是分水岭点。

 5取出256个队列所有值,分水岭算法完成。

 256个队列结构: http://www.xuebuyuan.com/2164320.html

漫水填充过程可以参考:http://blog.csdn.net/tangketan/article/details/39757513

参考《opencv3编程入门》书中的分水岭算法

//--------------------------------------【程序说明】-------------------------------------------
//		程序描述:分水岭算法综合示例
//		开发测试所用操作系统: Windows 10 64bit
//		开发测试所用IDE版本:Visual Studio 2015
//		开发测试所用OpenCV版本:	3.1.0
//		2014年06月 Created by @浅墨_毛星云
//		2014年11月 Revised by @浅墨_毛星云
//      2016年3月  changed by yuyangyg
//------------------------------------------------------------------------------------------------



//---------------------------------【头文件、命名空间包含部分】----------------------------
//		描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include<fstream>

using namespace cv;
using namespace std;

//-----------------------------------【宏定义部分】-------------------------------------------- 
//  描述:定义一些辅助宏 
//------------------------------------------------------------------------------------------------ 
#define WINDOW_NAME1 "【程序窗口1】"        //为窗口标题定义的宏 
#define WINDOW_NAME2 "【分水岭算法效果图】"        //为窗口标题定义的宏

//-----------------------------------【全局函变量声明部分】--------------------------------------
//		描述:全局变量的声明
//-----------------------------------------------------------------------------------------------
Mat g_maskImage, g_srcImage;
Point prevPt(-1, -1);

//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数的声明
//-----------------------------------------------------------------------------------------------
static void ShowHelpText();
static void on_Mouse(int event, int x, int y, int flags, void*);

void watershed1(InputArray _src, InputOutputArray _markers);
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main(int argc, char** argv)
{
	//【0】改变console字体颜色
	system("color 6F");

	//【0】显示帮助文字
	ShowHelpText();

	//【1】载入原图并显示,初始化掩膜和灰度图
	g_srcImage = imread("1.jpg", 1);
	imshow(WINDOW_NAME1, g_srcImage);
	Mat srcImage, grayImage;
	g_srcImage.copyTo(srcImage);
	cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);
	cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);
	g_maskImage = Scalar::all(0);//元素初始化为零

	//【2】设置鼠标回调函数
	setMouseCallback(WINDOW_NAME1, on_Mouse, 0);

	//【3】轮询按键,进行处理
	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' || (char)c == ' ')
		{
			//定义一些参数
			int i, j, compCount = 0;
			vector<vector<Point> > contours;
			vector<Vec4i> hierarchy;

			//寻找轮廓
			findContours(g_maskImage, contours, hierarchy, CV_RETR_CCOMP, CV_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);//在和srcImage同样大小的空白图像上画轮廓

			//compCount为零时的处理
			if (compCount == 0)
				continue;

			//生成随机颜色
			vector<Vec3b> colorTab;
			for (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();
			Mat maskShows;
			convertScaleAbs(maskImage*20, maskShows);
			imshow("maskShows", maskShows);
			watershed1(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];
				}
			
			imshow("watershedImage", watershedImage);
			//混合灰度图和分水岭效果图并显示最终的窗口
			watershedImage = watershedImage*0.5 + grayImage*0.5;
			imshow(WINDOW_NAME2, watershedImage);
			
		}
	}

	return 0;
}


//-----------------------------------【onMouse( )函数】---------------------------------------
//		描述:鼠标消息回调函数
//-----------------------------------------------------------------------------------------------
static 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 == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON))
		prevPt = Point(-1, -1);
	else if (event == CV_EVENT_LBUTTONDOWN)
		prevPt = Point(x, y);

	//鼠标左键按下并移动,绘制出白色线条
	else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
	{
		Point pt(x, y);
		if (prevPt.x < 0)
			prevPt = pt;
		line(g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0);
		line(g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0);
		prevPt = pt;
		imshow(WINDOW_NAME1, g_srcImage);
	}
}


//-----------------------------------【ShowHelpText( )函数】----------------------------------  
//      描述:输出一些帮助信息  
//----------------------------------------------------------------------------------------------  
static void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	//printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
	//printf("\n\n\t\t\t此为本书OpenCV2版的第77个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION);
	printf("\n\n  ----------------------------------------------------------------------------\n");

	//输出一些帮助信息  
	printf("\n\n\n\t欢迎来到【分水岭算法】示例程序~\n\n");
	printf("\t请先用鼠标在图片窗口中标记出大致的区域,\n\n\t然后再按键【1】或者【SPACE】启动算法。"
		"\n\n\t按键操作说明: \n\n"
		"\t\t键盘按键【1】或者【SPACE】- 运行的分水岭分割算法\n"
		"\t\t键盘按键【2】- 恢复原始图片\n"
		"\t\t键盘按键【ESC】- 退出程序\n\n\n");
}



opencv中分水岭分割源码

//#include "precomp.hpp"


/****************************************************************************************\
*                                       Watershed                                        *
\****************************************************************************************/

namespace cv
{
	// A node represents a pixel to label
	struct WSNode  //像素节点
	{
		int next;
		int mask_ofs;  //相对于mask图像的偏移
		int img_ofs;   //相对于原图像的偏移
	};

	// Queue for WSNodes   //像素节点队列
	struct WSQueue
	{
		WSQueue() { first = last = 0; }
		int first, last;
	};


	static int
		allocWSNodes(std::vector<WSNode>& storage)//分配空间
	{
		int sz = (int)storage.size();
		int newsz = MAX(128, sz * 3 / 2);//空间扩展为128,或者sz的1.5倍???

		storage.resize(newsz);
		if (sz == 0)
		{
			storage[0].next = 0;
			sz = 1;
		}
		for (int i = sz; i < newsz - 1; i++)
			storage[i].next = i + 1;
		storage[newsz - 1].next = 0;
		return sz;
	}

}


void watershed1(InputArray _src, InputOutputArray _markers)
{
	// Labels for pixels
	const int IN_QUEUE = -2; // Pixel visited  加入到队列记做-2
	const int WSHED = -1;    // Pixel belongs to watershed 属于mask记做-1
						     // possible bit values = 2^8
	const int NQ = 256;      //队列长度,和灰度等级相同

	Mat src = _src.getMat(), dst = _markers.getMat();
	Size size = src.size();

	// Vector of every created node
	std::vector<WSNode> storage;
	int free_node = 0, node;
	// Priority queue of queues of nodes
	// from high priority (0) to low priority (255)
	WSQueue q[NQ];                     //数组每一个元素都是队列
	// Non-empty queue with highest priority
	int active_queue;
	int i, j;
	// Color differences
	int db, dg, dr;
	int subs_tab[513];                 //255+256=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])

	// Create a new node with offsets mofs and iofs in queue idx

#define ws_push(idx,mofs,iofs)          \
    {                                       \
        if( !free_node )                    \
            free_node = allocWSNodes( storage );\
        node = free_node;                   \
        free_node = storage[free_node].next;\
        storage[node].next = 0;             \
        storage[node].mask_ofs = mofs;      \
        storage[node].img_ofs = iofs;       \
        if( q[idx].last )                   \
            storage[q[idx].last].next=node; \
        else                                \
            q[idx].first = node;            \
        q[idx].last = node;                 \
    }

	// Get next node from queue idx
#define ws_pop(idx,mofs,iofs)           \
    {                                       \
        node = q[idx].first;                \
        q[idx].first = storage[node].next;  \
        if( !storage[node].next )           \
            q[idx].last = 0;                \
        storage[node].next = free_node;     \
        free_node = node;                   \
        mofs = storage[node].mask_ofs;      \
        iofs = storage[node].img_ofs;       \
    }

	// Get highest absolute channel difference in diff
#define c_diff(ptr1,ptr2,diff)           \
    {                                        \
        db = std::abs((ptr1)[0] - (ptr2)[0]);\
        dg = std::abs((ptr1)[1] - (ptr2)[1]);\
        dr = std::abs((ptr1)[2] - (ptr2)[2]);\
        diff = ws_max(db,dg);                \
        diff = ws_max(diff,dr);              \
        assert( 0 <= diff && diff <= 255 );  \
    } //找出两个像素点在BGR三个通道中相差最大的值记为diff

	CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1);
	CV_Assert(src.size() == dst.size()); //输入图像要求是CV_8UC3,格式CV_32SC1

	// Current pixel in input image  步长 = 一行字节数 / sizeof(像素数据类型)
	const uchar* img = src.ptr();  //函数Mat.ptr(row)其中row表示行索引.
	// Step size to next row in input image
	int istep = int(src.step / sizeof(img[0])); //原图像到下一行需要步数。
	cout << "src.step="<<src.step<<" sizeof(img[0]))="<<sizeof(img[0])<<endl;//img[0]是uchar型故为一个字节
	// Current pixel in mask image
	int* mask = dst.ptr<int>();//mask[i]表示第i行的头指针
	                           
	// Step size to next row in mask image
	int mstep = int(dst.step / sizeof(mask[0]));//mask图像到下一行需要的步数CV_32SC1为所以占四个字节
	cout <<"dst.step="<<dst.step<<" sizeof(mask[0])="<< sizeof(mask[0])<<endl;

	for (i = 0; i < 256; i++)
		subs_tab[i] = 0;
	for (i = 256; i <= 512; i++)
		subs_tab[i] = i - 256; //用来max和min

	// 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;// mask的首行和末行画成分水岭

	// 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; // boundary pixels  mask的左右边缘记为分水岭

		for (j = 1; j < size.width - 1; j++)
		{
			int* m = mask + j;
			if (m[0] < 0) m[0] = 0;//若该点为负数,记做0
			if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0))//分水岭点的四周都不是分水岭点
			{
				// Find smallest difference to adjacent markers找出与分水岭点在单一像素通道上,相差最小的值
				const 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);
				}

				// Add to according queue
				assert(0 <= idx && idx <= 255);
				ws_push(idx, i*mstep + j, i*istep + j * 3);     //将该点在img和mask中的坐标(一维表示)存储在q[idx]队列中
				m[0] = IN_QUEUE;                                //已经加到队列,下面一行遇到这个点怎么处理??
			}
		}
	}

	// find the first non-empty queue找到第一个非空队列
	for (i = 0; i < NQ; i++)
		if (q[i].first)
			break;      //比如 q[0]表示所有标记区附近与标记区距离为0的点   

	// if there is no markers, exit immediately
	if (i == NQ)    //与邻居像素差为i,如果为256说明所有队列为空
		return;

	active_queue = i;
	img = src.ptr();
	mask = dst.ptr<int>();

	// recursively fill the basins递归地填满聚水盆
	for (;;) 
	{
		int mofs, iofs; // 将二维图像线性化后图像像素的坐标 mask_offset 和 img_offset 的缩写
		int lab = 0, t;
		int* m;
		const uchar* ptr;

		// Get non-empty queue with highest priority
		// Exit condition: empty priority queue
		
		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;
			// 如果这个灰度上的队列处理完了,就继续找下一个非空队列
		}

		// Get next node
		ws_pop(active_queue, mofs, iofs); // 从q[active_queue]队列中取出一个结点数据

		// Calculate pointer to current pixel in input and marker image
		m = mask + mofs;
		ptr = img + iofs;

		// Check surrounding pixels for labels
		// to determine label for current pixel
		t = m[-1]; // Left 左邻居的标记
		if (t > 0) lab = t;
		t = m[1]; // Right
		if (t > 0)
		{
			if (lab == 0) lab = t;
			else if (t != lab) lab = WSHED; //左右邻居都大于零且左右邻居的标记不一样,这该点为分水岭点若只有一个邻居大于零,该邻居的标记赋给这个点。
		}                                   //输入图像时要给mask的每一个标记值不同,可以有maskshow图像看出。
		t = m[-mstep]; // Top
		if (t > 0)
		{
			if (lab == 0) lab = t;
			else if (t != lab) lab = WSHED;
		}
		t = m[mstep]; // Bottom
		if (t > 0)
		{
			if (lab == 0) lab = t;
			else if (t != lab) lab = WSHED;// 如果该像素点的标记类型和四个邻居中任意一个像素标记类型都 > 0 且不同,则为分水岭
		}

		// Set label to current pixel in marker image
	    // 因为标记点要么是初始种子点,要么是初始阶段延伸的种子点的邻接点
		  // 该点一定存在一个邻接点是标记点,所以lab一定会赋值一次,不为 0
		assert(lab != 0);
		m[0] = lab;// 若lab > 0 ,则该点被周围的标记点扩充;若lab = -1(WSHED),则该点定义为分水岭,继续下一个循环 

		if (lab == WSHED)
			continue;

		// Add adjacent, unlabeled pixels to corresponding queue
		if (m[-1] == 0)
		{
			c_diff(ptr, ptr - 3, 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;
		}
	}
}



CV_IMPL void cvWatershed(const CvArr* _src, CvArr* _markers)
{
	cv::Mat src = cv::cvarrToMat(_src), markers = cv::cvarrToMat(_markers);
	cv::watershed(src, markers);
}



效果图




参考《opencv3编程入门》

         opencv3.10源码

           http://blog.csdn.net/tangketan/article/details/39757513  

          http://blog.csdn.net/u011375993/article/details/46793655

          http://www.xuebuyuan.com/2164320.html


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值