理论
核心流程:
找出每个分割物体的前景:cv::distanceTransform
对图片上每个像素分类:cv::connectedComponents
分水岭算法:cv::watershed
代码
void SplitImage(cv::Mat& image, std::vector<cv::Mat>& result)
{
//输入
cv::Mat localMat = image.clone();
cv::Mat gray, binary, shifted;
//cv::imshow("image", image);
//cv::waitKey(0);
//滤波后的二值化
cv::cvtColor(localMat, gray, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(gray, gray, cv::Size(5, 5), 2); //高斯滤波
threshold(gray, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
cv::imshow("binary", binary);
//cv::waitKey(0);
//去噪
cv::Mat dist, sureFg, sureBg, unknown;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));
cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel);
cv::imshow("morphologyEx", binary);
cv::dilate(binary, sureBg, kernel, cv::Point(-1, -1), 2);//膨胀
// 距离变换 + 提取前景
cv::distanceTransform(binary, dist, cv::DistanceTypes::DIST_L2, 5);
//normalize(dist, dist, 0, 1, cv::NORM_MINMAX);
//此处不用百分比的原因是,最好根据分割物体的中心到边缘的像素距离来选定threshhold
threshold(dist, sureFg, 30, 255, cv::THRESH_BINARY);
//cv::erode(sureFg, sureFg, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
sureFg.convertTo(sureFg, CV_8UC1);
cv::imshow("sureFg result", sureFg);
cv::subtract(sureBg, sureFg, unknown);//分割不清的地方
//cv::imshow("unknown", unknown);
// markers
cv::Mat markers = cv::Mat::zeros(localMat.size(), CV_32S);// 如果使用 CV_8UC1 ,watershed 函数会报错
//connectedComponents 获取联通件,markers中每个像素值0:背景,1,2,3...:代表每个连通件的序号
int num_labels = cv::connectedComponents(sureFg, markers, 8, CV_32S);
// 为所有的标记加1,因为经过connectedComponents背景已经置0,+1后原背景也成为类别(1),unknown(0)则代表要不确定的区域(待算法确认属于1,2,3,4...)
markers += 1;
cv::Mat mask = (unknown == 255);
markers.setTo(0, mask);
//分水岭算法watershed,masker中的值表示 -1:未知区域,0表示背景区域(前景和背景之间的过渡区域),结果标签图将会被修改,边界区域的标记将变为-1
cv::watershed(localMat, markers);
展示markers到底获取了什么东西
cv::Mat afterWatershed;
convertScaleAbs(markers, afterWatershed);
cv::applyColorMap(afterWatershed, afterWatershed, cv::COLORMAP_JET);
imshow("After Watershed", afterWatershed);
//背景(1)置0,
cv::Mat maskNegativeOne = (markers == -1);
cv::Mat maskOne = (markers == 1);
markers.setTo(0, maskNegativeOne | maskOne);
std::vector<std::vector<cv::Point>> contours;
for (int i = 2; i < num_labels; i++)
{
std::vector<std::vector<cv::Point>> temp;
cv::Mat maskGreaterThanOne = (markers == i);
cv::findContours(maskGreaterThanOne, temp, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
//imshow("maskGreaterThanOne result", maskGreaterThanOne);
//cv::waitKey(0);
contours.insert(contours.end(), temp.begin(), temp.end());
}
markers.convertTo(markers, CV_8UC1);
//imshow("markers result", markers);
//查找轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(markers, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
// 画图
cv::Mat imageContours = cv::Mat::zeros(localMat.size(), CV_8UC1);
srand(unsigned(time(0)));
cv::RNG rng(12345);
std::vector<cv::Scalar> colors;
for (size_t i = 0; i < contours.size(); ++i) {
// 计算轮廓的质心
cv::Moments M = cv::moments(contours[i]);
int cx = cvRound(M.m10 / M.m00);
int cy = cvRound(M.m01 / M.m00);
// 随机选择颜色
cv::Scalar color;
color[0] = rng.uniform(0, 256);
color[1] = rng.uniform(0, 256);
color[2] = rng.uniform(0, 256);
// 绘制轮廓
cv::drawContours(localMat, contours, static_cast<int>(i), color, 2);
// 绘制标记
cv::drawMarker(localMat, cv::Point(cx, cy), cv::Scalar(0, 255, 0), cv::MARKER_STAR, 8, 2);
}
cv::imshow("Contours", localMat);
cv::waitKey(0);
return;
}
结果: