环境:OpenCV3.2.0 + VS2017
91、合并Y方向重叠的轮廓
以轮廓的最小垂直外接矩形框的y为依据,合并y重叠的轮廓。
数学逻辑:几何合并的数学表达
- 坐标系统:假设矩形由左上角坐标
(x, y)
和宽高(width, height)
定义。- 合并公式:
- 合并后左上角:
(min(x1, x2), min(y1, y2))
- 合并后右下角:
(max(x1+w1, x2+w2), max(y1+h1, y2+h2))
- 合并后尺寸:
width = max_x - min_x
,height = max_y - min_y
91.1:简单直接,但对于大量轮廓可能效率较低(O(n²))
91.2:使用并查集算法,效率更高
std::vector<std::vector<cv::Point>> contour_end;//最终轮廓
std::vector<cv::Rect> bound_Rect_end;
if (1) { // 合并 Y方向重叠的轮廓
cv::Mat visual_bR = cv::Mat::zeros(480, 640, CV_8UC3);
imgOriginal.copyTo(visual_bR);
cv::RNG rng(12345);
std::vector<std::vector<cv::Point>> mergedContours;
//mergedContours = mergeOverlappingContoursY(contour_retained);
mergedContours = mergeOverlappingContoursYOptimized(contour_retained); // 优化版本
if (debug) cout << "Y方向重叠合并后 mergedContours.size() = " << mergedContours.size() << endl;
if (debug) {
for (size_t i = 0; i < mergedContours.size(); i++) {
cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
cv::drawContours(visual_bR, mergedContours, i, color, 2);
}
if (debug) cv::imshow("Merged Contours", visual_bR);
}
int remainNum = 0;//剩余的轮廓数
std::vector<cv::Rect> boundRect;
for (int i = 0; i < mergedContours.size(); i++)
{
std::vector<cv::Point> curContours = mergedContours.at(i);
if (curContours.size() < 40) continue;
boundRect.push_back(cv::boundingRect(curContours));
if (debug) cv::rectangle(visual_bR, boundRect[boundRect.size() - 1].tl(), boundRect[boundRect.size() - 1].br(), cv::Scalar(0, 255, 0), 1);
if (debug) cv::putText(visual_bR, std::to_string(boundRect.size() - 1), boundRect[boundRect.size() - 1].tl(), cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(255, 135, 160), 1);
if (debug) cv::putText(visual_bR, std::to_string(boundRect.size() - 1), boundRect[boundRect.size() - 1].br(), cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(255, 135, 160), 1);
//if (debug) cv::drawContours(visual_bR, curContours, i, cv::Scalar(255, 135, 160), -1, CV_AA);
if (boundRect.size() - 1 >= 0) {
cv::Rect curBR = boundRect.at(boundRect.size() - 1);
double whRatio = curBR.width*1.0 / curBR.height;//宽高比
double full = curContours.size()*1.0 / (curBR.width*curBR.height);
if (debug) std::cout << "--- curBR_" << boundRect.size() - 1 << curBR << whRatio << " \tfull=" << full << std::endl;
//if (whRatio > 1) continue;//宽高比不满足要求的直接 continue
//if (curBR.width > imgOriginal.cols / 3) continue;
//if (curBR.height > imgOriginal.rows / 3) continue;
if (curBR.width < 150) continue;
//if (curBR.height > 150) continue;
}
cv::RotatedRect curMinRect = cv::minAreaRect(curContours);
float longerSide = curMinRect.size.width > curMinRect.size.height ? curMinRect.size.width : curMinRect.size.height;
float shorterSide = curMinRect.size.width < curMinRect.size.height ? curMinRect.size.width : curMinRect.size.height;
double lsRatio = longerSide * 1.0 / shorterSide;//长宽比
if (debug) {
cv::Point2f vertices[4];
curMinRect.points(vertices);
for (int i = 0; i < 4; i++)
line(visual_bR, vertices[i], vertices[(i + 1) % 4], cv::Scalar(80, 175, 210), 2);
if (debug) cv::putText(visual_bR, std::to_string(i), vertices[1], cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(80, 175, 210), 1);
if (debug) cv::putText(visual_bR, std::to_string(i), vertices[3], cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(80, 175, 210), 1);
cv::RotatedRect curMR = curMinRect;
if (debug) std::cout << i << " curMR.angle=" << curMR.angle << " \t, curMR.center=" << curMR.center << "\t, curMR.size=" << curMR.size << lsRatio << std::endl;
}
if (lsRatio < 6.4) continue;
//if (lsRatio > 2.5) continue;
remainNum++;
cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
cv::drawContours(visual_bR, mergedContours, i, color, -1, CV_AA);
//cv::drawContours(visual_bR, mergedContours, i, cv::Scalar(255, 255, 255), -1, CV_AA); //用全黑色填充
contour_end.push_back(curContours);
bound_Rect_end.push_back(boundRect[boundRect.size() - 1]);
}
if (debug) cv::putText(visual_bR, std::to_string(remainNum), cv::Point(visual_bR.cols / 3, visual_bR.rows / 3), cv::FONT_HERSHEY_COMPLEX, 1.45, cv::Scalar(80, 75, 210), 1);
if (debug) cv::imshow("Merged Contours", visual_bR);
//visual_bR.copyTo(canvas);
}
91.1 直接根据最小垂直外接矩形框的y合并 mergeOverlappingContoursY
/*
合并 Y方向上有重叠的轮廓
contours 输入的轮廓集合
返回:合并后的轮廓集合
*/
std::vector<std::vector<cv::Point>> mergeOverlappingContoursY(const std::vector<std::vector<cv::Point>>& contours)
{
if (contours.empty()) return contours;
// 存储轮廓及其边界矩形
std::vector<std::pair<cv::Rect, std::vector<cv::Point>>> contourRects;
for (const auto& contour : contours) {
if (!contour.empty()) {
contourRects.emplace_back(cv::boundingRect(contour), contour);
}
}
// 按 Y 坐标排序
std::sort(contourRects.begin(), contourRects.end(),
[](const std::pair<cv::Rect, std::vector<cv::Point>>& a,
const std::pair<cv::Rect, std::vector<cv::Point>>& b) {
return a.first.y < b.first.y;
});
// 合并重叠的轮廓
std::vector<std::vector<cv::Point>> mergedContours;
for (size_t i = 0; i < contourRects.size(); ++i) {
cv::Rect currentRect = contourRects[i].first;
std::vector<cv::Point> currentContour = contourRects[i].second;
// 检查是否已经处理过
if (currentRect.width == 0 && currentRect.height == 0) {
continue;
}
// 尝试合并与当前轮廓在 Y 方向上有重叠的轮廓
for (size_t j = i + 1; j < contourRects.size(); ++j) {
cv::Rect otherRect = contourRects[j].first;
// 跳过已处理的轮廓
if (otherRect.width == 0 && otherRect.height == 0) {
continue;
}
// 检查 Y 方向是否有重叠
bool yOverlap =
(currentRect.y <= otherRect.y + otherRect.height) &&
(currentRect.y + currentRect.height >= otherRect.y);
if (yOverlap) {
// 合并轮廓点
currentContour.insert(currentContour.end(),
contourRects[j].second.begin(),
contourRects[j].second.end());
//opencv中以重载运算符:从 按位或 变为 矩形合并
currentRect |= otherRect; // 更新当前矩形
contourRects[j].first = cv::Rect(0, 0, 0, 0); // 标记已处理
}
}
// 将合并后的轮廓添加到结果中
mergedContours.push_back(currentContour);
}
return mergedContours;
}
91.2 使用并查集合并 Y方向上有重叠的轮廓 mergeOverlappingContoursYOptimized
- 计算每个轮廓的边界矩形
- 使用并查集数据结构跟踪重叠的轮廓 (对轮廓下标做并查集)
- 根据并查集结果合并轮廓 (根据下标判断是否属同一集合)
- 返回合并后的轮廓
/*
使用并查集合并 Y方向上有重叠的轮廓
contours 输入的轮廓集合
返回:合并后的轮廓集合
*/
std::vector<std::vector<cv::Point>> mergeOverlappingContoursYOptimized(const std::vector<std::vector<cv::Point>>& contours)
{
if (contours.empty()) return contours;
// 存储轮廓及其边界矩形
std::vector<cv::Rect> rects;
for (const auto& contour : contours) {
if (!contour.empty()) {
rects.push_back(cv::boundingRect(contour));
}
}
// 初始化并查集
std::vector<int> parent(rects.size());
for (int i = 0; i < parent.size(); ++i) {
parent[i] = i;
}
// 查找函数
auto find = [&](int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]]; // 路径压缩
x = parent[x];
}
return x;
};
// 合并函数
auto unite = [&](int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootY] = rootX;
}
};
// 检查重叠并合并
for (size_t i = 0; i < rects.size(); ++i) {
for (size_t j = i + 1; j < rects.size(); ++j) {
// 检查 Y 方向是否有重叠
bool yOverlap = (rects[i].y <= rects[j].y + rects[j].height) &&
(rects[i].y + rects[i].height >= rects[j].y);
if (yOverlap) {
unite(i, j);
}
}
}
// 根据并查集结果合并轮廓
std::vector<std::vector<cv::Point>> mergedContours;
std::vector<bool> processed(rects.size(), false); //标记已处理的轮廓
for (size_t i = 0; i < rects.size(); ++i) {
if (processed[i]) continue;
int root = find(i);
std::vector<cv::Point> mergedContour;
for (size_t j = i; j < rects.size(); ++j) {
if (find(j) == root) { //同一集合的轮廓
mergedContour.insert(mergedContour.end(),
contours[j].begin(),
contours[j].end());
processed[j] = true; //标记为已处理
}
}
mergedContours.push_back(mergedContour);
}
return mergedContours;
}
拓展:重叠条件修改
【重叠y超一定比例才合并
// 计算重叠比例
float overlapHeight = std::min(currentRect.y + currentRect.height, otherRect.y + otherRect.height) -
std::max(currentRect.y, otherRect.y);
float minHeight = std::min(currentRect.height, otherRect.height);
float overlapRatio = overlapHeight / minHeight;
// 只有当重叠比例超过阈值时才合并
if (yOverlap && overlapRatio > 0.5) {
// 合并轮廓
}
【X方向重叠条件
// 添加X方向重叠条件
bool xOverlap = (rects[i].x <= rects[j].x + rects[j].width) &&
(rects[i].x + rects[i].width >= rects[j].x);
【重叠面积阈值
// 添加重叠面积阈值
float overlapArea = calculateOverlapArea(rects[i], rects[j]);
float minOverlapRatio = 0.2; // 20%重叠
if (yOverlap && overlapArea / std::min(rects[i].area(), rects[j].area()) > minOverlapRatio) {
unite(i, j);
}