如何将边界框与 groupRectangle 合并?

nz_*_*_21 0 c++ opencv image-processing

我有一个带有边界框的图像,如下所示:

在此处输入图片说明

我想合并重叠的边界框。

我试过: cv::groupRectangles(detected, 1, 0.8)

我的期望是每个集群都有一个盒子。

但我得到了这个:

在此处输入图片说明

正如你所看到的,问题是,中间的飞镖板和右边的飞镖板没有盒子

我该如何解决?我更喜欢使用 OpenCV api 而不是编码我自己的合并算法。

我看到它消除了恰好由一个盒子包围的区域。我希望它不要那样做。

我曾尝试随机调整参数,但结果更糟。我希望得到一些正确方向的指导。

Mik*_*iki 5

如何定义重叠的矩形?

我们需要一种方法来定义两个矩形何时重叠。我们可以使用&相交运算符来找到两个矩形的交集,并检查它是否不为空:

bool overlap(const cv::Rect& lhs, const cv::Rect& rhs) {
    return (lhs & rhs).area() > 0;
}
Run Code Online (Sandbox Code Playgroud)

如果我们想忽略小的交叉点,我们可以在交叉点区域上使用阈值:

bool overlap(const cv::Rect& lhs, const cv::Rect& rhs, int th) {
    return (lhs & rhs).area() > th;
}
Run Code Online (Sandbox Code Playgroud)

但是现在阈值取决于矩形的尺寸。我们可以使用[0, 1] 范围内的“Intersection over Union”指标 (IoU),并在该区间应用阈值。

bool overlap(const cv::Rect& lhs, const cv::Rect& rhs, double th) {
    double i = static_cast<double>((lhs & rhs).area());
    double u = static_cast<double>((lhs | rhs).area());
    double iou = i / u;
    return iou > th;
}
Run Code Online (Sandbox Code Playgroud)

这在一般情况下效果很好,但如果两个矩形的大小非常不同,则可能会显示出意外的结果。另一种方法是检查第一个矩形是否与第二个矩形的大部分区域相交,反之亦然:

bool overlap(const cv::Rect& lhs, const cv::Rect& rhs, double th) {
    double i = static_cast<double>((lhs & rhs).area());
    double ratio_intersection_over_lhs_area = i / static_cast<double>(lhs.area());
    double ratio_intersection_over_rhs_area = i / static_cast<double>(rhs.area());
    return (ratio_intersection_over_lhs_area > th) || (ratio_intersection_over_rhs_area > th);        
}    
Run Code Online (Sandbox Code Playgroud)

好的,现在我们有几种方法来定义两个矩形何时重叠。选一个。

如何找到重叠的矩形?

我们可以使用cv::partition将重叠的矩形放在同一个集群中的谓词对矩形进行聚类。这甚至会将两个彼此不直接重叠但由一个或多个重叠矩形连接的矩形放入同一个集群中。该函数的输出是一个簇向量,其中每个簇由一个矩形向量组成:

std::vector<std::vector<cv::Rect>> cluster_rects(const std::vector<cv::Rect>& rects, const double th)
{
    std::vector<int> labels;
    int n_labels = cv::partition(rects, labels, [th](const cv::Rect& lhs, const cv::Rect& rhs) {
        double i = static_cast<double>((lhs & rhs).area());
        double ratio_intersection_over_lhs_area = i / static_cast<double>(lhs.area());
        double ratio_intersection_over_rhs_area = i / static_cast<double>(rhs.area());
        return (ratio_intersection_over_lhs_area > th) || (ratio_intersection_over_rhs_area > th);
    });

    std::vector<std::vector<cv::Rect>> clusters(n_labels);
    for (size_t i = 0; i < rects.size(); ++i) {
        clusters[labels[i]].push_back(rects[i]);
    }

    return clusters;
}
Run Code Online (Sandbox Code Playgroud)

例如,从这张图片中的矩形:

在此处输入图片说明

我们获得这些集群(阈值为0.2)。注意:

  1. 在左上角的集群中,三个矩形彼此不重叠
  2. 右上角的矩形在它自己的簇上,因为它与其他矩形的相交不够。

在此处输入图片说明

如何找到代表集群的矩形?

嗯,这真的取决于应用程序。它可以是所有矩形的并集:

cv::Rect union_of_rects(const std::vector<cv::Rect>& cluster)
{
    cv::Rect one;
    if (!cluster.empty())
    {
        one = cluster[0];
        for (const auto& r : cluster) { one |= r; }
    }
    return one;
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

或者它可以是最大的内接矩形(下面的代码):

在此处输入图片说明

或者是其他东西。例如,如果您有一个与每个矩形相关联的分数(例如,它是一个有信心的检测),您可以按分数对每个集群进行排序并只取第一个。这是非最大值抑制 (NMA) 的一个示例,您只保留每个集群的最高分矩形(此答案中未显示)。

选一个。

在我用于创建这些图像的工作代码下方。请玩它:)

#include <opencv2/opencv.hpp>

