如何在OpenCV中可靠检测被部分遮挡的圆形标记?
问题
我正在做一个基于OpenCV检测地图上圆形定位钉标记的项目,部分标记和街道相连,导致标准轮廓过滤方法没法可靠检测。
我已经试过几种优化检测的方法:
- 形态学操作:用了
cv2.morphologyEx、cv2.erode和cv2.dilate,试了不同核尺寸,但要么漏检标记,要么误检变多; - 模板匹配:试过
cv2.matchTemplate,结果更差,对部分遮挡的标记完全不可靠; - 颜色分割:已经实现,但担心不同地图的街道颜色差异会影响结果一致性。
附上当前实现代码:
import cv2 import numpy as np from matplotlib import pyplot as plt # Read the image image = cv2.imread('Capture.jpg') # Convert to grayscale gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Apply thresholding to create a binary image ret, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) kernel = np.ones((3,3), np.uint8) eroded = cv2.erode(thresh, kernel, iterations=1) dilated = cv2.dilate(eroded, kernel, iterations=1) # dilated = cv2.morphologyEx(thresh, cv2.MORPH_HITMISS, np.ones((3,3), np.uint8)) # Find contours contours, hierarchy = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Debug: Display all detected contours image_with_contours = image.copy() cv2.drawContours(image_with_contours, contours, -1, (0, 255, 0), 1) plt.figure(figsize=(12, 6)) plt.imshow(cv2.cvtColor(image_with_contours, cv2.COLOR_BGR2RGB)) plt.title('All Contours') plt.show() # Function to check if a contour is likely a marker pin based on circularity def is_marker_pin(contour): area = cv2.contourArea(contour) perimeter = cv2.arcLength(contour, True) if perimeter == 0: # To avoid division by zero return False circularity = 4 * np.pi * (area / (perimeter * perimeter)) x, y, w, h = cv2.boundingRect(contour) aspect_ratio = float(w) / h return 50 < area < 2500 and 0.5 < aspect_ratio < 2.5 and 0.6 < circularity < 1.5 # Filter contours based on shape characteristics marker_contours = [cnt for cnt in contours if is_marker_pin(cnt)] # Calculate and print the centroid of each marker contour centroids = [] for cnt in marker_contours: M = cv2.moments(cnt) if M['m00'] != 0: cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00']) centroids.append((cx, cy)) else: centroids.append((0, 0)) # Debug: Display only marker contours and their centroids image_with_marker_contours = image.copy() cv2.drawContours(image_with_marker_contours, marker_contours, -1, (0, 255, 0), 1) for (cx, cy) in centroids: cv2.circle(image_with_marker_contours, (cx, cy), 5, (255, 0, 0), -1) plt.figure(figsize=(12, 6)) plt.imshow(cv2.cvtColor(image_with_marker_contours, cv2.COLOR_BGR2RGB)) plt.title('Marker Contours with Centroids') plt.show() # Create a mask for the marker pins mask = np.zeros_like(gray) cv2.drawContours(mask, marker_contours, -1, (255), thickness=cv2.FILLED) # Apply the mask to the original image result = cv2.bitwise_and(image, image, mask=mask) # Display the result plt.figure(figsize=(12, 6)) plt.subplot(1, 2, 1) plt.imshow(mask, cmap='gray') plt.title('Mask') plt.subplot(1, 2, 2) plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) plt.title('Markers Isolated') plt.show() # Print the number of detected markers and their centroids print(f"Number of detected markers: {len(marker_contours)}") print("Centroids of the detected markers:") for i, (cx, cy) in enumerate(centroids): print(f"Marker {i + 1}: ({cx}, {cy})")
项目使用的图片:

