How to merge bounding boxes with groupRectangle? - c++

I have an image with bounding boxes like so:
I want to merge overlapping bounding boxes.
I tried: cv::groupRectangles(detected, 1, 0.8)
My expectation was that I get a single box for each cluster.
But I got this:
As you can see, the problem is, there is no box for the dartboard in the middle and for the right one.
How do I resolve this? I would preferably like to use the OpenCV api rather than coding my own merging algorithm.
I see that it eliminates regions bounded by exactly one box. I want it to not do that.
I have tried tweaking the parameters randomly but I've gotten much worse results. I would love some guidance in the right direction.

How to define overlapping rectangles?
We need a way to define when two rectangles overlap. We can use the & intersection operator to find the intersection of the two rectangles, and check that it's not empty:
bool overlap(const cv::Rect& lhs, const cv::Rect& rhs) {
return (lhs & rhs).area() > 0;
}
If we want to ignore small intersections, we can use a threshold over the intersection area:
bool overlap(const cv::Rect& lhs, const cv::Rect& rhs, int th) {
return (lhs & rhs).area() > th;
}
But now the threshold depends on the dimensions of the rectangles. We can use the "Intersection over Union" metric (IoU) which is in the range [0, 1], and apply a threshold in that interval.
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;
}
This works well in general, but may show unexpected results if the two rectangles have a very different size. Another approach could be to check if the first rectangle intersects with the second one for most of its area, and vice versa:
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);
}
Ok, now we have a few ways to define when two rectangles overlap. Pick one.
How to find overlapping rectangles?
We can cluster the rectangles with cv::partition with a predicate that puts overlapping rectangles in the same cluster. This will put in the same cluster even two rectangles that do not directly overlap each other, but are linked by one or more overlapping rectangles. The output of this function is a vector of cluster, where each cluster consists in a vector of rectangles:
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;
}
For example, from the rectangles in this image:
we obtain these clusters (with a threshold of 0.2). Note that:
in the top left cluster the three rectangles do not overlap with each other
the rectangle on the top right is on its own cluster, because it doesn't intersect enough with the other rectangles.
How to find a rectangle that represents a cluster?
Well, that's really application dependent. It can be the union of all rectangles:
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;
}
Or it can be the maximum inscribed rectangle (code below):
Or something else. For example, if you have a score associated with each rectangle (e.g. it's a detection with a confidence) you can sort each cluster by score and take only the first one. This is a an example of non-maxima-suppression (NMA) and you keep only the highest score rectangle for each cluster (Not showed in this answer).
Pick one.
Below the working code I used for creating these images. Please play with it :)
#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;
}
// https://stackoverflow.com/a/30418912/5008845
// https://stackoverflow.com/a/34905215/5008845
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;
}

Unfortunately, you cannot fine-tune groupRectangles(). The second parameter for your example should be 0 though. With 1, all singular rectangles have to be merged somewhere.
You could first grow small rectangles and stay with a conservative threshold parameter if you want a better clustering of the small ones. Not an optimal solution though.
If you want to cluster based on overlap condition, I would suggest to write your own simple algorithm for that. groupRectangles() simply does not do that. It finds rectangles similar in size and position; it does not accumulate rectangles that form a cluster.
You could fill a mask cv::Mat1b mask(image.size(), uchar(0)); with the rectangles and then use cv::connectedComponents() to find merged regions. Note that filling is trivial, loop over all rectangles and call mask(rect).setTo(255);. If the overlap is not always reliable, you could use cv::dilate() to grow rectangles in the mask before the connected-components step.
You could test all rectangles for overlaps and associate them accordingly. For a huge amount of rectangles, I suggest disjoint-set/union-find data structure for efficiency.

Related

Snake active contour algorithm with C++ and OpenCV 3

