1. 程式人生 > >使用OpenCV和C++實現的分水嶺演算法(Watershed)

使用OpenCV和C++實現的分水嶺演算法(Watershed)

分水嶺演算法(watershed)是一種比較基本的數學形態學分割演算法,其基本思想是將灰度影象轉換為梯度影象,將梯度值看作高低起伏的山嶺,將區域性極小值及其鄰域看作一個“集水盆”。設想一個個“集水盆”中存在積水,且水位不斷升高,淹沒梯度較低的地方,當水漫過程停止後,影象就可以被分割成幾塊連通區域。

分水嶺演算法有不同的實現方法。本文要實現的是通過人為標註一些種子點,將這些種子點看作集水盆的底部,利用區域增長的方法,完成影象的分割。試圖實現OpenCV中cv::watershed函式的功能,經過測試,與OpenCV相比分割結果相似,但效能差很多。(前者32ms左右,後者8ms左右,原因可能是迴圈中使用了cv::mat來訪問影象中的元素,改用指標速度可能會提高很多)。

OpenCV函式的執行結果:(OpenCV函式對分割邊緣也做了處理,我寫的那個程式沒有)


程式執行結果:


參考:

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<vector>
#include<iostream>
#include<queue>
#include<fstream>
cv::Mat marker_mask;
cv::Mat g_markers;
cv::Mat img0, img, img_gray,wshed;
cv::Point_<int> prev_pt(-1,-1);
using std::vector;
using std::queue;
static void my_watershed(cv::Mat img,cv::Mat& markers,int comp_count);
static void mouse_event(int event,int x, int y,int flags, void*)
{
	if(img.rows==0)
		return;
	 if(event==CV_EVENT_LBUTTONUP||!(flags&CV_EVENT_FLAG_LBUTTON))
		 prev_pt=cv::Point_<int>(-1,-1);
	 else if(event==CV_EVENT_LBUTTONDOWN)
		 prev_pt=cv::Point2i(x,y);
	 else if(event==CV_EVENT_MOUSEMOVE&&(flags&CV_EVENT_FLAG_LBUTTON))
	 {
		 cv::Point2i pt(x,y);
		 if(prev_pt.x<0)
			 prev_pt=pt;
		 cv::line(marker_mask,prev_pt,pt,cv::Scalar(255,255,255),1,8,0);
		 cv::line(img,prev_pt,pt,cv::Scalar(255,255,255),1,8,0);
		 prev_pt=pt;
		 cv::imshow("image",img);
	 }
}
int main()
{
	img0=cv::imread("Lenna.png",1);
	img=img0.clone();
	CvRNG rng = cvRNG(-1); 
	img_gray=img0.clone();
	wshed=img0.clone();
	marker_mask=cv::Mat(cv::Size(img0.cols,img0.rows),8,1);
	g_markers=cv::Mat(cv::Size(img0.cols,img0.rows),CV_32S,1);
	cv::cvtColor(img,marker_mask,CV_BGR2GRAY);
	cv::cvtColor(marker_mask,img_gray,CV_GRAY2BGR);
	for(int i=0;i<marker_mask.rows;i++)
		for(int j=0;j<marker_mask.cols;j++)
			marker_mask.at<unsigned char>(i,j)=0;
	for(int i=0;i<g_markers.rows;i++)
		for(int j=0;j<g_markers.cols;j++)
			g_markers.at<int>(i,j)=0;
	cv::imshow("image",img);
	cv::imshow("watershed transform",wshed);
	cv::setMouseCallback("image",mouse_event,0);
	for(;;)
	{
		int c=cv::waitKey(0);
		if((char)c==27)
			break;
		if((char)c=='r')
		{
			for(int i=0;i<marker_mask.rows;i++)
				for(int j=0;j<marker_mask.cols;j++)
					marker_mask.at<unsigned char>(i,j)=0;
			img0.copyTo(img);
			cv::imshow("image",img);
		}
		if((char)c=='w'||(char)c==' ')
		{
			vector<vector<cv::Point>> contours;
			CvMat* color_tab=0;
			int comp_count=0;
			cv::findContours(marker_mask,contours,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE,cv::Point(0,0));
			for(int i=0;i<g_markers.rows;i++)
				for(int j=0;j<g_markers.cols;j++)
						g_markers.at<int>(i,j)=0;
			vector<vector<cv::Point> >::iterator iter=contours.begin();
			for(int i=0;i<(int)contours.size();i++)
			{
				cv::drawContours(g_markers,contours,i,cv::Scalar::all(comp_count+1),
					1,8,vector<cv::Vec4i>());
				comp_count++;
			}
		
			if(comp_count==0)
				continue;
			color_tab=cvCreateMat(1,comp_count,CV_8UC3);
			for(int 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);
			}
			cv::Mat temp=g_markers.clone();

			double t=(double)cvGetTickCount();
			//my_watershed(img0,g_markers,comp_count);
			cv::watershed(img0,g_markers);
			t=(double)cvGetTickCount()-t;
			std::cout<<"exec time= "<<t/(cvGetTickFrequency()*1000.)<<std::endl;
		for(int i=0;i<g_markers.rows;i++)
			for(int j=0;j<g_markers.cols;j++)
			{
				int idx=g_markers.at<int>(i,j);
				uchar* dst=&wshed.at<uchar>(i,j*3);
				if(idx==-1)
					dst[0]=dst[1]=dst[2]=(uchar)255;
				else if(idx<=0||idx>comp_count)
					dst[0]=dst[1]=dst[2]=(uchar)8;
				else{
					uchar* ptr=color_tab->data.ptr+(idx-1)*3;
					dst[0]=ptr[0];dst[1]=ptr[1];dst[2]=ptr[2];
				}
			}
			cv::addWeighted(wshed,0.5,img_gray,0.5,0,wshed);
			cv::imshow("watershed transform",wshed);
			cvReleaseMat(&color_tab);
		}
	}
    return 0;
}
static void my_watershed(cv::Mat img0,cv::Mat& markers,int comp_count)
{
	cv::Mat gray=cv::Mat(cv::Size(img0.rows,img0.cols),8,1);
	cv::cvtColor(img0,gray,CV_BGR2GRAY);
	cv::Mat imge=cv::Mat(cv::Size(img0.rows,img0.cols),8,1);
	cv::Sobel(gray,imge,CV_8U,1,1);
	vector<queue<cv::Point2i>*>Labeleddata;//影象中各連通區域的點
	queue<cv::Point2i>* pque;//某連通區域已包含的點
	queue<cv::Point2i> quetem; //用於提取某連通區域中輸入種子點中的初始種子點
	vector<int*> SeedCounts;
	int* Array;
	cv:: Point2i temp;
	int row=imge.rows,col=imge.cols;
	cv::Mat marker_saved=markers.clone();
	bool up,down,right,left,uplef,uprig,downlef,downrig;
	int m,n;
	for(int i=0;i<comp_count;i++)
	{
		Array=new int[256];
		SeedCounts.push_back(Array);//統計某waterlevel的各個連通區域中種子點的個數
		pque=new queue<cv::Point2i>[256]; 
		Labeleddata.push_back(pque);//儲存該連通區域中種子生長所得的點		
	}
	for(int i=0;i<row;i++)
		for(int j=0;j<col;j++)
		{
			if(markers.at<int>(i,j)>0)
			{
				temp.x=i;
				temp.y=j;
				quetem.push(temp);
			    int num=markers.at<int>(i,j);
				markers.at<int>(i,j)=-1;//該點已處理,其他種子點生長時將繞過該點
				while(!quetem.empty())
				{
					up=down=right=left=uplef=uprig=downlef=downrig=false;
					temp=quetem.front(); //提取出一個點,在該點的八連通區域內尋找可生長點
					m=temp.x;
					n=temp.y;
					quetem.pop();

					if(m-1>=0)//若上方可生長則新增為新種子
					{
						if(markers.at<int>(m-1,n)==num)
						{
							temp.x=m-1;
							temp.y=n;
							quetem.push(temp);
							markers.at<int>(m-1,n)=-1;
						}
						else{
							up=true;
						}
					}
					if(m-1>=0&&n-1>=0)
					{
						if(markers.at<int>(m-1,n-1)==num)
						{
							temp.x=m-1;
							temp.y=n-1;
							quetem.push(temp);
							markers.at<int>(m-1,n-1)=-1;
						}
						else{
							uplef=true;
						}
					}
					if(m+1<=row-1)
					{
						if(markers.at<int>(m+1,n)==num)
						{
							temp.x=m+1;
							temp.y=n;
							quetem.push(temp);
							markers.at<int>(m+1,n)=-1;
						}
						else{
							down=true;
						}
					}
					if(m+1<=row-1&&n+1<=col-1)
					{
						if(markers.at<int>(m+1,n+1)==num)
						{
							temp.x=m+1;
							temp.y=n+1;
							quetem.push(temp);
							markers.at<int>(m+1,n+1)=-1;
						}
						else{
							downrig=true;
						}
					}
					if(n+1<=col-1)
					{
						if(markers.at<int>(m,n+1)==num)
						{
							temp.x=m;
							temp.y=n+1;
							quetem.push(temp);
							markers.at<int>(m,n+1)=-1;
						}
						else{
							right=true;
						}
					}
					if(m-1>=0&&n+1<=col-1)
					{
						if(markers.at<int>(m-1,n+1)==num)
						{
							temp.x=m-1;
							temp.y=n+1;
							quetem.push(temp);
							markers.at<int>(m-1,n+1)=-1;
						}
						else{
							uprig=true;
						}
					}
					if(n-1>=0)
					{
						if(markers.at<int>(m,n-1)==num)
						{
							temp.x=m;
							temp.y=n-1;
							quetem.push(temp);
							markers.at<int>(m,n-1)=-1;
						}
						else{
							left=true;
						}
					}
					if(m+1<=row-1&&n-1>=0)
					{
						if(markers.at<int>(m+1,n-1)==num)
						{
							temp.x=m+1;
							temp.y=n-1;
							quetem.push(temp);
							markers.at<int>(m+1,n-1)=-1;
						}
						else{
							downlef=true;
						}
					}
					//八連通區域中有未標記點,則該點屬於初始種子點
					if(up||down||right||left||uplef||downlef||uprig||downrig)
					{
						temp.x=m;
						temp.y=n;
						Labeleddata[comp_count-1][imge.at<uchar>(m,n)].push(temp);
						SeedCounts[comp_count-1][imge.at<uchar>(m,n)]++;
					}
				}
			}
		}
		bool active;
		int waterlevel;
		for(waterlevel=0;waterlevel<180;waterlevel++)
		{
			active=true;
			while(active) //當1-count_com個連通區域都無可生長點時結束迴圈
			{
				active=false;
				for(int i=0;i<comp_count;i++)//將區域i中將waterlevel梯度以下的點用於區域增長
				{
					if(!Labeleddata[i][waterlevel].empty())//區域增長,經過多次迭代,直至該區域,該waterlevel無可生長點。
					{
						active=true;	
						while(SeedCounts[i][waterlevel]>0) //SeedCount中保留了前一輪生長後各區域,各waterlevel的種子點個數,本輪生長結束後,將根據Labeleddata中的元素個數更新
						{
							SeedCounts[i][waterlevel]--;
							temp=Labeleddata[i][waterlevel].front();
							Labeleddata[i][waterlevel].pop();
							m=temp.x;
							n=temp.y;
							int num=marker_saved.at<int>(m,n);
							if(m-1>=0)
							{
								if(!marker_saved.at<int>(m-1,n))//上方點未處理過
								{
									temp.x=m-1;
									temp.y=n;
									marker_saved.at<int>(m-1,n)=num;
									if(imge.at<uchar>(m-1,n)<=waterlevel)
										Labeleddata[i][waterlevel].push(temp);
									else{
										Labeleddata[i][imge.at<uchar>(m-1,n)].push(temp); //本次生長不處理,可能在waterlevel變化到某值時再用於生長
										SeedCounts[i][imge.at<uchar>(m-1,n)]++;
									}
								}
							}
							if(m+1<=row-1)
							{
								if(!marker_saved.at<int>(m+1,n))
								{
									temp.x=m+1;
									temp.y=n;
									marker_saved.at<int>(m+1,n)=num;
									if(imge.at<uchar>(m+1,n)<=waterlevel)
										Labeleddata[i][waterlevel].push(temp);
									else{
										Labeleddata[i][imge.at<uchar>(m+1,n)].push(temp);
										SeedCounts[i][imge.at<uchar>(m+1,n)]++;
									}
								}
							}
							if(n+1<=col-1)
							{
								if(!marker_saved.at<int>(m,n+1))
								{
									temp.x=m;
									temp.y=n+1;
									marker_saved.at<int>(m,n+1)=num;
									if(imge.at<uchar>(m,n+1)<=waterlevel)
										Labeleddata[i][waterlevel].push(temp);
									else{
										Labeleddata[i][imge.at<uchar>(m,n+1)].push(temp);
										SeedCounts[i][imge.at<uchar>(m,n+1)]++;
									}
								}
							}
							if(n-1>=0)
							{
								if(!marker_saved.at<int>(m,n-1))
								{
									temp.x=m;
									temp.y=n-1;
									marker_saved.at<int>(m,n-1)=num;
									if(imge.at<uchar>(m,n-1)<=waterlevel)
										Labeleddata[i][waterlevel].push(temp);
									else
									{
										Labeleddata[i][imge.at<uchar>(m,n-1)].push(temp);
										SeedCounts[i][imge.at<uchar>(m,n-1)]++;
									}
								}
							}
						}
							SeedCounts[i][waterlevel]=Labeleddata[i][waterlevel].size();
						}
						
					}
			}
		}
		markers=marker_saved.clone();
}