A few days ago, I got an email from a PyImageSearch reader asking about circle detection. See below for the gist:
Love your blog. I saw your post on detecting rectangles/squares in images, but I was wondering, how do you detect circles in images using OpenCV?
As you’ve probably already found out, detecting circles in images using OpenCV is substantially harder than detecting other shapes with sharp edges.
But don’t worry!
In this blog post I’ll show you how to utilize the
cv2.HoughCircles function to detect circles in images using OpenCV.
Read on to find out how.
Looking for the source code to this post?
Jump right to the downloads section.
OpenCV and Python versions:
This example will run on Python 2.7/Python 3.4+ and OpenCV 2.4.X/OpenCV 3.0+.
The cv2.HoughCircles Function
In order to detect circles in images, you’ll need to make use of the
cv2.HoughCircles function. It’s definitely not the easiest function to use, but with a little explanation, I think you’ll get the hang of it.
Take a look at the function signature below:
cv2.HoughCircles(image, method, dp, minDist)
image: 8-bit, single channel image. If working with a color image, convert to grayscale first.
method: Defines the method to detect circles in images. Currently, the only implemented method is
cv2.HOUGH_GRADIENT, which corresponds to the Yuen et al. paper.
dp: This parameter is the inverse ratio of the accumulator resolution to the image resolution (see Yuen et al. for more details). Essentially, the larger the
dpgets, the smaller the accumulator array gets.
minDist: Minimum distance between the center (x, y) coordinates of detected circles. If the
minDistis too small, multiple circles in the same neighborhood as the original may be (falsely) detected. If the
minDistis too large, then some circles may not be detected at all.
param1: Gradient value used to handle edge detection in the Yuen et al. method.
param2: Accumulator threshold value for the
cv2.HOUGH_GRADIENTmethod. The smaller the threshold is, the more circles will be detected (including false circles). The larger the threshold is, the more circles will potentially be returned.
minRadius: Minimum size of the radius (in pixels).
maxRadius: Maximum size of the radius (in pixels).
If this method seems complicated, don’t worry. It’s actually not too bad.
But I will say this — be ready to play around with the parameter values from image to image. The
minDist parameter is especially important to get right. Without an optimal
minDist value, you may end up missing out on some circles, or you may detecting many false circles.
Detecting Circles in Images using OpenCV and Hough Circles
Ready to apply the
cv2.HoughCircles function to detect circles in images?
Great. Let’s jump into some code:
# import the necessary packages
import numpy as np
# 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())
Lines 2-4 import the necessary packages we’ll need. We’ll utilize NumPy for numerical processing,
argparse for parsing command line arguments, and
cv2 for our OpenCV bindings.
Then, on Lines 7-9 we parse our command line arguments. We’ll need only a single switch,
--image, which is the path to the image we want to detect circles in.
Let’s go ahead and load the image:
# load the image, clone it for output, and then convert it to grayscale
image = cv2.imread(args["image"])
output = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
We load our image off disk on Line 12 and create a copy of it on Line 13 so we can draw our detected circles without destroying the original image.
As we’ll see, the
cv2.HoughCircles function requires an 8-bit, single channel image, so we’ll go ahead and convert from the RGB color space to grayscale on Line 14.
Okay, time to detect the circles:
# detect circles in the image
circles = cv2.HoughCircles(gray, cv2.cv.CV_HOUGH_GRADIENT, 1.2, 100)
# ensure at least some circles were found
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
# 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, 255, 0), 4)
cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
# show the output image
cv2.imshow("output", np.hstack([image, output]))
Detecting the circles is handled by the
cv2.HoughCircles function on Line 17. We pass in the image we want to detect circles as the first argument, the circle detection method as the second argument (currently, the
cv2.cv.HOUGH_GRADIENT method is the only circle detection method supported by OpenCV and will likely be the only method for some time), an accumulator value of 1.5 as the third argument, and finally a
minDist of 100 pixels.
A check is made on Line 20 to ensure at least one circle was found in the image.
Line 22 then handles converting our circles from floating point (x, y) coordinates to integers, allowing us to draw them on our output image.
From there, we start looping over the center (x, y) coordinates and the radius of the circle on Line 25.
We draw the actual detected circle on Line 28 using the
cv2.circle function, followed by drawing a rectangle at the center of the circle on Line 29.
Finally, Lines 32 and 33 display our output image.
So there you have it — detecting circles in images using OpenCV.
But let’s go ahead and take a look at some results.
Fire up a shell, and execute the following command:
$ python detect_circles.py --image images/simple.png
We’ll start with something simple, detecting a red circle on a black background:
Not bad! Our Python script has detected the red circle, outlined it in green, and then placed an orange square at the center of it.
Let’s move on to something else:
$ python detect_circles.py --image images/soda.png
Again, our Python script is able to detect the circular region of the can.
Now, let’s try the 8 circle problem.
In this problem we have one large circle, followed by seven circles placed inside the large one.
Since this is a much smaller image than the previous ones (and we are detecting multiple circles), I’m going to adjust the
minDist to be 75 pixels rather than 100.
Hm. Now it looks like we have ran into a problem.
cv2.HoughCircles function was able to detect only seven of the circles instead of all eight, leaving out the one in the center.
Why did this happen?
It’s due to the
minDist parameter. The center (x, y) coordinates for the large outer circle are identical to the center inner circle, thus the center inner circle is discarded.
Unfortunately, there is not a way around this problem unless we make
minDist unreasonably small, and thus generating many “false” circle detections.
In this blog post I showed you how to use the
cv2.HoughCircles function in OpenCV to detect circles in images.
Unlike detecting squares or rectangles in images, detecting circles is substantially harder since we cannot reply on approximating the number of points in a contour.
To help us detect circles in images, OpenCV has supplied the
cv2.HoughCircles method may seem complicated at first, I would argue that the most important parameter to play with is the
minDist, or the minimum distance between the center (x, y) coordinates of detected circles.
If you set
minDist too small, you may end up with many falsely detected circles. On the other hand, if
minDist is too large, then you may end up missing some circles. Setting this parameter definitely takes some fine tuning.
Have a Question?
Do you have a question about OpenCV and Python? Just send me a message. And I’ll do my best to answer it on this blog.