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.
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)));
}
}