分水岭算法
建议
背景:OTSU、形态学得到背景
前景:距离变换公式得到前景
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void waterSegment(InputArray& _src, OutputArray& _dst, int& noOfSegment);
int main(int argc, char** argv) {
Mat inputImage = imread("coins.jpg");
assert(!inputImage.data);
Mat graImage, outputImage;
int offSegment;
waterSegment(inputImage, outputImage, offSegment);
waitKey(0);
return 0;
}
void waterSegment(InputArray& _src,OutputArray& _dst,int& noOfSegment)
{
Mat src = _src.getMat();//dst = _dst.getMat();
Mat grayImage;
cvtColor(src, grayImage,CV_BGR2GRAY);
threshold(grayImage, grayImage, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(9, 9), Point(-1, -1));
morphologyEx(grayImage, grayImage, MORPH_CLOSE, kernel);
distanceTransform(grayImage, grayImage, DIST_L2, DIST_MASK_3, 5);
normalize(grayImage, grayImage,0,1, NORM_MINMAX);
grayImage.convertTo(grayImage, CV_8UC1);
threshold(grayImage, grayImage,0,255, THRESH_BINARY | THRESH_OTSU);
morphologyEx(grayImage, grayImage, MORPH_CLOSE, kernel);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Mat showImage = Mat::zeros(grayImage.size(), CV_32SC1);
findContours(grayImage, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
for (size_t i = 0; i < contours.size(); i++)
{
//这里static_cast<int>(i+1)是为了分水岭的标记不同,区域1、2、3。。。。这样才能分割
drawContours(showImage, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i+1)), 2);
}
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, src, MORPH_ERODE, k);
watershed(src, showImage);
//随机分配颜色
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(showImage.size(), CV_8UC3);
int index = 0;
for (int row = 0; row < showImage.rows; row++) {
for (int col = 0; col < showImage.cols; col++) {
index = showImage.at<int>(row, col);
if (index > 0 && index <= contours.size()) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else if (index == -1)
{
dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
}
封装子函数:
void segMerge(Mat& image, Mat& segments, int& numSeg)
{
vector<Mat> samples;
int newNumSeg = numSeg;
//初始化变量长度的Vector
for (size_t i = 0; i < newNumSeg; i++)
{
Mat sample;
samples.push_back(sample);
}
for (size_t i = 0; i < segments.rows; i++)
{
for (size_t j = 0; j < segments.cols; j++)
{
int index = segments.at<uchar>(i, j);
if (index >= 0 && index <= newNumSeg)//把同一个区域的点合并到一个Mat中
{
if (!samples[index].data)//数据为空不能合并,否则报错
{
samples[index] = image(Rect(j, i, 1, 1));
}
else//按行合并
{
vconcat(samples[index], image(Rect(j, i, 2, 1)), samples[index]);
}
}
//if (index >= 0 && index <= newNumSeg)
// samples[index].push_back(image(Rect(j, i, 1, 1)));
}
}
vector<Mat> hist_bases;
Mat hsv_base;
int h_bins = 35;
int s_bins = 30;
int histSize[2] = { h_bins , s_bins };
float h_range[2] = { 0,256 };
float s_range[2] = { 0,180 };
const float* range[2] = { h_range,s_range };
int channels[2] = { 0,1 };
Mat hist_base;
for (size_t i = 1; i < numSeg; i++)
{
if (samples[i].dims > 0)
{
cvtColor(samples[i], hsv_base, CV_BGR2HSV);
calcHist(&hsv_base, 1, channels, Mat(), hist_base, 2, histSize, range);
normalize(hist_base, hist_base, 0, 1, NORM_MINMAX);
hist_bases.push_back(hist_base);
}
else
{
hist_bases.push_back(Mat());
}
}
double similarity = 0;
vector<bool> merged;//是否合并的标志位
for (size_t i = 0; i < hist_bases.size(); i++)
{
for (size_t j = i+1; j < hist_bases.size(); j++)
{
if (!merged[j])//未合并的区域进行相似性判断
{
if (hist_bases[i].dims > 0 && hist_bases[j].dims > 0)//这里维数判断没必要,直接用个data就可以了
{
similarity = compareHist(hist_bases[i], hist_bases[j], HISTCMP_BHATTACHARYYA);
if (similarity > 0.8)
{
merged[j] = true;//被合并的区域标志位true
if (i != j)//这里没必要,i不可能等于j
{
newNumSeg --;//分割部分减少
for (size_t p = 0; p < segments.rows; p++)
{
for (size_t k = 0; k < segments.cols; k++)
{
int index = segments.at<uchar>(p, k);
if (index == j) segments.at<uchar>(p, k) = i;
}
}
}
}
}
}
}
}
numSeg = newNumSeg;//返回合并之后的区域数量
}
验证通过的子程序:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
// 分水岭
void waterSegment(InputArray& _src, OutputArray& _dst,int& noOfSegment)
{
Mat src ;// =_src.getMat();//dst = _dst.getMat();
_src.copyTo(src);
resize(src, src, Size(0.9*src.cols, 0.9*src.rows));//不这么做就会出错
Mat grayImage;
cvtColor(src, grayImage, CV_BGR2GRAY);
threshold(grayImage, grayImage, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(9, 9), Point(-1, -1));
morphologyEx(grayImage, grayImage, MORPH_CLOSE, kernel);
distanceTransform(grayImage, grayImage, DIST_L2, DIST_MASK_3, 5);
normalize(grayImage, grayImage, 0, 1, NORM_MINMAX);
grayImage.convertTo(grayImage, CV_8UC1);
threshold(grayImage, grayImage, 0, 255, THRESH_BINARY | THRESH_OTSU);
morphologyEx(grayImage, grayImage, MORPH_CLOSE, kernel);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Mat showImage = Mat::zeros(grayImage.size(), CV_32SC1);
findContours(grayImage, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
for (size_t i = 0; i < contours.size(); i++)
{
//这里static_cast<int>(i+1)是为了分水岭的标记不同,区域1、2、3。。。。这样才能分割
drawContours(showImage, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i + 1)), 2);
}
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, src, MORPH_ERODE, k);//形态变换
watershed(src, showImage);
//随机分配颜色
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(showImage.size(), CV_8UC3);
int index = 0;
for (int row = 0; row < showImage.rows; row++) {
for (int col = 0; col < showImage.cols; col++) {
index = showImage.at<int>(row, col);
if (index > 0 && index <= contours.size()) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else if (index == -1)
{
dst.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
resize(dst, dst, Size(10/9*dst.cols, 10/9*dst.rows));//不这么做就会出错
dst.copyTo(_dst);
}
鼠标事件——分水岭
#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat srcImage, srcImage_, maskImage;
Mat maskWaterShed; // watershed()函数的参数
Point clickPoint; // 鼠标点下去的位置
void on_Mouse(int event, int x, int y, int flags, void*)
{
// 如果鼠标不在窗口中则返回
if (x < 0 || x >= srcImage.cols || y < 0 || y >= srcImage.rows)
return;
// 如果鼠标左键被按下,获取鼠标当前位置;当鼠标左键按下并且移动时,绘制白线;
if (event == EVENT_LBUTTONDOWN)
{
clickPoint = Point(x, y);
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
Point point(x, y);
line(maskImage, clickPoint, point, Scalar::all(255), 5, 8, 0);
line(srcImage, clickPoint, point, Scalar::all(255), 5, 8, 0);
clickPoint = point;
namedWindow("在图像中做标记", 0);
imshow("在图像中做标记", srcImage);
}
}
void helpText()
{
cout << "先用鼠标在图片窗口中标记出大致的区域" << endl;
cout << "如果想把图片分割为N个区域,就要做N个标记" << endl;
cout << "键盘按键【1】 - 运行的分水岭分割算法" << endl;
cout << "键盘按键【2】 - 恢复原始图片" << endl;
cout << "键盘按键【0】 - 依次分割每个区域(必须先按【1】)" << endl;
cout << "键盘按键【ESC】 - 退出程序" << endl << endl;
}
int main()
{
/* 操作提示 */
helpText();
srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\大恒样品\\空纹理.bmp");
resize(srcImage, srcImage, Size(srcImage.cols / 2, srcImage.rows / 2));
//srcImage = imread("fly.jpg");
srcImage_ = srcImage.clone(); // 程序中srcImage会被改变,所以这里做备份
maskImage = Mat(srcImage.size(), CV_8UC1); // 掩模,在上面做标记,然后传给findContours
maskImage = Scalar::all(0);
int areaCount = 1; // 计数,在按【0】时绘制每个区域
namedWindow("在图像中做标记", 0);
imshow("在图像中做标记", srcImage);
setMouseCallback("在图像中做标记", on_Mouse, 0);
while (true)
{
int c = waitKey(0);
if ((char)c == 27) // 按【ESC】键退出
break;
if ((char)c == '2') // 按【2】恢复原图
{
maskImage = Scalar::all(0);
srcImage = srcImage_.clone();
namedWindow("在图像中做标记", 0);
imshow("在图像中做标记", srcImage);
}
if ((char)c == '1') // 按【1】处理图片
{
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
if (contours.size() == 0) // 如果没有做标记,即没有轮廓,则退出该if语句
break;
cout << contours.size() << "个轮廓" << endl;
maskWaterShed = Mat(maskImage.size(), CV_32S);
maskWaterShed = Scalar::all(0);
/* 在maskWaterShed上绘制轮廓 */
for (int index = 0; index < contours.size(); index++)
drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);
/* 如果imshow这个maskWaterShed,我们会发现它是一片黑,原因是在上面我们只给它赋了1,2,3这样的值,通过代码80行的处理我们才能清楚的看出结果 */
watershed(srcImage_, maskWaterShed); // 注释一
vector<Vec3b> colorTab; // 随机生成几种颜色
for (int i = 0; i < contours.size(); i++)
{
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat resImage = Mat(srcImage.size(), CV_8UC3); // 声明一个最后要显示的图像
for (int i = 0; i < maskImage.rows; i++)
{
for (int j = 0; j < maskImage.cols; j++)
{ // 根据经过watershed处理过的maskWaterShed来绘制每个区域的颜色
int index = maskWaterShed.at<int>(i, j); // 这里的maskWaterShed是经过watershed处理的
if (index == -1) // 区域间的值被置为-1(边界)
resImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
else if (index <= 0 || index > contours.size()) // 没有标记清楚的区域被置为0
resImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
else // 其他每个区域的值保持不变:1,2,...,contours.size()
resImage.at<Vec3b>(i, j) = colorTab[index - 1]; // 然后把这些区域绘制成不同颜色
}
}
namedWindow("resImage", 0);
imshow("resImage", resImage);
addWeighted(resImage, 0.3, srcImage_, 0.7, 0, resImage);
namedWindow("分水岭结果", 0);
imshow("分水岭结果", resImage);
}
if ((char)c == '0') // 多次点按【0】依次显示每个被分割的区域,需要先按【1】处理图像
{
Mat resImage = Mat(srcImage.size(), CV_8UC3); // 声明一个最后要显示的图像
for (int i = 0; i < maskImage.rows; i++)
{
for (int j = 0; j < maskImage.cols; j++)
{
int index = maskWaterShed.at<int>(i, j);
if (index == areaCount)
resImage.at<Vec3b>(i, j) = srcImage_.at<Vec3b>(i, j);
else
resImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
}
}
namedWindow("分水岭结果", 0);
imshow("分水岭结果", resImage);
areaCount++;
if (areaCount == 4)
areaCount = 1;
}
}
cv::waitKey();
system("pause");
return 0;
}
Mat srcImage;
//srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\瓷砖\\方格.bmp");
//srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\瓷砖\\条纹3.bmp");
srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\瓷砖\\空纹理.bmp");
//srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\瓷砖\\花纹.bmp");
//srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\大恒样品\\方格.bmp");//C:\Users\Administrator\Desktop\样品\大恒样品
//srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\大恒样品\\条纹3.bmp");
//srcImage = imread("C:\\Users\\Administrator\\Desktop\\样品\\大恒样品\\空纹理.bmp");
//srcImage= imread("C:\\Users\\Administrator\\Desktop\\样品\\大恒样品\\花纹.bmp");
cv::resize(srcImage, srcImage, Size(srcImage.cols / 2, srcImage.rows / 2));
namedWindow("resImage", 0);
imshow("resImage", srcImage);
//waitKey();
// 【mask两点】
//mask的第一点 maskImage
Mat maskImage;
maskImage = Mat(srcImage.size(), CV_8UC1); // 掩模,在上面做标记,然后传给findContours
maskImage = Scalar::all(0);
Point point1(0, 0), point2(10, 10);
line(maskImage, point1, point2, Scalar::all(255), 5, 8, 0);
//mask的第二点 maskImage
Point point3(srcImage.cols / 2, srcImage.rows / 2), point4(srcImage.cols / 2, srcImage.rows / 2);
line(maskImage, point3, point4, Scalar::all(255), 5, 8, 0);
// 【轮廓】
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// 【分水岭】
// 参数二:maskWaterShed(CV_32S)
Mat maskWaterShed; // watershed()函数的参数
maskWaterShed = Mat(maskImage.size(), CV_32S);//空白掩码 maskWaterShed
maskWaterShed = Scalar::all(0);
/* 在maskWaterShed上绘制轮廓 */
for (int index = 0; index < contours.size(); index++)
drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);
/* 如果imshow这个maskWaterShed,我们会发现它是一片黑,原因是在上面我们只给它赋了1,2,3这样的值,通过代码80行的处理我们才能清楚的看出结果 */
// 参数一:srcImage(CV_8UC3)
watershed(srcImage, maskWaterShed); //int index = maskWaterShed.at<int>(row, col);操作
// 【随机生成几种颜色】
vector<Vec3b> colorTab;
for (int i = 0; i < contours.size(); i++)
{
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat dst = Mat::zeros(maskWaterShed.size(), CV_8UC3);
Mat dst_edge = Mat::zeros(maskWaterShed.size(), CV_8UC3);
int index = maskWaterShed.at<int>(maskWaterShed.rows / 2, maskWaterShed.cols / 2);
int index_temp = 0;
for (int i = 0; i < maskWaterShed.rows; i++)
{
for (int j = 0; j < maskWaterShed.cols; j++)
{
index_temp = maskWaterShed.at<int>(i, j);
//cout << index_temp << endl;
if (index_temp == index)//取中心的标签区域
{
dst_edge.at<Vec3b>(i, j) = colorTab[index - 1];
dst.at<Vec3b>(i, j) = srcImage.at<Vec3b>(i, j);
}
}
}
namedWindow("分割结果", 0);
imshow("分割结果", dst);
Mat dst_add;
addWeighted(dst_edge, 0.3, srcImage, 0.7, 0, dst_add);
namedWindow("加权结果", 0);
imshow("加权结果", dst_add);
namedWindow("边缘区域", 0);
imshow("边缘区域", dst_edge);
imwrite("a.bmp", dst_edge);
建议缩小图像尺寸加快分割速度
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void Water_Cut(InputArray& src, OutputArray& dst, OutputArray& edge)
{
Mat srcImage;
src.copyTo(srcImage);
//cv::resize(srcImage, srcImage, Size(srcImage.cols / 2, srcImage.rows / 2));
namedWindow("resImage", 0);
imshow("resImage", srcImage);
//waitKey();
// 【mask两点】
//mask的第一点 maskImage
Mat maskImage;
maskImage = Mat(srcImage.size(), CV_8UC1); // 掩模,在上面做标记,然后传给findContours
maskImage = Scalar::all(0);
Point point1(0, 0), point2(10, 10);
line(maskImage, point1, point2, Scalar::all(255), 5, 8, 0);
//mask的第二点 maskImage
Point point3(srcImage.cols / 2, srcImage.rows / 2), point4(srcImage.cols / 2, srcImage.rows / 2);
line(maskImage, point3, point4, Scalar::all(255), 5, 8, 0);
// 【轮廓】
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// 【分水岭】
// 参数二:maskWaterShed(CV_32S)
Mat maskWaterShed; // watershed()函数的参数
maskWaterShed = Mat(maskImage.size(), CV_32S);//空白掩码 maskWaterShed
maskWaterShed = Scalar::all(0);
/* 在maskWaterShed上绘制轮廓 */
for (int index = 0; index < contours.size(); index++)
drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);
/* 如果imshow这个maskWaterShed,我们会发现它是一片黑,原因是在上面我们只给它赋了1,2,3这样的值,通过代码80行的处理我们才能清楚的看出结果 */
// 参数一:srcImage(CV_8UC3)
watershed(srcImage, maskWaterShed); //int index = maskWaterShed.at<int>(row, col);操作
// 【随机生成几种颜色】
vector<Vec3b> colorTab;
for (int i = 0; i < contours.size(); i++)
{
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat dst_ = Mat::zeros(maskWaterShed.size(), CV_8UC3);
Mat dst_edge = Mat::zeros(maskWaterShed.size(), CV_8UC3);
int index = maskWaterShed.at<int>(maskWaterShed.rows / 2, maskWaterShed.cols / 2);
int index_temp = 0;
for (int i = 0; i < maskWaterShed.rows; i++)
{
for (int j = 0; j < maskWaterShed.cols; j++)
{
index_temp = maskWaterShed.at<int>(i, j);
//cout << index_temp << endl;
if (index_temp == index)//取中心的标签区域
{
dst_edge.at<Vec3b>(i, j) = Vec3b((uchar)255, (uchar)255, (uchar)255); //colorTab[index - 1];
dst_.at<Vec3b>(i, j) = srcImage.at<Vec3b>(i, j);
}
}
}
namedWindow("分割结果", 0);
imshow("分割结果", dst_);
/*Mat dst_add;
addWeighted(dst_edge, 0.3, srcImage, 0.7, 0, dst_add);
namedWindow("加权结果", 0);
imshow("加权结果", dst_add);*/
namedWindow("边缘区域", 0);
imshow("边缘区域", dst_edge);
imwrite("方格.bmp", dst_edge);
dst_.copyTo(dst);
dst_edge.copyTo(edge);
}
参考:
https://blog.csdn.net/Dangkie/article/details/77806211
// 参考外接框:https://www.cnblogs.com/little-monkey/p/7429579.html
// 参考填充区:https://blog.csdn.net/klamen/article/details/56016865
// 动态分水岭(重要):https://www.2cto.com/kf/201611/565921.html