OpenCV - find bounding box of largest blob in binary image - opencv

What is the most efficient way to find the bounding box of the largest blob in a binary image using OpenCV? Unfortunately, OpenCV does not have specific functionality for blob detection. Should I just use findContours() and search for the largest in the list?

Here. It. Is.
(FYI: try not to be lazy and figure out what happens in my function below.
cv::Mat findBiggestBlob(cv::Mat & matImage){
int largest_area=0;
int largest_contour_index=0;
vector< vector<Point> > contours; // Vector for storing contour
vector<Vec4i> hierarchy;
findContours( matImage, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); // Find the contours in the image
for( int i = 0; i< contours.size(); i++ ) {// iterate through each contour.
double a=contourArea( contours[i],false); // Find the area of contour
if(a>largest_area){
largest_area=a;
largest_contour_index=i; //Store the index of largest contour
//bounding_rect=boundingRect(contours[i]); // Find the bounding rectangle for biggest contour
}
}
drawContours( matImage, contours, largest_contour_index, Scalar(255), CV_FILLED, 8, hierarchy ); // Draw the largest contour using previously stored index.
return matImage;
}

If you want to use OpenCV libs, check out OpenCVs SimpleBlobDetector. Here's another stack overflow showing a small tutorial of it: How to use OpenCV SimpleBlobDetector
This only gives you key points though. You could use this as an initial search to find the blob you want, and then possibly use the findContours algorithm around the most likely blobs.
Also the more information you know about your blob, you can provide parameters to filter out the blobs you don't want. You might want to test out the area parameters of the SimpleBlobDetector. Possibly could could compute the area based on the size of the area of the image and then iteratively allow for a smaller blob if the algorithm does not detect any blobs.
Here is the link to the main OpenCV documentation: http://docs.opencv.org/modules/features2d/doc/common_interfaces_of_feature_detectors.html#simpleblobdetector

To find the bounding box of the largest blob, I used findContours, followed by the following code:
double maxArea = 0;
for (MatOfPoint contour : contours) {
double area = Imgproc.contourArea(contour);
if (area > maxArea) {
maxArea = area;
largestContour = contour;
}
}
Rect boundingRect = Imgproc.boundingRect(largestContour);

Since no one has posted a complete OpenCV solution, here's a simple approach using thresholding + contour area filtering
Input image
Largest blob/contour highlighted in green
import cv2
# Load image, grayscale, Gaussian blur, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find contours and sort using contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
# Highlight largest contour
cv2.drawContours(image, [c], -1, (36,255,12), 3)
break
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()

TimZaman, your code has a bug but I can't comment so I start a new and correct answer.
Here is my solution based on 1"'s and TimZaman's ideas:
Mat measure::findBiggestBlob(cv::Mat &src){
int largest_area=0;
int largest_contour_index=0;
Mat temp(src.rows,src.cols,CV_8UC1);
Mat dst(src.rows,src.cols,CV_8UC1,Scalar::all(0));
src.copyTo(temp);
vector<vector<Point>> contours; // storing contour
vector<Vec4i> hierarchy;
findContours( temp, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
for( int i = 0; i< contours.size(); i++ ) // iterate
{
double a=contourArea( contours[i],false); //Find the largest area of contour
if(a>largest_area)
{
largest_area=a;
largest_contour_index=i;
}
}
drawContours( dst, contours,largest_contour_index, Scalar(255), CV_FILLED, 8, hierarchy );
// Draw the largest contour
return dst;
}

Related

how to get Matrix(ROI) in openCV using by line that inclined

I already tried to search about openCV ROI function, but All of them used rectangle roi function.
I want to get roi using by inclined line that get from hough transform function.
My situation is next :
I have multiple vertical lines(little inclined) that output from hough transform function.
i want to get image(Matrix) between vertical lines.
enter image description here
i want to get divided matrix in my image (For example, A image, B image, C image etc.. )
Is there ROI function that used line in openCV?
or
any another method?
I think you need to use contours to define your roi. If it is not a perfect square you can not use the ROI function, because this is always a perfect square (not even a rotated square)
int main()
{
enum hierIdx { H_NEXT = 0, H_PREVIOUS, H_FIRST_CHILD, H_PARENT };
cv::Mat img = cv::imread("example_image.jpg", cv::IMREAD_UNCHANGED);
// convert RGB to gray scale image
cv::Mat imgGrs;
cv::cvtColor(img, imgGrs, cv::COLOR_RGB2GRAY);
// because it was a .jpg the grey values are messed up
// we fix it by thresholding at 128
cv::threshold(imgGrs, imgGrs, 128, 255, cv::THRESH_BINARY);
imgGrs = ~imgGrs;
// now create contours (we need the hierarchy to find the inner shapes)
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(imgGrs.clone(), contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
//cv::drawContours(img, contours, -1, cv::Scalar(255, 0, 0), 1);
int iLen = (int)hierarchy.size();
int idxChild = -1;
// find first child of master
for (int i = 0; i < iLen; i++){
if (hierarchy[i][H_PARENT] < 0) {
idxChild = hierarchy[i][H_FIRST_CHILD];
break;
}
}
// used for erosion of mask
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
while (idxChild >= 0)
{
// create image to use as mask for section
cv::Mat mask = cv::Mat::zeros(imgGrs.size(), CV_8U);
cv::drawContours(mask, contours, idxChild, cv::Scalar(255), CV_FILLED);
// make masker 1 pixel smaller so we wont see the outer contours
cv::erode(mask, mask, element);
// ok nu we create a singled out part we want
cv::Mat part = imgGrs & mask;
// Crop it to the AOI rectangle
cv::Rect aoi = cv::boundingRect(contours[idxChild]);
part = part(aoi);
// part is now the aoi image you asked for
// proceed to next AOI
idxChild = hierarchy[idxChild][H_NEXT];
}
return 0;
}

Contour results are not accurate with detection binary mask edges [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
Now I have this mask generated based on the size of an image and I have a set of rectangles to know where are these rectangles so I tried to extract contours to know where are these rectangles ...
mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8) # Mask of original image
#some process to draw these rectangles merged...
_, bw = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)
img, contours, hierarchy = cv2.findContours(bw.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for j in range(len(contours)):
outputBoxes.append(cv2.boundingRect(contours[j]))
Another trial
_, bws = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)
img, contours, hierarchy = cv2.findContours(bws, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE, offset=(0,0))
cv2.drawContours(bws, contours,-1,(255,255,0))
img, contours, hierarchy = cv2.findContours(bws, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE, offset=(0,0))
but with no vain
I can't get the rect dimensions exactly or even approximately
** NOTE This generated image is small part of long process
first one I detected some rects so I merged them as one in the second one then I should take this white one dimensions to be determined in the original one
As seen, it is not accurate too
Python or c++ are good for me
opencv 3.x
Here is the results I get:
Obviously from here you have pretty clear contours and you can just print out the lengths.
With this code:
Mat origImage = mat;
Mat canny_output = mat;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
cv::Mat greyMat, colorMat;
cv::cvtColor(mat, greyMat, CV_BGR2GRAY);
int thresh = 100;
RNG rng(12345);
///// Detect edges using canny
Canny(greyMat, canny_output, thresh, thresh * 2, 3);
/// Find contours
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
int largest_area = 0;
int largest_contour_index = 0;
Rect bounding_rect;
/// Draw contours
Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
for (int i = 0; i< contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
double a=contourArea( contours[i],false); // Find the area of contour
if(a>largest_area){
largest_area=a;
largest_contour_index=i; //Store the index of largest contour
bounding_rect=boundingRect(contours[i]); // Find the bounding rectangle for biggest contour
}
}
rectangle(origImage, bounding_rect, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),2);
/// Show in a window
namedWindow("Contours", CV_WINDOW_AUTOSIZE);
resize(drawing, drawing, Size(640, 480));
resize(mat, mat, Size(640, 480));
resize(origImage, origImage, Size(640, 480));
imshow("Contours", drawing);
cv::namedWindow("img");
cv::imshow("mat", mat);
cv::imshow("mat", origImage);
cv::imshow("mat123", drawing);
cv::waitKey(0);

OpenCV: How can I remove unwanted blobs or how can I copy wanted parts into an empty image?

From the following image, how could I find the result image?
The images shown here are threshold images. I have tried using morphological operators but they even remove the blob I want. How could I solve this problem?
Any hints?
Following is the result image I am interested to get/find:
import cv2
diff = cv2.imread('Image.png',0)
ret, thresh = cv2.threshold(diff, 12.5, 255, cv2.THRESH_BINARY)
thresh = cv2.dilate(thresh, None, iterations = 1)
cv2.imshow('img', thresh) # This is the first picture I have shown
cv2.waitKey(0)
You are most of the way there, all you need to do now is find the blobs, add some contours and find the biggest one. Easy! below is the code in C++, ill leave it up to you to work out how to convert it to Python:
cv::Mat mat = imread("g0cVU.png");
Mat origImage = mat;
Mat canny_output = mat;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
cv::Mat greyMat, colorMat;
cv::cvtColor(mat, greyMat, CV_BGR2GRAY);
int thresh = 100;
RNG rng(12345);
///// Detect edges using canny
Canny(greyMat, canny_output, thresh, thresh * 2, 3);
/// Find contours
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
int largest_area = 0;
int largest_contour_index = 0;
Rect bounding_rect;
/// Draw contours
Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
for (int i = 0; i< contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
double a=contourArea( contours[i],false); // Find the area of contour
if(a>largest_area){
largest_area=a;
largest_contour_index=i; //Store the index of largest contour
bounding_rect=boundingRect(contours[i]); // Find the bounding rectangle for biggest contour
}
}
rectangle(origImage, bounding_rect, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),2);
/// Show in a window
namedWindow("Contours", CV_WINDOW_AUTOSIZE);
imshow("Contours", drawing);
cv::namedWindow("img");
cv::imshow("mat", mat);
cv::imshow("mat", origImage);
cv::imshow("mat123", drawing);
cv::waitKey(0);
Which gives this results:
You can see in the bottom image the largest contor has a brown rectangle drawn around it.
o and obviously once you have the largest blob (or whatever blob you deem "the correct one") you can just set everything else to black which is fairly straightforward.

