1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
|
import cv2
import numpy as np
def stddev(im, window_size):
"""
Compute the standard deviation in a window_size x window_size area for each pixel.
"""
assert window_size % 2 != 0 # window_size must be odd
k = window_size // 2
h, w = im.shape
std = np.ones((h-2*k, w-2*k)) * 255
for y in range(k, h - k):
for x in range(k, w - k):
std[y-k][x-k] = np.std(im[y - k:y+k, x-k:x+k])
return std.astype(np.uint8)
def is_valid_circle(label_id, stats, labels):
"""
Check is the connected component #label_id is a valid circle.
"""
x, y, w, h, n_pixels = stats
# Check number of pixels
if n_pixels < 500 or n_pixels > 10000:
return False
# Check coverage
# optimal for a circle is pi/4 ~ 0.78
# put 0.7 to have a margin
total_area = h * w
if n_pixels / total_area < 0.7:
return False
# Could add some shape descriptors using labels
# ...
return True
def find_circles(im_gray):
# 1. Resize, mainly to speed-up detection
resize_factor = 0.25
height, width = im_gray.shape
resized_height = int(round(height * resize_factor))
resized_width = int(round(width * resize_factor))
im_resized = cv2.resize(im_gray, (resized_height, resized_width),
interpolation=cv2.INTER_CUBIC)
# 2. Compute std
std_window = 9
blur_window = 13
im_std = stddev(im_resized, std_window)
im_std_soft = cv2.medianBlur(im_std, ksize=blur_window)
# 3. Threshold (THRESH_OTSU to find the optimal threshold)
threshold_value, thresholded = cv2.threshold(im_std_soft, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# 4. Find connected components
n_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(thresholded)
# 5. Fit a circle
circles = []
for label_id in range(n_labels):
if not is_valid_circle(label_id, stats[label_id], labels):
continue
im_label = (labels == label_id).astype(np.uint8) * 255
_, contours, _ = cv2.findContours(im_label, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Should check for contours[i] ...
contours = contours[0].reshape((-1, 2))
ellipse = cv2.fitEllipse(contours)
(xc, yc), (h, w), angle = ellipse
radius = (h + w)/4
# Rescale and offset
xc = (xc + std_window // 2) / resize_factor
yc = (yc + std_window // 2) / resize_factor
radius = radius / resize_factor
circles.append((xc, yc, radius))
return circles, im_std, im_std_soft, thresholded # for debug
def plot_circles(im_bgr, circles):
cross = 10
for xc, yc, r in circles:
xc = int(round(xc))
yc = int(round(yc))
r = int(round(r))
cv2.circle(im_bgr, (xc, yc), 3, color=(0, 0, 255), thickness=-1)
cv2.line(im_bgr, (xc - cross, yc), (xc + cross, yc), color=(0, 0, 255), thickness=3)
cv2.line(im_bgr, (xc, yc - cross), (xc, yc + cross), color=(0, 0, 255), thickness=3)
cv2.circle(im_bgr, (xc, yc), r, color=(0, 0, 255), thickness=1, lineType=cv2.LINE_AA)
filename="3wXEJNV.jpg"
im_gray = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
im_bgr = cv2.cvtColor(im_gray, cv2.COLOR_GRAY2BGR) # for display
circles, im_std, im_std_soft, thresholded = find_circles(im_gray)
plot_circles(im_bgr, circles)
cv2.imwrite(filename + "_circles.jpg", im_bgr)
# To debug
cv2.imwrite(filename + "_std.png", im_std)
cv2.imwrite(filename + "_std_soft.png", im_std_soft)
cv2.imwrite(filename + "_thresholded.png", thresholded) |
Partager