【OpenCV 4开发详解】分割图像——分水岭法

本文首发于 “小白学视觉”微信公众号,欢迎关注公众号
本文作者为小白,版权归 人民邮电出版社发行所有,禁止转载,侵权必究!

经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。

分水岭算法与漫水填充法相似,都是模拟水淹过山地的场景,区别是漫水填充法是从局部某个像素值进行分割,是一种局部分割算法,而分水岭法是从全局出发,需要对全局都进行分割。

分水岭算法会在多个局部最低点开始注水,随着注水量的增加,水位越来越高会淹没局部像素值较小的像素点,最后两个相邻的凹陷区域的水会汇集在一起,并在汇集处形成了分水岭。分水岭的计算过程是一个迭代标注的过程,经典的计算方式主要分为以下两个步骤:

  • Step1:排序过程,首先对图像像素的灰度级进行排序,确定灰度值较小的像素点,该像素点即为开始注水点;
  • Step2:淹没过程,对每个最低点开始不断注水,不断掩模周围的像素点,不同注水处的水汇集在一起,形成分割线。

OpenCV 4提供了用于实现分水岭法分割图像的watershed()函数,该函数的函数原型在代码清单8-19中给出。

代码清单8-19 watershed()函数原型
	void cv::watershed(InputArray  image,
	                       InputOutputArray  markers 
	                       )
  • image:输入图像,数据类型为CV_8U的三通道图像。
  • markers:输入/输出CV_32S的单通道图像的标记结果,与原图像具有相同的尺寸。

该函数根据期望标记结果实现图像分水岭分割。函数的第一个参数是需要进行分水岭分割的图像,该图像必须是CU_8U的三通道彩色图像。函数第二个参数用于输入期望分割的区域,在将图像传递给函数之前,必须使用大于0的整数索引粗略的勾画图像期望分割的区域。因此,每个标记的区域被表示为具有像素值1、2、3等的一个或多个连通分量。标记图像的尺寸与输入图像相同且数据类型为CV_32S,可以使用findContours()函数和drawContours()函数从二值掩码中得到此类标记图像,标记图像中所有没有被标记的像素值都为0。在函数输出时,两个区域之间的分割线用-1表示。

为了了解该函数的用法,在代码清单8-20中给出了利用watershed()函数对图像进行分割的示例程序。程序中通过图像的边缘区域对图像进行标记,首先利用Canny()函数计算图像的边缘,之后利用findContours()函数计算图像中的连通域,并通过drawContours()函数绘制连通域得到符合格式要求的标记图像,最后利用watershed()函数对图像进行分割。为了增加分割后不同区域之间的对比度,随机对不同区域进行上色,结果如图8-12所示,同时提取原图像中每个被分割的区域,部分结果在图8-13给出。

