欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

opencv8-图像分割-分水岭算法

程序员文章站 2022-05-16 11:20:56
...

因为现在在做的项目里牵涉到图像分割,这两天一直在找各种资料。终于可以更新了!

先补充点基础知识:

数字图像的质量取决于层次(Hierarchy)、对比度、清晰度。

层次越多视觉效果就越好。

对比度=最大亮度/最小亮度。

在对图像的研究和应用中,人们往往仅对图像中的某些部分感兴趣,这些部分一般称为目标或前景。这就是图像分割的意义啦!

概念:

图像分割就是指根据图像的灰度、颜色、纹理和形状等特征把图像划分成若干互不交叠的区域,并使这些特征在同一区域内呈现出相似性。

换句话说图像分割是基于亮度值的不连续性和相似性。

-不连续性是基于亮度的不连续变化分割图像,如边缘

-根据制定的准则将图像分割为相似的区域,如阈值处理、区域生长、区域分离和合并。

目的:

为了辨识和分析目标,图像分割就是要要把图像分成各有特性的区域并提取出感兴趣目标。

四大类方法:

1、基于阈值的分割方法

以像素性质的分布进行阈值处理。

要有一个可以比较的阈值,可以提前选取好,或者算法自动选取。这个方法主要是基于图像的灰度特征,把图像中每个像素的灰度值都跟阈值比较,得到比较结果后进行分类,相似灰度的像素在一个类中。所以此方法最重要的就是如何选取一个好的阈值。

2、基于边缘的分割方法

边缘处有很多具有明显差异的灰度不连续处,常用间断检测、边缘连接和边界检测。

3、基于区域的分割方法

直接搜寻区域进行分割。主要有种子区域生长法、区域分裂合并法和分水岭法。

4、基于图论的分割方法

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

今天先给大家介绍经典的图像分割算法-分水岭

函数原型:

 void watershed( InputArray image, InputOutputArray markers );

输入的是一个32位的有符号整数标记图,每个非零的像素表示一个标记。我们将图像中已知属于某个区域的像素进行标记。基于这个初始标记,分水岭算法开始确定其他像素的归属区域。

原理:

将图像看做拓扑结构的地图,那么均匀区域对应的是被陡峭地区包围的平坦盆地。但这个算法存在过分割问题。

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
//#include "watershedSegmenter.h"

class watershedSegmenter
{
private:
	//用来表示标记(图)
	cv::Mat  markers;
public:
	void setMarkers(const cv::Mat & markerImage){
	
		//watershed()的输入参数必须为一个32位有符号的标记,所以要先进行转换
		markerImage.convertTo(markers,CV_32S);
	}
	//执行watershed()
	cv::Mat process(const cv::Mat &image){
	
		cv::watershed(image,markers);
		return markers;
	}
	//以图像形式返回结果
	cv::Mat getSegmentation(){
	
		cv::Mat temp;
		//从32S到8u(0-255)会进行饱和运算,所以像素高于255 的一律复制为255
		markers.convertTo(temp,CV_8U);
		return temp;
	}
	//以图像形式返回分水岭
	cv::Mat getWatersheds(){
	
		cv::Mat temp;
		markers.convertTo(temp,CV_8U,255,255);
		return temp;
	}
};
int main()
{
    // Read input image 原图
    cv::Mat image= cv::imread("E://group.jpg",1);
    if (!image.data)
        return 0; 

    // Display the image
    cv::namedWindow("Original Image");
    cv::imshow("Original Image",image);

	//二值图-像素反转
	cv::Mat binary;
	binary=cv::imread("E://binary.jpg",0);
	cv::namedWindow("Binary Image");
    cv::imshow("Binary Image",binary);

	//二值图像获得前景。腐蚀,移除噪点与微小物体
	cv::Mat foreground;
	cv::erode(binary,foreground,cv::Mat(),cv::Point(-1,-1),6);
	cv::namedWindow("foreground Image");
    cv::imshow("foreground Image",foreground);
	//膨胀二值图获取背景
	cv::Mat background;
	cv::dilate(binary,background,cv::Mat(),cv::Point(-1,-1),6);
	cv::threshold(background,background,1,128,cv::THRESH_BINARY_INV);
	cv::namedWindow("background Image");
    cv::imshow("background Image",background);
	//形成标记图
	cv::Mat markers(binary.size(),CV_8U,cv::Scalar(0));
	markers=foreground+background;
	cv::namedWindow("Markers");
    cv::imshow("Markers",markers);

	//创建分水岭分割对象
	watershedSegmenter segmenters;
	//设置标记并处理
	segmenters.setMarkers(markers);
	segmenters.process(image);
	cv::namedWindow("Segmenters");
	cv::imshow("Segmenters",segmenters.getSegmentation());

	cv::namedWindow("watershed");
	cv::imshow("watershed",segmenters.getSegmentation());
	
	cv::waitKey(0);
}