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