Search code examples
pythonopencvnumberscircleimage

How can I number circles in a certain order using python?


I want to get the shade value of each circles from an image.

  1. I try to detect circles using HoughCircle.
  2. I get the center of each circle.
  3. I put the text (the circle numbers) in a circle.
  4. I set the pixel subset to obtain the shading values and calculate the averaged shading values.
  5. I want to get the results of circle number, the coordinates of the center, and averaged shading values in CSV format.

But, in the 3rd step, the circle numbers were randomly assigned. So, it's so hard to find circle number.

How can I number circles in a sequence?

enter image description here

# USAGE
# python detect_circles.py --image images/simple.png

# import the necessary packages
import numpy as np
import argparse
import cv2
import csv

# define a funtion of ROI calculating the average value in specified sample size
def ROI(img,x,y,sample_size):
    Each_circle=img[y-sample_size:y+sample_size, x-sample_size:x+sample_size]
    average_values=np.mean(Each_circle)
    return average_values

# open the csv file named circles_value
circles_values=open('circles_value.csv', 'w')

# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, help = "Path to the image")
args = vars(ap.parse_args())

# load the image, clone it for output, and then convert it to grayscale
image = cv2.imread(args["image"])
output = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# detect circles in the image
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2,50, 100, 1, 1, 20, 30)

# ensure at least some circles were found
if circles is not None:
    # convert the (x, y) coordinates and radius of the circles to integers
    circles = np.round(circles[0, :]).astype("int")


number=1
font = cv2.FONT_HERSHEY_SIMPLEX

# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
    # draw the circle in the output image, then draw a rectangle
    # corresponding to the center of the circle
    number=str(number)
    cv2.circle(output, (x, y), r, (0, 255, 0), 4)
    cv2.rectangle(output, (x - 10, y - 10), (x + 10, y + 10), (0, 128, 255), -1)
    # number each circle, but its result shows irregular pattern 
    cv2.putText(output, number, (x,y), font,0.5,(0,0,0),2,cv2.LINE_AA)
    # get the average value in specified sample size (20 x 20)
    sample_average_value=ROI(output, x, y, 20)
    # write the csv file with number, (x,y), and average pixel value
    circles_values.write(number+','+str(x)+','+str(y)+','+str(sample_average_value)+'\n')
    number=int(number)
    number+=1

# show the output image
cv2.namedWindow("image", cv2.WINDOW_NORMAL)
cv2.imshow("image", output)
cv2.waitKey(0)

# close the csv file
circles_values.close()

Solution

  • You could sort your circles based on their x, y values, the width of the image and a rough line height, for example:

    import numpy as np
    import argparse
    import cv2
    import csv
    
    # define a funtion of ROI calculating the average value in specified sample size
    def ROI(img,x,y,sample_size):
        Each_circle=img[y-sample_size:y+sample_size, x-sample_size:x+sample_size]
        average_values=np.mean(Each_circle)
        return average_values
    
    # open the csv file named circles_value
    
    with open('circles_value.csv', 'wb') as circles_values:
        csv_output = csv.writer(circles_values)
    
        # construct the argument parser and parse the arguments
        ap = argparse.ArgumentParser()
        ap.add_argument("-i", "--image", required = True, help = "Path to the image")
        args = vars(ap.parse_args())
    
        # load the image, clone it for output, and then convert it to grayscale
        image = cv2.imread(args["image"])
        output = image.copy()
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
        # detect circles in the image
        circles = cv2.HoughCircles(gray, cv2.cv.CV_HOUGH_GRADIENT, 1.2,50, 100, 1, 1, 20, 30)
    
        # ensure at least some circles were found
        if circles is not None:
            # convert the (x, y) coordinates and radius of the circles to integers
            circles = np.round(circles[0, :]).astype("int")
    
        font = cv2.FONT_HERSHEY_SIMPLEX
        height =  40
    
        # loop over the (x, y) coordinates and radius of the circles
        for number, (x, y, r) in enumerate(sorted(circles, key=lambda v: v[0] + (v[1] / height) * image.shape[1]), start=1):
            text = str(number)
            (tw, th), bl = cv2.getTextSize(text, font, 0.5, 2)      # So the text can be centred in the circle
            tw /= 2
            th = th / 2 + 2
    
            # draw the circle in the output image, then draw a rectangle
            # corresponding to the center of the circle
            cv2.circle(output, (x, y), r, (0, 255, 0), 3)
            cv2.rectangle(output, (x - tw, y - th), (x + tw, y + th), (0, 128, 255), -1)
            # number each circle, centred in the rectangle
            cv2.putText(output, text, (x-tw, y + bl), font, 0.5, (0,0,0), 2, cv2.CV_AA)
            # get the average value in specified sample size (20 x 20)
            sample_average_value = ROI(output, x, y, 20)
            # write the csv file with number, (x,y), and average pixel value
            csv_output.writerow([number, x, y, sample_average_value])
    
        # show the output image
        cv2.namedWindow("image", cv2.WINDOW_NORMAL)
        cv2.imshow("image", output)
        cv2.waitKey(0)
    

    Also, it is easier to use Python's CSV library to write entries to your output file. This way you don't need to convert each entry to a string and add commas between each entry. enumerate() can be used to count each circle automatically. Also getTextSize() can be used to determine the dimensions of the text to be printed enabling you to centre it in the rectangle.

    This would give you an output as follows:

    Numbered circles

    And a CSV starting as:

    1,2,29,nan
    2,51,19,nan
    3,107,22,100.72437499999999
    4,173,23,102.33291666666666
    5,233,26,88.244791666666671
    6,295,22,92.953541666666666
    7,358,28,142.51625000000001
    8,418,26,155.12875
    9,484,31,127.02541666666667
    10,547,25,112.57958333333333