# Remove circles from an image in Python

2014-10-21

This post is a follow-up to a post by Steve on Image Processing on how to remove circles from an image by specifying their center and radius. Read Filling circles to see how to do it in Matlab. I'll show how to do it in Python with SciPy and OpenCV.

Some initial imports we'll need:

``````import cv2
import numpy as np
``````

The starting point is an image with some circular objects:

To read this file as a grayscale image I do:

``````img = cv2.imread("coins.png", cv2.IMREAD_GRAYSCALE)

``````

Let's hide the coin at `x=348`, `y=317` and radius 45 pixels. Similar to Matlab recipe, we create a mask first.

``````# location and size of the circle
xc, yc, r = 348, 317, 45
# size of the image
H, W = img.shape
# x and y coordinates per every pixel of the image
x, y = np.meshgrid(np.arange(W), np.arange(H))
# squared distance from the center of the circle
d2 = (x - xc)**2 + (y - yc)**2
# mask is True inside of the circle
``````

We may simply set image to black (`0`) or white (`255`) under the mask as in the Matlab recipe, or we may calculate an average color outside of the coin, and fill inner pixels with this color:

``````outside = np.ma.masked_where(mask, img)
average_color = outside.mean()
``````

New image now looks like this:

The coin is missing, but the circle is clearly visible. A nicer way to fill the circle is to use OpenCV `inpaint` function. It will reconstruct the missing part from the pixels around it.

``````# we need to convert the mask to 8bit single channel image
``````

The inpainted image:

Now what if I wanted to find and remove all coins automatically? The approach suggested in Steve's article (apply binary threshold to mask all coins which happen to be brighter than the background) will not work, unless I carefully select a different background and take care of uniform illumination.

A different approach is to use Hough transform to detect circles:

``````circles = cv2.HoughCircles(img, cv2.cv.CV_HOUGH_GRADIENT,
``````

Hough transform is somewhat finicky as far as parameters should be selected on a case-by-case basis. Beyond the parameters in the example above you may want to adjust two other parameters: `param1` (default value is 100), which more or less translates into sensitivity of the edge detection procedure (see OpenCV docs for a more techinal definition), and `param2` (default value is 100) is a circle detection threshold. The smaller is `param2`, the more false circles will be detected.

To make sure only the right circles were selected we'll draw them over the original color image:

``````color_img = cv2.imread("coins.png")
red = (0,0,255)
for x, y, r in circles[0]:
cv2.circle(color_img, (x,y), r, red, 2)
``````

The detected circles are not very precise, they don't cover shadow and reflex zone around the coin, so we may want to make them slightly bigger when creating a mask.

``````black = np.zeros(img.shape)
for x, y, r in circles[0]:
cv2.circle(black, (x,y), int(r+15), 255, -1)  # -1 to draw filled circles
``````

Now we may simply inpaint all found coins:

``````bytemask = np.asarray(black, dtype=np.uint8)
``````

And all coins magically disappear:

We used three OpenCV features:

• Hough transform
• `inpaint` function
• primitive drawing procedures (`cv2.circle`)

If we skip inpainting, which is unique to OpenCV right now, we may also do everything in scikit-image. It tends to have a nicer Python interface. Let me know if you'd like to see some examples.

If you like this post, you may also like Counting objects and calculating objects' density in Python.