如何用OpenCV提取图像所有目标?解决目标粘连分割问题
解决OpenCV粘连目标提取问题
我需要用OpenCV提取图像中的所有目标,当前代码在目标相互分离的图像上运行正常,但当目标彼此靠近粘连时,无法将它们分开,这影响了后续处理。
正常示例:
异常示例:
我知道正常示例也存在一些小问题,但并不关键。以下是我正在使用的代码:
int main(int argc, char** argv) { cv::CommandLineParser parser( argc, argv, "{@input | | input image}"); cv::Mat src = cv::imread(cv::samples::findFile(parser.get<cv::String>("@input")), cv::IMREAD_UNCHANGED); if (src.empty()) { std::cout << "Could not open or find the image!\n" << std::endl; std::cout << "usage: " << argv[0] << " <Input image>" << std::endl; return -1; } cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat filtred; cv::bilateralFilter(gray, filtred, 11, 17, 17); cv::Mat canny; cv::Canny(filtred, canny, 40., 120.); cv::imshow("canny", canny); cv::waitKey(0); cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2)); cv::Mat closed; cv::morphologyEx(canny, closed, cv::MORPH_CLOSE, kernel); cv::imshow("morph", closed); std::vector<std::vector<cv::Point>> contours; cv::findContours(closed, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); cv::Mat with_contours; src.copyTo(with_contours); for (int i = 0; i < contours.size(); i++) if (cv::contourArea(contours[i]) > 100.) { cv::Rect rect = cv::boundingRect(contours[i]); cv::rectangle(with_contours, rect, cv::Scalar(0, 255, 0), 2); } imshow("contours", with_contours); }
实用改进方案
1. 距离变换+分水岭算法(针对类圆形粘连目标)
如果你的目标是类圆形,这个方法能精准分离粘连区域:
- 先通过二值化得到前景/背景分割图
- 距离变换计算每个前景像素到最近背景的距离,距离越大越接近目标中心
- 阈值提取目标中心作为种子,再用分水岭算法分割粘连目标
代码示例:
// 替换原有的轮廓检测流程 cv::Mat binary; // Otsu自动阈值二值化,适配不同亮度的图像 cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU); // 距离变换 cv::Mat dist; cv::distanceTransform(binary, dist, cv::DIST_L2, 5); cv::normalize(dist, dist, 0, 1.0, cv::NORM_MINMAX); // 提取种子点(目标中心区域) cv::Mat seeds; cv::threshold(dist, seeds, 0.5, 255, cv::THRESH_BINARY); seeds.convertTo(seeds, CV_8U); // 标记种子连通域 std::vector<std::vector<cv::Point>> seedContours; cv::findContours(seeds, seedContours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); cv::Mat markers = cv::Mat::zeros(binary.size(), CV_32S); for (int i = 0; i < seedContours.size(); i++) { cv::drawContours(markers, seedContours, i, cv::Scalar(i+1), -1); } // 执行分水岭分割 cv::watershed(src, markers); // 可视化结果 cv::Mat result = src.clone(); for (int i = 1; i <= seedContours.size(); i++) { cv::Mat mask = (markers == i); // 用随机颜色标记不同目标 result.setTo(cv::Scalar(rand()%255, rand()%255, rand()%255), mask); } cv::imshow("分割结果", result); cv::waitKey(0);
2. 替换形态学操作:用腐蚀代替闭运算
原代码的闭运算(膨胀+腐蚀)会让粘连目标更紧密,改成腐蚀可以先断开粘连:
// 替换原有的morphologyEx代码 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2)); cv::Mat eroded; cv::erode(canny, eroded, kernel); // 腐蚀断开粘连 cv::imshow("腐蚀后", eroded);
注意:腐蚀核的大小需要根据粘连程度调整,过大可能会把小目标腐蚀掉。
3. 调整Canny边缘检测参数
原代码的Canny阈值(40,120)可能让边缘过度连接,尝试降低高阈值或拉大阈值差:
cv::Canny(filtred, canny, 30., 90.); // 调整后的阈值,边缘更细致
4. 凸缺陷分割(针对有凹陷的粘连目标)
如果粘连处有明显凹陷,可以通过凸缺陷找到分割点:
// 针对单个粘连轮廓的分割示例 for (auto& cnt : contours) { if (cv::contourArea(cnt) < 100) continue; std::vector<cv::Point> hull; cv::convexHull(cnt, hull); std::vector<cv::Vec4i> defects; cv::convexityDefects(cnt, cv::convexHull(cnt, false), defects); // 筛选深度足够的凸缺陷(深度>阈值,比如10) for (auto& d : defects) { float depth = d[3] / 256.0; if (depth > 10) { int startIdx = d[0]; int endIdx = d[1]; // 用start和end点分割轮廓(具体分割逻辑需根据目标形状调整) cv::line(with_contours, cnt[startIdx], cnt[endIdx], cv::Scalar(0,0,255), 2); } } }
代码小修正
原代码中std::vector<contour>是笔误,应该改为std::vector<std::vector<cv::Point>>;另外最后需要加cv::waitKey(0);否则程序会直接退出,看不到结果。
内容的提问来源于stack exchange,提问作者SabakaSamson
相关产品推荐
相关产品推荐