I am trying to implement the snake algorithm for active contour using C++ and OpenCV 3. I am working with the version that uses the gradient descent. As base test I am trying to draw a contour of a lip. This is the base image.
This is the evolution of the contour without external forces (alpha = 0.001, beta = 3, step-size=0.3).
When I add the external force, this is the result.
As external force I have used just the edge detection with Sobel derivative.
This is the code I use for points update.
array<Mat, 2> edges = edgeMatrices(croppedImage);
const float ALPHA = 0.001, BETA = 3, GAMMA = 0.3, // Gamma is step size.
a = GAMMA * ALPHA, b = GAMMA * BETA;
const uint16_t CYCLES = 1000;
const float p = b, q = -a - 4 * b, r = 1 + 2 * a + 6 * b;
Mat pMatrix = pentadiagonalMatrix(POINTS_NUM, p, q, r).inv();
for (uint16_t i = 0; i < CYCLES; ++i) {
// Extract the x and y derivatives for current points.
auto externalForces = external(edges, x, y);
x = pMatrix * (x + GAMMA * externalForces[0]);
y = pMatrix * (y + GAMMA * externalForces[1]);
// Draw the points.
if (i % 200 == 0 && i > 0)
drawPoints(croppedImage, x, y, { 0.2f * i, 0.2f * i, 0 });
}
This is the code for computing the derivatives.
array<Mat, 2> edgeMatrices(Mat &img) {
// Convert image.
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
// Apply scharr filter.
Mat grad_x, grad_y, blurred_x, blurred_y;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
int kernSize = 3;
Sobel(gray, grad_x, ddepth, 1, 0, kernSize, scale, delta, BORDER_DEFAULT);
Sobel(gray, grad_y, ddepth, 0, 1, kernSize, scale, delta, BORDER_DEFAULT);
GaussianBlur(grad_x, blurred_x, Size(5, 5), 30);
GaussianBlur(grad_y, blurred_y, Size(5, 5), 30);
return { blurred_x, blurred_y };
}
array<Mat, 2> external(array<Mat, 2> &edgeMat, Mat &x, Mat &y) {
array<Mat, 2> ext;
ext[0] = { Size{ 1, POINTS_NUM }, CV_32FC1 };
ext[1] = { Size{ 1, POINTS_NUM }, CV_32FC1 };
for (size_t i = 0; i < POINTS_NUM; ++i) {
ext[0].at<float>(0, i) = - edgeMat[0].at<short>(y.at<float>(0, i), x.at<float>(0, i));
ext[1].at<float>(0, i) = - edgeMat[1].at<short>(y.at<float>(0, i), x.at<float>(0, i));
}
return ext;
}
As you can see, the contour points converge in a very strange way and not towards the edge of the lip (that was the result I would expect).
I am not able to understand if it is an error about implementation or about tuning the parameters or it is just is normal behaviour and I misunderstood something about the algorithm.
I have some doubts on the derivative matrices, I think that they should be regularized in some way, but I am not sure which is the right one. Can someone help me?
The only implementations I have found are of the greedy method.

Hough Circular Transform

