Is there an OpenCV function that can determine if a contour is circular?
fitEllipse() seems to work for any contour (correct me if I am wrong, I did a quick test).
isContourConvex() would'nt even detect my best circles.
I am attempting to detect pizzas (camera is at an angle so the pizza shapes are ellipses). HoughCircles works for the pizza's general position but not for the pizza's elliptical shape (it either find circles too small or too large).
static void detectPizzas(const Mat& roi)
{
Mat blur, edges;
GaussianBlur(roi, blur, Size(3, 3), 0);
Canny(blur, edges, 200, 275);
dilate(edges, edges, getStructuringElement(CV_SHAPE_ELLIPSE, { 3, 3 }));
std::vector<std::vector<Point>> contours;
findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// Following draws ellipses to all contours that aren't even circular.
// How can I discriminate between 'circular' contours and non-circular contours?
for (int i = 0; i < contours.size(); i++)
{
// Doesn't detect any ellipses
//if (!isContourConvex(contours[i]))
// continue;
RotatedRect r = fitEllipse(contours[i]);
ellipse(edges, r, { 125 }, 2);
}
imshow("edges", edges);
}
Sample images:
Related
How Can I detect the circles and count the number in this image. I'm new to open cv and c++.Can any one help with this issue. I tried with hough circle . But didn't work .
The skeletonized binary image is as follows.
Starting from this image (I removed the border):
You can follow this approach:
1) Use findContour to get the contours.
2) Keep only internal contours. You can do that checking the sign of the area returned by contourArea(..., true). You'll get the 2 internal contours:
3) Now that you have the two contours, you can find a circle with minEnclosingCircle (in blue), or fit an ellipse with fitEllipse (in red):
Here the full code for reference:
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Get contours
vector<vector<Point>> contours;
findContours(img, contours, RETR_TREE, CHAIN_APPROX_NONE);
// Create output image
Mat3b out;
cvtColor(img, out, COLOR_GRAY2BGR);
Mat3b outContours = out.clone();
// Get internal contours
vector<vector<Point>> internalContours;
for (size_t i = 0; i < contours.size(); ++i) {
// Find orientation: CW or CCW
double area = contourArea(contours[i], true);
if (area >= 0) {
// Internal contour
internalContours.push_back(contours[i]);
// Draw with different color
drawContours(outContours, contours, i, Scalar(rand() & 255, rand() & 255, rand() & 255));
}
}
// Get circles
for (const auto& cnt : internalContours) {
Point2f center;
float radius;
minEnclosingCircle(cnt, center, radius);
// Draw circle in blue
circle(out, center, radius, Scalar(255, 0, 0));
}
// Get ellipses
for (const auto& cnt : internalContours) {
RotatedRect rect = fitEllipse(cnt);
// Draw ellipse in red
ellipse(out, rect, Scalar(0, 0, 255), 2);
}
imshow("Out", out);
waitKey();
return 0;
}
First of all you have to find all contours at your image (see function cv::findContours).
You have to analyse these contours (check it for accordance to your requirements).
P.S. The figure at the picture is definitely not circle. So I can't say exactly how do you have to check received contours.
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 pretty new to programming. And I want to make program which is able to filtrate image from small objects and non-convex objects so only shapes such as rectangles, triangles, circles etc. stay.
What have I done so far?
I managed to obtain image in binary by two separate ways (color detection and canny function) Then I created contours with function findContours. So that is working flawlessly.
here's the code:
vector<Point> approxShape;
vector<vector<Point>> FiltredContours;
vector<vector<Point>> TRI;
vector<vector<Point>> RECT;
vector<vector<Point>> PENTA;
Mat WSO= Mat::zeros(im.size(), CV_8UC3); //Without Small Objects
for ( int j = 0; j < contours.size(); j++)
{
if ((fabs(contourArea(contours[j]))) >100)
drawContours(WSO, contours, j,Scalar(128,128,128),2,8,hiearchy,0, Point()); // to see how it looks before it goes further
{
approxPolyDP( Mat(contours[j]), approxShape, arcLength(Mat(contours[j]), true) * 0.02, true);
if (isContourConvex(approxShape))
{
FiltredContours.push_back(approxShape);
}
}
}
///--------Show image after filtring small obj. -----
imshow("WSO",WSO);
////--------Filtred-Image-Drawing---------------------
Mat approxmat = Mat::zeros(imHSV.size(),CV_8UC3);
drawContours(approxmat, FiltredContours, -1,barva,2,8,hiearchy,0, Point());//drawContours(approxkresba, FiltredContours, -1,Scalar(255, 0, 0),2,8,hiearchy,0, Point());
namedWindow("Filtred objects",CV_WINDOW_AUTOSIZE);
imshow("Filtred objects",approxmat);
I tried to change parameters in contourArea and in approxPollyDP as well. It still doesn't work the way I thought it would.
What I'm trying to do is measure the thickness of the eyeglasses frames. I had the idea to measure the thickness of the frame's contours (may be a better way?). I have so far outlined the frame of the glasses, but there are gaps where the lines don't meet. I thought about using HoughLinesP, but I'm not sure if this is what I need.
So far I have conducted the following steps:
Convert image to grayscale
Create ROI around the eye/glasses area
Blur the image
Dilate the image (have done this to remove any thin framed glasses)
Conduct Canny edge detection
Found contours
These are the results:
This is my code so far:
//convert to grayscale
cv::Mat grayscaleImg;
cv::cvtColor( img, grayscaleImg, CV_BGR2GRAY );
//create ROI
cv::Mat eyeAreaROI(grayscaleImg, centreEyesRect);
cv::imshow("roi", eyeAreaROI);
//blur
cv::Mat blurredROI;
cv::blur(eyeAreaROI, blurredROI, Size(3,3));
cv::imshow("blurred", blurredROI);
//dilate thin lines
cv::Mat dilated_dst;
int dilate_elem = 0;
int dilate_size = 1;
int dilate_type = MORPH_RECT;
cv::Mat element = getStructuringElement(dilate_type,
cv::Size(2*dilate_size + 1, 2*dilate_size+1),
cv::Point(dilate_size, dilate_size));
cv::dilate(blurredROI, dilated_dst, element);
cv::imshow("dilate", dilated_dst);
//edge detection
int lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
cv::Canny(dilated_dst, dilated_dst, lowThreshold, lowThreshold*ratio, kernel_size);
//create matrix of the same type and size as ROI
Mat dst;
dst.create(eyeAreaROI.size(), dilated_dst.type());
dst = Scalar::all(0);
dilated_dst.copyTo(dst, dilated_dst);
cv::imshow("edges", dst);
//join the lines and fill in
vector<Vec4i> hierarchy;
vector<vector<Point>> contours;
cv::findContours(dilated_dst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
cv::imshow("contours", dilated_dst);
I'm not entirely sure what the next steps would be, or as I said above, if I should use HoughLinesP and how to implement it. Any help is very much appreciated!
I think there are 2 main problems.
segment the glasses frame
find the thickness of the segmented frame
I'll now post a way to segment the glasses of your sample image. Maybe this method will work for different images too, but you'll probably have to adjust parameters, or you might be able to use the main ideas.
Main idea is:
First, find the biggest contour in the image, which should be the glasses. Second, find the two biggest contours within the previous found biggest contour, which should be the glasses within the frame!
I use this image as input (which should be your blurred but not dilated image):
// this functions finds the biggest X contours. Probably there are faster ways, but it should work...
std::vector<std::vector<cv::Point>> findBiggestContours(std::vector<std::vector<cv::Point>> contours, int amount)
{
std::vector<std::vector<cv::Point>> sortedContours;
if(amount <= 0) amount = contours.size();
if(amount > contours.size()) amount = contours.size();
for(int chosen = 0; chosen < amount; )
{
double biggestContourArea = 0;
int biggestContourID = -1;
for(unsigned int i=0; i<contours.size() && contours.size(); ++i)
{
double tmpArea = cv::contourArea(contours[i]);
if(tmpArea > biggestContourArea)
{
biggestContourArea = tmpArea;
biggestContourID = i;
}
}
if(biggestContourID >= 0)
{
//std::cout << "found area: " << biggestContourArea << std::endl;
// found biggest contour
// add contour to sorted contours vector:
sortedContours.push_back(contours[biggestContourID]);
chosen++;
// remove biggest contour from original vector:
contours[biggestContourID] = contours.back();
contours.pop_back();
}
else
{
// should never happen except for broken contours with size 0?!?
return sortedContours;
}
}
return sortedContours;
}
int main()
{
cv::Mat input = cv::imread("../Data/glass2.png", CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat inputColors = cv::imread("../Data/glass2.png"); // used for displaying later
cv::imshow("input", input);
//edge detection
int lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
cv::Mat canny;
cv::Canny(input, canny, lowThreshold, lowThreshold*ratio, kernel_size);
cv::imshow("canny", canny);
// close gaps with "close operator"
cv::Mat mask = canny.clone();
cv::dilate(mask,mask,cv::Mat());
cv::dilate(mask,mask,cv::Mat());
cv::dilate(mask,mask,cv::Mat());
cv::erode(mask,mask,cv::Mat());
cv::erode(mask,mask,cv::Mat());
cv::erode(mask,mask,cv::Mat());
cv::imshow("closed mask",mask);
// extract outermost contour
std::vector<cv::Vec4i> hierarchy;
std::vector<std::vector<cv::Point>> contours;
//cv::findContours(mask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
cv::findContours(mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// find biggest contour which should be the outer contour of the frame
std::vector<std::vector<cv::Point>> biggestContour;
biggestContour = findBiggestContours(contours,1); // find the one biggest contour
if(biggestContour.size() < 1)
{
std::cout << "Error: no outer frame of glasses found" << std::endl;
return 1;
}
// draw contour on an empty image
cv::Mat outerFrame = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1);
cv::drawContours(outerFrame,biggestContour,0,cv::Scalar(255),-1);
cv::imshow("outer frame border", outerFrame);
// now find the glasses which should be the outer contours within the frame. therefore erode the outer border ;)
cv::Mat glassesMask = outerFrame.clone();
cv::erode(glassesMask,glassesMask, cv::Mat());
cv::imshow("eroded outer",glassesMask);
// after erosion if we dilate, it's an Open-Operator which can be used to clean the image.
cv::Mat cleanedOuter;
cv::dilate(glassesMask,cleanedOuter, cv::Mat());
cv::imshow("cleaned outer",cleanedOuter);
// use the outer frame mask as a mask for copying canny edges. The result should be the inner edges inside the frame only
cv::Mat glassesInner;
canny.copyTo(glassesInner, glassesMask);
// there is small gap in the contour which unfortunately cant be closed with a closing operator...
cv::dilate(glassesInner, glassesInner, cv::Mat());
//cv::erode(glassesInner, glassesInner, cv::Mat());
// this part was cheated... in fact we would like to erode directly after dilation to not modify the thickness but just close small gaps.
cv::imshow("innerCanny", glassesInner);
// extract contours from within the frame
std::vector<cv::Vec4i> hierarchyInner;
std::vector<std::vector<cv::Point>> contoursInner;
//cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// find the two biggest contours which should be the glasses within the frame
std::vector<std::vector<cv::Point>> biggestInnerContours;
biggestInnerContours = findBiggestContours(contoursInner,2); // find the one biggest contour
if(biggestInnerContours.size() < 1)
{
std::cout << "Error: no inner frames of glasses found" << std::endl;
return 1;
}
// draw the 2 biggest contours which should be the inner glasses
cv::Mat innerGlasses = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1);
for(unsigned int i=0; i<biggestInnerContours.size(); ++i)
cv::drawContours(innerGlasses,biggestInnerContours,i,cv::Scalar(255),-1);
cv::imshow("inner frame border", innerGlasses);
// since we dilated earlier and didnt erode quite afterwards, we have to erode here... this is a bit of cheating :-(
cv::erode(innerGlasses,innerGlasses,cv::Mat() );
// remove the inner glasses from the frame mask
cv::Mat fullGlassesMask = cleanedOuter - innerGlasses;
cv::imshow("complete glasses mask", fullGlassesMask);
// color code the result to get an impression of segmentation quality
cv::Mat outputColors1 = inputColors.clone();
cv::Mat outputColors2 = inputColors.clone();
for(int y=0; y<fullGlassesMask.rows; ++y)
for(int x=0; x<fullGlassesMask.cols; ++x)
{
if(!fullGlassesMask.at<unsigned char>(y,x))
outputColors1.at<cv::Vec3b>(y,x)[1] = 255;
else
outputColors2.at<cv::Vec3b>(y,x)[1] = 255;
}
cv::imshow("output", outputColors1);
/*
cv::imwrite("../Data/Output/face_colored.png", outputColors1);
cv::imwrite("../Data/Output/glasses_colored.png", outputColors2);
cv::imwrite("../Data/Output/glasses_fullMask.png", fullGlassesMask);
*/
cv::waitKey(-1);
return 0;
}
I get this result for segmentation:
the overlay in original image will give you an impression of quality:
and inverse:
There are some tricky parts in the code and it's not tidied up yet. I hope it's understandable.
The next step would be to compute the thickness of the the segmented frame. My suggestion is to compute the distance transform of the inversed mask. From this you will want to compute a ridge detection or skeletonize the mask to find the ridge. After that use the median value of ridge distances.
Anyways I hope this posting can help you a little, although it's not a solution yet.
Depending on lighting, frame color etc this may or may not work but how about simple color detection to separate the frame ? Frame color will usually be a lot darker than human skin. You'll end up with a binary image (just black and white) and by calculating the number (area) of black pixels you get the area of the frame.
Another possible way is to get better edge detection, by adjusting/dilating/eroding/both until you get better contours. You will also need to differentiate the contour from the lenses and then apply cvContourArea.
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...