def find_concetric_circles(gray_img, min_ring_count=3, visual_debug=False): # get threshold image used to get crisp-clean edges using blur to remove small features edges = cv2.adaptiveThreshold(cv2.blur(gray_img, (3, 3)), 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 11) _, contours, hierarchy = cv2.findContours(edges, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE, offset=(0, 0)) #TC89_KCOS if visual_debug is not False: cv2.drawContours(visual_debug, contours, -1, (200, 0, 0)) if contours is None or hierarchy is None: return [] clusters = get_nested_clusters(contours, hierarchy[0], min_nested_count=min_ring_count) concentric_cirlce_clusters = [] #speed up code by caching computed ellipses ellipses = {} # for each cluster fit ellipses and cull members that dont have good ellipse fit for cluster in clusters: if visual_debug is not False: cv2.drawContours(visual_debug, [contours[i] for i in cluster], -1, (0, 0, 255)) candidate_ellipses = [] for i in cluster: c = contours[i] if len(c) > 5: if not i in ellipses: e = cv2.fitEllipse(c) fit = max(dist_pts_ellipse(e, c)) ellipses[i] = e, fit else: e, fit = ellipses[i] a, b = e[1][0] / 2., e[1][1] / 2. if fit < max(2, max(e[1]) / 20): candidate_ellipses.append(e) if visual_debug is not False: cv2.ellipse(visual_debug, e, (0, 255, 0), 1) if candidate_ellipses: cluster_center = np.mean(np.array( [e[0] for e in candidate_ellipses]), axis=0) candidate_ellipses = [ e for e in candidate_ellipses if np.linalg.norm(e[0] - cluster_center) < max(3, min(e[1]) / 20) ] if len(candidate_ellipses) >= min_ring_count: concentric_cirlce_clusters.append(candidate_ellipses) if visual_debug is not False: cv2.ellipse(visual_debug, candidate_ellipses[-1], (0, 255, 255), 4) #return clusters sorted by size of outmost cirlce biggest first. return sorted(concentric_cirlce_clusters, key=lambda e: -max(e[-1][1]))
def find_concetric_circles(gray_img,min_ring_count=3, visual_debug=False): # get threshold image used to get crisp-clean edges using blur to remove small features edges = cv2.adaptiveThreshold(cv2.blur(gray_img,(3,3)), 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 11) contours, hierarchy = cv2.findContours(edges, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE,offset=(0,0)) #TC89_KCOS if visual_debug is not False: cv2.drawContours(visual_debug,contours,-1,(200,0,0)) if contours is None or hierarchy is None: return [] clusters = get_nested_clusters(contours,hierarchy[0],min_nested_count=min_ring_count) concentric_cirlce_clusters = [] #speed up code by caching computed ellipses ellipses = {} # for each cluster fit ellipses and cull members that dont have good ellipse fit for cluster in clusters: if visual_debug is not False: cv2.drawContours(visual_debug, [contours[i] for i in cluster],-1, (0,0,255)) candidate_ellipses = [] for i in cluster: c = contours[i] if len(c)>5: if not ellipses.has_key(i): e = cv2.fitEllipse(c) fit = max(dist_pts_ellipse(e,c)) ellipses[i] = e,fit else: e,fit = ellipses[i] a,b = e[1][0]/2.,e[1][1]/2. if fit<max(2,max(e[1])/20): candidate_ellipses.append(e) if visual_debug is not False: cv2.ellipse(visual_debug, e, (0,255,0),1) if candidate_ellipses: cluster_center = np.mean(np.array([e[0] for e in candidate_ellipses]),axis=0) candidate_ellipses = [e for e in candidate_ellipses if np.linalg.norm(e[0]-cluster_center)<max(3,min(e[1])/20) ] if len(candidate_ellipses) >= min_ring_count: concentric_cirlce_clusters.append(candidate_ellipses) if visual_debug is not False: cv2.ellipse(visual_debug, candidate_ellipses[-1], (0,255,255),4) #return clusters sorted by size of outmost cirlce biggest first. return sorted(concentric_cirlce_clusters,key=lambda e:-max(e[-1][1]))
def find_concentric_circles( edge, scale, img_contrast, found_pos, found_size, first_check=True, min_ellipses_num=2, ): if first_check: concentric_circle_clusters = [] # OpenCV version compatibility note: # - Opencv3 returns three results: image, contours, hierarchy # - Opencv4 returns two results: contours, hierarchy # We do not use `image` in any case. Therefore, we can ensure # compatibility by using `*_` *_, contours, hierarchy = cv2.findContours( edge, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_TC89_KCOS) # We use CHAIN_APPROX_TC89_KCOS because it is faster than # the default method CV_CHAIN_APPROX_NONE if contours is None or hierarchy is None: return [] clusters = get_nested_clusters(contours, hierarchy[0], min_ellipses_num) # speed up code by caching computed ellipses ellipses = {} for cluster in clusters: candidate_ellipses = [] first_ellipse = True for i in cluster: c = contours[i] if len(c) > 100: continue if i in ellipses: e, fit = ellipses[i] else: if len(c) >= 5: e = cv2.fitEllipse(c) # Discard duplicates if first_ellipse: duplicates = [ k for k in range(len(found_pos)) if LA.norm(e[0] - found_pos[k]) < found_size[k] + min(e[1]) ] if len(duplicates) > 0: ellipses[i] = e, 100.0 break fit = 0 else: fit = max(dist_pts_ellipse(e, c)) if min( e[1]) else 0.0 e = e if min(e[1]) else (e[0], (0.1, 0.1), e[2]) else: e_center = ( float(c[len(c) // 2][0][0]), float(c[len(c) // 2][0][1]), ) e = (e_center, (0.1, 0.1), 0.0) # Discard duplicates if first_ellipse: duplicates = [ k for k in range(len(found_pos)) if LA.norm(e_center - found_pos[k]) < found_size[k] + 1 ] if len(duplicates) > 0: ellipses[i] = e, 100.0 break fit = 0 ellipses[i] = e, fit # Discard the contour which does not fit the ellipse so well if first_ellipse or fit < max(1, max(e[1]) / 50): e = (e[0], e[1], e[2], i) candidate_ellipses.append(e) first_ellipse = False # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue # Discard the ellipses whose center is far away from the center of the second innermost ellipse cluster_center = np.array(candidate_ellipses[1][0]) if max(candidate_ellipses[-1][1]) > 200: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 5 ] elif max(candidate_ellipses[-1][1]) > 100: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 10 ] else: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(max(e[1]) / 20, 3) ] # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue c = contours[candidate_ellipses[-1][3]] boundary = ( (np.amin(c, axis=0)[0][0], np.amax(c, axis=0)[0][0]), (np.amin(c, axis=0)[0][1], np.amax(c, axis=0)[0][1]), ) candidate_ellipses = [(e[0], e[1], e[2]) for e in candidate_ellipses] concentric_circle_clusters.append((candidate_ellipses, boundary)) # Return clusters sorted by the number of ellipses and the size of largest ellipse return sorted(concentric_circle_clusters, key=lambda x: (-len(x[0]), -max(x[0][-1][1]))) else: *_, contours, hierarchy = cv2.findContours( edge, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE) if contours is None or hierarchy is None: return [] clusters = get_nested_clusters(contours, hierarchy[0], min_ellipses_num) # speed up code by caching computed ellipses ellipses = {} for cluster in clusters: candidate_ellipses = [] first_ellipse = True for i in cluster: c = contours[i] if i in ellipses: e, fit = ellipses[i] else: if len(c) >= 5: e = cv2.fitEllipse(c) fit = max(dist_pts_ellipse(e, c)) if min(e[1]) else 0.0 if min(e[1]) == 0: e = (e[0], (e[1][0] + 1.0, e[1][1] + 1.0), e[2]) else: fit = 0 e_center = ( float(c[len(c) // 2][0][0]), float(c[len(c) // 2][0][1]), ) e_size = ( float(abs(c[-1][0][0] - c[0][0][0]) + 1), float(abs(c[-1][0][1] - c[0][0][1]) + 1), ) e = (e_center, e_size, 0) ellipses[i] = e, fit # Discard the contour which does not fit the ellipse so well if first_ellipse: fit_thres = 0.5 + (256 - img_contrast) / 256 else: if img_contrast <= 96: fit_thres = max( e[1]) * scale / 10 + (256 - img_contrast) / 256 else: fit_thres = max(0.5, max(e[1]) * scale / 10) if fit < fit_thres: candidate_ellipses.append(e) if len(candidate_ellipses) == 4: break first_ellipse = False # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue # Discard the ellipses whose center is far away from the center of the innermost ellipse cluster_center = np.array(candidate_ellipses[0][0]) if max(candidate_ellipses[-1][1]) * scale > 200: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 5 ] elif max(candidate_ellipses[-1][1]) * scale > 100: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 10 ] else: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(max(e[1]) / 20, 2) ] # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue return [(candidate_ellipses, [[0, 0], [0, 0]])] return []
def find_concentric_circles( edge, scale, img_contrast, found_pos, found_size, first_check=True, min_ellipses_num=2, ): if first_check: concentric_circle_clusters = [] # OpenCV version compatibility note: # - Opencv3 returns three results: image, contours, hierarchy # - Opencv4 returns two results: contours, hierarchy # We do not use `image` in any case. Therefore, we can ensure # compatibility by using `*_` *_, contours, hierarchy = cv2.findContours( edge, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_TC89_KCOS ) # We use CHAIN_APPROX_TC89_KCOS because it is faster than # the default method CV_CHAIN_APPROX_NONE if contours is None or hierarchy is None: return [] clusters = get_nested_clusters(contours, hierarchy[0], min_ellipses_num) # speed up code by caching computed ellipses ellipses = {} for cluster in clusters: candidate_ellipses = [] first_ellipse = True for i in cluster: c = contours[i] if len(c) > 100: continue if i in ellipses: e, fit = ellipses[i] else: if len(c) >= 5: e = cv2.fitEllipse(c) # Discard duplicates if first_ellipse: duplicates = [ k for k in range(len(found_pos)) if LA.norm(e[0] - found_pos[k]) < found_size[k] + min(e[1]) ] if len(duplicates) > 0: ellipses[i] = e, 100.0 break fit = 0 else: fit = max(dist_pts_ellipse(e, c)) if min(e[1]) else 0.0 e = e if min(e[1]) else (e[0], (0.1, 0.1), e[2]) else: e_center = ( float(c[len(c) // 2][0][0]), float(c[len(c) // 2][0][1]), ) e = (e_center, (0.1, 0.1), 0.0) # Discard duplicates if first_ellipse: duplicates = [ k for k in range(len(found_pos)) if LA.norm(e_center - found_pos[k]) < found_size[k] + 1 ] if len(duplicates) > 0: ellipses[i] = e, 100.0 break fit = 0 ellipses[i] = e, fit # Discard the contour which does not fit the ellipse so well if first_ellipse or fit < max(1, max(e[1]) / 50): e = (e[0], e[1], e[2], i) candidate_ellipses.append(e) first_ellipse = False # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue # Discard the ellipses whose center is far away from the center of the second innermost ellipse cluster_center = np.array(candidate_ellipses[1][0]) if max(candidate_ellipses[-1][1]) > 200: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 5 ] elif max(candidate_ellipses[-1][1]) > 100: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 10 ] else: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(max(e[1]) / 20, 3) ] # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue c = contours[candidate_ellipses[-1][3]] boundary = ( (np.amin(c, axis=0)[0][0], np.amax(c, axis=0)[0][0]), (np.amin(c, axis=0)[0][1], np.amax(c, axis=0)[0][1]), ) candidate_ellipses = [(e[0], e[1], e[2]) for e in candidate_ellipses] concentric_circle_clusters.append((candidate_ellipses, boundary)) # Return clusters sorted by the number of ellipses and the size of largest ellipse return sorted( concentric_circle_clusters, key=lambda x: (-len(x[0]), -max(x[0][-1][1])) ) else: *_, contours, hierarchy = cv2.findContours( edge, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE ) if contours is None or hierarchy is None: return [] clusters = get_nested_clusters(contours, hierarchy[0], min_ellipses_num) # speed up code by caching computed ellipses ellipses = {} for cluster in clusters: candidate_ellipses = [] first_ellipse = True for i in cluster: c = contours[i] if i in ellipses: e, fit = ellipses[i] else: if len(c) >= 5: e = cv2.fitEllipse(c) fit = max(dist_pts_ellipse(e, c)) if min(e[1]) else 0.0 if min(e[1]) == 0: e = (e[0], (e[1][0] + 1.0, e[1][1] + 1.0), e[2]) else: fit = 0 e_center = ( float(c[len(c) // 2][0][0]), float(c[len(c) // 2][0][1]), ) e_size = ( float(abs(c[-1][0][0] - c[0][0][0]) + 1), float(abs(c[-1][0][1] - c[0][0][1]) + 1), ) e = (e_center, e_size, 0) ellipses[i] = e, fit # Discard the contour which does not fit the ellipse so well if first_ellipse: fit_thres = 0.5 + (256 - img_contrast) / 256 else: if img_contrast <= 96: fit_thres = max(e[1]) * scale / 10 + (256 - img_contrast) / 256 else: fit_thres = max(0.5, max(e[1]) * scale / 10) if fit < fit_thres: candidate_ellipses.append(e) if len(candidate_ellipses) == 4: break first_ellipse = False # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue # Discard the ellipses whose center is far away from the center of the innermost ellipse cluster_center = np.array(candidate_ellipses[0][0]) if max(candidate_ellipses[-1][1]) * scale > 200: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 5 ] elif max(candidate_ellipses[-1][1]) * scale > 100: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(e[1]) / 10 ] else: candidate_ellipses = [ e for e in candidate_ellipses if LA.norm(e[0] - cluster_center) < max(max(e[1]) / 20, 2) ] # Discard false positives if len(candidate_ellipses) < min_ellipses_num: continue return [(candidate_ellipses, [[0, 0], [0, 0]])] return []