基于距离变换和分水岭算法的图像分割

一、图像分割的定义

根据灰度、颜色、纹理和形状等特征,把图像分成若干个特定的、具有独特性质的区域,这些特征在同一区域内呈现出相似性,而在不同区域间呈现出明显的差异性,并提出感兴趣目标的技术和过程。 它是由图像处理到图像分析的关键步骤。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像索赋予相同的编号。
目的是将图像中像素根据一定的规则分为若干(N)个聚(cluster)集合,每个集合包含一类像素。将对象在背景提取出来。如下图所示:
基于距离变换和分水岭算法的图像分割_第1张图片
基于距离变换和分水岭算法的图像分割_第2张图片

二、图像分割的方法

图像分割方法:基于阈值的分割方法、基于边缘的分割方法、基于区域的分割方法、基于特定理论的分割方法等。图像分割方法概述如下:

1、基于阈值的分割方法

阈值法的基本思想是基于图像的灰度特征来计算一个或多个灰度阈值,并将图像中每个像素的灰度值与阈值相比较,最后将像素根据比较结果分到合适的类别中。因此,该类方法最为关键的一步就是按照某个准则函数来求解最佳灰度阈值。

2、基于边缘的分割方法

所谓边缘是指图像中两个不同区域的边界线上连续的像素点的集合,是图像局部特征不连续性的反映,体现了灰度、颜色、纹理等图像特性的突变。通常情况下,基于边缘的分割方法指的是基于灰度值的边缘检测,它是建立在边缘灰度值会呈现出阶跃型或屋顶型变化这一观测基础上的方法。

阶跃型边缘两边像素点的灰度值存在着明显的差异,而屋顶型边缘则位于灰度值上升或下降的转折处。对于阶跃状边缘常用微分算子进行边缘检测,其位置对应一阶导数的极值点,对应二阶导数的过零点(零交叉点)。常用的一阶微分算子有Roberts算子Prewitt算子Sobel算子,二阶微分算子有Laplace算子Kirsh算子等。在实际中各种微分算子常用小区域模板来表示,微分运算是利用模板和图像卷积来实现。这些算子对噪声敏感,只适合于噪声较小不太复杂的图像。

3、基于区域的分割方法

此类方法是将图像按照相似性准则分成不同的区域,主要包括种子区域生长法、区域分裂合并法和分水岭法等几种类型。

种子区域生长法是从一组代表不同生长区域的种子像素开始,接下来将种子像素邻域里符合条件的像素合并到种子像素所代表的生长区域中,并将新添加的像素作为新的种子像素继续合并过程,直到找不到符合条件的新像素为止。该方法的关键是选择合适的初始种子像素以及合理的生长准则。

区域分裂合并法(Gonzalez,2002)的基本思想是首先将图像任意分成若干互不相交的区域,然后再按照相关准则对这些区域进行分裂或者合并从而完成分割任务,该方法既适用于灰度图像分割也适用于纹理图像分割。

分水岭法(Meyer,1990)是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。该算法的实现可以模拟成洪水淹没的过程,图像的最低点首先被淹没,然后水逐渐淹没整个山谷。当水位到达一定高度的时候将会溢出,这时在水溢出的地方修建堤坝,重复这个过程直到整个图像上的点全部被淹没,这时所建立的一系列堤坝就成为分开各个盆地的分水岭。分水岭算法对微弱的边缘有着良好的响应,但图像中的噪声会使分水岭算法产生过分割的现象。

4、基于图论的分割方法

此类方法把图像分割问题与图的最小割(min cut)问题相关联。首先将图像映射为带权无向图G=,图中每个节点N∈V对应于图像中的每个像素,每条边∈E连接着一对相邻的像素,边的权值表示了相邻像素之间在灰度、颜色或纹理方面的非负相似度。而对图像的一个分割s就是对图的一个剪切,被分割的每个区域C∈S对应着图中的一个子图。而分割的最优原则就是使划分后的子图在内部保持相似度最大,而子图之间的相似度保持最小。基于图论的分割方法的本质就是移除特定的边,将图划分为若干子图从而实现分割。目前所了解到的基于图论的方法有GraphCutGrabCutRandom Walk等。

5、基于能量泛函的分割方法

该类方法主要指的是活动轮廓模型(active contour model)以及在其基础上发展出来的算法,其基本思想是使用连续曲线来表达目标边缘,并定义一个能量泛函使得其自变量包括边缘曲线,因此分割过程就转变为求解能量泛函的最小值的过程,一般可通过求解函数对应的欧拉(Euler.Lagrange)方程来实现,能量达到最小时的曲线位置就是目标的轮廓所在。按照模型中曲线表达形式的不同,活动轮廓模型可以分为两大类:参数活动轮廓模型(parametric active contour model)和几何活动轮廓模型(geometric active contour model)。

参数活动轮廓模型是基于Lagrange框架,直接以曲线的参数化形式来表达曲线,最具代表性的是由Kasset a1(1987)所提出的Snake模型。该类模型在早期的生物图像分割领域得到了成功的应用,但其存在着分割结果受初始轮廓的设置影响较大以及难以处理曲线拓扑结构变化等缺点,此外其能量泛函只依赖于曲线参数的选择,与物体的几何形状无关,这也限制了其进一步的应用。

