分水岭算法

分水岭算法是一种基于区域的图像分割算法。在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近(求梯度)的像素点互相连接起来构成一个封闭的轮廓。

算法原理介绍

我们假设图像中每个像素的灰度值表示该点的海拔高度,那么每一个局部极小值及其影响区域称为集水盆地,而集水盆地的边界则形成分水岭。这些集水的盆地就是我们要识别的物体或区域

对灰度图的地形学解释,我们我们考虑三类点:

1. 局部最小值点,该点对应一个盆地的最低点,当我们在盆地里滴一滴水的时候,由于重力作用,水最终会汇聚到该点。注意:可能存在一个最小值面,该平面内的都是最小值点,如下图a所示,局部最小点可能只有一个,也可能是个平面。

2. 盆地的其它位置点,该位置滴的水滴会汇聚到局部最小点。

3. 盆地的边缘点,是该盆地和其它盆地交接点,即分水岭(边缘点的集合),图b表示,在该点滴一滴水,都会等概率的流向任何一个盆地。
 

                                                                                                 图a

                                                                                                  图b

 

 

 

用不同颜色的水(标签)填充每个孤立的山谷(局部极小值)。当水上升时,根据附近的峰(梯度),不同山谷不同的颜色的水,显然会开始融合。为了避免这种情况,你在水就要融合的地方及时增加屏障(增高水坝)。你继续填满水,建造屏障,直到所有的山峰都被淹没。然后,您创建的屏障会给出分割结果。

在真实图像中,由于噪声点或者其它干扰因素的存在,使用分水岭算法常常存在过度分割的现象,这是因为很多很小的局部极值点的存在,从而导致分割后的图像不能将图像中有意义的区域表示出来。

为了解决过度分割的问题,可以使用基于标记(mark)图像的分水岭算法,就是通过先验知识,来指导分水岭算法,以便获得更好的图像分段效果。通常的mark图像,都是在某个区域定义了一些灰度层级,在这个区域的洪水淹没过程中,水平面都是从定义的高度开始的,这样可以避免一些很小的噪声极值区域的分割。

具体的过程可以浏览

http://www.cmm.mines-paristech.fr/~beucher/wtshed.html#watshed

分水岭算法常用的操作步骤:

彩色图像灰度化,然后再求梯度图,最后在梯度图的基础上进行分水岭算法,求得分段图像的边缘线。

OpenCV代码

#include "opencv2/imgproc/imgproc.hpp"  
#include "opencv2/highgui/highgui.hpp"  

#include <iostream>  

using namespace cv;
using namespace std;

Vec3b RandomColor(int value);  //生成随机颜色函数  

int main(int argc, char* argv[])
{
	Mat image = imread("2.jpg");    //载入RGB彩色图像  
	imshow("Source Image", image);

	//灰度化,滤波,Canny边缘检测  
	Mat imageGray;
	cvtColor(image, imageGray, CV_RGB2GRAY);//灰度转换  
	GaussianBlur(imageGray, imageGray, Size(5, 5), 2);   //高斯滤波  
	imshow("Gray Image", imageGray);
	Canny(imageGray, imageGray, 80, 150);
	imshow("Canny Image", imageGray);

	//查找轮廓  
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
	Mat imageContours = Mat::zeros(image.size(), CV_8UC1);  //轮廓     
	Mat marks(image.size(), CV_32S);   //Opencv分水岭第二个矩阵参数  
	marks = Scalar::all(0);
	int index = 0;
	int compCount = 0;
	for (; index >= 0; index = hierarchy[index][0], compCount++)
	{
		//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点  
		drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);
		drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);
	}

	//我们来看一下传入的矩阵marks里是什么东西  
	Mat marksShows;
	convertScaleAbs(marks, marksShows);
	imshow("marksShow", marksShows);
	imshow("轮廓", imageContours);
	watershed(image, marks);

	//我们再来看一下分水岭算法之后的矩阵marks里是什么东西  
	Mat afterWatershed;
	convertScaleAbs(marks, afterWatershed);
	imshow("After Watershed", afterWatershed);

	//对每一个区域进行颜色填充  
	Mat PerspectiveImage = Mat::zeros(image.size(), CV_8UC3);
	for (int i = 0; i < marks.rows; i++)
	{
		for (int j = 0; j < marks.cols; j++)
		{
			int index = marks.at<int>(i, j);
			if (marks.at<int>(i, j) == -1)
			{
				PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
			}
			else
			{
				PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);
			}
		}
	}
	imshow("After ColorFill", PerspectiveImage);

	//分割并填充颜色的结果跟原始图像融合  
	Mat wshed;
	addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);
	imshow("AddWeighted Image", wshed);

	waitKey();

}

Vec3b RandomColor(int value) //生成随机颜色函数
{
	value = value % 255;  //生成0~255的随机数  
	RNG rng;
	int aa = rng.uniform(0, value);
	int bb = rng.uniform(0, value);
	int cc = rng.uniform(0, value);
	return Vec3b(aa, bb, cc);
}

参考:

https://www.cnblogs.com/mikewolf2002/p/3304118.html

https://blog.csdn.net/dcrmg/article/details/52498440

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值