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

如何用OpenCV提取赛道海拔曲线并获取坐标?求非颜色依赖方案

赛道剖面曲线数据提取:摆脱颜色依赖的优化方案

问题背景

我正专注于优化不同运动项目的配速策略,需要获取赛道的距离与海拔变化信息。希望通过OpenCV从下载的赛道剖面示意图(PNG格式)中自动提取数据,替代耗时的手动处理方案。

参考相关问题后,我尝试识别剖面曲线,按像素步长采样并保存为CSV,但作为OpenCV新手,实现效果不佳,且方法高度依赖颜色,更换不同样式的图像就需要修改代码。求不依赖颜色的更优方案。

测试用赛道剖面示例图

Course profile

我的实现代码

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='')

当前运行结果

Result

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

Profile 2


优化方案(摆脱颜色依赖)

核心思路:基于形状与边缘特征识别

赛道剖面曲线的本质是横向延伸的连续线条,且是图中最具趋势性的线条,我们可以通过以下步骤实现不依赖颜色的识别:

步骤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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:05:57