高效合并相交的边界矩形的方法

27
我尝试使用OpenCV简化以下图像:
这里有许多红色形状。其中一些完全包含其他形状。其中一些与其相邻形状相交。我的目标是通过用它们的联合多边形的边界框替换任何两个相交形状来统一所有相交形状(重复直到没有更多相交形状)。
通过相交,我也指接触。希望这使100%清楚:
我正在尝试使用标准形态学运算有效地完成此操作;显然可以以O(N ^ 2)的方式天真地做到,但这将太慢。膨胀无法解决问题,因为某些形状只相距1px,如果它们不相交,则不想将它们合并。

使用函数GroupRectangles怎么样(尽管它的时间复杂度为O(N^2))? - GilLevi
它对于最后一个案例不起作用-请参见https://dev59.com/PGEi5IYBdhLWcg3wRauf - Ben Dowling
3个回答

16

更新:我之前误解了问题。我们不想删除完全包含在其他矩形内部的矩形。我们只想替换相交的矩形。因此,对于第一种情况,我们什么也不用做。

新的API(2.4.9)支持&和|操作符。

来自OpenCV文档

  • rect = rect1 & rect2(矩形交集)
  • rect = rect1 | rect2(包含rect2和rect3的最小面积矩形)

它还支持等式比较(==)

  • rect == rect1

所以现在很容易完成任务。对于每对矩形rect1和rect2,

if((rect1 & rect2) == rect1) ... // rect1 is completely inside rect2; do nothing.
else if((rect1 & rect2).area() > 0) // they intersect; merge them.
    newrect = rect1 | rect2;
    ... // remove rect1 and rect2 from list and insert newrect.

更新2:(用于Java翻译)

我对Java知之甚少。我也从未使用过Java API。这里提供一些伪代码(我认为很容易进行翻译)

对于&运算符,我们需要一个可以找到两个矩形的交集的方法。

Method: Intersect (Rect A, Rect B)
left = max(A.x, B.x)
top  = max(A.y, B.y)
right = min(A.x + A.width, B.x + B.width)
bottom = min(A.y + A.height, B.y + B.height)
if(left <= right && top <= bottom) return Rect(left, top, right - left, bottom - top)
else return Rect()

对于|运算符,我们需要一个类似的方法

Method: Merge (Rect A, Rect B)
left = min(A.x, B.x)
top  = min(A.y, B.y)
right = max(A.x + A.width, B.x + B.width)
bottom = max(A.y + A.height, B.y + B.height)
return Rect(left, top, right - left, bottom - top)

对于==运算符,我们可以使用重载的equals方法。