Im trying to implement Hough Transform using gradient direction. I know that there is an implementation in OpenCv but I want to do it myself.
I'm using Sobel to get the X and Y gradient. Then for every pixel the
magnitute ---> sqrt(sobelX^2 + sobelY^2)
directions --> atan2(sobelY,sobelX) * 180/PI
if the magnitude is higher then 220 (so almost black) this is the edge.
And then the direction is used on the circle equation.
But the results are not acceptable. Any help?
I know there are the cv::polar and cv::cartToPolar, but I want to optimize code so that all equations will be calculated on fly, no empty loops.
cv::Mat sobelX,sobelY;
Sobel(mat, sobelX, CV_32F, 1, 0, kernelSize, 1, 0, cv::BORDER_REPLICATE);
Sobel(mat, sobelY, CV_32F, 0, 1, kernelSize, 1, 0, cv::BORDER_REPLICATE);
//cv::Canny(mat,mat,100,200,kernelSize,false);
debug::showImage("sobelX",sobelX);
debug::showImage("SobelY",sobelY);
debug::showImage("MAT",mat);
cv::Mat magnitudeMap,angleMap;
magnitudeMap = cv::Mat::zeros(mat.rows,mat.cols,mat.type());
angleMap = cv::Mat::zeros(mat.rows,mat.cols,mat.type());
std::vector<cv::Mat> hough_spaces(max);
for(int i=0; i<max; ++i)
{
hough_spaces[i] = cv::Mat::zeros(mat.rows,mat.cols,mat.type());
}
for(int x=0; x<mat.rows; ++x)
{
for(int y=0; y<mat.cols; ++y)
{
const float magnitude = sqrt(sobelX.at<uchar>(x,y)*sobelX.at<uchar>(x,y)+sobelY.at<uchar>(x,y)*sobelY.at<uchar>(x,y));
const float theta= atan2(sobelY.at<uchar>(x,y),sobelX.at<uchar>(x,y)) * 180/CV_PI;
magnitudeMap.at<uchar>(x,y) = magnitude;
if(magnitude > 225)//mat.at<const uchar>(x,y) == 255)
{
for(int radius=min; radius<max; ++radius)
{
const int a = x - radius * cos(theta);//lookup::cosArray[static_cast<int>(theta)];//+ 0.5f;
const int b = y - radius * sin(theta);//lookup::sinArray[static_cast<int>(theta)]; //+ 0.5f;
if(a >= 0 && a <hough_spaces[radius].rows && b >= 0 && b<hough_spaces[radius].cols) {
hough_spaces[radius].at<uchar>(a,b)+=10;
}
}
}
}
}
debug::showImage("magnitude",magnitudeMap);
for(int radius=min; radius<max; ++radius)
{
double min_f,max_f;
cv::Point min_loc,max_loc;
cv::minMaxLoc(hough_spaces[radius],&min_f,&max_f,&min_loc,&max_loc);
if(max_f>=treshold)
{
circles.emplace_back(cv::Point3f(max_loc.x,max_loc.y,radius));
// debug::showImage(std::to_string(radius).c_str(),hough_spaces[radius]);
}
}
circles.shrink_to_fit();

opencv euclidean clustering vs findContours

I have the following image mask:
I want to apply something similar to cv::findContours, but that algorithm only joins connected points in the same groups. I want to do this with some tolerance, i.e., I want to add the pixels near each other within a given radius tolerance: this is similar to Euclidean distance hierarchical clustering.
Is this implemented in OpenCV? Or is there any fast approach for implementing this?
What I want is something similar to this,
http://www.pointclouds.org/documentation/tutorials/cluster_extraction.php
applied to the white pixels of this mask.
Thank you.
You can use partition for this:
partition splits an element set into equivalency classes. You can define your equivalence class as all points within a given euclidean distance (radius tolerance)
If you have C++11, you can simply use a lambda function:
int th_distance = 18; // radius tolerance
int th2 = th_distance * th_distance; // squared radius tolerance
vector<int> labels;
int n_labels = partition(pts, labels, [th2](const Point& lhs, const Point& rhs) {
return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < th2;
});
otherwise, you can just build a functor (see details in the code below).
With appropriate radius distance (I found 18 works good on this image), I got:
Full code:
#include <opencv2\opencv.hpp>
#include <vector>
#include <algorithm>
using namespace std;
using namespace cv;
struct EuclideanDistanceFunctor
{
int _dist2;
EuclideanDistanceFunctor(int dist) : _dist2(dist*dist) {}
bool operator()(const Point& lhs, const Point& rhs) const
{
return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < _dist2;
}
};
int main()
{
// Load the image (grayscale)
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Get all non black points
vector<Point> pts;
findNonZero(img, pts);
// Define the radius tolerance
int th_distance = 18; // radius tolerance
// Apply partition
// All pixels within the radius tolerance distance will belong to the same class (same label)
vector<int> labels;
// With functor
//int n_labels = partition(pts, labels, EuclideanDistanceFunctor(th_distance));
// With lambda function (require C++11)
int th2 = th_distance * th_distance;
int n_labels = partition(pts, labels, [th2](const Point& lhs, const Point& rhs) {
return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < th2;
});
// You can save all points in the same class in a vector (one for each class), just like findContours
vector<vector<Point>> contours(n_labels);
for (int i = 0; i < pts.size(); ++i)
{
contours[labels[i]].push_back(pts[i]);
}
// Draw results
// Build a vector of random color, one for each class (label)
vector<Vec3b> colors;
for (int i = 0; i < n_labels; ++i)
{
colors.push_back(Vec3b(rand() & 255, rand() & 255, rand() & 255));
}
// Draw the labels
Mat3b lbl(img.rows, img.cols, Vec3b(0, 0, 0));
for (int i = 0; i < pts.size(); ++i)
{
lbl(pts[i]) = colors[labels[i]];
}
imshow("Labels", lbl);
waitKey();
return 0;
}
I suggest to use DBSCAN algorithm. It is exactly what you are looking for. Use a simple Euclidean Distance or even Manhattan Distance may work better.
The input is all white points(threshold-ed). The output is a groups of points(your connected component)
Here is a DBSCAN C++ implenetation
EDIT:
I tried DBSCAN my self and here is the result:
As you see, Just the really connected points are considered as one cluster.
This result was obtained using the standerad DBSCAN algorithm with EPS=3 (static no need to be tuned) MinPoints=1 (static also) and Manhattan Distance