代码清单8-20 myWatershed.cpp分水岭法分割图像
1.	#include <opencv2\opencv.hpp>
2.	#include <iostream>
3.	
4.	using namespace std;
5.	using namespace cv;
6.	
7.	int main()
8.	{
9.		Mat img, imgGray, imgMask;
10.		Mat maskWaterShed;  // watershed()函数的参数
11.		img = imread("HoughLines.jpg");  //原图像
12.		if (img.empty())
13.		{
14.			cout << "请确认图像文件名称是否正确" << endl;
15.			return -1;
16.		}
17.		cvtColor(img, imgGray, COLOR_BGR2GRAY);
18.		//GaussianBlur(imgGray, imgGray, Size(5, 5), 10, 20);  //模糊用于减少边缘数目
19.	
20.		//提取边缘并进行闭运算
21.		Canny(imgGray, imgMask, 150, 300);
22.		//Mat k = getStructuringElement(0, Size(3, 3));
23.		//morphologyEx(imgMask, imgMask, MORPH_CLOSE, k);
24.	
25.		imshow("边缘图像", imgMask);
26.		imshow("原图像", img);
27.	
28.		//计算连通域数目
29.		vector<vector<Point>> contours;
30.		vector<Vec4i> hierarchy;
31.		findContours(imgMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
32.	
33.		//在maskWaterShed上绘制轮廓,用于输入分水岭算法
34.		maskWaterShed = Mat::zeros(imgMask.size(), CV_32S);
35.		for (int index = 0; index < contours.size(); index++)
36.		{
37.			drawContours(maskWaterShed, contours, index, Scalar::all(index + 1),
38.				-1, 8, hierarchy, INT_MAX);
39.		}
40.		//分水岭算法   需要对原图像进行处理
41.		watershed(img, maskWaterShed);
42.	
43.		vector<Vec3b> colors;  // 随机生成几种颜色
44.		for (int i = 0; i < contours.size(); i++)
45.		{
46.			int b = theRNG().uniform(0, 255);
47.			int g = theRNG().uniform(0, 255);
48.			int r = theRNG().uniform(0, 255);
49.			colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
50.		}
51.	
52.		Mat resultImg = Mat(img.size(), CV_8UC3);  //显示图像
53.		for (int i = 0; i < imgMask.rows; i++)
54.		{
55.			for (int j = 0; j < imgMask.cols; j++)
56.			{
57.				// 绘制每个区域的颜色
58.				int index = maskWaterShed.at<int>(i, j);
59.				if (index == -1)  // 区域间的值被置为-1(边界)
60.				{
61.					resultImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
62.				}
63.				else if (index <= 0 || index > contours.size())  // 没有标记清楚的区域被置为0 
64.				{
65.					resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
66.				}
67.				else  // 其他每个区域的值保持不变:1,2,…,contours.size()
68.				{
69.					resultImg.at<Vec3b>(i, j) = colors[index - 1];  // 把些区域绘制成不同颜色
70.				}
71.			}
72.		}
73.	
74.		resultImg = resultImg * 0.6 + img * 0.4;
75.		imshow("分水岭结果", resultImg);
76.	
77.		//绘制每个区域的图像
78.		for (int n = 1; n <= contours.size(); n++)
79.		{
80.			Mat resImage1 = Mat(img.size(), CV_8UC3);  // 声明一个最后要显示的图像
81.			for (int i = 0; i < imgMask.rows; i++)
82.			{
83.				for (int j = 0; j < imgMask.cols; j++)
84.				{
85.					int index = maskWaterShed.at<int>(i, j);
86.					if (index == n)
87.						resImage1.at<Vec3b>(i, j) = img.at<Vec3b>(i, j);
88.					else
89.						resImage1.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
90.				}
91.			}
92.			//显示图像
93.			imshow(to_string(n), resImage1);
94.		}
95.	
96.		waitKey(0);
97.		return 0;
98.	}
【OpenCV 4开发详解】分割图像——分水岭法_第1张图片
图8-12 myWatershed.cpp程序中分水岭分割结果
【OpenCV 4开发详解】分割图像——分水岭法_第2张图片
图8-13 myWatershed.cpp程序中被分割区域的原图像

 

OpenCV 4开发详解
往期推荐
【OpenCV 4开发详解】图像模板匹配
【OpenCV 4开发详解】图像卷积
【OpenCV 4开发详解】图像噪声的种类与生成
【OpenCV 4开发详解】均值滤波
【OpenCV 4开发详解】方框滤波
【OpenCV 4开发详解】高斯滤波
【OpenCV 4开发详解】可分离滤波
【OpenCV 4开发详解】中值滤波
【OpenCV 4开发详解】边缘检测原理
【OpenCV 4开发详解】Scharr算子
【OpenCV 4开发详解】Laplacian算子
【OpenCV 4开发详解】Canny算法
【OpenCV 4开发详解】图像距离变换
【OpenCV 4开发详解】图像连通域分析
【OpenCV 4开发详解】图像腐蚀
【OpenCV 4开发详解】图像膨胀
【OpenCV 4开发详解】形态学应用
【OpenCV 4开发详解】检测直线
【OpenCV 4开发详解】直线拟合
【OpenCV 4开发详解】直线检测
【OpenCV 4开发详解】轮廓发现与绘制
【OpenCV 4开发详解】轮廓面积与长度
【OpenCV 4开发详解】图像矩的计算与应用
【OpenCV 4开发详解】点集拟合
【OpenCV 4开发详解】QR二维码检测
【OpenCV 4开发详解】分割图像——漫水填充法
经过几个月的努力,市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》将春节后由人民邮电出版社发行。如果小伙伴觉得内容有帮助,希望到时候多多支持!
关注小白的小伙伴可以提前看到书中的内容,我们创建了学习交流群,欢迎各位小伙伴添加小白微信加入交流群,添加小白时请备注“学习OpenCV 4”。

你可能感兴趣的:(OpenCV,4开发详解)