Opencv圖像識別從零到精通(26)---分水嶺

       分水嶺是區域分割三個方法的最後一個,對於前景背景的分割有不錯的效果。

      分水嶺分割方法,是一種基於拓撲理論的數學形態學的分割方法,其基本思想是把圖像看作是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的海拔高度,每一個局部極小值及其影響區域稱爲集水盆,而集水盆的邊界則形成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明。在每一個局部極小值表面,刺穿一個小孔,然後把整個模型慢慢浸入水中,隨着浸入的加深,每一個局部極小值的影響域慢慢向外擴展,在兩個集水盆匯合處構築大壩,即形成分水嶺。

      分水嶺算法一般和區域生長法或聚類分析法相結合。

      分水嶺算法一般用於分割感興趣的圖像區域,應用如細胞邊界的分割,分割出相片中的頭像等等

      分水嶺算法主要用於圖像分段,通常是把一副彩色圖像灰度化,然後再求梯度圖,最後在梯度圖的基礎上進行分水嶺算法,求得分段圖像的邊緣線。

opencv中的算法是先把輸入圖像轉化成梯度圖(標量)

     如果把梯度圖看成是一個地形的話,就會發現,梯度高的地方就成了山脈,梯度低的地方就是山谷

我們經過標記爲不同的區域後,就從各個標記的地方注水進去,注入的水越來越多的時候,就會出現把流過低些的山脈,從而流到別的山谷中,那麼他們就連一了一片區域。

區域分割的要求是把不同的標記分割成不同的地方。所以如果一直注水,可能就會覆蓋別的區域了。這時算法就採取某種方法,修大壩使標記的不同區域不會因爲注水而相連

他們會互不相干的擴張領地,直到把整個領地都擴張完爲止。

再看看下圖,是一個圖像的地形拓撲


對灰度圖的地形學解釋,我們我們考慮三類點

1. 局部最小值點,該點對應一個盆地的最低點,當我們在盆地裏滴一滴水的時候,由於重力作用,水最終會匯聚到該點。注意:可能存在一個最小值面,該平面內的都是最小值點。

2. 盆地的其它位置點,該位置滴的水滴會匯聚到局部最小點。

3. 盆地的邊緣點,是該盆地和其它盆地交接點,在該點滴一滴水,會等概率的流向任何一個盆地。

<span style="font-size:18px;">函數聲明:CV_EXPORTS_W void watershed( InputArray image, InputOutputArray markers );

                         InputArray image  要分割的原始圖片 

                         InputOutputArray markers 標記數組,非零的32位有符號的int型數組,用於標記出要分割的關鍵                        點,進而區域生長,擴展出感興趣的區域。</span>

<span style="font-size:18px;">#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
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* );


int main( int argc, char** argv )
{	

	//【1】載入原圖並顯示,初始化掩膜和灰度圖
	g_srcImage = imread("lena.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, 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( 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( WINDOW_NAME2, watershedImage );
		}
	}

	return 0;
}

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 == 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), 5, 8, 0 );
		line( g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0 );
		prevPt = pt;
		imshow(WINDOW_NAME1, g_srcImage);
	}
}
</span>


matlab

這裏給出一個最簡單,不過有過度切割的現象,還有很多的好的標記分割方法,想學習的可以再深入,這裏給出的是入門,效果不是太好

<span style="font-size:18px;">clear,clc%三種方法進行分水嶺分割
%讀入圖像
filename='pears.png';
f=imread(filename);
Info=imfinfo(filename);
if Info.BitDepth>8
f=rgb2gray(f);
end
b=im2bw(f,graythresh(f));%二值化,注意應保證集水盆地的值較低(爲0),否則就要對b取反
d=bwdist(b); %求零值到最近非零值的距離,即集水盆地到分水嶺的距離
l=watershed(-d); %matlab自帶分水嶺算法,l中的零值即爲風水嶺
w=l==0; %取出邊緣
g=b&~w; %用w作爲mask從二值圖像中取值
figure
subplot(2,3,1),
imshow(f);
subplot(2,3,2),
imshow(b);
subplot(2,3,3),
imshow(d);
subplot(2,3,4),
imshow(l);
subplot(2,3,5),
imshow(w);
subplot(2,3,6),
imshow(g);</span>



圖像識別算法交流 QQ羣:145076161,歡迎圖像識別與圖像算法,共同學習與交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章