OpenCV groupRectangles - getting grouped and ungrouped rectangles

I'm using OpenCV and want to group together rectangles that have significant overlap. I've tried using groupRectangles for this, which takes a group threshold argument. With a threshold of 0 it doesn't do any grouping at all, and with a threshold of 1 is only returns rectangles that were the result of at least 2 rectangles. For example, given the rectangles on the left in the image below you end up with the 2 rectangles on the right:
What I'd like to end up with is 3 rectangles. The 2 on the right in the image above, plus the rectangle in the top right of the image to the left that doesn't overlap with any other rectangles. What's the best way to achieve this?
The solution I ended up going with was to duplicate all of the initial rectangles before calling groupRectangles. That way every input rectangle is guaranteed to be grouped with at least one other rectangle, and will appear in the output:
int size = rects.size();
for( int i = 0; i < size; i++ )
{
rects.push_back(Rect(rects[i]));
}
groupRectangles(rects, 1, 0.2);
A little late to the party, however "duplicating" solution did not properly work for me. I also had another problem where merged rectangles would overlap and would need to be merged.
So I came up with an overkill solution (might require C++14 compiler). Here's usage example:
std::vector<cv::Rect> rectangles, test1, test2, test3;
rectangles.push_back(cv::Rect(cv::Point(5, 5), cv::Point(15, 15)));
rectangles.push_back(cv::Rect(cv::Point(14, 14), cv::Point(26, 26)));
rectangles.push_back(cv::Rect(cv::Point(24, 24), cv::Point(36, 36)));
rectangles.push_back(cv::Rect(cv::Point(37, 20), cv::Point(40, 40)));
rectangles.push_back(cv::Rect(cv::Point(20, 37), cv::Point(40, 40)));
test1 = rectangles;
test2 = rectangles;
test3 = rectangles;
//Output format: {Rect(x, y, width, height), ...}
//Merge once
mergeRectangles(test1);
//Output rectangles: test1 = {Rect(5, 5, 31, 31), Rect(20, 20, 20, 20)}
//Merge until there are no rectangles to merge
mergeRectangles(test2, true);
//Output rectangles: test2 = {Rect(5, 5, 35, 35)}
//Override default merge (intersection) function to merge all rectangles
mergeRectangles(test3, false, [](const cv::Rect& r1, const cv::Rect& r2) {
return true;
});
//Output rectangles: test3 = {Rect(5, 5, 35, 35)}
Function:
void mergeRectangles(std::vector<cv::Rect>& rectangles, bool recursiveMerge = false, std::function<bool(const cv::Rect& r1, const cv::Rect& r2)> mergeFn = nullptr) {
static auto defaultFn = [](const cv::Rect& r1, const cv::Rect& r2) {
return (r1.x < (r2.x + r2.width) && (r1.x + r1.width) > r2.x && r1.y < (r2.y + r2.height) && (r1.y + r1.height) > r2.y);
};
static auto innerMerger = [](std::vector<cv::Rect>& rectangles, std::function<bool(const cv::Rect& r1, const cv::Rect& r2)>& mergeFn) {
std::vector<std::vector<std::vector<cv::Rect>::const_iterator>> groups;
std::vector<cv::Rect> mergedRectangles;
bool merged = false;
static auto findIterator = [&](std::vector<cv::Rect>::const_iterator& iteratorToFind) {
for (auto groupIterator = groups.begin(); groupIterator != groups.end(); ++groupIterator) {
auto foundIterator = std::find(groupIterator->begin(), groupIterator->end(), iteratorToFind);
if (foundIterator != groupIterator->end()) {
return groupIterator;
}
}
return groups.end();
};
for (auto rect1_iterator = rectangles.begin(); rect1_iterator != rectangles.end(); ++rect1_iterator) {
auto groupIterator = findIterator(rect1_iterator);
if (groupIterator == groups.end()) {
groups.push_back({rect1_iterator});
groupIterator = groups.end() - 1;
}
for (auto rect2_iterator = rect1_iterator + 1; rect2_iterator != rectangles.end(); ++rect2_iterator) {
if (mergeFn(*rect1_iterator, *rect2_iterator)) {
groupIterator->push_back(rect2_iterator);
merged = true;
}
}
}
for (auto groupIterator = groups.begin(); groupIterator != groups.end(); ++groupIterator) {
auto groupElement = groupIterator->begin();
int x1 = (*groupElement)->x;
int x2 = (*groupElement)->x + (*groupElement)->width;
int y1 = (*groupElement)->y;
int y2 = (*groupElement)->y + (*groupElement)->height;
while (++groupElement != groupIterator->end()) {
if (x1 > (*groupElement)->x)
x1 = (*groupElement)->x;
if (x2 < (*groupElement)->x + (*groupElement)->width)
x2 = (*groupElement)->x + (*groupElement)->width;
if (y1 >(*groupElement)->y)
y1 = (*groupElement)->y;
if (y2 < (*groupElement)->y + (*groupElement)->height)
y2 = (*groupElement)->y + (*groupElement)->height;
}
mergedRectangles.push_back(cv::Rect(cv::Point(x1, y1), cv::Point(x2, y2)));
}
rectangles = mergedRectangles;
return merged;
};
if (!mergeFn)
mergeFn = defaultFn;
while (innerMerger(rectangles, mergeFn) && recursiveMerge);
}
By checking out groupRectangles() in opencv-3.3.0 source code:
if( groupThreshold <= 0 || rectList.empty() )
{
// ......
return;
}
I saw that if groupThreshold is set to less than or equal to 0, the function would just return without doing any grouping.
On the other hand, the following code removed all rectangles which don't have more than groupThreshold similarities.
// filter out rectangles which don't have enough similar rectangles
if( n1 <= groupThreshold )
continue;
That explains why with groupThreshold=1 only rectangles with at least 2 overlappings are in the output.
One possible solution could be to modify the source code shown above (replacing n1 <= groupThreshold with n1 < groupThreshold) and re-compile OpenCV.

