1

Let's say I have a grayscaled, thresholded image like below:

enter image description here

And I need the contours of the circles. How do I do this? I tried findContours with multiple options:

  • RETR_EXTERNAL: this only returns the contour of the outer rectangle

  • RETR_TREE: this returns too many contours (contour of outer circle and inner circle)

I need only 1 contour for each circle. I want to draw another color circle over the original circle (either green or red). I specify upfront how many circles there are (in this case 120), but because it generates 120 * 2 = 240 contours (2 contours per circle), the program fails.

So, is there a way to only get the contours of the circles within the rectangle and ONLY get 1 contour per circle?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
vdvaxel
  • 667
  • 1
  • 14
  • 41
  • Possible duplicate of [Python OpenCV: draw outer contours inside a specific contour](https://stackoverflow.com/questions/51919841/python-opencv-draw-outer-contours-inside-a-specific-contour) – Dodge Aug 19 '18 at 19:34
  • 1
    Traverse the contour hierarchy to identify the outer circle contours only. – Dan Mašek Aug 19 '18 at 19:40
  • Maybe RETR_LIST may give something valueable. Take also a look here: https://docs.opencv.org/3.4.0/d9/d8b/tutorial_py_contours_hierarchy.html and there https://stackoverflow.com/questions/51749668/opencv-not-finding-all-contours/51753560?noredirect=1#comment90467678_51753560 Perhaps it can help. – John_Sharp1318 Aug 20 '18 at 02:50
  • Filter by area, width/height ratio, approx points number and so on, I get 234/240 circles in total. Note, the bottom four circles are connected to the bottom lines,so it's not easy to distinguish. This is my result. https://i.stack.imgur.com/XKXDC.png – Kinght 金 Aug 21 '18 at 03:28

1 Answers1

2

Assuming the input image named as "circles.jpg".

First of all, import required libraries and load image in gray format. Use Numpy.where() function to purify the foreground and background of the image. Then find all the contours on this image:

import cv2
import numpy as np
image_gray = cv2.imread("circles.jpg", 0)
image_gray = np.where(image_gray > 30, 255, image_gray)
image_gray = np.where(image_gray <= 30, 0, image_gray)
image_gray = cv2.adaptiveThreshold(image_gray, 255,
                                   cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                   cv2.THRESH_BINARY, 115, 1)
_, contours, _ = cv2.findContours(image_gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

Secondly, create an empty image with same width and height. Then, draw the contours on the newly created image, with a filtering condition - the contour must have a circle-like shape:

image_copy = np.zeros_like(image_gray)  # create a new emtpy image
for cnt in contours:
    peri = cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
    (x, y, w, h) = cv2.boundingRect(cnt)
    ar = w / float(h)
    if len(approx) > 5 and 0.9 < ar < 1.1:  # filtering condition
        cv2.drawContours(image_copy, [cnt], 0, 255, -1)

Thirdly, you need to store the area of each contour into a list and remove any contours that are too small:

cnt_areas = []
cnt_circles = []
for cnt in contours:
    (x, y, w, h) = cv2.boundingRect(cnt)
    cnt_areas.append(w*h)
    cnt_circles.append(cnt)

import statistics
median_size_limit = statistics.median(cnt_areas) * 0.3
cnt_circles = [cnt for i, cnt in enumerate(cnt_circles)
               if cnt_areas[i] > median_size_limit]

And finally, display and check the result:

# Draw the result and display
image = cv2.cvtColor(image_gray, cv2.COLOR_GRAY2RGB)
cv2.drawContours(image, cnt_circles, -1, (0, 0,255), 2)
cv2.imshow("Result Preview", image)
cv2.waitKey()

Preview of final result can be seen here:

Preview of Result

Howard GENG
  • 1,075
  • 7
  • 16