如何用OpenCV提取赛道海拔曲线并获取坐标?求非颜色依赖方案
赛道剖面曲线数据提取:摆脱颜色依赖的优化方案
问题背景
我正专注于优化不同运动项目的配速策略,需要获取赛道的距离与海拔变化信息。希望通过OpenCV从下载的赛道剖面示意图(PNG格式)中自动提取数据,替代耗时的手动处理方案。
参考相关问题后,我尝试识别剖面曲线,按像素步长采样并保存为CSV,但作为OpenCV新手,实现效果不佳,且方法高度依赖颜色,更换不同样式的图像就需要修改代码。求不依赖颜色的更优方案。
测试用赛道剖面示例图

我的实现代码
import cv2 import numpy as np import matplotlib.pyplot as plt image_path = r"image" image = cv2.imread(image_path) hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) color_blue1 = np.array([100, 50, 50]) color_blue2 = np.array([140, 255, 255]) mask = cv2.inRange(hsv_image, color_blue1, color_blue2) blue_curve = cv2.bitwise_and(image, image, mask=mask) # Converting the result to grayscale gray_curve = cv2.cvtColor(blue_curve, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray_curve, 50, 150) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) curve_contour = max(contours, key=cv2.contourArea) curve_points = curve_contour.squeeze() curve_points = curve_points[np.argsort(curve_points[:, 0])] def sample_curve(points, step): x_values = points[:, 0] sampled_points = [] for x in range(x_values.min(), x_values.max(), step): interval_points = points[(x_values >= x) & (x_values < x + step)] if interval_points.size: avg_y = interval_points[:, 1].mean() sampled_points.append([x, avg_y]) return np.array(sampled_points) # Set step frequency (pixels) step_frequency = 5 sampled_curve = sample_curve(curve_points, step_frequency) plt.figure(figsize=(15, 7)) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.plot(curve_points[:, 0], curve_points[:, 1], 'r-', label='Detected curve') plt.scatter(sampled_curve[:, 0], sampled_curve[:, 1], color='blue', label='Sampled points') plt.legend() plt.show() np.savetxt('sampled_curve.csv', sampled_curve, delimiter=',', header='distance,altitude', comments='')
当前运行结果

补充测试图(标记较少的赛道剖面)

优化方案(摆脱颜色依赖)
核心思路:基于形状与边缘特征识别
赛道剖面曲线的本质是横向延伸的连续线条,且是图中最具趋势性的线条,我们可以通过以下步骤实现不依赖颜色的识别:
步骤1:预处理图像,突出边缘
跳过颜色筛选,直接转灰度图后做降噪与边缘增强,适配不同对比度的图像:
# 读取图像并转灰度 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 高斯模糊降噪,避免Canny误识别细碎噪声 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Canny边缘检测,可根据图像调整阈值 edges = cv2.Canny(blurred, 30, 100)
步骤2:提取最长连续轮廓
赛道曲线是图中最长的连续线条,通过筛选最长轮廓定位目标:
contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 按轮廓长度排序,取最长的那个(即赛道曲线) contours = sorted(contours, key=lambda x: cv2.arcLength(x, False), reverse=True) curve_contour = contours[0] curve_points = curve_contour.squeeze() # 按x坐标排序,保证点的顺序从左到右 curve_points = curve_points[np.argsort(curve_points[:, 0])]
步骤3:优化采样逻辑(过滤异常点)
保留原采样逻辑的同时,加入滑动窗口过滤偏离整体趋势的孤立点:
def sample_curve(points, step): # 滑动窗口过滤异常点 window_size = 5 if len(points) < window_size: y_rolling = points[:,1] else: y_rolling = np.convolve(points[:,1], np.ones(window_size)/window_size, mode='same') filtered_points = points[np.abs(points[:,1] - y_rolling) < 10] # 阈值可根据图像调整 x_values = filtered_points[:,0] sampled_points = [] for x in range(x_values.min(), x_values.max(), step): interval_points = filtered_points[(x_values >= x) & (x_values < x + step)] if interval_points.size: avg_y = interval_points[:,1].mean() sampled_points.append([x, avg_y]) return np.array(sampled_points)
步骤4:适配不同图像的通用技巧
- 移除网格线:对于带网格的图,用霍夫直线检测识别并移除横向网格线,避免干扰曲线提取
lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=50, minLineLength=100, maxLineGap=10) if lines is not None: for line in lines: x1,y1,x2,y2 = line[0] # 判断是否为横向直线(斜率接近0) if abs(y2-y1) < 5: cv2.line(edges, (x1,y1), (x2,y2), (0,0,0), 2)
- 低对比度图像处理:用自适应阈值二值化替代普通边缘检测,增强曲线与背景的区分度
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
完整优化代码
import cv2 import numpy as np import matplotlib.pyplot as plt image_path = r"image" image = cv2.imread(image_path) # 预处理:灰度+模糊+边缘检测 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) edges = cv2.Canny(blurred, 30, 100) # 移除横向网格线(可选,根据图像情况调整) lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=50, minLineLength=100, maxLineGap=10) if lines is not None: for line in lines: x1,y1,x2,y2 = line[0] if abs(y2-y1) < 5: cv2.line(edges, (x1,y1), (x2,y2), (0,0,0), 2) # 提取最长轮廓 contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) contours = sorted(contours, key=lambda x: cv2.arcLength(x, False), reverse=True) curve_contour = contours[0] curve_points = curve_contour.squeeze() curve_points = curve_points[np.argsort(curve_points[:, 0])] # 优化采样函数:过滤异常点 def sample_curve(points, step): window_size = 5 if len(points) < window_size: y_rolling = points[:,1] else: y_rolling = np.convolve(points[:,1], np.ones(window_size)/window_size, mode='same') filtered_points = points[np.abs(points[:,1] - y_rolling) < 10] x_values = filtered_points[:,0] sampled_points = [] for x in range(x_values.min(), x_values.max(), step): interval_points = filtered_points[(x_values >= x) & (x_values < x + step)] if interval_points.size: avg_y = interval_points[:,1].mean() sampled_points.append([x, avg_y]) return np.array(sampled_points) # 采样并保存 step_frequency = 5 sampled_curve = sample_curve(curve_points, step_frequency) # 可视化 plt.figure(figsize=(15, 7)) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.plot(curve_points[:, 0], curve_points[:, 1], 'r-', label='Detected curve') plt.scatter(sampled_curve[:, 0], sampled_curve[:, 1], color='blue', label='Sampled points') plt.legend() plt.show() np.savetxt('sampled_curve.csv', sampled_curve, delimiter=',', header='distance,altitude', comments='')
内容的提问来源于stack exchange,提问作者Márton Horváth
相关产品推荐
相关产品推荐