Finding HSV Thresholds Via Histograms with OpenCV

I'm trying to write a method that will find the proper threshold values in HSV space for an object placed at the center of the screen. These values are used for an object tracking algorithm. I've tested that piece of code with hand coded threshold values and it works well. The idea behind the method is that it should calculate the histograms for each of the channels and then return the 5th and 95th percentile for each to be used as the threshold values. (credit: How to find RGB/HSV color parameters for color tracking?) The image being passed is a picture of the object to be tracked (which is set by the user before the whole process begins. Here is the code
std::vector<cv::Scalar> HSV_Threshold_Determiner::Get_Threshold_Values(const cv::Mat& image)
{
cv::Mat inputImage;
cv::cvtColor(image, inputImage, CV_BGR2HSV);
std::vector<cv::Mat> bgrPlanes;
cv::split(inputImage, bgrPlanes);
cv::Mat hHist, sHist, vHist;
int hMax = 180, svMax = 256;
float hRanges[] = { 0, (float)hMax };
const float* hRange = { hRanges };
float svRanges[] = { 0, (float)svMax };
const float* svRange = { svRanges };
//float sRanges[] = { 0, 256 };
cv::calcHist(&bgrPlanes[0], 1, 0, cv::Mat(), hHist, 1, &hMax, &hRange);
cv::calcHist(&bgrPlanes[1], 1, 0, cv::Mat(), sHist, 1, &svMax, &svRange);
cv::calcHist(&bgrPlanes[2], 1, 0, cv::Mat(), vHist, 1, &svMax, &svRange);
int totalEntries = image.cols * image.rows;
int fiveCutoff = (int)(totalEntries * .05);
int ninetyFiveCutoff = (int)(totalEntries * .95);
float hTotal = 0, sTotal = 0, vTotal = 0;
bool hMinFound = false, hMaxFound = false, sMinFound = false, sMaxFound = false,
vMinFound = false, vMaxFound = false;
cv::Scalar hThresholds;
cv::Scalar sThresholds;
cv::Scalar vThresholds;
for(int i = 0; i < vHist.rows; ++i)
{
if(i < hHist.rows)
{
hTotal += hHist.at<float>(i, 0);
if(hTotal >= fiveCutoff && !hMinFound)
{
hThresholds.val[0] = i;
hMinFound = true;
}
else if(hTotal>= ninetyFiveCutoff && !hMaxFound)
{
hThresholds.val[1] = i;
hMaxFound = true;
}
}
sTotal += sHist.at<float>(i, 0);
vTotal += vHist.at<float>(i, 0);
if(sTotal >= fiveCutoff && !sMinFound)
{
sThresholds.val[0] = i;
sMinFound = true;
}
else if(sTotal >= ninetyFiveCutoff && !sMaxFound)
{
sThresholds.val[1] = i;
sMaxFound = true;
}
if(vTotal >= fiveCutoff && !vMinFound)
{
vThresholds.val[0] = i;
vMinFound = true;
}
else if(vTotal >= ninetyFiveCutoff && !vMaxFound)
{
vThresholds.val[1] = i;
vMaxFound = true;
}
if(vMaxFound && sMaxFound && hMaxFound)
{
break;
}
}
std::vector<cv::Scalar> returnVect;
returnVect.push_back(hThresholds);
returnVect.push_back(sThresholds);
returnVect.push_back(vThresholds);
return returnVect;
}
What I am trying to do is sum up the number of entries in each bucket until I get to a number that is greater than or equal to five percent and ninety-five percent of the total. Unfortunately the numbers I get are never close to the ones I get if I do the thresholding by hand.
Mat img = ... // from camera or some other source
// STEP 1: learning phase
Mat hsv, imgThreshed, processed, denoised;
cv::GaussianBlur(img, denoised, cv::Size(5,5), 2, 2); // remove noise
cv::cvtColor(denoised, hsv, CV_BGR2HSV);
// lets say we picked manually a region of 100x100 px with the interested color/object using mouse
cv::Mat roi = hsv (cv::Range(mousex-50, mousey+50), cv::Range(mousex-50, mousey+50));
// must split all channels to get Hue only
std::vector<cv::Mat> hsvPlanes;
cv::split(roi, hsvPlanes);
// compute statistics for Hue value
cv::Scalar mean, stddev;
cv::meanStdDev(hsvPlanes[0], mean, stddev);
// ensure we get 95% of all valid Hue samples (statistics 3*sigma rule)
float minHue = mean[0] - stddev[0]*3;
float maxHue = mean[0] + stddev[0]*3;
// STEP 2: detection phase
cv::inRange(hsvPlanes[0], cv::Scalar(minHue), cv::Scalar(maxHue), imgThreshed);
imshow("thresholded", imgThreshed);
cv_erode(imgThreshed, processed, 5); // minimizes noise
cv_dilate(processed, processed, 20); // maximize left regions
imshow("final", processed);
//STEP 3: do some blob/contour detection on processed image & find maximum blob/region, etc ...
A much simpler solution - just calculate mean & std. deviation for a region of interest, i.e. containing the Hue value.
Since Hue is the most stable component in the image, the other components saturation & value should be discarded as they vary too much. However you can still compute mean for them if needed.