这很好,但我不认为他有矩形的坐标,他必须先检测它们。 - Rui Marques
@RuiMarques 他实际上有Rect形状。他在一个评论中提到了它。 (https://dev59.com/52Ik5IYBdhLWcg3wqPoi#8ccKoYgBc1ULPQZFGE0F) - asif
我很想知道如何将相同的代码翻译成Java!请帮忙。浏览API但没有找到。最坏的情况是JNI调用。 - Tim Long
感谢 @asif 的答案更新。我现在正在尝试利用 OpenCV Java API。这些操作符本身在 Java 中实现并不难。让我们看看 JNI 性能如何。还有很多其他功能需要从 C++ 库移植到 Java。 - Tim Long

11
为了实现您想要的效果,我们将使用findContours。关键点是理解当mode设置为CV_RETR_TREE时它是如何工作的。在这种情况下,hierarchy被构造成每个偶数深度级别包含外部轮廓,而奇数深度级别包含内部轮廓。我们需要做的是遍历层次结构树,并打印与偶数深度级别相关联的轮廓。

首先,我们找到名为original的图像的轮廓。

typedef std::vector<std::vector<cv::Point> > Contours;
typedef std::vector<cv::Vec4i> Hierarchy;

Contours contours;
Hierarchy hierarchy;
cv::findContours(original, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);
为了在名为processed的图像上打印外轮廓,我们需要一个递归函数。
void printExternalContours(cv::Mat img, Contours const& contours, Hierarchy const& hierarchy, int const idx)
{
    //for every contour of the same hierarchy level
    for(int i = idx; i >= 0; i = hierarchy[i][0])
    {
        //print it
        cv::drawContours(img, contours, i, cv::Scalar(255));

        //for every of its internal contours
        for(int j = hierarchy[i][2]; j >= 0; j = hierarchy[j][0])
        {
            //recursively print the external contours of its children
            printExternalContours(img, contours, hierarchy, hierarchy[j][2]);
        }
    }
}

printExternalContours(processed, contours, hierarchy, 0);

下方显示结果,其中originalprocessed并排展示。

原始图 处理后的图

如果您需要绝对的矩形形状,只需使用boundingRect获取给定点集(在此情况下是每个轮廓)的最小包围矩形,并使用rectangle进行绘制。换句话说,将

cv::drawContours(img, contours, i, cv::Scalar(255));
cv::rectangle(img, cv::boundingRect(contours[i]), cv::Scalar(255));

findContours函数需要一张单通道8位图像,因此你可以从原始图像中制作灰度图像,然后对其进行阈值处理以获得完美的黑色背景,或者在你的情况下,也许使用红色通道就足够了,但请确保背景是完全黑色的。

关于findContours的复杂性,我不能证明它比O(N^2)更好,也没有在快速的谷歌搜索后找到任何相关信息,但我相信OpenCV实现了最好的已知算法。


谢谢。我已经尝试过了,但它似乎会消除包含的(但不相交的)形状。例如:http://i.imgur.com/x5izyFc.png - Assaf Lavie
输入实际上是先前调用findContours的结果;然后我取每个轮廓的边界框。 - Assaf Lavie
1
@AssafLavie,CV_RETR_LIST也不起作用,让我再想一下。 - brunocodutra
我想知道洪水填充算法是否会对此有所帮助。使用它来“遍历”连接的形状,同时跟踪每个形状的边界框...(尽管这不是一种基于过滤器的解决方案) - Assaf Lavie
@AssafLavie 我考虑过这个问题,但我预见到了几个特殊情况。我正在基于我最初提出的方案寻找解决方案,我认为它会奏效,请稍等片刻。 - brunocodutra
显示剩余8条评论

5

给定两个边界框轮廓,形式为(x,y,w,h),下面是一个函数,用于创建一个单一的边界框(假设这些框相互接触或包含)。返回合并后的边界框的(x,y,w,h),即左上角的x、y坐标,宽度和高度。以下是说明

(x1,y1)       w1                           (x3,y3)         w3
  ._____________________.                    .____________________________.
  |                     |                    |                            | 
  |                     |  h1                |                            |
  |   (x2,y2)           |                    |                            |
  |     ._______________|_______.      -->   |                            |
  |     |               |       |            |                            |  h3
  ._____|_______________.       |            |                            |
        |                       |  h2        |                            |
        |                       |            |                            |
        |           w2          |            |                            |
        ._______________________.            .____________________________.

代码

def combineBoundingBox(box1, box2):
    x = min(box1[0], box2[0])
    y = min(box1[1], box2[1])
    w = box2[0] + box2[2] - box1[0]
    h = max(box1[1] + box1[3], box2[1] + box2[3]) - y
    return (x, y, w, h)

例子

有这两个边界框:

>>> print(box1)
>>> print(box2)
(132, 85, 190, 231)
(264, 80, 121, 230)

>>> new = combineBoundingBox(box1, box2) 
>>> print(new)
(132, 80, 253, 236)

这是可视化结果:之前 -> 之后。 输入图像描述 输入图像描述

1
这是一种简单的方法。如果您还提到如何找出两个盒子是否相互接触,您的答案将更加完整。 - Rishabh Gupta
无法适用于所有矩形的对齐方式。 - HugoLasticot

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