I am trying to detect circles in my images. I have written the following code in C# using EmguCV. Most of the times it works, but there are some cases that it detects smaller or larger circles that are slightly shifted to a side.
Here is my code:
Thread.Sleep(1000);
imgCrp.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");
imgCrpLab = imgCrp.Convert<Lab, Byte>();
imgIsolatedCathNTipBW = new Image<Gray, Byte>(imgCrp.Size);
CvInvoke.cvInRangeS(imgCrpLab.Split()[2], new MCvScalar(0), new MCvScalar(100), imgIsolatedCathNTipBW);
imgCrpNoBgrnd = imgCrp.Copy(imgIsolatedCathNTipBW.Not());
imgCrpNoBgrndGray = imgCrpNoBgrnd.Convert<Gray, Byte>().PyrUp().PyrDown();
Thread.Sleep(1000);
imgCrpNoBgrndGray.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");
Gray cannyThreshold = new Gray(150);
Gray cannyThresholdLinking = new Gray(85);
Gray circleAccumulatorThreshold = new Gray(15);
imgCrpNoBgrndGrayCanny = imgCrpNoBgrndGray.Canny(cannyThreshold.Intensity, cannyThresholdLinking.Intensity);
Thread.Sleep(1000);
imgCrpNoBgrndGrayCanny.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");
circarrTip = imgCrpNoBgrndGrayCanny.HoughCircles(
cannyThreshold,
circleAccumulatorThreshold,
1, //Resolution of the accumulator used to detect centers of the circles
500, //min distance
15, //min radius
42 //max radius
)[0]; //Get the circles from the first channel
imgCathNoTip = imgIsolatedCathNTipBW.Copy().Not();
foreach (CircleF circle in circarrTip)
{
circLarger2RemTip = circle;
circLarger2RemTip.Radius = circle.Radius;
imgCathNoTip.Draw(circLarger2RemTip, new Gray(140), 1); // -1 IS TO FILL THE CIRCLE
}
Thread.Sleep(1000);
imgCathNoTip.Save(DateTime.Now.ToString("yyMMddHHmmss") + ".jpg");
Sleep commands are just to make sure that the filenames will be different and will be removed later. I have also attached the images that have been save by this code during the process. The last image shows the detected circle which is larger and also shifted to the right.
Can anyone kindly check my code and let me know how I can improve it to detect circles more accurately?
Thanks in advance.
Automatic circle detection is an important element of many image processing algorithms. Traditionally the Hough transform has been used to find circular objects in images but more modern approaches that make use of heuristic optimisation techniques have been developed.
In order to detect the circles, or any other geometric shape, we first need to detect the edges of the objects present in the image. The edges in an image are the points for which there is a sharp change of color. For instance, the edge of a red ball on a white background is a circle.
The circle Hough Transform (CHT) is a basic feature extraction technique used in digital image processing for detecting circles in imperfect images. The circle candidates are produced by “voting” in the Hough parameter space and then selecting local maxima in an accumulator matrix.
cv2. HoughCircles(image, method, dp, minDist) Where Image is the image file converted to grey scale Method is the algorithm used to detct the circles. Dp is the inverse ratio of the accumulator resolution to the image resolution. minDist is the Minimum distance between the center coordinates of detected circles.
analogue to my answers in Detect semi-circle in opencv I see a problem: Don't extract canny edge detection before hough circle detection, since openCV houghCircle itself computes Gradient AND canny. So what you are trying to do is to extract canny from and edge image and detect circles in that, leading to (in the best case) 2 new edges around each edge => wrong way!
As it is done in the openCV tutorial, you can compute HoughCircles directly on your grayscale image, giving this result for me:
input:
parameters:
cannyHigh = 100
cannyLow = 20
minSize = 0
maxSize = 100
code:
int mainHough()
{
cv::Mat input = cv::imread("../inputData/CircleDetectGray.jpg");
// you could load as grayscale if you want, but I used it for (colored) output too
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
float canny1 = 100;
float canny2 = 20;
// canny here only for visualizing the chosen parameters
//cv::Mat canny;
//cv::Canny(gray, canny, canny1,canny2);
//cv::imshow("canny",canny);
std::vector<cv::Vec3f> circles;
/// Apply the Hough Transform to find the circles
cv::HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 1, gray.cols/8, canny1,canny2, 0, 100 );
std::cout << "found " << circles.size() << " circles" << std::endl;
/// Draw the circles detected
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 );
}
result:
using my RANSAC method I posted in Detect semi-circle in opencv (my 2nd answer but slightly changed to search the most complete circle) (input: canny edges)
code:
float verifyCircle(cv::Mat dt, cv::Point2f center, float radius, std::vector<cv::Point2f> & inlierSet)
{
unsigned int counter = 0;
unsigned int inlier = 0;
float minInlierDist = 2.0f;
float maxInlierDistMax = 100.0f;
float maxInlierDist = radius/25.0f;
if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist;
if(maxInlierDist>maxInlierDistMax) maxInlierDist = maxInlierDistMax;
// choose samples along the circle and count inlier percentage
for(float t =0; t<2*3.14159265359f; t+= 0.05f)
{
counter++;
float cX = radius*cos(t) + center.x;
float cY = radius*sin(t) + center.y;
if(cX < dt.cols)
if(cX >= 0)
if(cY < dt.rows)
if(cY >= 0)
if(dt.at<float>(cY,cX) < maxInlierDist)
{
inlier++;
inlierSet.push_back(cv::Point2f(cX,cY));
}
}
return (float)inlier/float(counter);
}
inline void getCircle(cv::Point2f& p1,cv::Point2f& p2,cv::Point2f& p3, cv::Point2f& center, float& radius)
{
float x1 = p1.x;
float x2 = p2.x;
float x3 = p3.x;
float y1 = p1.y;
float y2 = p2.y;
float y3 = p3.y;
// PLEASE CHECK FOR TYPOS IN THE FORMULA :)
center.x = (x1*x1+y1*y1)*(y2-y3) + (x2*x2+y2*y2)*(y3-y1) + (x3*x3+y3*y3)*(y1-y2);
center.x /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );
center.y = (x1*x1 + y1*y1)*(x3-x2) + (x2*x2+y2*y2)*(x1-x3) + (x3*x3 + y3*y3)*(x2-x1);
center.y /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );
radius = sqrt((center.x-x1)*(center.x-x1) + (center.y-y1)*(center.y-y1));
}
std::vector<cv::Point2f> getPointPositions(cv::Mat binaryImage)
{
std::vector<cv::Point2f> pointPositions;
for(unsigned int y=0; y<binaryImage.rows; ++y)
{
//unsigned char* rowPtr = binaryImage.ptr<unsigned char>(y);
for(unsigned int x=0; x<binaryImage.cols; ++x)
{
//if(rowPtr[x] > 0) pointPositions.push_back(cv::Point2i(x,y));
if(binaryImage.at<unsigned char>(y,x) > 0) pointPositions.push_back(cv::Point2f(x,y));
}
}
return pointPositions;
}
int mainRANSAC_circle()
{
cv::Mat color = cv::imread("../inputData/CircleDetectGray.jpg");
cv::Mat gray;
// convert to grayscale
// you could load as grayscale if you want, but I used it for (colored) output too
cv::cvtColor(color, gray, CV_BGR2GRAY);
cv::Mat mask;
float canny1 = 100;
float canny2 = 20;
cv::Mat canny;
cv::Canny(gray, canny, canny1,canny2);
cv::imshow("canny",canny);
mask = canny;
std::vector<cv::Point2f> edgePositions;
edgePositions = getPointPositions(mask);
// create distance transform to efficiently evaluate distance to nearest edge
cv::Mat dt;
cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3);
//TODO: maybe seed random variable for real random numbers.
unsigned int nIterations = 0;
cv::Point2f bestCircleCenter;
float bestCircleRadius;
float bestCirclePercentage = 0;
float minRadius = 10; // TODO: ADJUST THIS PARAMETER TO YOUR NEEDS, otherwise smaller circles wont be detected or "small noise circles" will have a high percentage of completion
//float minCirclePercentage = 0.2f;
float minCirclePercentage = 0.05f; // at least 5% of a circle must be present? maybe more...
int maxNrOfIterations = edgePositions.size(); // TODO: adjust this parameter or include some real ransac criteria with inlier/outlier percentages to decide when to stop
for(unsigned int its=0; its< maxNrOfIterations; ++its)
{
//RANSAC: randomly choose 3 point and create a circle:
//TODO: choose randomly but more intelligent,
//so that it is more likely to choose three points of a circle.
//For example if there are many small circles, it is unlikely to randomly choose 3 points of the same circle.
unsigned int idx1 = rand()%edgePositions.size();
unsigned int idx2 = rand()%edgePositions.size();
unsigned int idx3 = rand()%edgePositions.size();
// we need 3 different samples:
if(idx1 == idx2) continue;
if(idx1 == idx3) continue;
if(idx3 == idx2) continue;
// create circle from 3 points:
cv::Point2f center; float radius;
getCircle(edgePositions[idx1],edgePositions[idx2],edgePositions[idx3],center,radius);
// inlier set unused at the moment but could be used to approximate a (more robust) circle from alle inlier
std::vector<cv::Point2f> inlierSet;
//verify or falsify the circle by inlier counting:
float cPerc = verifyCircle(dt,center,radius, inlierSet);
// update best circle information if necessary
if(cPerc >= bestCirclePercentage)
if(radius >= minRadius)
{
bestCirclePercentage = cPerc;
bestCircleRadius = radius;
bestCircleCenter = center;
}
}
std::cout << "bestCirclePerc: " << bestCirclePercentage << std::endl;
std::cout << "bestCircleRadius: " << bestCircleRadius << std::endl;
// draw if good circle was found
if(bestCirclePercentage >= minCirclePercentage)
if(bestCircleRadius >= minRadius);
cv::circle(color, bestCircleCenter,bestCircleRadius, cv::Scalar(255,255,0),1);
cv::imshow("output",color);
cv::imshow("mask",mask);
cv::imwrite("../outputData/1_circle_color.png", color);
cv::imwrite("../outputData/1_circle_mask.png", mask);
//cv::imwrite("../outputData/1_circle_normalized.png", normalized);
cv::waitKey(0);
return 0;
}
I achieved this result instead:
Have no C# code, sorry.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With