如何用OpenCV/Python绘制高尔夫球杆挥杆的插值轨迹曲线?
解决方案:绘制平滑带渐隐效果的高尔夫挥杆轨迹
1. 先解决轨迹抖动/折线问题:平滑检测点
YOLOv5的检测结果可能存在帧间小偏移,直接连点会产生生硬折线,可用以下两种方法平滑坐标:
滑动平均法(简单易实现)
维护一个最近N帧的检测点队列,取队列平均坐标作为当前轨迹点,抵消小幅抖动:
from collections import deque import numpy as np # 保存最近10帧的杆头坐标,可根据帧率调整长度 point_queue = deque(maxlen=10) def smooth_point(current_point): if current_point is not None: point_queue.append(current_point) if len(point_queue) > 1: avg_x = int(np.mean([p[0] for p in point_queue])) avg_y = int(np.mean([p[1] for p in point_queue])) return (avg_x, avg_y) return current_point
卡尔曼滤波(精准轨迹预测)
如果需要更稳定的追踪,用OpenCV卡尔曼滤波器预测杆头位置,修正检测误差:
import cv2 import numpy as np # 初始化卡尔曼滤波器(状态量:x,y,dx,dy;观测值:x,y) kalman = cv2.KalmanFilter(4, 2) kalman.measurementMatrix = np.array([[1, 0, 0, 0], [0, 1, 0, 0]], np.float32) kalman.transitionMatrix = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]], np.float32) kalman.processNoiseCov = np.eye(4, dtype=np.float32) * 0.03 def kalman_update(current_point): if current_point is not None: measurement = np.array([[np.float32(current_point[0])], [np.float32(current_point[1])]]) kalman.correct(measurement) prediction = kalman.predict() return (int(prediction[0]), int(prediction[1]))
2. 绘制类似目标图的渐隐轨迹
目标轨迹是带拖尾渐变的效果,而非生硬直线,推荐以下两种实现方式:
方式一:透明轨迹画布叠加(效果自然)
创建带alpha通道的画布,每次绘制轨迹后降低画布透明度,再叠加到原帧:
import cv2 trail_canvas = None prev_point = None cap = cv2.VideoCapture("你的高尔夫视频路径.mp4") while cap.isOpened(): ret, frame = cap.read() if not ret: break # 替换为你的YOLOv5检测逻辑,返回杆头坐标(x,y) current_point = detect_club_head(frame) # 用滑动平均或卡尔曼滤波平滑点 smoothed_point = smooth_point(current_point) if trail_canvas is None: # 初始化和帧同尺寸的透明画布 trail_canvas = np.zeros_like(frame, dtype=np.float32) if smoothed_point and prev_point: # 在画布上绘制轨迹线,颜色、厚度可调整 cv2.line(trail_canvas, prev_point, smoothed_point, (0, 255, 0), 3) # 降低画布透明度,实现轨迹渐隐(数值越小,轨迹消失越快) trail_canvas *= 0.95 # 叠加轨迹到原帧 frame = cv2.addWeighted(frame, 1.0, trail_canvas.astype(np.uint8), 0.8, 0) prev_point = smoothed_point cv2.imshow("挥杆轨迹", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
方式二:按时间梯度绘制线条
保存最近M个轨迹点,从最新到最旧绘制透明度逐渐降低的线条:
from collections import deque import cv2 # 保存最近50个轨迹点,数量根据轨迹长度需求调整 trail_points = deque(maxlen=50) cap = cv2.VideoCapture("你的高尔夫视频路径.mp4") while cap.isOpened(): ret, frame = cap.read() if not ret: break current_point = detect_club_head(frame) smoothed_point = smooth_point(current_point) if smoothed_point: trail_points.append(smoothed_point) # 绘制渐变轨迹 for i in range(1, len(trail_points)): if not trail_points[i-1] or not trail_points[i]: continue # 计算透明度权重,越旧的点越淡 alpha = i / len(trail_points) # 颜色渐变(也可以固定颜色,通过addWeighted控制透明度) color = (0, int(255 * alpha), 0) thickness = max(1, int(3 * alpha)) cv2.line(frame, trail_points[i-1], trail_points[i], color, thickness) cv2.imshow("挥杆轨迹", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
关键调优提示
- 检测稳定性:给YOLOv5设置较高置信度阈值(如
conf=0.7),或用高尔夫球杆专用数据集微调模型,减少误检测导致的轨迹跳变。 - 参数适配:滑动窗口长度、轨迹衰减系数、轨迹点数量需根据视频帧率和挥杆速度调整,找到最优效果。
- 性能优化:高帧率视频下,避免设置过大的轨迹队列长度,防止内存占用过高。
内容的提问来源于stack exchange,提问作者Yogendra Yatnalkar
相关产品推荐
相关产品推荐

