I've got a vector of 2D points and I need to find all the contours formed by these points. Unfortunately, cv::findContours can't handle an array of points, it requires a binary image.
So the question is there any workaround to get contours of points? Maybe it is possible to form a binary image using the points and then use this image in cv::findContours function?
Please advise here.
If you know the size of image, you can create a binary image of zeros and fill all the 2D points with value 255. Then use cv::findContours to find all the contours in binary image.
Following code snippet may help you:
// If pts is your array of float points and r,c are number of rows and columns of image
//calculate total number of points in array
int n = sizeof(pts) / sizeof(*pts);
//if points are stored in vector, then use n = pts.size()
//create binary image
cv::Mat image = cv::Mat::zeros(Size(c, r), CV_8UC1);
//fill all the points with value 255
for (int i = 0; i < n; i++) {
image.at<uchar>(p[i]) = 255;
}
//find all contours in binary image and save in contours variale
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(image, contours, hierarchy, RETR_LIST, CHAIN_APPROX_NONE);
Related
Few days back ive found a code which is sorting a contours inside vector by using their areas.But i could not understand the code very well especially in comparator function. Why does the contour 1&2 are convert into Mat in the contourArea parameter? Can anyone explain me the whole code in step by step. Your explanation will be usefull for my learning process
// comparison function object
bool compareContourAreas ( std::vector<cv::Point> contour1, std::vector<cv::Point> contour2 )
{
double i = fabs( contourArea(cv::Mat(contour1)) );
double j = fabs( contourArea(cv::Mat(contour2)) );
return ( i < j );
}
[...]
// find contours
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours( binary_image, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE,
cv::Point(0, 0) );
// sort contours
std::sort(contours.begin(), contours.end(), compareContourAreas);
// grab contours
std::vector<cv::Point> biggestContour = contours[contours.size()-1];
std::vector<cv::Point> smallestContour = contours[0];
Your input image is binary, so it only exists of 0 and 1. When you use cv::findContours it searches for points with the value '1' and are touching other 1's, this makes a contour. Then puts all contours in std::vectorstd::vector<cv::Point> contours.
When you would take a grid and draw the points of one contour in it, you will get a 2D array of points, this basically is a one-channel cv::Mat.
In 'compareContourAreas' you take two contours out of your vector 'contours' and compare the absolute sum of all of the points in the contour. To add all the points you use contourArea, which needs a cv::Mat as input, so you first need to convert your contour-vector of points to a cv::Mat.
Then with the sort function you sort all the contours from small to big.
Is it possible to get the expanded or contracted version of a contour?
For example in the below image, I have used cv::findContour() and cv::drawContour on a binary image to get the contours:
I would like to draw another contour which has a customed pixel distance from the original contour, like these:
Except for eroding, which I think it might not be a good idea as it seems hard to control the pixel distance using eroding, I have no idea on how to solve this problem. May I know what should be the correct direction?
Using cv::erode with a small kernel and multiple iterations may be enough for your needs, even if it's not exact.
C++ code:
cv::Mat img = ...;
int iterations = 10;
cv::erode(img, img,
cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3)),
cv::Point(-1,-1),
iterations);
Demo:
# img is the image containing the original black contour
for form in [cv.MORPH_RECT, cv.MORPH_CROSS]:
eroded = cv.erode(img, cv.getStructuringElement(form, (3,3)), iterations=10)
contours, hierarchy = cv.findContours(~eroded, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
vis = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
cv.drawContours(vis, contours, 0, (0,0,255))
cv.drawContours(vis, contours, 1, (255,0,0))
show_image(vis)
10 iterations with cv.MORPH_RECT with a 3x3 kernel:
10 iterations with cv.MORPH_CROSS with a 3x3 kernel:
You can change the offset by adjusting the number of iterations.
A much more accurate approach would be to use cv::distanceTransform to find all pixels that lie approximately 10px away from the contour:
dist = cv.distanceTransform(img, cv.DIST_L2, cv.DIST_MASK_PRECISE)
ring = cv.inRange(dist, 9.5, 10.5) # take all pixels at distance between 9.5px and 10.5px
show_image(ring)
contours, hierarchy = cv.findContours(ring, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
vis = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
cv.drawContours(vis, contours, 0, (0,0,255))
cv.drawContours(vis, contours, 2, (255,0,0))
show_image(vis)
You'll get two contours on each side of the original contour. Use findContours with RETR_EXTERNAL to recover only the outer contour. To also recover the inner contour, use RETR_LIST
I think the solution can be easier, without dilataion and new contours.
For each contour search mass center: cv::moments(contours[i]) -> cv::Point2f mc(mu.m10 / mu.m00), mu.m01 / mu.m00));
For each point point of contour: make shift for mass center -> multiply by coefficient K -> shift backward: pt_new = (k * (pt - mc) + mc);
But coefficient k must be individual for each point. I will calculate it a little later...
Currently I am using the following sequence
vector<vector<Point>> contours
1. findContours(srcMat, contours, ...)
2. convert contours to Point2f
3. findHomography(src, dst, RANSAC)
4. warpPerspective(srcMat, destMat, homo)
5. findContours
I would like to avoid step#4, while also transforming the Mat since I use some ROI relative to the contours from the transformed Mat.
The answer to running warpPerspective but on contours is to use cv::perspectiveTransform with the translation matrix.
The limitation is that it can transform only one contour at a time. Sample below.
vector<vector<Point2f>> contours; // from findContours
Mat trnsmat = getPerspectiveTransform(srcPoints, destPoints);
for (int i=0; i< contours.size(); i++)
cv::perspectiveTransform(contours[i], contours[i], trnsmat);
I assume your goal is to project the contour co-ordinates into the transformed space without using the entire image?
Load the contour co-ordinates into RoiMat structure and multiply it with the homography matrix computed using your findHomography function.
There's no need to warp the entire original image for the same.
If you want to view the transformed ROI on the image, probably pick a few interest points(for reference) from the original image and add it to the RoiMat structure.
In Python you can isolate each contour with the code below and afterwards do whatever processing you've to perform on each contour.
contours, hierarchy = cv2.findContours(image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
contor_img = image[y:y+h, x:x+w]
Now you can process each contour individually(contor_img)
I am working in C++ and opencv
I am detecting the big contour in an image because I have a black area in it.
In this case, the area is only horizontally, but it can be in any place.
Mat resultGray;
cvtColor(result,resultGray, COLOR_BGR2GRAY);
medianBlur(resultGray,resultGray,3);
Mat resultTh;
Mat canny_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
Canny( resultGray, canny_output, 100, 100*2, 3 );
findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
Vector<Point> best= contours[0];
int max_area = -1;
for( int i = 0; i < contours.size(); i++ ) {
Scalar color = Scalar( 0, 0, 0 );
if(contourArea(contours[i])> max_area)
{
max_area=contourArea(contours[i]);
best=contours[i];
}
}
Mat approxCurve;
approxPolyDP(Mat(best),approxCurve,0.01*arcLength(Mat(best),true),true);
Wiht this, i have the big contour and it approximation (in approxCurve). Now, I want to obtain the corners of this approximation and get the image inside this contour, but I dont know how can I do it.
I am using this How to remove black part from the image?
But the last part I dont understad very well.
Anyone knows how can I obtain the corners? It is another way more simple that this?
Thanks for your time,
One much simpler way you could do that is to check the image pixels and find the minimum/maximum coordinates of non-black pixels.
Something like this:
int maxx,maxy,minx,miny;
maxx=maxy=-std::numeric_limits<int>::max();
minx=miny=std::numeric_limits<int>::min();
for(int y=0; y<img.rows; ++y)
{
for(int x=0; x<img.cols; ++x)
{
const cv::Vec3b &px = img.at<cv::Vec3b>(y,x);
if(px(0)==0 && px(1)==0 && px(2)==0)
continue;
if(x<minx) minx=x;
if(x>maxx) maxx=x;
if(y<miny) miny=y;
if(y>maxy) maxy=y;
}
}
cv::Mat subimg;
img(cv::Rect(cv::Point(minx,miny),cv::Point(maxx,maxy))).copyTo(subimg);
In my opinion, this approach is more reliable since you don't have to detect any contour, which could lead to false detections depending on the input image.
In a very efficient way, you can sample the original image until you find a pixel on, and from there move along a row and along a column to find the first (0,0,0) pixel. It will work, unless in the good part of the image you can have (0,0,0) pixels. If this is the case (e.g.: dead pixel), you can add a double check checking the neighbourhood of this (0,0,0) pixel (it should contain other (0,0,0) pixels.
I have retrieved a contour from an image and want to specifically work on the pixels in the contour. I need to find the sum (not area) of the pixel values in the contour. OpenCV only supports rectangle shaped ROI, so I have no idea how to do this. cvSum also only accepts complete images and doesn't have a mask option, so I am a bit lost on how to proceed. Does anyone have any suggestions on how to find the sum of the values of pixels in a specific contour?
First get all of your contours. Use this information to create a binary image with the white parts being the contour's outline and area. Perform an AND operation on the two images. The result will be the contours and area on a black background. Then just sum all of the pixels in this image.
If I understand right you want to sum all the pixel intensities from a gray image that are inside a contour. If so, the method that i think of is to draw that contour on a blank image and fill it , in so making yourself a mask. After that to optimize the process you can also compute the bounding rect of the contour with :
CvRect cvBoundingRect(CvArr* points, int update=0 );
After this you can make an intermediate image with :
void cvAddS(const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=NULL);
using the value 0, the mask obtained from the contour and setting before as ROI the bounding rect.
After this, a sum on the resulting image will be a little faster.
To access the contour's point individually follow the code
vector<vector<Point> > contours;
...
printf("\n Contours pixels \n");
for(int a=0; a< contours.size(); a++)
{
printf("\nThe contour NO = %d size = %d \n",a, contours[a].size() );
for( int b = 0; b < contours[a].size(); b++ )
{
printf(" [%d, %d] ",contours[a][b].x, contours[a][b].y );
}
}