如何提取视频中电缆轮廓的中心线并记录其点集?
电缆轮廓中心线提取方案
问题描述
需要为视频中的电缆轮廓绘制中心线,并将中心线的点记录到数组中。现有基于OpenCV的代码已完成电缆轮廓检测,但此前尝试将轮廓点拆分为上下两部分取均值生成中心线的方法,因视频中电缆运动导致轮廓上下部分点数量不均,结果极不准确且不完整,需生成贯穿轮廓中间的准确中心线。
现有代码
import cv2 from PIL import Image from function import get_limits, get_limits_black import numpy as np black = [0,0,0] cap = cv2.VideoCapture("/Users/samayjain/PycharmProjects/PythonProject/2025-06-07 11-24-40 Side.mp4") while True: ret, frame = cap.read() roi = frame[0:1500, 0:1400] hsvImage = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) lowerBlack, upperBlack = get_limits_black(color=black) mask_black = cv2.inRange(hsvImage, lowerBlack, upperBlack) contours, _ = cv2.findContours(mask_black, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) i = 0 max_width = 0 index = 0 c_x,c_y,c_w,c_h= 0,0,0,0 for contour in contours: min_area = 1000 if cv2.contourArea(contour) > min_area: x, y, w, h = cv2.boundingRect(contour) if x + w > max_width: index = i max_width = x + w c_x,c_y,c_w,c_h=x,y,w,h M = cv2.moments(contour) if M["m00"] != 0: centroid_x = int(M["m10"] / M["m00"]) centroid_y = int(M["m01"] / M["m00"]) i += 1 cv2.rectangle(frame, (c_x, c_y), (c_x + c_w, c_y + c_h), (0, 255, 0), 2) cv2.drawContours(frame, contours, index, (0, 255, 0), 2) cv2.circle(frame, (centroid_x, centroid_y), 10, (0, 0, 255), -1) cv2.imshow("Original Frame with Contours", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
解决方案
针对电缆这类长条状轮廓,推荐两种稳定的中心线提取方法:
方法1:水平扫描线取中点
遍历轮廓包围盒内的每一行(y坐标),找到该行中属于电缆区域的左右边界,取两者中点作为中心线的点。该方法不受轮廓点数量影响,结果稳定。
方法2:骨架提取(形态学细化)
对电缆的二值掩码进行形态学细化操作,得到轮廓的骨架,骨架即为中心线。需注意预处理去除噪声,避免骨架出现分支。
以下是整合了水平扫描线法的修改代码,同时记录中心线点到数组:
import cv2 from function import get_limits_black import numpy as np black = [0,0,0] cap = cv2.VideoCapture("/Users/samayjain/PycharmProjects/PythonProject/2025-06-07 11-24-40 Side.mp4") # 存储所有帧的中心线点 all_centerline_points = [] while True: ret, frame = cap.read() if not ret: break # 视频读取完毕退出 roi = frame[0:1500, 0:1400] hsvImage = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) lowerBlack, upperBlack = get_limits_black(color=black) mask_black = cv2.inRange(hsvImage, lowerBlack, upperBlack) contours, _ = cv2.findContours(mask_black, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) target_contour = None max_width = 0 c_x,c_y,c_w,c_h= 0,0,0,0 # 筛选目标电缆轮廓(最宽的大轮廓) for contour in contours: if cv2.contourArea(contour) > 1000: x, y, w, h = cv2.boundingRect(contour) if x + w > max_width: max_width = x + w c_x,c_y,c_w,c_h = x,y,w,h target_contour = contour if target_contour is not None: # 创建仅包含目标轮廓的掩码 contour_mask = np.zeros_like(mask_black) cv2.drawContours(contour_mask, [target_contour], -1, 255, thickness=cv2.FILLED) centerline_points = [] # 遍历包围盒内的每一行 for y in range(c_y, c_y + c_h): # 获取当前行的所有白色像素列坐标 cols = np.where(contour_mask[y, c_x:c_x+c_w] == 255)[0] if len(cols) >= 2: left = cols[0] + c_x right = cols[-1] + c_x mid_x = (left + right) // 2 centerline_points.append((mid_x, y)) # 将当前帧的中心线点存入总数组 all_centerline_points.append(centerline_points) # 绘制中心线 for (x, y) in centerline_points: cv2.circle(frame, (x, y), 2, (0, 0, 255), -1) # 绘制轮廓和包围盒 cv2.rectangle(frame, (c_x, c_y), (c_x + c_w, c_y + c_h), (0, 255, 0), 2) if target_contour is not None: cv2.drawContours(frame, [target_contour], -1, (0, 255, 0), 2) cv2.imshow("Centerline Result", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() # 可打印查看某帧的中心线点示例 if all_centerline_points: print("第一帧中心线点数量:", len(all_centerline_points[0])) print("部分点示例:", all_centerline_points[0][:5])
代码说明
- 轮廓筛选:保留面积大于1000的轮廓,并选择最宽的作为目标电缆轮廓,避免干扰。
- 单独掩码:为目标轮廓创建独立掩码,确保只处理电缆区域。
- 扫描线取中点:对每一行的电缆区域左右边界取中,得到连续的中心线点,存入数组。
- 可视化:将中心线用红色圆点绘制在帧上,方便验证效果。
内容的提问来源于stack exchange,提问作者turnip
相关产品推荐
相关产品推荐

