0
votes

I have a task to detect Circles and Radio buttons in an Image. For this I tried Hough circles by having different parameters.

Issues: If the circles in the Image are of same radius of Radio buttons both are detected, but in our case it should only detect only one.

Is there a way to differentiate between circles and Radio buttons (when they are not checked). Right now I am limiting them by Radius with 2 different functions one for circle and one for radio button. The above code is for circles

    circle_contours=[]
    # Converting the image Gray scale
    gray = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
    # Blur the image to reduce noise
    img_blur = cv2.medianBlur(gray, 5)
    # Apply hough transform on the image
    circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1,20, param1=50, param2=20, 
    minRadius=11, maxRadius=21)
    # Draw detected circles
    if circles is not None:
       circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
       # Draw outer circle
       cv2.circle(image1, (i[0], i[1]), i[2], (34, 255, 34), 2)
       circle_contours.append(circles)

I have used a similar approach for radio buttons but with different parameters as below.

    radio_buttons= cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1,20, param1=50, param2=16, 
    minRadius=9, maxRadius=10)

Original Image

Image 1:

Image1

Image 2:

Image2

For the Image1 it detects circles correctly and when it is passed to the radio buttons function it also draws circles(Image2) for the inner part of it with a reduced radius which are also detected as radio buttons

In Image3Image3 it has to detect Circle and Radio buttons, where my code is only able to detect circles.

I have also tried using draw contours but it had issues when the Image also has checkboxes.

Is there any other approach or a better way for detection?

1
Share original image file in lossless format (png), please.. - Alex Alex
@AlexAlex I have added the picture to the original post. - shashi

1 Answers

1
votes
  1. Find and draw all the contours in the answer-sheet.

  1. Apply HoughCircles

Step #1: We could start with finding all contours in the given answer-sheet.

  • contourIdx=-1 means to draw all the contours.

  • import cv2
    import numpy as np
    
    image = cv2.imread('zip_grade_form.png')
    
    # Converting the image Gray scale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    ret, thresh = cv2.threshold(src=gray, thresh=127, maxval=255, type=0)
    
    contours, hierarchy = cv2.findContours(image=thresh,
                                           mode=cv2.RETR_TREE,
                                           method=cv2.CHAIN_APPROX_SIMPLE)
    
    gray = cv2.drawContours(image=gray, contours=contours, contourIdx=-1,
                         color=(255, 255, 255), thickness=2)
    

    Result:

  • enter image description here

  • From above we can see that all features except circles are removed. We use the findContours method to remove unwanted artifacts.

Step#2: Apply HoughCircles. The same code you wrote on the question. Result:

  • enter image description here

Code:

import cv2
import numpy as np

image = cv2.imread('zip_grade_form.png')

# Converting the image Gray scale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(src=gray, thresh=127, maxval=255, type=0)

contours, hierarchy = cv2.findContours(image=thresh,
                                       mode=cv2.RETR_TREE,
                                       method=cv2.CHAIN_APPROX_SIMPLE)

gray = cv2.drawContours(image=gray, contours=contours, contourIdx=-1,
                        color=(255, 255, 255), thickness=2)

cv2.imwrite("gray.png", gray)

img_blur = cv2.medianBlur(gray, 5)
circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=16,
                           minRadius=9, maxRadius=10)

circle_contours = []

if circles is not None:
    circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
        # Draw outer circle
        cv2.circle(image, (i[0], i[1]), i[2], (108, 105, 255), 2)
        circle_contours.append(circles)


cv2.imwrite("circles.png", image)

Update

  • For detecting check-boxes and radio-buttons you need to calculate the contour-perimeter (p) and the contour-approximation (a). source

  • We can separate each object using p and a value since each object has a unique p and a values.

  • For instance, in image-3,

    • check-box: p= 73 and a = 4
    • radio-button: p = 64 and a = 8.
  • You can find the values observing the code.

  • Apply the 1st step again.

    • Result:

    • enter image description here

  • Now find the contours in the above image:

    • if len(approx) == 8 and int(p) == 64:
          cv2.drawContours(image, [c], -1, (180, 105, 255), 3)
      elif len(approx) == 4 and int(p) == 73:
          cv2.drawContours(image, [c], -1, (180, 105, 255), 3)
      
    • Result:

      • enter image description here
    • Code:

      import cv2
      
      from imutils import grab_contours as grb_cns
      from imutils import resize as rsz
      
      image = cv2.imread('K1Z94.png')
      
      # Converting the image Gray scale
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      
      ret, thresh = cv2.threshold(src=gray, thresh=127, maxval=255, type=0)
      
      contours, hierarchy = cv2.findContours(image=thresh.copy(),
                                              mode=cv2.RETR_TREE, 
                                               method=cv2.CHAIN_APPROX_SIMPLE)
      
      gray = cv2.drawContours(image=gray, contours=contours, contourIdx=-1, color=(255, 255, 255), thickness=2)
      
      
      resized = rsz(gray, width=300)
      ratio = gray.shape[0] / float(gray.shape[0])
      
      canny = cv2.Canny(gray, 50, 200)
      
      thresh = cv2.threshold(src=canny, thresh=60, maxval=255,
                         type=cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]
      
      cns = cv2.findContours(image=thresh.copy(), mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)
      
      cns = grb_cns(cns)
      
      for c in cns:
           p = cv2.arcLength(c, True)  # perimeter
           approx = cv2.approxPolyDP(c, 0.04 * p, True)
      
           M = cv2.moments(c)
      
           # check if the all values of M are 0.
           all_zr = all(value == 0 for value in M.values())
      
           if not all_zr:
               cX = int((M["m10"] / M["m00"]))
               cY = int((M["m01"] / M["m00"]))
               c = c.astype("float")
               c *= ratio
               c = c.astype("int")
      
               # Circles: (radio-buttons)
               if len(approx) == 8 and int(p) == 64:
                   cv2.drawContours(image, [c], -1, (180, 105, 255), 3)
               elif len(approx) == 4 and int(p) == 73:
                   cv2.drawContours(image, [c], -1, (180, 105, 255), 3)
      
        cv2.imwrite("result.png", image)