OpenCV: Merging overlapping rectangles

marcman picture marcman · Apr 8, 2015 · Viewed 7.7k times · Source

In using OpenCV for detection tasks I keep running into the problem of merging overlapping bounding boxes; that is, essentially finding the bounding box around the union of two overlapping bounding boxes. This comes up a lot in object detection when, for some reason, the object of interest gets fractured into a number of bounding boxes, rather than one all-encompassing one.

There are a few answers on StackOverflow for algorithmic solutions and helpful external libraries (e.g. this, this, this), and there's also the groupRectangles function provided by OpenCV (and the litany of associated questions/bugs: this, this, etc).

I seem to find that the aforementioned solutions are a bit overly complex for the task I'm trying to perform. A lot of the algorithmic solutions solve this problem from a mathematical perspective (more like a thought experiment), while doing operations like rect1 | rect2 can get really slow when the number of rectangles is high (it's O(N^2) to handle everything) and groupRectangles has some quirks that make it only partially effective. So I came up with a somewhat hackish solution that actually works pretty effectively. I thought I'd share it below for anyone else who needs a quick fix for this common problem.

Feel free to comment and critique it.

Answer

marcman picture marcman · Apr 8, 2015
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)));
    }
}