分水嶺是區域分割三個方法的最後一個,對於前景背景的分割有不錯的效果。
分水嶺分割方法,是一種基於拓撲理論的數學形態學的分割方法,其基本思想是把圖像看作是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的海拔高度,每一個局部極小值及其影響區域稱爲集水盆,而集水盆的邊界則形成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明。在每一個局部極小值表面,刺穿一個小孔,然後把整個模型慢慢浸入水中,隨着浸入的加深,每一個局部極小值的影響域慢慢向外擴展,在兩個集水盆匯合處構築大壩,即形成分水嶺。
分水嶺算法一般和區域生長法或聚類分析法相結合。
分水嶺算法一般用於分割感興趣的圖像區域,應用如細胞邊界的分割,分割出相片中的頭像等等
分水嶺算法主要用於圖像分段,通常是把一副彩色圖像灰度化,然後再求梯度圖,最後在梯度圖的基礎上進行分水嶺算法,求得分段圖像的邊緣線。
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,歡迎圖像識別與圖像算法,共同學習與交流