几何活动轮廓模型的曲线运动过程是基于曲线的几何度量参数而非曲线的表达参数,因此可以较好地处理拓扑结构的变化,并可以解决参数活动轮廓模型难以解决的问题。而水平集(Level Set)方法(Osher,1988)的引入,则极大地推动了几何活动轮廓模型的发展,因此几何活动轮廓模型一般也可被称为水平集方法。

三、Opencv中相应的API

(1)距离变换:计算源图像的每个像素到最近的零像素的距离.

distanceTransform(

InputArray src, //8位单通道(二进制)源图像

OutputArray dst, //输出具有计算距离的图像,8位或32位浮点的单通道图像,大小与src相同.

OutputArray labels, //输出二维数组标签labels(离散维诺Voronoi图)

int distanceType, //距离类型 = DIST_L1/DIST_L2

int maskSize, //距离变换的掩膜大小,DIST_MASK_3(maskSize = 3x3),最新的支持DIST_MASK_5(mask = 5x5),推荐3x3

int labelType=DIST_LABEL_CCOMP //要生成的标签数组的类型

)

(2)分水岭:用分水岭算法执行基于标记的图像分割

watershed(

InputArray image, //输入8位3通道图像(输入锐化原图8位3通道)

InputOutputArray markers // 输入或输出32位单通道的标记,和图像一样大小。(输入高峰轮廓标记)

)

四、具体算法的实现

#include
#include "stdafx.h"
#include

using namespace cv;
using namespace std;


int main()
{
	Mat src = imread("D:\\cv_study\\分水岭图像分割\\ma.jpg");
	if (src.empty())
		cout << "read error" << endl;
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			if (src.at(i, j) == Vec3b(255, 255, 255))
			{
				src.at(i, j)[0] = 0;
				src.at(i, j)[1] = 0;
				src.at(i, j)[2] = 0;
			}
		}
	}
	imshow("黑背景", src);
	Mat kernel = (Mat_(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
	Mat imgLaplance;
	Mat sharpenImg ;
	filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
	src.convertTo(sharpenImg, CV_32F);
	Mat resultImg = sharpenImg - imgLaplance;
	resultImg.convertTo(resultImg, CV_8UC3);
	imshow("边缘锐化", resultImg);
	src = resultImg;
	Mat grayImg;
	cvtColor(resultImg, grayImg, CV_BGR2GRAY);
	threshold(grayImg, grayImg, 80, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("二值化", grayImg);
	Mat distImg;
	distanceTransform(grayImg, distImg, DIST_L1,5, CV_32F);
	normalize(distImg, distImg, 0, 1, NORM_MINMAX);
	imshow("距离变换", distImg);
	threshold(distImg, distImg, 0.55, 1, THRESH_BINARY); //全局阈值0.4-1
	imshow("distance binary image", distImg);
	Mat k1 = Mat::ones(Size(7, 7), CV_8UC1);
	erode(distImg, distImg, k1);
	imshow("腐蚀高峰", distImg);
	Mat dist_8u;
	distImg.convertTo(dist_8u, CV_8U);
	vector>contours;
	vectorh;
	Mat markers = Mat::zeros(src.size(), CV_32SC1);
	findContours(dist_8u, contours, h, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
	for (size_t i = 0; i < contours.size(); i++) 
	{
		drawContours(markers, contours, i, Scalar::all(static_cast(i) + 1), -1); //绘制轮廓并填充:输入,轮廓点,轮廓索引号,颜色(转为int,背景色就是从0开始的,+1避免从0开始),thickness(正值代表线宽,负值代表填充)
	}
	circle(markers, Point(9, 9), 5, Scalar(255, 255, 255), -1); //左上角画圆:原图,圆心,半径,线颜色,thickness(正值代表圆线宽,负值代表填充圆)
	imshow("marker image", markers * 1000);
	watershed(src, markers);
    Mat mark = Mat::zeros(src.size(), CV_8UC1);
	markers.convertTo(mark, CV_8UC1);
	bitwise_not(mark, mark, Mat()); //位取反,变黑背景
	imshow("watershed image", mark);
    vector colors;
	for (size_t i = 0; i < contours.size(); i++) 
	{  //生成随机颜色
		int r = theRNG().uniform(0, 255);
		int g = theRNG().uniform(0, 255);
		int b = theRNG().uniform(0, 255);
		colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
	}
	Mat dst = Mat::zeros(markers.size(), CV_8UC3);
	for (int row = 0; row < markers.rows; row++) 
	{
		for (int col = 0; col < markers.cols; col++)
		{
			int index = markers.at(row, col); //取得分水岭分割对象的不同灰度等级的值

			if (index > 0 && index <= static_cast(contours.size())) 
			{
				dst.at(row, col) = colors[index - 1];  //每一个非0灰度等级着一种色
			}
			else 
			{
				dst.at(row, col) = Vec3b(0, 0, 0); //非对象着黑色
			}
		}
	}
	imshow("Final Result", dst);
    waitKey();
    return 0;
}
结果如下:
基于距离变换和分水岭算法的图像分割_第3张图片
基于距离变换和分水岭算法的图像分割_第4张图片
基于距离变换和分水岭算法的图像分割_第5张图片
基于距离变换和分水岭算法的图像分割_第6张图片
基于距离变换和分水岭算法的图像分割_第7张图片
处理结果不太好,希望大家指正,谢谢!








你可能感兴趣的:(opencv)