I need some help with opencv and gearwheel detection.
My task: count gearwheel teeth from images like this:
Im trying to use HoughCircles method but got bad results lile this:
Otsu threshold:
Code (on openCV Java wrapper):
Mat des = new Mat(sourceImg.rows(), sourceImg.cols(), sourceImg.type());
Imgproc.cvtColor(sourceImg, sourceImg, Imgproc.COLOR_BGR2GRAY, 4);
Imgproc.GaussianBlur(sourceImg,des, new Size(3,3),0,0);
Imgproc.threshold(des, des, 0, 255, Imgproc.THRESH_OTSU | Imgproc.THRESH_OTSU);
Imgproc.Canny(des, des, 0 , 1);
displayImage(Mat2BufferedImage(des));
Mat circles = new Mat();
Imgproc.HoughCircles(des, circles, Imgproc.CV_HOUGH_GRADIENT, 1.0, 50, 70.0, 30.0, 100, 0);
/// Draw the circles detected
for(int i = 0; i < circles.cols(); i++ )
{
double vCircle[] = circles.get(0,i);
if (vCircle == null)
break;
Point pt = new Point(Math.round(vCircle[0]), Math.round(vCircle[1]));
int radius = (int)Math.round(vCircle[2]);
// draw the found circle
Core.circle(des, pt, radius, new Scalar(255,255,255), 3);
Core.circle(des, pt, 3, new Scalar(255,0,0), 3);
}
What is right way for my task? How to count teeth? Thanks for your answers.
Here's what I tried. The code is in C++ but you can easily adapt it to Java.
load the image and resize it to half the size
erode the image, use Canny to detect edges, then dilate to connect the edges
find contours and choose the largest contour
find the convexhull of this largest contour. Number of point in the convexhull will give you a rough value for the number of teeth
Here's the largest contour and the convexhull points:
I get a value of 77 with the following code.
Mat gray = imread("16atchc.jpg", 0);
Mat small, bw, er, kernel;
resize(gray, small, Size(), .5, .5);
kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
erode(small, er, kernel);
Canny(er, bw, 50, 150);
dilate(bw, bw, kernel);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
int imax = 0, areamax = 0;
findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
Rect rect = boundingRect(contours[idx]);
int area = rect.width * rect.height;
if (area > areamax)
{
areamax = area;
imax = idx;
}
}
vector<Point> hull;
convexHull(contours[imax], hull);
cout << contours[imax].size() << ", " << hull.size() << endl;
Related
From the following image, how could I find the result image?
The images shown here are threshold images. I have tried using morphological operators but they even remove the blob I want. How could I solve this problem?
Any hints?
Following is the result image I am interested to get/find:
import cv2
diff = cv2.imread('Image.png',0)
ret, thresh = cv2.threshold(diff, 12.5, 255, cv2.THRESH_BINARY)
thresh = cv2.dilate(thresh, None, iterations = 1)
cv2.imshow('img', thresh) # This is the first picture I have shown
cv2.waitKey(0)
You are most of the way there, all you need to do now is find the blobs, add some contours and find the biggest one. Easy! below is the code in C++, ill leave it up to you to work out how to convert it to Python:
cv::Mat mat = imread("g0cVU.png");
Mat origImage = mat;
Mat canny_output = mat;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
cv::Mat greyMat, colorMat;
cv::cvtColor(mat, greyMat, CV_BGR2GRAY);
int thresh = 100;
RNG rng(12345);
///// Detect edges using canny
Canny(greyMat, canny_output, thresh, thresh * 2, 3);
/// Find contours
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
int largest_area = 0;
int largest_contour_index = 0;
Rect bounding_rect;
/// 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());
double a=contourArea( contours[i],false); // Find the area of contour
if(a>largest_area){
largest_area=a;
largest_contour_index=i; //Store the index of largest contour
bounding_rect=boundingRect(contours[i]); // Find the bounding rectangle for biggest contour
}
}
rectangle(origImage, bounding_rect, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),2);
/// Show in a window
namedWindow("Contours", CV_WINDOW_AUTOSIZE);
imshow("Contours", drawing);
cv::namedWindow("img");
cv::imshow("mat", mat);
cv::imshow("mat", origImage);
cv::imshow("mat123", drawing);
cv::waitKey(0);
Which gives this results:
You can see in the bottom image the largest contor has a brown rectangle drawn around it.
o and obviously once you have the largest blob (or whatever blob you deem "the correct one") you can just set everything else to black which is fairly straightforward.
I'm trying to draw a box around a foot in a photo. I can do it with other objects, including ones that aren't fully in the image (I have pasted my code below). However, when I try to use it with a foot, it draws the contour perfectly but the rectangle is always the around the full image. I have attached pictures below of the result. Is there any way to change my code to fix this, or is there any other method possible of drawing a box around the foot?
Original Image
Contour and Bounding Box
RNG rng(12345);
Mat threshold_output;
vector<vector<cv::Point> > contours;
vector<Vec4i> hierarchy;
Mat srcImage;
UIImageToMat(image, srcImage);
cvtColor(srcImage, srcImage, CV_BGR2GRAY);
blur(srcImage, srcImage, cv::Size(3,3));
threshold(srcImage, threshold_output, 100, 255, THRESH_BINARY );
findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );
vector<vector<cv::Point> > contours_poly( contours.size() );
vector<cv::Rect> boundRect( contours.size() );
for( int i = 0; i < contours.size(); i++ ) {
approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );
boundRect[i] = boundingRect( Mat(contours_poly[i]) );
}
Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
for( int i = 0; i< contours.size(); i++ )
{
if (contourArea(contours[i]) < 1000) {
continue;
}
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
drawContours(drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, cv::Point());
rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );
cout << "Height:" << boundRect[i].height << " Width: " << boundRect[i].width << endl;
}
return MatToUIImage(drawing);
Since the bottom part of the foot reaches the image boundary, the contour seems to wrap around the perimeter of the image and thereby create a large bounding box that includes the entire image.
A quick and dirty solution will be to set the border pixels of the image to zero before applying findContours(). For a better solution, you have to think about the actual problem that you are trying to solve.
I'm trying to detect a circular object in the middle of my images. Here is a sample image:
The left half is the greyscaled and Gaussian blurred input image; the right half is the same image after Otsu thresholding. The tiny silver of shadow on the lower left corner is leading the Otsu threshold astray. Is there any way to set a circular region of interest so the corner noises can be avoided?
Using the Hough Circle Transform directly on a good thresholded image kind of works for this specific case, even though the detected circle is a little bit offset:
cv::Mat thres;
cv::threshold(gray, thres, 110, 255, cv::THRESH_BINARY);
std::vector<cv::Vec3f> circles;
cv::HoughCircles(thres, circles, cv::HOUGH_GRADIENT, 1, thres.rows/2, 20, 15);
for (size_t i = 0; i < circles.size(); i++)
{
cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
cv::circle(input, center, 3, cv::Scalar(0, 255, 255), -1);
cv::circle(input, center, radius, cv::Scalar(0, 0, 255), 1);
}
⇨
⇨
On more complex cases you might have to try other threshold methods, as well as fill the internal parts (holes) of the segments to reconstruct them back to an elliptical form.
The processing pipeline illustrated below performs the following operations to improve the detection of the coin:
Converts the input image to grayscale;
Applies a threshold;
Executes a morphology operation to join nearby segments;
Fills the holes inside a segment;
and finally, invokes cv::HoughCircles() to detect the circular shape.
⇨
⇨
⇨
⇨
It's possible to notice that the coin detection is a little bit more centralized with this approach. Anyway, here's the C++ sample code for that magic:
// Load input image
cv::Mat input = cv::imread("coin.jpg");
if (input.empty())
{
std::cout << "!!! Failed to open image" << std::endl;
return -1;
}
// Convert it to grayscale
cv::Mat gray;
cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY);
// Threshold the grayscale image for segmentation purposes
cv::Mat thres;
cv::threshold(gray, thres, 110, 255, cv::THRESH_BINARY);
//cv::imwrite("threhsold.jpg", thres);
// Dirty trick to join nearby segments
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(15, 15));
cv::morphologyEx(thres, thres, cv::MORPH_OPEN, element);
//cv::imwrite("morph.jpg", thres);
// Fill the holes inside the segments
fillHoles(thres);
//cv::imwrite("filled.jpg", thres);
// Apply the Hough Circle Transform to detect circles
std::vector<cv::Vec3f> circles;
cv::HoughCircles(thres, circles, cv::HOUGH_GRADIENT, 1, thres.rows/2, 20, 15);
std::cout << "* Number of detected circles: " << circles.size() << std::endl;
for (size_t i = 0; i < circles.size(); i++)
{
cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
cv::circle(input, center, 3, cv::Scalar(0,255,255), -1);
cv::circle(input, center, radius, cv::Scalar(0,0,255), 1);
}
cv::imshow("Output", input);
//cv::imwrite("output.jpg", input);
cv::waitKey(0);
Helper function:
void fillHoles(cv::Mat& img)
{
if (img.channels() > 1)
{
std::cout << "fillHoles !!! Image must be single channel" << std::endl;
return;
}
cv::Mat holes = img.clone();
cv::floodFill(holes, cv::Point2i(0,0), cv::Scalar(1));
for (int i = 0; i < (img.rows * img.cols); i++)
if (holes.data[i] == 255)
img.data[i] = 0;
}
You could use Hough for finding circles:
/// Apply the Hough Transform to find the circles
HoughCircles( src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );
After you find the biggest circle, you can set to 0 all the pixels outside
I want to compute the red circles radius (fig 2). I have troubles finding these circles using HoughCircles from OpenCV. As you can see in fig. 2 I can only find the little circles in center which are shown in black using HoughCircles.
original fig 2.
Since I know the center of the red circles (which are the same as the red ones), is there a way to compute simply the radius of the red circles ?
Is it also possible to have a generic way of computing radius of circles on a more complex image such as this one :
Edit : Here the interesting part of my code after obtaining fig 2 :
threshold(maskedImage, maskedImage, thresh, 255, THRESH_BINARY_INV | THRESH_OTSU);
std::vector<Vec3f> circles;
// Canny(maskedImage, maskedImage, thresh, thresh * 2, 3);
HoughCircles(maskedImage, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows / 4, cannyThreshold, accumulatorThreshold, 0, 0);
Mat display = src_display.clone();
for (size_t i = 0; i < circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// circle center
circle(display, center, 3, Scalar(0, 255, 0), -1, 8, 0);
// circle outline
circle(display, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}
I have tried to use play with cannyThreshold and accumulator without results. Real images are 5x biggers. Here a link for example 1 after threshold.
Thanks
You already know the smaller circles in the image(which you have drawn in black).
Prepare a mask image using these circles so the areas having smaller circles will have non-zero pixels. We'll call it mask:
In the original image, fill these circle areas in a dark color(say black). This will result in an image like your fig 2. We'll call it filled
Threshold the filled image to obtain the dark areas. We'll call it binary. You can use Otsu thresholding for this. Result will look something like this:
Take the distance transform of this binary image. Use an accurate distance estimation method for this. We'll call this dist. It'll look something like this. The colored one is just a heat map for more clarity:
Use the mask to obtain the peak regions from dist. The max value of each such region should give you the radius of the larger circle. You can also do some processing on these regions to arrive at a more reasonable value for radius rather than just picking up the max.
For selecting the regions, you can either find the contours of the mask and then extract that region from dist image, or, since you already know the smaller circles from applying hough-circle transform, prepare a mask from each of those circles and extract that region from dist image. I'm not sure if you can calculate max or other stats by giving a mask. Max will definitely work because the rest of the pixels are 0. You might be able calculate the stats of the region if you extract those pixels to another array.
Figures below show such mask and the extracted region from dist. For this I get a max around 29 which is consistent with the radius of that circle. Note that the images are not to scale.
mask for a circle, extracted region from dist
Here's the code (I'm not using hough-circles transform):
Mat im = imread(INPUT_FOLDER_PATH + string("ex1.jpg"));
Mat gray;
cvtColor(im, gray, CV_BGR2GRAY);
Mat bw;
threshold(gray, bw, 0, 255, CV_THRESH_BINARY|CV_THRESH_OTSU);
// filtering smaller circles: not using hough-circles transform here.
// you can replace this part with you hough-circles code.
vector<int> circles;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
Rect rect = boundingRect(contours[idx]);
if (abs(1.0 - ((double)rect.width/rect.height) < .1))
{
Mat mask = Mat::zeros(im.rows, im.cols, CV_8U);
drawContours(mask, contours, idx, Scalar(255, 255, 255), -1);
double area = sum(mask).val[0]/255;
double rad = (rect.width + rect.height)/4.0;
double circArea = CV_PI*rad*rad;
double dif = abs(1.0 - area/circArea);
if (dif < .5 && rad < 50 && rad > 30) // restrict the radius
{
circles.push_back(idx); // store smaller circle contours
drawContours(gray, contours, idx, Scalar(0, 0, 0), -1); // fill circles
}
}
}
threshold(gray, bw, 0, 255, CV_THRESH_BINARY_INV|CV_THRESH_OTSU);
Mat dist, distColor, color;
distanceTransform(bw, dist, CV_DIST_L2, 5);
double max;
Point maxLoc;
minMaxLoc(dist, NULL, &max);
dist.convertTo(distColor, CV_8U, 255.0/max);
applyColorMap(distColor, color, COLORMAP_JET);
imshow("", color);
waitKey();
// extract dist region corresponding to each smaller circle and find max
for(int idx = 0; idx < (int)circles.size(); idx++)
{
Mat masked;
Mat mask = Mat::zeros(im.rows, im.cols, CV_8U);
drawContours(mask, contours, circles[idx], Scalar(255, 255, 255), -1);
dist.copyTo(masked, mask);
minMaxLoc(masked, NULL, &max, NULL, &maxLoc);
circle(im, maxLoc, 4, Scalar(0, 255, 0), -1);
circle(im, maxLoc, (int)max, Scalar(0, 0, 255), 2);
cout << "rad: " << max << endl;
}
imshow("", im);
waitKey();
Results(scaled):
Hope this helps.
I'm using OpenCV 2.4.6 and Visual C++ 2010. I am trying to find the centroid of the contours of BoundingRect. Below is my code. Here I am getting the rectangles in proper positions. But the circles that i'm trying to point on the centroid of each Rectangle is shifted towards upper left side. How can i get those circle points in the centroid of the rectangles?
int x = cvFindContours(imgThresh,storage,&contours,sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
//printf("%d\n",contours->total);
for (; contours != 0; contours = contours->h_next)
{
rect = cvBoundingRect(contours); //extract bounding box for current contour
//drawing rectangle
printf("%d %d %d %d\n",rect.x, rect.y,rect.width,rect.height);
if(rect.width*rect.height<1400 && (rect.width*rect.height)>700)
{
cvRectangle(mCVImageColor,cvPoint(rect.x, rect.y),cvPoint(rect.x+rect.width, rect.y+rect.height),cvScalar(0, 0, 255),2, 8, 0);
CvPoint centroid[1];
centroid[0].x = ((rect.x+rect.width)/2);
centroid[0].y = ((rect.y+rect.height)/2);
cvCircle( mCVImageColor, centroid[0], 5, CV_RGB(255, 0, 0),-1,0,0);
}
}
This part is wrong:
centroid[0].x = ((rect.x+rect.width)/2);
centroid[0].y = ((rect.y+rect.height)/2);
the centroid is rect.x + rect.width/2 , rect.y + rect.height/2 (no parenthesis)