0

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()
IanS
  • 15,771
  • 9
  • 60
  • 84
GeoPark
  • 3
  • 3

2 Answers2

1

The mistake in your code is that your number is dependent upon the order of circles in list returned from cv2.HoughCircles which can be random, So what I would have done in this situation is to devise a formula which would convert the center(x, y) value of each circle to an ID, and the same circle would yield same ID given its center position remains same:

def get_id_from_center(x, y):
    return x + y*50
for (x, y, r) in circles:
    number = str(get_id_from_center(x, y))
ZdaR
  • 22,343
  • 7
  • 66
  • 87
1

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
Martin Evans
  • 45,791
  • 17
  • 81
  • 97
  • You are welcome! Don't forget to click on the grey tick under the up/down arrows to accept an answer as the accepted solution. – Martin Evans May 04 '17 at 07:51