You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:35:56