如何从赛道黑白图像提取1像素宽坐标并生成有序数据集
从黑白赛道图像生成单像素连续坐标数据集并按赛道走向排序
一、提取单像素连续赛道坐标
你之前用ImageMagick提取所有黑色像素的方法,本质是获取了赛道的区域像素,而非单像素线条。要得到连续的单像素坐标,核心是对赛道进行骨架提取(细化),把宽赛道压缩成单像素的连续线条。推荐用Python+OpenCV实现,可控性更强:
- 图像预处理:
- 读取灰度图,通过二值化区分赛道(黑色)和背景(白色),反转后让赛道变为白色像素便于处理。
- 用形态学操作清理噪声:开运算去掉背景小噪点,闭运算填补赛道的细微缺口,保证赛道连续。
- 骨架提取:
- 用OpenCV的
cv2.ximgproc.thinning()函数直接生成单像素骨架,这一步能精准得到赛道的核心线条,避免冗余像素。
- 用OpenCV的
- 提取坐标:
- 找出骨架上所有白色像素的坐标,注意OpenCV的坐标是(y,x)格式,需要转换成常规的(x,y)。
二、按赛道走向排序坐标
骨架提取后的坐标是无序的,需要按赛道的环形/线性走向排序,这里用贪心路径跟踪算法:
- 选择起点:可以选赛道最左、最右或最上的像素,也可以手动指定一个明显的起点(比如赛道起点)。
- 路径跟踪:
- 从起点出发,每次在当前点的8邻域内寻找未被访问过的骨架像素。
- 优先选择与上一步方向连续的像素(通过方向向量点积判断相似度),保证走向的连贯性。
- 如果遇到分支或断点,自动寻找最近的未访问像素,确保覆盖整个赛道。
- 闭环处理:如果是环形赛道,最终会回到起点,确保所有像素都被访问。
完整代码实现
import cv2 import numpy as np # 读取图像并预处理 img = cv2.imread("O0HQM.png", cv2.IMREAD_GRAYSCALE) # 二值化:反转后赛道为白色,背景为黑色 _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # 形态学清理:去噪+补缺口 kernel = np.ones((3,3), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) # 提取单像素骨架 skeleton = cv2.ximgproc.thinning(binary) # 转换为(x,y)坐标列表 coords = np.argwhere(skeleton == 255) coords = [(x, y) for y, x in coords] coords_set = set(coords) # 用于快速查找 # 按赛道走向排序坐标 def sort_raceline(coords): # 选起点:最左侧的像素(可根据需求调整) start = min(coords, key=lambda p: p[0]) current = start visited = {start} sorted_list = [start] # 8邻域方向 neighbors = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] while len(visited) < len(coords): # 找当前点的未访问邻域 candidates = [] for dx, dy in neighbors: neighbor = (current[0]+dx, current[1]+dy) if neighbor in coords_set and neighbor not in visited: candidates.append(neighbor) if not candidates: # 处理断点,找最近的未访问像素 unvisited = [p for p in coords if p not in visited] current = min(unvisited, key=lambda p: np.linalg.norm(np.array(p)-np.array(current))) visited.add(current) sorted_list.append(current) continue # 优先选择方向连续的像素 if len(sorted_list) > 1: prev_point = sorted_list[-2] prev_dir = (current[0]-prev_point[0], current[1]-prev_point[1]) # 计算方向相似度(点积越大越接近) def get_similarity(pt): curr_dir = (pt[0]-current[0], pt[1]-current[1]) return prev_dir[0]*curr_dir[0] + prev_dir[1]*curr_dir[1] current = max(candidates, key=get_similarity) else: # 第一步任选邻域点 current = candidates[0] visited.add(current) sorted_list.append(current) return sorted_list # 执行排序并保存为CSV sorted_coords = sort_raceline(coords) with open("raceline.csv", "w") as f: f.write("x,y\n") for x, y in sorted_coords: f.write(f"{x},{y}\n")
注意事项
- 二值化阈值(
127)可根据图像实际明暗调整,确保赛道和背景完全分离。 - 形态学核的大小(
(3,3))可根据赛道宽度调整,宽赛道可适当放大核尺寸。 - 如果赛道有分支,可手动指定起点或修改排序逻辑,优先跟踪主赛道。
内容的提问来源于stack exchange,提问作者jenototh
相关产品推荐
相关产品推荐

