分水岭是区域分割三个方法的最后一个,对于前景背景的分割有不错的效果。
分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
分水岭算法一般和区域生长法或聚类分析法相结合。
分水岭算法一般用于分割感兴趣的图像区域,应用如细胞边界的分割,分割出相片中的头像等等
分水岭算法主要用于图像分段,通常是把一副彩色图像灰度化,然后再求梯度图,最后在梯度图的基础上进行分水岭算法,求得分段图像的边缘线。
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,欢迎图像识别与图像算法,共同学习与交流