10

I am trying to use OpenCV's (Hough)Circle detection to.. detect circles. I created a solid circle on a black background, tried to play with the parameters, used blur and everything, but I am just not able to make it find anything.

Any ideas, suggestions etc. would be great, thank you!

my current code is something like this:

import cv2
import numpy as np

"""
params = dict(dp=1,
              minDist=1,
              circles=None,
              param1=300,
              param2=290,
              minRadius=1,
              maxRadius=100)
"""

img = np.ones((200,250,3), dtype=np.uint8)
for i in range(50, 80, 1):
    for j in range(40, 70, 1):
        img[i][j]*=200

cv2.circle(img, (120,120), 20, (100,200,80), -1)


gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray, 200, 300)

cv2.imshow('shjkgdh', canny)
gray = cv2.medianBlur(gray, 5)
circles = cv2.HoughCircles(gray, cv2.cv.CV_HOUGH_GRADIENT, 1, 20,
              param1=100,
              param2=30,
              minRadius=0,
              maxRadius=0)

print circles
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
    cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2)
    cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)

cv2.imshow('circles', img)
k = cv2.waitKey(0)
if k == 27:
    cv2.destroyAllWindows()
Shin
  • 678
  • 3
  • 12
  • 22
  • Ok, for some reason it suddenly works with quite a few parameters. I went through everything since yesterday without anything working, but now it does. It doesn't make much sense to me, but well... – Shin Oct 08 '14 at 10:27
  • Hough circles is a little.. tricky, in practice. – a-Jays Oct 08 '14 at 12:50

3 Answers3

21

Your code is working just fine. The problem is in your HoughCircles threshold parameters.

Let's try to understand the parameters that you're using from OpenCV Docs:

param1 – First method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the higher threshold of the two passed to the Canny() edge detector (the lower one is twice smaller).

param2 – Second method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first.

So, as you can see, internally the HoughCircles function calls the Canny edge detector, this means that you can use a gray image in the function, instead of their contours.

Now reduce the param1 to 30 and param2 to 15 and see the results in the code that follows:

import cv2
import numpy as np

img = np.ones((200,250,3), dtype=np.uint8)
for i in range(50, 80, 1):
    for j in range(40, 70, 1):
        img[i][j]*=200

cv2.circle(img, (120,120), 20, (100,200,80), -1)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

circles = cv2.HoughCircles(gray, cv2.cv.CV_HOUGH_GRADIENT, 1, 20,
              param1=30,
              param2=15,
              minRadius=0,
              maxRadius=0)

print circles
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
    cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2)
    cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)

cv2.imshow('circles', img)

k = cv2.waitKey(0)
if k == 27:
    cv2.destroyAllWindows()

HoughCircles

Eliezer Bernart
  • 2,386
  • 24
  • 33
  • Thank you :) I displayed the canny results to get a better idea about what it HoughCircles is actually working with. The canny results always looked clean and the same tho which probably caused the biggest part of my confusion. Somehow I managed to always use parameters that didn't fit even tho I have been trying out quite a few different parameters. Since the canny image seems to be the same with working and not working parameters, I am still not sure why some parameters work while others don't. – Shin Oct 08 '14 at 15:39
  • How is this working with maxRadius=0? Surely that would restrict any circles from appearing? – ComputerScientist Jul 17 '17 at 22:09
  • @ComputerScientist if you read the Documentation for the [HoughCircle](http://docs.opencv.org/3.0-beta/modules/imgproc/doc/feature_detection.html#houghcircles) you can see that `maxRadius` and `minRadius` default values are 0. So, you are not applying any threshold for the radius size. Hence, getting all the circles in the image. – Eliezer Bernart Jul 17 '17 at 23:25
12

If you're not getting HoughCircles to bring you pixel perfect solutions for obvious circles then you're not using it right

Your mistake is you're trying to hand-tune your hyperparameters by yourself. That's not going to work. Have the computer auto-tune the parameters for you:

import numpy as np
import argparse
import cv2
import signal

from functools import wraps
import errno
import os
import copy

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, help = "Path to the image")
args = vars(ap.parse_args())

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

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.cv.CV_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")

    for (x, y, r) in cir:
        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)

    cv2.imshow("output", np.hstack([orig_image, output]))
    cv2.waitKey(0)

The above code converts this: Original

To this:

HoughCircles

For more information about what this is doing, see: https://stackoverflow.com/a/46500223/445131

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
  • Cool code, that works reasonably well! Just some comments. It seems that minimum_circle_size is not being used at all. I would change "if guess_radius < 40" to if guess_radius < minimum_circle_size". Also "cv2.cv.CV_HOUGH_GRADIENT" is now "cv2.HOUGH_GRADIENT" – Dmitry Kamenetsky Oct 06 '20 at 06:41
  • 1
    That code was written in a hurry so there are bugs. The code was a brownian-motion attempt to "figure out which radius of circle is the one I want" since houghcircles returns many. And so knowing the exact radius of the circle you want, houghcircles produces a perfect result. The problem with the internal voting system of Houghcircles is it finds invalid circles in the imperfections in the background. – Eric Leschinski Oct 06 '20 at 13:55
0

I have written a GUI for this algorithm that you can change parameters on time to see how it works. Here you can see it from Github:

Github link: https://github.com/pwwiur/hough-counter

Installation:

Clone it to your directory:

git clone https://github.com/pwwiur/hough-counter.git

Install requirements:

pip install numpy opencv-python matplotlib pillow

Run:

python main.py

Demo

enter image description here

Amir Fo
  • 5,163
  • 1
  • 43
  • 51