当前核心问题:与街道部分相连的标记无法被可靠检测。请问怎么修改现有方法或轮廓过滤条件,才能可靠检测这类部分遮挡的标记?还有哪些技术或参数能提升检测精度?
优化方案与建议
1. 改进轮廓过滤逻辑,适配部分遮挡的圆形
当前圆形度阈值对被街道拉扯变形的标记不够友好,新增凸包缺陷分析判断是否为“带小缺口的圆形”:
def is_marker_pin(contour): area = cv2.contourArea(contour) # 先过滤面积异常的轮廓 if not (50 < area < 2500): return False # 凸包缺陷分析:判断是否是圆形被小部分遮挡 hull = cv2.convexHull(contour, returnPoints=False) if len(hull) > 3: defects = cv2.convexityDefects(contour, hull) if defects is not None: eq_radius = np.sqrt(area / np.pi) small_defect_count = 0 for i in range(defects.shape[0]): _, _, _, d = defects[i, 0] depth = d / 256.0 # 转换为实际深度值 if depth < eq_radius / 3: small_defect_count += 1 # 只有少量小缺陷,视为被遮挡的标记 if small_defect_count <= 2: return True # 原有判断逻辑,覆盖未被遮挡的标记 perimeter = cv2.arcLength(contour, True) if perimeter == 0: return False circularity = 4 * np.pi * (area / (perimeter ** 2)) x, y, w, h = cv2.boundingRect(contour) aspect_ratio = float(w) / h return 0.4 < circularity < 1.5 and 0.5 < aspect_ratio < 2.5
2. 调整形态学操作,针对性分离标记与街道
放弃简单开闭运算,改用形态学梯度+椭圆形核,优先切断细长的街道连接,保留圆形标记主体:
# 替换原有阈值后的形态学操作 kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)) # 形态学梯度突出轮廓边缘 gradient = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel_ellipse) # 小椭圆形核腐蚀,切断街道与标记的细连接 eroded = cv2.erode(gradient, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2,2)), iterations=1) # 膨胀恢复标记尺寸 dilated = cv2.dilate(eroded, kernel_ellipse, iterations=1)
3. 结合霍夫圆检测,互补轮廓检测不足
霍夫圆对部分遮挡的圆形鲁棒性更强,可与轮廓检测结果做交集验证去重:
# 在灰度图上运行霍夫圆检测 circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1.2, minDist=20, param1=50, param2=30, minRadius=5, maxRadius=30) if circles is not None: circles = np.uint16(np.around(circles)) hough_contours = [] for i in circles[0, :]: # 生成圆形轮廓用于匹配 circle_contour = cv2.ellipse2Poly((i[0], i[1]), (i[2], i[2]), 0, 0, 360, 5) hough_contours.append(circle_contour) # IOU匹配去重,合并两种检测结果 def iou_contour(cnt1, cnt2): mask1 = cv2.drawContours(np.zeros_like(gray), [cnt1], -1, 255, -1) mask2 = cv2.drawContours(np.zeros_like(gray), [cnt2], -1, 255, -1) intersection = cv2.countNonZero(cv2.bitwise_and(mask1, mask2)) union = cv2.countNonZero(cv2.bitwise_or(mask1, mask2)) return intersection / union if union !=0 else 0 merged_contours = marker_contours.copy() for h_cnt in hough_contours: matched = False for m_cnt in marker_contours: if iou_contour(h_cnt, m_cnt) > 0.5: matched = True break if not matched: merged_contours.append(h_cnt) marker_contours = merged_contours
4. 优化颜色分割的鲁棒性
改用HSV空间+多范围匹配,或结合颜色与灰度阈值结果,降低街道颜色差异的影响:
# 切换到HSV空间提取标记颜色 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 示例:提取红色标记(可通过采样自动调整范围) lower_red1 = np.array([0, 120, 70]) upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([170, 120, 70]) upper_red2 = np.array([180, 255, 255]) mask_red = cv2.inRange(hsv, lower_red1, upper_red1) | cv2.inRange(hsv, lower_red2, upper_red2) # 结合颜色和灰度阈值结果,过滤街道干扰 combined_thresh = cv2.bitwise_and(thresh, mask_red) # 后续用combined_thresh替代原thresh进行形态学操作
5. 调整轮廓提取模式
将cv2.RETR_EXTERNAL改为cv2.RETR_CCOMP提取层级轮廓,过滤街道大轮廓内部的小圆形标记:
contours, hierarchy = cv2.findContours(dilated, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) filtered_contours = [] for i, cnt in enumerate(contours): # hierarchy[i] = [next, prev, child, parent] if hierarchy[0][i][3] == -1: # 外层轮廓按原有规则判断 if is_marker_pin(cnt): filtered_contours.append(cnt) else: # 内层轮廓:检查是否是小圆形(被街道包裹的标记) area = cv2.contourArea(cnt) if 50 < area < 2500: perimeter = cv2.arcLength(cnt, True) if perimeter !=0: circularity = 4 * np.pi * (area/(perimeter**2)) if circularity > 0.4: filtered_contours.append(cnt) marker_contours = filtered_contours
内容的提问来源于stack exchange,提问作者i suck at programming
相关产品推荐
相关产品推荐

