分水岭分割方法是基于形态学操作
一、分水岭分割方法
1. 分水岭分割方法
基于浸泡理论的分水岭分割方法
基于连通图的方法
基于距离变换的方法
2. 分水岭算法应用
--- 分割粘连图像,实现形态学操作与对象计数
--- 图像分割
3. 基于距离的分水岭分割流程

二、Opencv中的相关API
1. 距离变换函数cv2.distanceTransform()
函数功能:计算源图像的每个像素到最近的零像素的距离。函数 cv::distanceTransform 计算从
每个二进制图像像素到最近的零像素的近似或精确距离。 对于零图像像素,距离显然为零。
void cv::distanceTransform ( InputArray src,
OutputArray dst,
int distanceType,
int maskSize,
int dstType = CV_32F
)参数解释:
src ----8位单通道二值图像(0-255)
dst ----计算距离后的输出图像,8位或32位浮点型的单通道图像
distanceType ----距离变换的类型
maskSize ----距离变换蒙版的大小, 在 DIST_L1 或 DIST_C 距离类型的情况下,该参数被强制
为 3,因为 3×3 掩膜给出与 5×5 或任何更大孔径相同的结果。
dstType ----输出图像的类型。 它可以是 CV_8U 或 CV_32F。 类型 CV_8U 只能用于函数的第一个变体和 distanceType == DIST_L1。
2. 分水岭函数watershed()
void watershed( InputArray image, InputOutputArray markers );image 是 3 通道彩色(CV_8UC3)图像,
markers 是单通道(CV_32S) 图像。
三、代码演示
1. 对象分离与计数
/*
对象分离与 计数
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src = imread("E:/技能学习/opencv图像分割/coins_001.jpg");
if (src.empty())
{
cout << "could not load image!" << endl;
return -1;
}
//namedWindow("input image", WINDOW_AUTOSIZE);
//imshow("input image", src);
Mat gray, binary, shifted;
pyrMeanShiftFiltering(src, shifted, 21, 51); //边缘保留的滤波方法
//imshow("shifted image", shifted);
//变为灰度图
cvtColor(shifted, gray, COLOR_BGR2GRAY);
//二值化
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
//imshow("binary image", binary);
//距离变化
Mat dist;
distanceTransform(binary, dist, DIST_L2, 3, CV_32F);
//由于距离变换得到的值比较小,所以对其值进行归一化 便于显示
//归一化
normalize(dist, dist, 0, 1, NORM_MINMAX);
//imshow("distance result", dist);
//距离变换之后再次进行二值化,目的是为了寻找局部最大
threshold(dist, dist, 0.4, 1, THRESH_BINARY);
//imshow("distance binary", dist);
//生成掩膜(markers)
//寻找轮廓
Mat dist_m;
vector<vector<Point>>contours;
// finContours只支持CV_8UC1的格式,所以要进行通道转换
dist.convertTo(dist_m, CV_8U);
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);
//完成分水岭变换
watershed(src, markers);
Mat mark ;
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat()); //非操作
imshow("watershed result", mark);
//产生随机颜色
vector<Vec3b> color;
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);
color.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 = <int>(row, col);
if (index > 0 && index <= contours.size())
{
dst.at<Vec3b>(row, col) = color[index - 1];
}
else
{
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("Final Result", dst);
cout << "number of object:" << contours.size() << endl;
waitKey(0);
destroyAllWindows();
return 0;
}2. 图像分割
/*
图像分割
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat watershedCluster(Mat &image, int &numComp);
void createDisplaySegments(Mat &markers, int numSegments, Mat &image);
int main(int argc, char** argv)
{
Mat src = imread("E:/技能学习/opencv图像分割/test.jpg");
if (src.empty())
{
cout << "could not load image!" << endl;
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
int numSegments;
Mat markers = watershedCluster(src, numSegments);
createDisplaySegments(markers, numSegments, src);
waitKey(0);
destroyAllWindows();
return 0;
}
Mat watershedCluster(Mat &image,int &numComp)
{
//二值化
Mat gray, binary;
cvtColor(image, gray, CV_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, 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); // finContours只支持CV_8UC1的格式,所以要进行通道转换
//标记开始
vector<vector<Point>>contours;
vector<Vec4i>hireachy;
findContours(dist, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
//cv::RETR_EXTERNAL:只检测最外围轮廓;
// cv::RETR_LIST:检测所有的轮廓,但是不建立等级关系;
//cv::RETR_CCOMP:检测所有的轮廓,但所有轮廓只建立两种等级关系,外围为顶层
// cv::RETR_TREE:检测所有的轮廓,所有轮廓建立一个等级树结
if (contours.empty())
{
return Mat();
}
Mat markers(dist.size(), CV_32S);
for (int i = 0; i < contours.size(); i++)
{
drawContours(markers, contours, i, Scalar(i + 1), -1, 8, hireachy, INT_MAX);
//INT_MAX表示 将在图上绘制的轮廓层次的最大深度,为0表示只绘制“第0层”,以此类推
}
circle(markers, Point(5, 5), 3, Scalar(255), -1);
//分水岭变换
watershed(image, markers);
numComp = contours.size();
return markers;
}
void createDisplaySegments(Mat &markers, int numSegments, Mat &image)
{
//产生随机颜色
vector<Vec3b> color;
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);
color.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 = <int>(row, col);
if (index > 0 && index <= numSegments)
{
dst.at<Vec3b>(row, col) = color[index - 1];
}
else
{
dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
}
}
}
imshow("Final Result", dst);
return;
}
















