pythonopencvcomputer-visionobject-detectionhough-transform

How to detect a circle with uncertain thickness and some noise in an binary image?


The input image is here : the input image

I try to use cv2.HoughCircles in opencv-python to find the expected circle, but the result is noise as in this picture : result in param2=0.2

the code is:

import cv2
import numpy as np

img = cv2.imread('image.png')
# apply GaussianBlur
kernel_size = (15, 15)
sigma = 0
blurred_image = cv2.GaussianBlur(img, kernel_size, sigma)
gray = cv2.cvtColor(blurred_image, cv2.COLOR_BGR2GRAY)

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT_ALT, dp=1.5, minDist=20, param1=50, param2=0.2, minRadius=100, maxRadius=400)

# draw the result
if circles is not None:
    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]), 1, (0, 0, 255), 3)

# show the result
cv2.imshow('used image', gray)
cv2.imshow('detected circles', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

If i set param2=0.8, then no any circle can be found!

What should I do to get a better result that fit the further perfect circle in the example picture above?

The "perfect circle" can be defined as follows: Given an angular resolution of 0.1 degrees, the area will be divided into 360/0.1 = 3600 segments.

A perfect circle includes as many sectors as possible, where these sectors contain pixels with grayscale values below a certain threshold (e.g., 15).

In other words, the goal is to have the proportion of sectors that meet the criteria as close to 1 as possible.

If there are multiple resulted circles with same proportion, any one of them can be considered a perfect circle.

An example "perfect circle" : perfect circle


Solution

  • This is sort of a static solution for now, but if you always have such nice contrast and you can accuretly get the coordinates, then fitting a circle with least square might not be such a bad idea:

    def fit_circle(x, y):
        A = np.c_[x, y, np.ones(len(x))] # design matrix A with columns for x, y, and a constant term
        f = x**2 + y**2 # get the function as x²+y²
        C, _, _, _ = np.linalg.lstsq(A, f, rcond=None) # optimize
        cx = C[0] / 2 # get the centre x coordinate
        cy = C[1] / 2 # get the centre y coordinate
        radius = np.sqrt(C[2] + cx**2 + cy**2) # calculate the radius 
        return round(cx), round(cy), round(radius) # return everythin as int
    

    To use this function, I did the following:

    im = cv2.imread("circle.png") # read as BGR
    imGray = cv2.imread("circle.png", 0) # read as gray
    y, x = np.where(imGray==0) # get x,y coords
    cx, cy, r = fit_circle(x,y) # get the circle properties, center and radius
    im = cv2.circle(im, (cx,cy), r, (255, 0, 0), 5)
    im = cv2.line(im, (cx-r,cy), (cx+r,cy), (0, 255,0), 2)
    im = cv2.line(im, (cx,cy-r), (cx,cy+r), (0, 255,0), 2)
    im = cv2.circle(im, (cx,cy), 10, (0, 0, 255), -1)
    cv2.imwrite("WithCircle.png", im) # save im for stack
    

    The result:

    circ

    As I said, very important that you can get those nice pixels that define your circle, this method can work with some added noise as well I presume, but definitely not good if you have any one-sided deviations.