I am trying to detect circles in my image containing a circle of dots, but unfortunately I am not able to do so. I am using opencv HoughTransform and I can't find parameters that make this work.
src = imread("encoded.jpg",1);
/// Convert it to gray
cvtColor(src, src_gray, CV_BGR2GRAY);
vector<Vec3f> circles;
/// Apply the Hough Transform to find the circles
HoughCircles(src_gray, circles, CV_HOUGH_GRADIENT, 1, 10,
100, 30, 1, 30 // change the last two parameters
// (min_radius & max_radius) to detect larger circles
);
/// Draw the circles detected
for (size_t i = 0; i < circles.size(); i++)
{
cout << "Positive" << endl;
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// circle center
circle(src, center, 3, Scalar(0, 255, 0), -1, 8, 0);
// circle outline
circle(src, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}
/// Show your results
namedWindow("Hough Circle Transform Demo", CV_WINDOW_AUTOSIZE);
imshow("Hough Circle Transform Demo", src_gray);
waitKey(0);
My Image is here:
Why can't HoughCircles detect circles in this image? It seems to be working on other simpler images like that of a circuit board.
The key is in having enough intuition about what HoughCircles is doing so you can build a program that auto-tunes the hyper parameters for all the various images you want to find circles in.
Core problem, some intuition
HoughCircles doesn't stand on its own, even though it suggests it might with the min and max radius parameters, you need to run hundreds or thousands of iterations to auto-tune and auto-dial in the right settings. Then after you're done you need post processing verification step to be 100% sure that the circle is what you wanted. The problem is you're trying to hand-tune the input parameters to HoughCircles yourself by using guess-and-check. That is NOT going to work, at all. Have the computer auto-tune these parameters for you.
When can hand-tuning for HoughCircles be satisfactory?
If you want to hardcode your parameters by hand, the one thing you absolutly need is the EXACT radius of your circle to within one or two pixels. You can guess the dp resolution and set the accumulator array voting threshold and you'll probably be okay. But if you don't know radius, HoughCircles output is useless because either it finds circles everywhere or nowhere. And suppose you do find an acceptable tuning by hand, you show it an image that's a few pixels different, and your HoughCircles freaks out and finds 200 circles in the image. Worthless.
There is hope:
Hope comes from the fact that HoughCircles is very quick even on large images. You can write a program for HoughCircles to auto-tune the settings perfectly. If you don't know the radius and it could be small or large, you start out with a large "minimum distance parameter", a very fine dp resolution, and a very high voting threshold. So as you start out iterating and HoughCircles predictably refuses to find any circles because settings are way too aggressive and votes don't clear threshold. But the loop keeps iterates and creeping up on the optimal settings, letting the optimal setting be the lightning rod that signals you're done. The first circle you find will be the pixel perfect largest and best circle in the image, and you'll be impressed at HoughCircles handing you a pixel-perfect circle right where it ought to be. It's just you had to run it 5 thousand times.
Example python code (sorry its not C++) :
It's still rough around the edges but you should be able to clean it up so you get a gratifying pixel perfect result in under a second.
import numpy as np
import argparse
import cv2
import signal
from functools import wraps
import errno
import os
import copy
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, help = "Path to the image")
args = vars(ap.parse_args())
# load the image, clone it for output, and then convert it to grayscale
image = cv2.imread(args["image"])
orig_image = np.copy(image)
output = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
cv2.waitKey(0)
circles = None
minimum_circle_size = 100 #this is the range of possible circle in pixels you want to find
maximum_circle_size = 150 #maximum possible circle size you're willing to find in pixels
guess_dp = 1.0
number_of_circles_expected = 1 #we expect to find just one circle
breakout = False
#hand tune this
max_guess_accumulator_array_threshold = 100 #minimum of 1, no maximum, (max 300?) the quantity of votes
#needed to qualify for a circle to be found.
circleLog = []
guess_accumulator_array_threshold = max_guess_accumulator_array_threshold
while guess_accumulator_array_threshold > 1 and breakout == False:
#start out with smallest resolution possible, to find the most precise circle, then creep bigger if none found
guess_dp = 1.0
print("resetting guess_dp:" + str(guess_dp))
while guess_dp < 9 and breakout == False:
guess_radius = maximum_circle_size
print("setting guess_radius: " + str(guess_radius))
print(circles is None)
while True:
#HoughCircles algorithm isn't strong enough to stand on its own if you don't
#know EXACTLY what radius the circle in the image is, (accurate to within 3 pixels)
#If you don't know radius, you need lots of guess and check and lots of post-processing
#verification. Luckily HoughCircles is pretty quick so we can brute force.
print("guessing radius: " + str(guess_radius) +
" and dp: " + str(guess_dp) + " vote threshold: " +
str(guess_accumulator_array_threshold))
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
dp=guess_dp, #resolution of accumulator array.
minDist=100, #number of pixels center of circles should be from each other, hardcode
param1=50,
param2=guess_accumulator_array_threshold,
minRadius=(guess_radius-3), #HoughCircles will look for circles at minimum this size
maxRadius=(guess_radius+3) #HoughCircles will look for circles at maximum this size
)
if circles is not None:
if len(circles[0]) == number_of_circles_expected:
print("len of circles: " + str(len(circles)))
circleLog.append(copy.copy(circles))
print("k1")
break
circles = None
guess_radius -= 5
if guess_radius < 40:
break;
guess_dp += 1.5
guess_accumulator_array_threshold -= 2
#Return the circleLog with the highest accumulator threshold
# ensure at least some circles were found
for cir in circleLog:
# convert the (x, y) coordinates and radius of the circles to integers
output = np.copy(orig_image)
if (len(cir) > 1):
print("FAIL before")
exit()
print(cir[0, :])
cir = np.round(cir[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
if (len(cir) > 1):
print("FAIL after")
exit()
for (x, y, r) in cir:
# draw the circle in the output image, then draw a rectangle
# corresponding to the center of the circle
cv2.circle(output, (x, y), r, (0, 0, 255), 2)
cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
# show the output image
cv2.imshow("output", np.hstack([orig_image, output]))
cv2.waitKey(0)
So if you run that, It takes a good 5 seconds, but it gets it nearly pixel perfect (Further hand-tuning of the auto-tuner gets it sub-pixel perfect):
The above code converts this:
To this:
The secret sauce to make this work is in how much information you have before you begin. If you know radius to a certain tolerance like 20 pixels, then this works perfect you're done. But if you don't, you have to be smart about how you creep up on the radius of maximum votes with carefully approaching resolution and voting threshold. If the circles are oddly shaped, the dp resolution will need to be higher, and the voting threshold will need to explore lower ranges.
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