std::vector<cv::Rect> create_some_rects()
{
    std::vector<cv::Rect> rects
    {
    {20, 20, 20, 40},
    {30, 40, 40, 40},
    {50, 46, 30, 40},
    {100, 120, 30, 40},
    {110, 130, 36, 20},
    {104, 124, 50, 30},
    {200, 80, 40, 50},
    {220, 90, 50, 30},
    {240, 84, 30, 70},
    {260, 60, 20, 30},
    };
    return rects;
}

void draw_rects(cv::Mat3b& img, const std::vector<cv::Rect>& rects)
{
    for (const auto& r : rects) {
        cv::Scalar random_color(rand() & 255, rand() & 255, rand() & 255);
        cv::rectangle(img, r, random_color);
    }
}

void draw_rects(cv::Mat3b& img, const std::vector<cv::Rect>& rects, const cv::Scalar& color)
{
    for (const auto& r : rects) {
        cv::rectangle(img, r, color);
    }
}

void draw_clusters(cv::Mat3b& img, const std::vector<std::vector<cv::Rect>>& clusters)
{
    for (const auto& cluster : clusters) {
        cv::Scalar random_color(rand() & 255, rand() & 255, rand() & 255);
        draw_rects(img, cluster, random_color);
    }
}

std::vector<std::vector<cv::Rect>> cluster_rects(const std::vector<cv::Rect>& rects, const double th)
{
    std::vector<int> labels;
    int n_labels = cv::partition(rects, labels, [th](const cv::Rect& lhs, const cv::Rect& rhs) {
        double i = static_cast<double>((lhs & rhs).area());
        double ratio_intersection_over_lhs_area = i / static_cast<double>(lhs.area());
        double ratio_intersection_over_rhs_area = i / static_cast<double>(rhs.area());
        return (ratio_intersection_over_lhs_area > th) || (ratio_intersection_over_rhs_area > th);
    });

    std::vector<std::vector<cv::Rect>> clusters(n_labels);
    for (size_t i = 0; i < rects.size(); ++i) {
        clusters[labels[i]].push_back(rects[i]);
    }

    return clusters;
}

cv::Rect union_of_rects(const std::vector<cv::Rect>& cluster)
{
    cv::Rect one;
    if (!cluster.empty())
    {
        one = cluster[0];
        for (const auto& r : cluster) { one |= r; }
    }
    return one;
}


// /sf/answers/2129323871/
// /sf/answers/2443365081/
cv::Rect findMaxRect(const cv::Mat1b& src)
{
    cv::Mat1f W(src.rows, src.cols, float(0));
    cv::Mat1f H(src.rows, src.cols, float(0));

    cv::Rect maxRect(0, 0, 0, 0);
    float maxArea = 0.f;

    for (int r = 0; r < src.rows; ++r)
    {
        for (int c = 0; c < src.cols; ++c)
        {
            if (src(r, c) == 0)
            {
                H(r, c) = 1.f + ((r > 0) ? H(r - 1, c) : 0);
                W(r, c) = 1.f + ((c > 0) ? W(r, c - 1) : 0);
            }

            float minw = W(r, c);
            for (int h = 0; h < H(r, c); ++h)
            {
                minw = std::min(minw, W(r - h, c));
                float area = (h + 1) * minw;
                if (area > maxArea)
                {
                    maxArea = area;
                    maxRect = cv::Rect(cv::Point(c - minw + 1, r - h), cv::Point(c + 1, r + 1));
                }
            }
        }
    }
    return maxRect;
}

cv::Rect largest_inscribed_of_rects(const std::vector<cv::Rect>& cluster)
{
    cv::Rect roi = union_of_rects(cluster);

    cv::Mat1b mask(roi.height, roi.width, uchar(255));
    for (const auto& r : cluster) {
        cv::rectangle(mask, r - roi.tl(), cv::Scalar(0), cv::FILLED);
    }

    cv::Rect largest_rect = findMaxRect(mask);
    largest_rect += roi.tl();

    return largest_rect;
}



std::vector<cv::Rect> find_one_for_cluster(const std::vector<std::vector<cv::Rect>>& clusters)
{
    std::vector<cv::Rect> one_for_cluster;
    for (const auto& cluster : clusters) {
        //cv::Rect one = union_of_rects(cluster);
        cv::Rect one = largest_inscribed_of_rects(cluster);
        one_for_cluster.push_back(one);
    }
    return one_for_cluster;
}


int main()
{
    cv::Mat3b img(200, 300, cv::Vec3b(0, 0, 0));

    std::vector<cv::Rect> rects = create_some_rects();

    cv::Mat3b initial_rects_img = img.clone();
    draw_rects(initial_rects_img, rects, cv::Scalar(127, 127, 127));

    std::vector<std::vector<cv::Rect>> clusters = cluster_rects(rects, 0.2);

    cv::Mat3b clustered_rects_img = initial_rects_img.clone();
    draw_clusters(clustered_rects_img, clusters);

    std::vector<cv::Rect> single_rects = find_one_for_cluster(clusters);

    cv::Mat3b single_rects_img = initial_rects_img.clone();
    draw_rects(single_rects_img, single_rects);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)