4

I am trying to detect all the circles in images like this.

I have many different images like this but in all the circles will be black (or almost black) and of the same size (+/- a couple of pixels). I believe there are exactly 2943 circles in each image. These conditions never change. I may not be able to control the sizes of the circles across images (usually ranges between 15-45 pixels in radii - the example image provided above has radii of 20-21 pixels).

I need to be able to detect the exact locations of the centers of these circles as accurately and precisely as possible (if possible, the radii too).

I tried using the cv2.HoughCircles function to do so, but got very inconsistent and unreliable results. Here is the code that I used:

from pylab import * import sys, cv2 filename = sys.argv[1]; img = cv2.imread(filename,0); cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR); circles = cv2.HoughCircles(img,cv2.cv.CV_HOUGH_GRADIENT,2,15,param1=100,param2=30,minRadius=15,maxRadius=25); circles = np.uint16(np.around(circles)); for i in circles[0,:]: cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),1); cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3); cv2.imwrite('1-%s'%(filename),cimg) print "%d circles found."%(len(circles[0])); 

The result was this image and this output: 2806 circles found.

There are many false circles and many of the true circles have been missed/ignored.

I am starting to believe that the HoughCircle method is not the most optimal way to go if all my circles are identical in a single image, and there might be some better object detection method available.

What do you suggest I use to detect every circle precisely and accurately across thousands of images if I can control the properties of the circles tightly enough?

3
  • 2
    Your result image gives 404. From the initial image we can see that your circles are touching each other, so as a start I'd use morphological operator to erode the image. Then simply cv2.findContours with cv2.CV_RETR_LIST flag, should result in a list which length hopefully will return the number of circles. Commented Feb 6, 2014 at 22:09
  • Fixed. Thanks for telling me about that. I am really confused about why the OBVIOUS circles on the top are being fitted with substantially smaller circles and off-centre too. Commented Feb 6, 2014 at 22:31
  • 2
    The link to the image seems dead. Do you mind posting an updated link to the raw image since this is such a great post with an really instructive answer. Commented Mar 15, 2017 at 8:21

1 Answer 1

9

I came up with this code, it is tuned to the exact image you have supplied, finds 2943 circles with the radius estimate as well, assuming all circles have the same radius. This is what it produces (cropped, original was too big):

Crop of the resulting image

You can see that its not completely ideal (the corner circle is a bit off).

It is based around thresholding and then contour operations rather than hough circles.

import cv2 import numpy as np original = cv2.imread("test.jpg", cv2.CV_LOAD_IMAGE_GRAYSCALE) retval, image = cv2.threshold(original, 50, 255, cv2.cv.CV_THRESH_BINARY) el = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) image = cv2.dilate(image, el, iterations=6) cv2.imwrite("dilated.png", image) contours, hierarchy = cv2.findContours( image, cv2.cv.CV_RETR_LIST, cv2.cv.CV_CHAIN_APPROX_SIMPLE ) drawing = cv2.imread("test.jpg") centers = [] radii = [] for contour in contours: area = cv2.contourArea(contour) # there is one contour that contains all others, filter it out if area > 500: continue br = cv2.boundingRect(contour) radii.append(br[2]) m = cv2.moments(contour) center = (int(m['m10'] / m['m00']), int(m['m01'] / m['m00'])) centers.append(center) print("There are {} circles".format(len(centers))) radius = int(np.average(radii)) + 5 for center in centers: cv2.circle(drawing, center, 3, (255, 0, 0), -1) cv2.circle(drawing, center, radius, (0, 255, 0), 1) cv2.imwrite("drawing.png", drawing) 

Hope it helps

Sign up to request clarification or add additional context in comments.

6 Comments

The dilate function - is that to make the circles look more rounder rather than the slightly hexagonal shape they were taking? I am trying to understand what about the image requires these steps so I can calibrate my photography better for the code.
Well the findContours operates on a binary image. For that you need to threshold, that can get rid of most of the black border you have around the circles, but makes circles "stick" together where they are touching. What the dilate does is it tries to separate these circles by shrinking them. If you run this, you can see the result in the dilated.png it produces.
I'd refer you to the OpenCV Docs but basically it creates a "kernel" that is then subsequently used in the dilation to separate the circles from each other.
I read all the possible documentations, I think there's a fundamental lack of understanding about what a kernel is. Where can I learn more and/or get a stronger mathematical understanding of what's going on here?
See this for example.
|