Get rectangular contours from image - c++

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.

Related

how to consider only the white region on the image as contour

I have a binary image, from which I need to consider only the white regions as contours but it also takes black region which is surrounded by white part as contour.
I don't want to use contour area, can we ignore the black regions while finding contours?
Here is the binary image and the orange color marked is also considered as contour, so do not want the black region surrounded with white to be considered as contour.
Contour image is:
My contouring code:
//contouring
vector<vector<Point> > contours;
findContours(img, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
vector<vector<Point> > contours_poly(contours.size());
vector<Rect> boundRect(contours.size());
vector<Point2f>centers(contours.size());
vector<float>radius(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
approxPolyDP(contours[i], contours_poly[i], 3, true);
boundRect[i] = boundingRect(contours_poly[i]);
minEnclosingCircle(contours_poly[i], centers[i], radius[i]);
}
Mat drawing = Mat::zeros(img.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
drawContours(drawing, contours_poly, (int)i, color);
}
Your code snippets do not compose a minimal reproducible example (https://stackoverflow.com/help/minimal-reproducible-example).
Therefore I could not run it for testing.
However - you might be able to achieve what you want by "playing" with the 2 last parameters of cv::findContours.
From opencv documentation of cv::findContours:
mode Contour retrieval mode, see [RetrievalModes][1]
method Contour approximation method, see [ContourApproximationModes][1]

OpenCV: Is it possible to detect rectangle from corners?

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

Get coordinates of contours in OpenCV

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

Search for contours within a contour / OpenCV c++

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...

How to fill contours that touch the image border?

Say I have the following binary image created from the output of cv::watershed():
Now I want to find and fill the contours, so I can separate the corresponding objects from the background in the original image (that was segmented by the watershed function).
To segment the image and find the contours I use the code below:
cv::Mat bgr = cv::imread("test.png");
// Some function that provides the rough outline for the segmented regions.
cv::Mat markers = find_markers(bgr);
cv::watershed(bgr, markers);
cv::Mat_<bool> boundaries(bgr.size());
for (int i = 0; i < bgr.rows; i++) {
for (int j = 0; j < bgr.cols; j++) {
boundaries.at<bool>(i, j) = (markers.at<int>(i, j) == -1);
}
}
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(
boundaries, contours, hierarchy,
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE
);
So far so good. However, if I pass the contours acquired above to cv::drawContours() as below:
cv::Mat regions(bgr.size(), CV_32S);
cv::drawContours(
regions, contours, -1, cv::Scalar::all(255),
CV_FILLED, 8, hierarchy, INT_MAX
);
This is what I get:
The leftmost contour was left open by cv::findContours(), and as a result it is not filled by cv::drawContours().
Now I know this is a consequence of cv::findContours() clipping off the 1-pixel border around the image (as mentioned in the documentation), but what to do then? It seems an awful waste to discard a contour just because it happened to brush off the image's border. And anyway how can I even find which contour(s) fall in this category? cv::isContourConvex() is not a solution in this case; a region can be concave but "closed" and thus not have this problem.
Edit: About the suggestion to duplicate the pixels from the borders. The problem is that my marking function is also painting all pixels in the "background", i.e. those regions I'm sure aren't part of any object:
This results in a boundary being drawn around the output. If I somehow avoid cv::findContours() to clip off that boundary:
The boundary for the background gets merged with that leftmost object:
Which results in a nice white-filled box.
Solution number 1: use image extended by one pixel in each direction:
Mat extended(bgr.size()+Size(2,2), bgr.type());
Mat markers = extended(Rect(1, 1, bgr.cols, bgr.rows));
// all your calculation part
std::vector<std::vector<Point> > contours;
findContours(boundaries, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
Mat regions(bgr.size(), CV_8U);
drawContours(regions, contours, -1, Scalar(255), CV_FILLED, 8, Mat(), INT_MAX, Point(-1,-1));
Note that contours were extracted from extended image, i.e. their x and y values are bigger by 1 from what they should be. This is why I use drawContours with (-1,-1) pixel offset.
Solution number 2: add white pixels from boundary of image to the neighbor row/column:
bitwise_or(boundaries.row(0), boundaries.row(1), boundaries.row(1));
bitwise_or(boundaries.col(0), boundaries.col(1), boundaries.col(1));
bitwise_or(boundaries.row(bgr.rows()-1), boundaries.row(bgr.rows()-2), boundaries.row(bgr.rows()-2));
bitwise_or(boundaries.col(bgr.cols()-1), boundaries.col(bgr.cols()-2), boundaries.col(bgr.cols()-2));
Both solution are half-dirty workarounds, but this is all I could think about.
Following Burdinov's suggestions I came up with the code below, which correctly fills all extracted regions while ignoring the all-enclosing boundary:
cv::Mat fill_regions(const cv::Mat &bgr, const cv::Mat &prospective) {
static cv::Scalar WHITE = cv::Scalar::all(255);
int rows = bgr.rows;
int cols = bgr.cols;
// For the given prospective markers, finds
// object boundaries on the given BGR image.
cv::Mat markers = prospective.clone();
cv::watershed(bgr, markers);
// Copies the boundaries of the objetcs segmented by cv::watershed().
// Ensures there is a minimum distance of 1 pixel between boundary
// pixels and the image border.
cv::Mat borders(rows + 2, cols + 2, CV_8U);
for (int i = 0; i < rows; i++) {
uchar *u = borders.ptr<uchar>(i + 1) + 1;
int *v = markers.ptr<int>(i);
for (int j = 0; j < cols; j++, u++, v++) {
*u = (*v == -1);
}
}
// Calculates contour vectors for the boundaries extracted above.
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(
borders, contours, hierarchy,
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE
);
int area = bgr.size().area();
cv::Mat regions(borders.size(), CV_32S);
for (int i = 0, n = contours.size(); i < n; i++) {
// Ignores contours for which the bounding rectangle's
// area equals the area of the original image.
std::vector<cv::Point> &contour = contours[i];
if (cv::boundingRect(contour).area() == area) {
continue;
}
// Draws the selected contour.
cv::drawContours(
regions, contours, i, WHITE,
CV_FILLED, 8, hierarchy, INT_MAX
);
}
// Removes the 1 pixel-thick border added when the boundaries
// were first copied from the output of cv::watershed().
return regions(cv::Rect(1, 1, cols, rows));
}