How Can I Correctly Classify The Number Of Positive (bright Color) Circles And Negative (dark Color) Circles In The Image
Solution 1:
You got almost every right, if you are not worried about one mis-classified blob, the partial blobs not being detected at all, and the (apparently) inaccurate size for some of the blobs.
The last problem to solve is to get a sensible threshold between the bright and the dark blobs. One way to do so is to use an adaptive threshold, like e.g. the Otsu's method or others.
Have a look here for more threshold methods from scikit-learn.
EDIT: Updated to better match what you were asking.
Briefly, compared to your code, I have done the following modifications:
- Put all the code inside functions (it helps me reason better)
- I have defined a contrast enhancement function, but it is not used in the code (because I was getting worse results.)
- define a function that generates masks associated to the circles (note that this function would by available, with slightly different parameters, in PyMRT - Disclaimer: I am the main author of it.)
- threshold the circles using the masks from above and the Otsu method for determining the optimal threshold value
(minor note: I saved the input image as blobs.jpg
).
This is how I would have done it, but I am sure that there is room for improving its robustness by tweaking the parameters.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from skimage.filters import threshold_otsu
# based on: https://stackoverflow.com/questions/46626267/how-to-generate-a-sphere-in-3d-numpy-array/46626448#46626448defcircle(shape, radius, position):
semisizes = (radius,) * 2
grid = [slice(-x0, dim - x0) for x0, dim inzip(position, shape)]
position = np.ogrid[grid]
arr = np.zeros(shape, dtype=float)
for x_i, semisize inzip(position, semisizes):
arr += (np.abs(x_i / semisize) ** 2)
return arr <= 1.0defenhance_contrast(
in_img,
save_filepath=None):
"""Enhance contrast."""
lab_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2LAB)
l_ch, a_ch, b_ch = cv2.split(lab_img)
# Applying CLAHE to L-channel
clahe_filter = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
l_ch = clahe_filter.apply(l_ch)
out_img = cv2.merge((l_ch, a_ch, b_ch))
out_img = cv2.cvtColor(out_img, cv2.COLOR_LAB2BGR)
if save_filepath:
cv2.imwrite(save_filepath, out_img)
return out_img
deffind_circles(
in_filepath,
out_filepath='circles_{in_filepath}',
enh_filepath='enh_{in_filepath}',
hough_circles_kws=(
('dp', 1), ('minDist', 15), ('param1', 30), ('param2', 30),
('minRadius', 5), ('maxRadius', 25)),
verbose=True):
"""Find circles in image."""
out_filepath = out_filepath.format(**locals())
enh_filepath = enh_filepath.format(**locals())
hough_circles_kws = dict(hough_circles_kws) if hough_circles_kws else {}
in_img = cv2.imread(in_filepath)
lab_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2LAB)
l_ch, a_ch, b_ch = cv2.split(lab_img)
blur_l_ch = cv2.medianBlur(l_ch, 1)
circles = cv2.HoughCircles(blur_l_ch, cv2.HOUGH_GRADIENT, **hough_circles_kws)
if circles isnotNone:
values_img = l_ch
# compute meansif verbose:
print('Image size: ', values_img.shape)
circles = np.squeeze(circles)
values = []
for x0, y0, r in circles:
mask = circle(values_img.shape, r, (y0, x0))
values.append(np.percentile(values_img[mask], 90))
circles = np.concatenate((circles, np.array(values).reshape(-1, 1)), -1)
threshold = threshold_otsu(np.array(values))
if verbose:
print('Threshold: ', threshold)
# plot circlesfor x0, y0, r, mean in circles:
if mean > threshold:
# good circles in green
cv2.circle(in_img, (int(x0), int(y0)), int(r), (0, 255, 0), 2)
else:
# bad circles in red
cv2.circle(in_img, (int(x0), int(y0)), int(r), (0, 0, 255), 2)
if verbose:
print('Circles:')
print(circles)
print('Num Circles: ', circles.shape[0])
print('Good Circles: ', np.sum(values > threshold))
if out_filepath:
cv2.imwrite(out_filepath.format(**locals()), in_img)
return out_filepath, circles, threshold
out_filepath, circles, threshold = find_circles('blobs.jpg')
This would generate the following output:
Imagesize: (230, 294)
Threshold: 96.1328125Circles:
[[ 36.5 108.5 21.10000038 155.5 ][170.5 124.5 24.39999962 170. ][ 43.5 156.5 21.10000038 156.5 ][ 33.5 57.5 22.20000076 190. ][101.5 40.5 19.89999962 90. ][ 75.5 78.5 18.79999924 88. ][254.5 171.5 16.60000038 82. ][138.5 52.5 15.39999962 90. ][123.5 148.5 14.39999962 90. ][ 42.5 199.5 15.39999962 174. ][138.5 15.5 14.10000038 88. ][ 86.5 176.5 15.39999962 90. ][256.5 23.5 15.5 146. ][211.5 140.5 14.39999962 87. ][132.5 193.5 13.19999981 90.1 ][174.5 35.5 9.60000038 93. ][ 81.5 129.5 11. 93. ][223.5 54.5 9.60000038 87. ][177.5 75.5 13.19999981 146. ][214.5 195.5 11. 90. ][259.5 126.5 9.60000038 90. ][ 62.5 22.5 11. 96. ][220.5 98.5 9.60000038 89. ][263.5 77.5 12.10000038 84.1 ][116.5 101.5 9.60000038 92. ][170.5 177.5 11. 91. ][251.5 215.5 11. 91. ][167.5 215.5 11. 87. ][214.5 14.5 9.60000038 92. ]]
NumCircles: 29GoodCircles: 7
and the corresponding image:
(and of course you can adapt the code above to better fit your needs).
EDIT: Included some code and figures.
It is also possible to plot a barchart of good/bad results:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
values= circles[:, -1]
data = [np.sum(values<= threshold), np.sum(values> threshold)]
labels = ['Bad', 'Good']
colors = ['red', 'green']
ax.bar(labels, data, color=colors)
plt.show()
Or to plot a complete histogram, e.g.:
fig, ax = plt.subplots()
hist, edges = np.histogram(values, bins=40)
widths = (edges[1:] - edges[:-1])
ax.bar(edges[:-1] + widths / 2, hist, widths) # plots the histogram
ax.axvline(x=threshold, color='black') # plots the threshold (optional)
plt.show()
EDIT: Included additional barplot and histogram
Post a Comment for "How Can I Correctly Classify The Number Of Positive (bright Color) Circles And Negative (dark Color) Circles In The Image"