如何从已识别的轮廓中获取曲线类线段的起止端点坐标
轮廓线段/曲线起止点提取解决方案
你之前采用极值点提取的方法仅对规则直线类轮廓有效,针对带弧度的曲线、不规则线段场景,可通过统计轮廓点邻域连通数的方法精准提取端点,原理是单像素宽度的轮廓端点仅存在1个相邻的轮廓点,中间线段点存在2个相邻轮廓点,交叉点存在2个以上相邻轮廓点。
具体实现步骤
- 对每个提取到的轮廓,生成单像素宽度的专属掩码图
- 遍历轮廓上的所有坐标点,统计每个点8邻域内属于当前轮廓的像素数量
- 筛选出邻域连通数为1的点,即为该轮廓的起止端点
- 若轮廓所有点连通数均为2,说明是闭合轮廓(如圆形、矩形外框),不存在端点可直接跳过
优化后的完整代码
import cv2 import numpy as np # 8邻域偏移坐标 neighbors = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] def get_contour_endpoints(contour, img_shape): # 生成当前轮廓的单像素掩码 mask = np.zeros(img_shape[:2], dtype=np.uint8) cv2.drawContours(mask, [contour], -1, 255, 1) endpoints = [] # 遍历轮廓所有点 for point in contour: x, y = point[0] count = 0 # 统计8邻域的轮廓像素数 for dx, dy in neighbors: nx = x + dx ny = y + dy # 边界判断 if 0 <= nx < img_shape[1] and 0 <= ny < img_shape[0]: if mask[ny, nx] == 255: count +=1 # 端点的邻域连通数为1 if count == 1: endpoints.append((x,y)) # 去重(避免噪声导致的重复点) endpoints = list(set(endpoints)) return endpoints # 加载图像 image = cv2.imread("你的图像本地路径.jpg") img_shape = image.shape # 灰度化+Canny边缘提取 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) edged = cv2.Canny(gray, 30, 200) # 轮廓检测,保留所有轮廓点 contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) print(f"检测到的轮廓数量 = {len(contours)}") # 遍历所有轮廓提取端点并绘制 for idx, cnt in enumerate(contours): endpoints = get_contour_endpoints(cnt, img_shape) print(f"轮廓{idx}的端点:{endpoints}") # 绘制端点,红色标记,半径5 for (x,y) in endpoints: cv2.circle(image, (x,y), 5, (0,0,255), -1) # 绘制轮廓 cv2.drawContours(image, contours, -1, (0, 255, 0), 3) cv2.imshow('结果', image) cv2.waitKey(0) cv2.destroyAllWindows()
精度优化方案
如果测试中仍存在端点漏检、误检,可增加以下预处理步骤:
- 对Canny输出的边缘图做一次形态学开运算,去除细小噪声毛刺
- 对长度小于阈值的轮廓直接过滤,避免噪声轮廓干扰
- 针对轮廓点先使用
cv2.approxPolyDP做轻度拟合,消除边缘抖动带来的邻域统计误差
内容的提问来源于stack exchange,提问作者Hasindu Dahanayake
相关产品推荐
相关产品推荐

