OpenCV:合并重叠的矩形

5
在使用OpenCV进行检测任务时,我经常遇到合并重叠边界框的问题;也就是说,基本上是找到围绕两个重叠边界框的并集的边界框。在对象检测中,当感兴趣的对象因某种原因被分成许多边界框而不是一个全包围的边界框时,这种情况经常出现。
有一些StackOverflow上的算法解决方案和有用的外部库(例如这个这个这个),还有OpenCV提供的groupRectangles函数(以及相关问题/错误:这个这个等)。
我发现上述解决方案对于我要执行的任务有点过于复杂。许多算法解决方案从数学角度解决了这个问题(更像是一种思想实验),而当矩形的数量很高时,像rect1 | rect2这样的操作会变得非常缓慢(处理所有内容的时间复杂度为O(N^2)),而groupRectangles也有一些怪癖,使其只能部分有效。因此,我想出了一个有点粗糙的解决方案,实际上效果相当不错。我想与其他需要快速解决这个常见问题的人分享如下内容。
欢迎评论和批评。

测试交集:cv::Rect intersect = rect1 && rect2; 合并:如果(intersection.width) combination = rect1 || rect2; - Micka
1
@Micka 也是可行的。我发帖的原因之一就是因为它相对来说也比较轻量级且易于实现。但一旦涉及到分区等问题,事情往往会不断扩大。 - marcman
关于我之前的评论,一个像素的重叠算不算重叠? - Antonio
@marcman 谢谢更新!顺便提到一件事,我想问关于另一个情况:假设在开始时有一个孤立的矩形,与任何其他矩形都不重叠。然后,通过合并另外两个矩形,您获得了一个与最初孤立的矩形重叠的矩形。这个矩形应该与另外两个合并吗?因为我认为这确实是您解决方案的缺陷,但是合理地说,您可以使用另一个算法进行第二次遍历,该算法不会绘制矩形,在那时使用更少的矩形应该是可行的。 - Antonio
@Antonio,你说得对,那确实是一个缺陷。我也认为第二次遍历是最简单的修复方法,但正如你所指出的,可能还有其他更有效的方法。 - marcman
显示剩余4条评论
1个回答

2
void mergeOverlappingBoxes(std::vector<cv::Rect> &inputBoxes, cv::Mat &image, std::vector<cv::Rect> &outputBoxes)
{
    cv::Mat mask = cv::Mat::zeros(image.size(), CV_8UC1); // Mask of original image
    cv::Size scaleFactor(10,10); // To expand rectangles, i.e. increase sensitivity to nearby rectangles. Doesn't have to be (10,10)--can be anything
    for (int i = 0; i < inputBoxes.size(); i++)
    {
        cv::Rect box = inputBoxes.at(i) + scaleFactor;
        cv::rectangle(mask, box, cv::Scalar(255), CV_FILLED); // Draw filled bounding boxes on mask
    }

    std::vector<std::vector<cv::Point>> contours;
    // Find contours in mask
    // If bounding boxes overlap, they will be joined by this function call
    cv::findContours(mask, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    for (int j = 0; j < contours.size(); j++)
    {
        outputBoxes.push_back(cv::boundingRect(contours.at(j)));
    }
}

一个问题是,如果内部矩形与另外两个被合并的矩形不连通,那么你仍然可能得到一个在大矩形内部的边界框。例如,在二维 XY 平面中填充了 TR、BR、BL 三个象限,而在 TL 象限中有一个小对象没有连接,则结果将是一个围绕整个平面的矩形,以及一个明显的在 TL 象限内部的矩形。一种解决方法是再次调用此函数,但这样很容易开始螺旋... - marcman
我可以建议一种更快、更轻的方式。首先,使用它们的成对重叠(运算符&)将所有相关的框分组。对于每组框,找到x和y轴的最小值和最大值。结果与您的相同,但方法不会占用过多的CPU资源。 - LovaBill
1
@William,你如何高效地对相关的框进行分组?假设你只有一个边界框向量,如何比O(N^2)更快地完成这项任务?我在上面的评论中提到过,我还没有对我的解决方案进行分析,但从经验上看,它比将每个“矩形”与其他所有矩形进行比较要快。 - marcman
我喜欢C4探测器的功能。请查看Pedestrian_ICRA.cpp中的void PostProcess()函数。不幸的是,他使用了自己的类,并需要一些额外的工作来使用cv::Rect。 - LovaBill
我不理解scaleFactor的用途。我期望一个比例因子应该是一个乘法项...在这里你只是在处理矩形... - Antonio
1
@Antonio,rect的操作列表可以在这里找到。+/- cv::Point()表示平移。+/- cv::Size()将放大或缩小rect。我也不得不进行测试以确保它的正确性。 - marcman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接