简介
分水岭算法在图像分割中是一种常见的算法,它可以将图像分成若干个区域,每个区域内的像素具有相似的特征。在本篇文章中,我们将使用 OpenCV 库中的分水岭算法实现对图像的分割。
一、算法原理
分水岭算法是一种基于图论的图像分割算法,它的基本思想是将图像看成一个地形图,图像中的像素点看成地形上的山峰,灰度值越大的像素点越像山峰,而灰度值越小的像素点越像山谷。然后在地形图上加入水,让水从山顶开始流动,当两个山峰之间的水流相遇时,就将它们分成两个区域。最终,所有的山峰都被水流分割成了若干个区域。
二、代码实现
下面是使用 OpenCV 库实现分水岭算法的代码,代码中使用的是一张名为 "4.jpg" 的图片,读取后进行了增强对比、二值化、距离变换、寻找标记、发现轮廓、分水岭算法等步骤,最终得到了分割后的图像。
#pragma once
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
// 定义函数原型
Mat watershedCluster(Mat &srcImg, int &numSegments);
// 读取图像
Mat m1 = imread("D:/4.jpg", 1);
// Laplace算子增强对比
Mat imgLp;
Laplacian(m1, imgLp, -1, 3);
Mat sharpImg = m1 - imgLp;
// 显示原图和增强后的图像
imshow("原图", m1);
imshow("增强", sharpImg);
// 图像二值化
Mat binaryImg;
cvtColor(sharpImg, binaryImg, COLOR_BGR2GRAY);
threshold(binaryImg, binaryImg, 130, 255, THRESH_BINARY);
imshow("二值化", binaryImg);
// 距离变换
Mat distanceImg;
distanceTransform(binaryImg, distanceImg, DIST_L1, 3, 5);
// 归一化距离变换结果
imshow("距离变换", distanceImg);
normalize(distanceImg, distanceImg, 0, 1, NORM_MINMAX);
imshow("归一化后", distanceImg);
// 再次二值化,寻找标记
threshold(distanceImg, distanceImg, 0.3, 1, THRESH_BINARY);
// 腐蚀标记
Mat kernel = getStructuringElement(MORPH_RECT, Size(13, 13), Point(-1, -1));
Mat erodeImg;
morphologyEx(distanceImg, erodeImg, MORPH_ERODE, kernel);
imshow("腐蚀标记", erodeImg);
// 发现轮廓
Mat erodeU8;
erodeImg.convertTo(erodeU8, CV_8U);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(erodeU8, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
// 绘制轮廓
Mat markers = Mat::zeros(m1.size(), CV_8UC1);
for (size_t i = 0; i < contours.size(); i++) {
drawContours(markers, contours, i, Scalar::all(static_cast<int>(i) + 1), FILLED);
}
imshow("轮廓", markers);
// 分水岭算法
markers.convertTo(markers, CV_32SC1);
watershed(m1 - imgLp, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("分水岭", mark);
// 为每个轮廓生成随机颜色
vector<Vec3b> 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<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size())) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("最终结果", dst);
waitKey(0);
}
三、实验结果
使用上述代码对 "4.jpg" 图片进行分割后,得到的最终结果如下图所示:
从上图中可以看出,分水岭算法成功地将图像分割成了若干个区域,每个区域内的像素具有相似的特征。