Let's say that I have the following output image:
Basically, I have video stream and I want to get coordinates of rectangle only in the output image. Here's my C++ code:
while(1)
{
capture >> frame;
if(frame.empty())
break;
cv::cvtColor( frame, gray, CV_BGR2GRAY ); // Grayscale image
Canny( gray, canny_output, thresh, thresh * 2, 3 );
// Find contours
findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
// 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() );
}
cv::imshow( "w", drawing );
waitKey(20); // waits to display frame
}
Thanks.
Look at the definition of the find contours function in the opencv documentation and see the parameters (link):
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
Parameters: here
Look at contours, like Rafa said each contour is stored in a vector of points and each vector of points is stored in a vector, so, by walking in the outer vector and then walking in the inner vector you'll be finding the points you wish.
However, if you want to detect only the bigger contour you might want to use CV_RETR_EXTERNAL as the mode parameter, because it'll detect only most external contour (the big rectangle).
If you still wish to maintain the smaller contours then you might use the CV_RETR_TREE and work out with the hierarchy structure: Using hierarchy contours
Looking at the documentation, the OutputArrayOfArrays contours is the key.
contours – Detected contours. Each contour is stored as a vector of points.
so, you've got a vector< vector<Point> > contours. The vector<Point>(inside) is the coordinates of a contour, and every contour is stored in a vector.
So for instance, to know the 5-th vector, it's vector<Point> fifthcontour = contours.at(4);
and you have the coordinates in that vector.
You can access to those coordinates as:
for (int i = 0; i < fifthcontour.size(); i++) {
Point coordinate_i_ofcontour = fifthcontour[i];
cout << endl << "contour with coordinates: x = " << coordinate_i_ofcontour.x << " y = " << coordinate_i_ofcontour.y;
}
Related
How to extract all rectangular contours from an image with a black background?
I want to get the black rectangle in the bottom (with some white text inside)
In the code I try to fetch all contours via cv::RETR_EXTERNAL with 4 corners, but it only fetches the whole image as one big contour?? What am I doing wrong?
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(img, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
for(int i = 0; i < contours.size(); i++){
std::cout << "contour found" << std::endl;
std::vector<cv::Point> approx;
cv::approxPolyDP(contours[i], approx, cv::arcLength(contours[i], true) * 0.02, true);
if(!cv::isContourConvex(approx)){
continue;
}
if(approx.size() == 4){
cv::rectangle(img, cv::boundingRect(contours[i]), cv::Scalar(0, 255, 0), 2);
}
}
cv::imwrite("img.png", img);
Found out I need to invert the image before I can use cv::RETR_EXTERNAL
img_inv = 255 - img;
You are using the wrong flag:
cv::RETR_EXTERNAL : If you use this flag, it returns only extreme outer flags. All child contours are left behind
https://docs.opencv.org/3.4.3/d9/d8b/tutorial_py_contours_hierarchy.html
You should use cv::RETR_LIST,because you need all the contours and you do not need the hierarchy.
I am trying to detect a rectangle using find contours, but I don't get any contours from the following image.
I cant detect any contours in the image. Is find contours is bad with the following image, or should I use hough transform.
UPDATE: I have updated the source code to use approximated polygon.
but I still I get the outlier bounding rect, I cant find the smallest rectangle that is in the screenshot.
I have another case which the current solution it doesnt work even when adding erosion or dilation.
image 2
and here is the code
using namespace cm;
using namespace cv;
using namespace std;
cv::Mat input = cv::imread("heightmap.png");
RNG rng(12345);
// convert to grayscale (you could load as grayscale instead)
cv::Mat gray;
cv::cvtColor(input,gray, CV_BGR2GRAY);
// compute mask (you could use a simple threshold if the image is always as good as the one you provided)
cv::Mat mask;
cv::threshold(gray, mask, 0, 255,CV_THRESH_OTSU);
cv::namedWindow("threshold");
cv::imshow("threshold",mask);
// find contours (if always so easy to segment as your image, you could just add the black/rect pixels to a vector)
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(mask,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
cv::Mat drawing = cv::Mat::zeros( mask.size(), CV_8UC3 );
vector<vector<cv::Point> > contours_poly( contours.size() );
vector<vector<cv::Point> > ( contours.size() );
vector<cv::Rect> boundRect( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{
approxPolyDP( cv::Mat(contours[i]), contours_poly[i], 3, true );
boundRect[i] = boundingRect( cv::Mat(contours_poly[i]) );
}
for( int i = 0; i< contours.size(); i++ )
{
cv::Scalar color = cv::Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );
}
// display
cv::imshow("input", input);
cv::imshow("drawing", drawing);
cv::waitKey(0);
The code you are using looks like its from this question.
It uses BinaryInv threshold because its detecting a black shape on white background.
Your example is the opposite so you should tweak your code to use Binary threshold type instead (or negate the image).
Without this fix, FindContours will detect the perimeter of the image which will be the biggest contour.
So I don't think the code is failing to detect contours, just not the "biggest contour" you expect.
Even with that fixed, the code you posted won't fit a rectangle to the rectangle in your example image, as the most obvious rectangular feature doesn't have a clean border. The approxPolyDP suggestion in the linked question might help but you'll have to improve the source image.
See this question for a comparison of this and Hough methods for finding rectangles.
Edit
You should be able to separate the rectangle in your example image from the other blob by calling Erode (3x3) twice.
You'll have to replace selecting the biggest contour with selecting the squarest.
I am using OpenCV C++ to find the contours in a video. I want to count the no of contours present in video in a a specified region or in between two lines drawn in a video. For example a stream of contours are moving in a video and I want to count them when they reach to a specific region in a video. I will decrease the count as they leave the specific region in the video. I know some basic stuffs to find contour, calculate the area etc. But I am not getting any programming tips to count the no of contours within specified region. Please help me with the related topics and some tips on programming. (I do not want to use cvBlob.h library)
Basically I am counting the number of cars entered in that region. If car is entered I will increment the count and if it leaves the region then I will decrease the count.
You can approximate the contour you found to a polygon or circle:
for( int i = 0; i < contours.size(); i++ )
{ approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );
boundRect[i] = boundingRect( Mat(contours_poly[i]) )
}
after that use the line equation y=a and compare the coordinates of the corners of rectangle or the center+radius of the circle to determine if the contour passed the line.
1.Use a part of your Mat image.(If your ROI is rectangle)
Mat src; // suppose this is your source frame
Mat src_of_interest = src(Rect(Point(x1, y1), Point(x2, y2)));
// Do the rest of finding contour..
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(src_of_interest.clone(), contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
//now you can do your job with contour vector.
int count = contours.size();
Mat dst = Mat::zeros(src.size(), CV_8UC3);
for(int i=0; i< count; i++)
{
drawContours(dst, contours, i, CV_RGB(255, 0, 0)); // draw contour in red
}
2. If your Region of Interest is not rectangle, try this approach:
vector<Point> contour_of_interest; // this contour is where you want to check
vector<vector<Point>> contours; // this is the contours you found
Mat dst = Mat::zeros(src.size(), CV_8U);
for(int i=0; i< count; i++)
{
drawContours(dst, contours, i, Scalar(255)); // set contour area to 255
}
Mat roi = Mat::zeros(src.size(), CV_8U);
vector<vector<Point>> coi_vector;
coi_vector.push_back(contour_of_interest);
drawContours(roi, coi_vector, 0, Scalar(255));
Mat and = dst & roi; // where 2 mats are both TRUE(not zero)
findContours(and.clone(), contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
//now you can do your job with contour vector.
int count = contours.size();
3. If you want to count contours between 4 points, try this approach:
vector<Point> contour_of_interest; // this contour is the area where you want to check
Point p1(x1, y1);
Point p2(x1, y2);
Point p3(x2, y2);
Point p4(x2, y1);
contour_of_interest.push_back(p1);
contour_of_interest.push_back(p2);
contour_of_interest.push_back(p3);
contour_of_interest.push_back(p4);
vector<vector<Point>> coi_list;
coi_list.push_back(contour_of_interest);
Mat mask = Mat:zeros(src.size(), CV_8U);
drawContours(mask, coi_list, 0, Scalar(255));
Mat src; // suppose this is your source frame
Mat src_of_interest = src & mask; // remove any pixels outside mask area
// Do the rest of finding contour..
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(src_of_interest.clone(), contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
//now you can do your job with contour vector.
int count = contours.size();
Mat dst = Mat::zeros(src.size(), CV_8UC3);
for(int i=0; i< count; i++)
{
drawContours(dst, contours, i, CV_RGB(255, 0, 0)); // draw contour in red
}
I have a photo where a person holds a sheet of paper. I'd like to detect the rectangle of that sheet of paper.
I have tried following different tutorials from OpenCV and various SO answers and sample code for detecting squares / rectangles, but the problem is that they all rely on contours of some kind.
If I follow the squares.cpp example, I get the following results from contours:
As you can see, the fingers are part of the contour, so the algorithm does not find the square.
I, also, tried using HoughLines() approach, but I get similar results to above:
I can detect the corners, reliably though:
There are other corners in the image, but I'm limiting total corners found to < 50 and the corners for the sheet of paper are always found.
Is there some algorithm for finding a rectangle from multiple corners in an image? I can't seem to find an existing approach.
You can apply a morphological filter to close the gaps in your edge image. Then if you find the contours, you can detect an inner closed contour as shown below. Then find the convexhull of this contour to get the rectangle.
Closed edges:
Contour:
Convexhull:
In the code below I've just used an arbitrary kernel size for morphological filter and filtered out the contour of interest using an area ratio threshold. You can use your own criteria instead of those.
Code
Mat im = imread("Sh1Vp.png", 0); // the edge image
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(11, 11));
Mat morph;
morphologyEx(im, morph, CV_MOP_CLOSE, kernel);
int rectIdx = 0;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(morph, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for (size_t idx = 0; idx < contours.size(); idx++)
{
RotatedRect rect = minAreaRect(contours[idx]);
double areaRatio = abs(contourArea(contours[idx])) / (rect.size.width * rect.size.height);
if (areaRatio > .95)
{
rectIdx = idx;
break;
}
}
// get the convexhull of the contour
vector<Point> hull;
convexHull(contours[rectIdx], hull, false, true);
// visualization
Mat rgb;
cvtColor(im, rgb, CV_GRAY2BGR);
drawContours(rgb, contours, rectIdx, Scalar(0, 0, 255), 2);
for(size_t i = 0; i < hull.size(); i++)
{
line(rgb, hull[i], hull[(i + 1)%hull.size()], Scalar(0, 255, 0), 2);
}
I am trying to track a custom circular marker in an image, and I need to check that a circle contains a minimum number of other circles/objects. My code for finding circles is below:
void findMarkerContours( int, void* )
{
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
vector<Point> approx;
cv::Mat dst = src.clone();
cv::Mat src_gray;
cv::cvtColor(src, src_gray, CV_BGR2GRAY);
//Reduce noise with a 3x3 kernel
blur( src_gray, src_gray, Size(3,3));
//Convert to binary using canny
cv::Mat bw;
cv::Canny(src_gray, bw, thresh, 3*thresh, 3);
imshow("bw", bw);
findContours(bw.clone(), contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
Mat drawing = Mat::zeros( bw.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) );
// contour
drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
//Approximate the contour with accuracy proportional to contour perimeter
cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true) *0.02, true);
//Skip small or non-convex objects
if(fabs(cv::contourArea(contours[i])) < 100 || !cv::isContourConvex(approx))
continue;
if (approx.size() >= 8) //More than 6-8 vertices means its likely a circle
{
drawContours( dst, contours, i, Scalar(0,255,0), 2, 8);
}
imshow("Hopefully we should have circles! Yay!", dst);
}
namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
imshow( "Contours", drawing );
}
As you can see the code to detect circles works quite well:
But now I need to filter out markers that I do not want. My marker is the bottom one. So once I have found a contour that is a circle, I want to check if there are other circular contours that exist within the region of the first circle and finally check the color of the smallest circle.
What method can I take to say if (circle contains 3+ smaller circles || smallest circle is [color] ) -> do stuff?
Take a look at the documentation for
findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
You'll see that there's an optional hierarchy output vector which should be handy for your problem.
hierarchy – Optional output vector, containing information about the image topology. It has as many elements as the number of contours.
For each i-th contour contours[i] , the elements hierarchy[i][0] ,
hiearchyi , hiearchyi , and hiearchyi are set to
0-based indices in contours of the next and previous contours at the
same hierarchical level, the first child contour and the parent
contour, respectively. If for the contour i there are no next,
previous, parent, or nested contours, the corresponding elements of
hierarchy[i] will be negative.
When calling findCountours using CV_RETR_TREE you'll be getting the full hierarchy of each contour that was found.
This doc explains the hierarchy format pretty well.
You are already searching for circles of a certain size
//Skip small or non-convex objects
if(fabs(cv::contourArea(contours[i])) < 100 || !cv::isContourConvex(approx))
continue;
So you can use that to look for smaller circles than the one youve got, instead of looking for < 100 look for contours.size
I imagine there is the same for color also...