Search code examples
pythonimage-processingscikit-image

fitting a circle to a binary image


I have been using skim age's thresholding algorithms to get some binary mask. For example, I obtain binary images like this:

Binary image obtained as a result of Otsu thresholding

What I am trying to figure out is how can I fit a circle to this binary mask. The constraint is the circle should cover as much of the white areas as possible and the whole circumference of the circle should lie entirely on the white parts.

I have been wrecking my head on how I can do this efficiently but have come up with no solution that works.

One approach I thought that might be something is:

  • Find some optimal center of the image/circle (I am not sure how to do this yet. Perhaps some raster scan like approach).
  • Compute circle for increasing radiii and figure out when it starts getting out of the white area or outside the image.
  • Then the centroid and radius will describe the circle.

Solution

  • Here is a solution that tries to make an optimal circle fit via minimization. It soon becomes apparent that the bubble isn't a circle :) Note the use of "regionprops" for easily determining area, centroid, etc. of regions.

    Circle fit to bubble

    from skimage import io, color, measure, draw, img_as_bool
    import numpy as np
    from scipy import optimize
    import matplotlib.pyplot as plt
    
    
    image = img_as_bool(io.imread('bubble.jpg')[..., 0])
    regions = measure.regionprops(measure.label(image))
    bubble = regions[0]
    
    y0, x0 = bubble.centroid
    r = bubble.major_axis_length / 2.
    
    def cost(params):
        x0, y0, r = params
        coords = draw.disk((y0, x0), r, shape=image.shape)
        template = np.zeros_like(image)
        template[coords] = 1
        return -np.sum(template == image)
    
    x0, y0, r = optimize.fmin(cost, (x0, y0, r))
    
    import matplotlib.pyplot as plt
    
    f, ax = plt.subplots()
    circle = plt.Circle((x0, y0), r)
    ax.imshow(image, cmap='gray', interpolation='nearest')
    ax.add_artist(circle)
    plt.show()