How can I detect registration markers on paper using OpenCV?

I've been trying to find the best way to detect the 4 black squares on the paper and use them to isolate the paper in its own image.
It seems that on your image there are only 4 black squares so what you have to do is:
Convert image to grays
Do threshold
Find black contours (before doing this in OpenCV you have to invert your image, because by default OpenCV finds white contours)
Cycle through these contours and find bounding rectangle.
Do the check:
A) Rectangle's area is bigger that some constant (in my solution it was 100)
B) Rectangle's width/height is near 1.0 (in my soultion it was [0.9, 1.1] range)
The code:
Mat img = imread("test.jpg"), gray;
vector<Vec4i> hierarchy;
vector<vector<Point2i> > contours;
cvtColor(img, gray, CV_BGR2GRAY);
threshold(gray, gray, 100, 255, THRESH_BINARY);
bitwise_not(gray, gray);
findContours(gray, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
for(size_t i=0; i<contours.size(); i++)
{
Rect rect = boundingRect(contours[i]);
double k = (rect.height+0.0)/rect.width;
if (0.9<k && k<1.1 && rect.area()>100)
{
drawContours(img, contours, i, Scalar(0,0,255));
}
}
imshow("result", img);
waitKey();
Result:
Also read this SO discussion - you don't need that 4 squares to detect paper.

OpenCV: Incorrect contour around blobs

I'm trying to draw contours around blobs in a binary image, however, sometimes, openCV draws a single contour around two distinct blobs. below is an example. How can i solve this issue?
Here it should draw two bounding boxes for the blob on the right and separately for the one of the left. I agree they are close but enough distance in between them. I'm only drawing External contours instead of the tree or list. I'm also using cvFindNextContour(contourscanner) as this is a easier implementation for my case.
Thanks
EDIT:
Image displayed in the "output" window is from a different function which does just image subtraction. Image displayed in the "contours" window is in the function pplfind(). "output" image is passed to img_con().
IplImage* img_con(IplImage* image){
int ppl;
CvMemStorage* memstr = cvCreateMemStorage();
IplImage* edges = cvCreateImage(cvGetSize(image),8,1);
cvCanny(image,edges,130,255);
CvContourScanner cscan = cvStartFindContours(image,memstr,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE,cvPoint(0,0));
ppl = pplfind(cscan,cvGetSize(image));
if (ppl !=0 )
printf("Estimated number of people: %d\n",ppl);
cvEndFindContours(&cscan);
cvClearMemStorage(memstr);
return edges;
}
int pplfind(CvContourScanner cscan, CvSize frSize){
ofstream file; char buff[50];
file.open("box.txt",ofstream::app);
int ppl =0;
CvSeq* c;
IplImage *out = cvCreateImage(frSize,8,3);
while (c = cvFindNextContour(cscan)){
CvRect box = cvBoundingRect(c,1);
if ((box.height > int(box.width*1.2))&&(box.height>20)){//&&(box.width<20)){//
ppl++;
cvRectangle(out,cvPoint(box.x,box.y),cvPoint(box.x+box.width,box.y+box.height),CV_RGB(255,0,50),1);
cvShowImage("contours",out);
//cvWaitKey();
}
//printf("Box Height: %d , Box Width: %d ,People: %d\n",box.height,box.width,ppl);
//cvWaitKey(0);
int coord = sprintf_s(buff,"%d,%d,%d\n",box.width,box.height,ppl);
file.write(buff,coord);
}
file.close();
cvReleaseImage(&out);
return ppl;
}
I've never used cvFindNextContour, but running cvFindContours with CV_RETR_EXTERNAL on your image seems to work fine:
I use OpenCV + Python, so this code might not be useful for you, but for the sake of completeness here it goes:
contours = cv.findContours(img, cv.CreateMemStorage(0), mode=cv.CV_RETR_EXTERNAL)
while contours:
(x,y,w,h) = cv.BoundingRect(contours)
cv.Rectangle(colorImg, (x,y), (x+w,y+h), cv.Scalar(0,255,255,255))
contours = contours.h_next()
Edit: you asked how to draw only those contours with certain properties; it would be something like this:
contours = cv.findContours(img, cv.CreateMemStorage(0), mode=cv.CV_RETR_EXTERNAL)
while contours:
(x,y,w,h) = cv.BoundingRect(contours)
if h > w*1.2 and h > 20:
cv.Rectangle(colorImg, (x,y), (x+w,y+h), cv.Scalar(0,255,255,255))
contours = contours.h_next()

Resources