关于分水岭算法的原理及表现,这篇博客不错,
https://blog.csdn.net/linqianbi/article/details/79121005
重要的思想:
上面的博客里面也有程序,用于计算图片中的硬币个数,程序都差不多,自己注释了一下,方便自己理解
实例一.分割粘连对象,实现形态学操作与对象计数
程序思路:
读取图像,将原图上进行pyrMeanShiftFiltering()处理,将背景变为纯色,同时避免过多的噪点,保留更多的边缘信息;
在平滑区进行滤波,保证后面二值化时的效果更好;
转成单通道,进行二值处理,进行距离变换,将距离变换的结果归一化,找到山峰;
然后再一次进行二值化处理,转到CV_8U类型的图像;进行寻找轮廓,绘制轮廓,每次绘制轮廓时用不同的值对每个轮廓进行标记;
进行形态学的操作,去掉干扰,让结果更好;
完成分水岭的变换,使用watershed函数,得到maskers根据masker中的像素值,进行索引颜色的填充
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src = imread("coins_001.jpg");
if (src.empty())
{
printf("could not load image...\n");
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
Mat gray, binary, shifted; //定义灰度图,二值图,转换图
pyrMeanShiftFiltering(src, shifted, 21, 51); //作用:将背景变成纯色,避免过多的噪点
//函数pyrMeanShiftFiltering讲解:https://blog.csdn.net/dcrmg/article/details/52705087
//imshow("shifted", shifted); 可以自己将这步显示出来看看效果
cvtColor(shifted, gray, COLOR_BGR2GRAY); //转成灰度图
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); //二值化
//imshow("binary", binary);
//距离变换
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
//距离变换函数介绍:https://blog.csdn.net/liubing8609/article/details/78483667
normalize(dist, dist, 0, 1, NORM_MINMAX);
//矩阵归一化:normalize()函数
//void cv::normalize(InputArry src,InputOutputArray dst,double alpha=1,double beta=0,
// int norm_type=NORM_L2,int dtype=-1,InputArray mark=noArry())
// src 输入数组;
// dst 输出数组,数组的大小和原数组一致;
// alpha 1, 用来规范值,2.规范范围,并且是下限;
// beta 只用来规范范围并且是上限;
// norm_type 归一化选择的数学公式类型;
// type 当为负,输出在大小深度通道数都等于输入,当为正,输出只在深度与输如不同,不同的地方游dtype决定;
// mark 掩码。选择感兴趣区域,选定后只能对该区域进行操作。
// binary,二值
threshold(dist, dist, 0.4, 1, THRESH_BINARY);
//imshow("distance binary", dist);
// 进行计算标记的分割块
Mat dist_m;
dist.convertTo(dist_m, CV_8U); //将dist_m转换成dist矩阵的CV_8U类型
vector<vector<Point>> contours;//vector<Point>点的集合,一系列的点组成轮廓的集合
findContours(dist_m, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //寻找轮廓
// create markers 生成种子
Mat markers = Mat::zeros(src.size(), CV_32SC1);
for (size_t t = 0; t < contours.size(); t++)
{
//绘制轮廓
drawContours(markers, contours, static_cast<int>(t), Scalar::all(static_cast<int>(t) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);
//imshow("markers", markers*10000);
// 形态学操作 - 彩色图像,目的是去掉干扰,让结果更好
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, src, MORPH_ERODE, k); //MORPH_ERODE表示选择的是腐蚀操作
// 完成分水岭变换
watershed(src, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
//imshow("watershed result", 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);
int index = 0;
for (int row = 0; row < markers.rows; row++)
{
for (int col = 0; col < markers.cols; col++)
{
index = markers.at<int>(row, col);
if (index > 0 && index <= contours.size())
{
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else
{
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("效果图", dst);
printf("检测到的硬币个数 : %d\n", contours.size());
waitKey(0);
return 0;
}
该程序的效果如上面博客里面效果图那样
实例二:基于分水岭算法的图像分割
程序如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat watershedCluster(Mat &image, int &numSegments);
void createDisplaySegments(Mat &segments, int numSegments, Mat &image);
int main(int argc, char** argv)
{
Mat src = imread("1.jpg");
if (src.empty())
{
printf("could not load image...\n");
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
int numSegments; //定义分割的种类个数
Mat markers = watershedCluster(src, numSegments);
createDisplaySegments(markers, numSegments, src);
cout << "numSegments:" << numSegments << endl;
waitKey(0);
return 0;
}
//完成分水岭的变换,并返回轮廓的数目
Mat watershedCluster(Mat &image, int &numComp)
{
// 二值化
Mat gray, binary;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
// 形态学与距离变换
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
//进行开运算,去掉小的点的干扰,分水岭分割是自动计算分类,如果有干扰,分类就会有很多
morphologyEx(binary, binary, MORPH_OPEN, k, Point(-1, -1));
Mat dist;
distanceTransform(binary, dist, DistanceTypes::DIST_L2, 3, CV_32F);
normalize(dist, dist, 0.0, 1.0, NORM_MINMAX);
// 开始生成标记
threshold(dist, dist, 0.1, 1.0, THRESH_BINARY);
normalize(dist, dist, 0, 255, NORM_MINMAX); //归一化
dist.convertTo(dist, CV_8UC1); //将dist转成CV_8UC1才能进行如下的操作
// 标记开始,先找到轮廓
vector<vector<Point>> contours;
vector<Vec4i> hireachy;
findContours(dist, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
if (contours.empty()) //若未找到轮廓
{
return Mat();
}
// 再进行轮廓的填充,绘制markers
Mat markers(dist.size(), CV_32S);
markers = Scalar::all(0);
for (int i = 0; i < contours.size(); i++)
{
drawContours(markers, contours, i, Scalar(i + 1), -1, 8, hireachy, INT_MAX);
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);
// 分水岭变换
watershed(image, markers);
numComp = contours.size();
return markers;
}
//这里注意是Mat &markers是引用传递的方式。
//形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,
//被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,
//但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过
//栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
//参考博客:https://blog.csdn.net/qq_35789421/article/details/89087183
void createDisplaySegments(Mat &markers, int numSegments, Mat &image)
{
// 生成随机颜色
vector<Vec3b> colors;
for (size_t i = 0; i < numSegments; 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);
int index = 0;
for (int row = 0; row < markers.rows; row++)
{
for (int col = 0; col < markers.cols; col++)
{
index = markers.at<int>(row, col);
if (index > 0 && index <= numSegments)
{
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else
{
dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
}
}
}
imshow("分水岭图像分割-演示", dst);
}
效果图: