Remove circles from an image in Python
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
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 mask = d2 < r**2
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() img[mask] = average_color
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 bytemask = np.asarray(mask*255, dtype=np.uint8) inpainted = cv2.inpaint(img, bytemask, inpaintRadius=10, flags=cv2.INPAINT_TELEA)
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, dp=1.5, minDist=30, minRadius=15, maxRadius=60)
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:
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: 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: 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) inpainted = cv2.inpaint(img, bytemask, inpaintRadius=5, flags=cv2.INPAINT_TELEA)
And all coins magically disappear:
We used three OpenCV features:
- Hough transform
- primitive drawing procedures (
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.