Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HoughCircles can't detect circles on this image

Tags:

c++

opencv

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: Input Image

Why can't HoughCircles detect circles in this image? It seems to be working on other simpler images like that of a circuit board.

like image 485
Utkarsh Dixit Avatar asked Jun 27 '16 07:06

Utkarsh Dixit


1 Answers

I had your exact problem and found a solution

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: Original

To this:

HoughCircles

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.

like image 74
Eric Leschinski Avatar answered Oct 03 '22 03:10

Eric Leschinski