ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

道路机器人路面标志识别:VOC格式数据集制作与YOLO训练实战

道路机器人路面标志识别:VOC格式数据集制作与YOLO训练实战 简介这套道路机器人交通标志识别数据集采用VOC标注格式面向自动驾驶、机器人导航及计算机视觉学习者覆盖交通灯、马路、左右转、黄线、人行道、机器人等路面导航标志可用于目标检测、实例分割或语义分类等任务的训练与评估。包内共816个文件其中407个xml标注文件与407个jpg原图一一对应xml包含目标类别与边界框坐标jpg为真实道路视频抽帧画面另附2个txt说明文件便于理解标注规范压缩包仅5.75MB轻量易上手。数据集来源于连续行车场景标注内容贴近实际环境能有效检验模型对光照变化、道路标线及交通设施的感知能力学习者既可直接拆分训练集与验证集也可替换检测类别进行迁移学习。目前已有836人学习下载对需要补充路面导航训练样本的研究者或开发者而言是一份实用且标注规范的入门级数据集。1. 道路机器人的路面导航标志识别为什么说VOC格式是这条路的起点一台园区巡检机器人或配送机器人要在有行人、有自行车、有同行机器人的路面上正常走它必须回答几个问题前方红灯能不能过路面黄线要不要跟着走路口有没有人行道和左右转箭头迎面过来的到底是人还是另一台机器人。这些问题都落在一个共同的技术底座上——目标检测。而目标检测的训练数据最透明、最不挑工具的数据格式就是VOC格式。我不打算在这里展开一堆概念先讲清楚最常用的落地路径从采集路面图像、按VOC格式标注到转YOLO训练再到踩坑排查。适合谁看手里有路面视觉数据、正准备自己标数据集做识别的工程师或者想评估这个方向能不能投入的团队。2. 路面标志识别的VOC数据集怎么做目录约定与类别定义2.1 VOC格式的目录约定和一张标注XML的核心字段VOC格式本身不是算法它是存放检测数据集的通用约定。常见做法是放在一个VOCdevkit目录树下VOCdevkit/ └── VOC2007/ ├── JPEGImages/ # 原始图像 ├── Annotations/ # 每张图像对应一个XML标注 └── ImageSets/ └── Main/ # train.txt / val.txt / trainval.txt每个XML的内容才是关键。以一张1080p路面图像为例它的标注文件核心字段长这样annotation folderJPEGImages/folder filenameroad_clip_001_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nametraffic_light_green/name bndbox xmin1420/xmin ymin260/ymin xmax1480/xmax ymax420/ymax /bndbox /object object nameyellow_line/name bndbox xmin200/xmin ymin850/ymin xmax1700/xmax ymax880/ymax /bndbox /object /annotationsize里的width和height必须和实际图像一致很多转换脚本会用这两个值做归一化填错后期会出现大量负坐标。object字段里的name是目标类别名bndbox写矩形框的左上角和右下角。VOC格式相当直白一张图一个XML每个目标一个object没有多余层级。这也是为什么几乎所有检测框架都留了VOC转换入口把VOC当中间层是成本最低的选择。提示ImageSets/Main下还要有train.txt、val.txt和trainval.txt每行一个不带.jpg后缀的文件名。后续转YOLO时的数据划分从这些txt里读不是从Annotations目录里扫。2.2 路面标志的类别定义从交通灯到机器人的一套可执行方案标题里点名了六类目标交通灯、马路、左右转、黄线、人行道、机器人。落到VOC格式时这个清单不能直接当成类别名要做几轮拆解。我的做法是先列一张类别决策表所有标注员、训练脚本、评估报告都用同一份目标对象推荐类别名标注意见交通灯traffic_light_red / traffic_light_green / traffic_light_yellow按灯色拆类导航决策依赖颜色本质上是轻量的语义识别马路road_area前方可行驶区域整条马路不适合bbox只标车前近处有效区域或改用分割左右转turn_left / turn_right / turn_straight以机器人行驶方向为基准不能按图里看到的视角定黄线yellow_line避免一条线标成超长框按固定长度分块人行道crosswalk斑马线区域分段标注别把路口大片路面圈进来机器人robot其他道路机器人动态目标单列一个类交通灯按灯色拆类这里多说一句。有的项目图省事只标一个traffic_light类训练完才发现模型知道哪里有灯但不知道灯是什么颜色下游还要再接分类器去抠图识别多一环就多一个误差源。路面机器人要的最终信号是“能不能过路口”所以按红、绿、黄三色拆类模型一步到位输出灯色导航决策直接可消费。马路这个类别是VOC格式的一个边界问题。马路在图像里通常是一整块或长条形用检测框框出来会带进大量背景像素模型学不到稳定特征。我一般两条路选一条要么只标“前方可行驶区域”约定好近处截断线要么把车道边界、路沿单独作为line类处理跟黄线一个思路。后者更贴近导航的实际需要。黄线的标法直接影响模型能不能用。一条黄线从画面底部一直延伸到远处整体标一个bbox模型要么学成“画面下方正中有一大片黄”要么把远处碎线全漏掉。常见做法是每隔1到1.5米截一段每段一个小框段与段允许少量重叠模型学会的是局部线特征而不是一个宽高比异常的大框。机器人这一类的边界要定义清楚。园区混行场景里行人和机器人的外观差异远距离时并不明显。如果训练数据里经常出现行人建议额外加一个person类否则模型为了凑robot的AP把行人框进来演示时很漂亮真跑起来就是安全事故。2.3 图像采集阶段就要为VOC留好余量很多项目在采集环节图省事用一段白天的视频就开工结果夜间、逆光、雨天的性能全是窟窿。路面标志和车牌识别这类任务一样模型能学到多少与采集覆盖面强相关。我一般要求采集时覆盖三个变量时段白天、黄昏、夜晚、天气晴、阴、雨后反光、视角平视为主允许轻微俯仰。分辨率尽量不低于1920x1080远处转向箭头和交通灯只有十几个像素分辨率降到640标注框小到下采样后几乎消失。采集回来的原始视频不要抽帧后全部保留。先做一次初筛运动模糊、过曝、被遮挡超过一半的帧删掉或标记成difficult。可以用OpenCV对每帧做亮度直方图统计按白天、黄昏、夜间粗略分桶避免训练集里夜间样本只有几个却不自知。注意VOC的difficult字段在转YOLO时通常会被丢掉所以确定某张图不适合训练时直接不进入train.txt比标记difficult更可靠。3. 从图片到VOC再转YOLO标注工具、场景划分与转换脚本3.1 标注工具怎么选CVAT批量标LabelImg精修VOC标注工具有不少我的选择是CVAT做初标LabelImg做精修。CVAT可以本地部署对视频帧序列支持track模式给第一帧框好后面几十帧自动插值路面连续场景能省掉一大半重复劳动。LabelImg是老牌工具界面轻量、保存加载都很快适合对CVAT自动插值出现的漂移框做逐帧修正。不管用哪个工具导出时都要固定选择VOC格式。CVAT导出格式里选Pascal VOC它会自动生成JPEGImages、Annotations和ImageSets/Main结构。有个细节CVAT导出VOC时类别名在object里按创建顺序编号但那个编号只是标注顺序不代表类别ID顺序后面转YOLO时必须以自己维护的classes.txt为准不能直接信导出文件里的隐含顺序。3.2 数据划分按场景分别按文件随机分路面图像是从连续视频抽帧出来的相邻帧几乎一模一样。直接对整个文件夹做随机划分同一个路口的同一段视频帧会同时出现在train和val里模型等于先看答案再考试mAP虚高跑到新路线上立刻现原形。正确做法是按场景分组同一视频片段、同一个路口的图像必须分到一起然后以场景为单位划分训练集和验证集。下面这个脚本从文件名解析出片段ID按片段ID分配数据import os import random from collections import defaultdict trainval_dir VOCdevkit/VOC2007/ImageSets/Main def parse_clip_id(filename): # 约定文件名格式: clip_id_frame.jpg例如 road_07_0001.jpg - road_07 parts filename.split(_) return _.join(parts[:-1]) images [f for f in os.listdir(VOCdevkit/VOC2007/JPEGImages) if f.endswith(.jpg)] clips defaultdict(list) for img in images: clips[parse_clip_id(img)].append(img) clip_ids list(clips.keys()) random.shuffle(clip_ids) split int(len(clip_ids) * 0.8) train_clip_ids set(clip_ids[:split]) val_clip_ids set(clip_ids[split:]) def write_set(path, filenames): with open(path, w) as f: f.writelines(name.replace(.jpg, ) \n for name in sorted(filenames)) train_files [img for cid in train_clip_ids for img in clips[cid]] val_files [img for cid in val_clip_ids for img in clips[cid]] write_set(os.path.join(trainval_dir, trainval.txt), train_files) write_set(os.path.join(trainval_dir, train.txt), train_files) write_set(os.path.join(trainval_dir, val.txt), val_files)parse_clip_id取文件名里去掉最后一段帧号的部分。如果命名不带片段ID采集时就按“场景文件夹/视频名/帧号”组织或者让CVAT用task id做前缀。trainval.txt是完整集合train.txt和val.txt是训练、验证子集YOLO转换时主要读后两个。提示划分比例按场景数来不要按图像数来。如果只有10个场景视频分8/2没问题如果某个场景特别长验证集图片占比会偏高这是正常的关键是确保验证集里没有训练场景的镜头。3.3 VOC转YOLO转换脚本与四个边界处理VOC格式不能直接送进YOLO训练。YOLO要的是每张图一个txt文件每行表示一个目标class_id cx cy w h四个值都归一化到0到1。转换脚本的核心是把XML里的bndbox转成中心点加宽高表示import os import xml.etree.ElementTree as ET CLASSES [ traffic_light_red, traffic_light_green, traffic_light_yellow, road_area, turn_left, turn_right, turn_straight, yellow_line, crosswalk, robot, person ] def convert_voc_xml_to_yolo(xml_path, out_dir, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.findtext(name).strip() if name not in CLASSES: continue bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 越界裁剪防止负坐标和超宽高进入训练 xmin max(0.0, min(xmin, img_width - 1)) xmax max(0.0, min(xmax, img_width - 1)) ymin max(0.0, min(ymin, img_height - 1)) ymax max(0.0, min(ymax, img_height - 1)) if xmax - xmin 1 or ymax - ymin 1: continue cx (xmin xmax) / 2.0 / img_width cy (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height class_id CLASSES.index(name) lines.append(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines))这段脚本处理了四个容易出问题的点。第一越界裁剪标注员手滑拖出画面边缘很常见不裁剪会产出负坐标或归一化宽度超过1的值训练时轻则警告重则loss变NaN。第二过滤掉宽或高小于一个像素的无效框。第三class_id用的是CLASSES列表索引顺序这个顺序必须和训练时data yaml里的names顺序完全一致。第四w和h是归一化值模型输出也是这个单位不要手工加像素单位。转换完别急着训练先抽几张图做可视化验证。把txt读回来画在原图上确认框位置和类别对应正确。这一步只花几分钟能拦住一大半标注问题。4. 用YOLO在VOC转出的数据集上跑通路面识别训练命令与必调参数4.1 数据配置和最小训练命令YOLO系列对VOC的支持很成熟。YOLOv5自带voc.yaml模板YOLOv8以上版本需要自己写一个data yaml。以路面箭头、远处交通灯这类密集小目标场景为主我用的配置模板是这样path: /data/road_signs train: images/train val: images/val names: 0: traffic_light_red 1: traffic_light_green 2: traffic_light_yellow 3: road_area 4: turn_left 5: turn_right 6: turn_straight 7: yellow_line 8: crosswalk 9: robot 10: personpath写数据集根目录train和val是相对路径。YOLO训练时从图像同名txt读标注所以images/train目录下每张jpg必须有一个同名txt对应上一章转换脚本的产物。目录结构通常是/data/road_signs/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── road_signs.yaml训练命令我一般从YOLOv8起步硬件不够再降级到YOLOv5yolo detect train \ dataroad_signs.yaml \ modelyolov8s.pt \ epochs200 \ imgsz1280 \ batch16 \ patience30 \ projectroad_model \ nameexp_rgb参数逻辑不是随便填的。model选了yolov8s而不是n路面标志里箭头和黄线都是小目标n的参数量在这种细粒度任务上容易欠拟合s是性价比平衡点。imgsz设1280而不是默认640因为很多输入图像是1080p箭头宽度经常只有十几个像素640下采样后特征几乎消失。batch16对应显存限制如果只有单张12G卡imgsz1280时batch降到8或设成auto让程序自动按显存算。epochs设200配patience30验证指标30轮不涨就提前停能省不少时间。4.2 路面识别的参数要克制增强别过猛推理阈值要重调路面标志识别和车牌识别有共性目标细长、远距离、小尺寸。通用目标检测的部分默认参数在这里反而不合适。YOLO默认开启的mosaic增强会把四张图拼在一起再随机裁剪对正常路面图而言拼图会把黄线截断成几段模型学到的线特征变了形。我的做法是把mosaic降到0.5或直接关掉。其他增强按路面场景调hsv_h和hsv_s对交通灯颜色识别影响不大可保持默认甚至加大一点flip_lr要谨慎左右转箭头水平翻转后类别语义完全反转如果数据集开了flip标签必须同步翻转否则模型会学到“箭头方向无所谓”。scale增强建议在0.5到1.5之间过大的缩放会把远处小目标缩成噪声。锚框方面YOLOv5之后的自动锚框会按数据集重新计算。如果训练日志里看到anchors完全没动大概率是数据集太小或目标尺寸分布单一。黄线和人行道的框纵横比往往异常大可以单独统计所有框的平均宽高比必要时手动指定锚框。推理阶段的conf_thres和iou_thres也要单独说。路面场景里黄线分段多、目标之间距离近默认0.45的iou_thres很容易把相邻两段黄线框合并成一个线段连续性表现很差。我一般把iou_thres降到0.35conf_thres设0.25起步夜间场景再降低一点。4.3 验证阶段mAP不是唯一标准视觉识别效果要看混淆矩阵和失败图训练结束后val目录里会生成混淆矩阵和PR曲线。路面识别的视觉识别效果我建议重点关注三件事。第一交通灯红黄绿三类之间的混淆黄色和红色在过曝场景下边界模糊混淆矩阵里红黄互相串说明采集数据里两者亮度重叠太多。第二robot和person的混淆这是安全等级最高的错误宁可召回低也不要让模型把行人框成robot。第三yellow_line在远距离的表现对应PR曲线里yellow_line的召回率明显低于其他类大概率是标注分段不规范。把所有val里置信度最低的50张图拉出来看一遍比盯着整体mAP有用得多。很多时候mAP上了0.85翻图才发现漏检全集中在逆光和夜间场景。失败样本的价值在于指导补采数据而不是继续调参。调参多少带点玄学成分建议每次只改一个变量多变量同时改动后出了问题根本定位不到原因。5. 路面识别避坑记录从VOC标注到训练的五个高频翻车点5.1 标签名不统一导致类别错乱现象训练启动时报错class not found或者classes清单里出现两个相似名字模型的类别数比预期多。原因标注阶段多个人协作有人写traffic_light_green有人写Green还有人的标注文件里名字带空格。VOC格式没有机制约束标签名转换脚本遇到未登记的name往往直接跳过或当成新类。解决标注前统一发一份类别清单CVAT里设置label列表让标注员只能选转换前加一道扫描校验把Annotations下所有XML的name字段收集统计和CLASSES列表比对多一个少一个都中止转换。用shell命令就能做grep name VOCdevkit/VOC2007/Annotations/*.xml | sed s/.*name\(.*\)\/name.*/\1/ | sort | uniq -c输出里有任何不在类别清单里的词先回头修标注别指望转换脚本过滤过滤掉的框等于白标。5.2 bbox越界让loss直接变NaN现象训练前几个epoch正常中途某一步loss突然变成NaN后面回不来。原因标注框有一部分伸出图像边界转换脚本没裁剪时生成了cx或w大于1的值模型回归预测的目标异常梯度爆炸。解决转换脚本里加裁剪逻辑对应第3章的写法。训练前再跑一个检查读取每个txt坐标小于0或大于1就报文件名。还有一种隐蔽情况XML里写的是1920x1080实际图像是1600x900size字段和图像不一致归一化坐标全偏同样会炸图像统一后务必全量检查size字段。5.3 黄线分段不统一召回率上不去现象验证集里黄线检测断断续续近处粗线段有框远处细线段全部漏检。原因标注员A把一条黄线标成一个大框标注员B每2米切一段模型对“黄线”的尺寸分布学得非常混乱同一张图同一类目标宽高比从1:1到1:20都有检测头很难兼顾。解决标注规范写明“黄线标注单元长度1到1.5米相邻框重叠不超过20%”同时把黄线类别的标注框宽度统计打印出来超过设定阈值就重新审查。这属于标注质量管控问题调anchor只能缓解根治还得回到规范上。5.4 左右转箭头方向标反现象val的混淆矩阵里turn_left和turn_right互相串数值还特别均匀。原因标注员以“自己在图里看到的箭头朝向”为判断依据没有统一基准方向。路面箭头在图像里有时斜着、有时从远处看是折断的不同人对“朝左”的理解可能完全相反。解决规范里明确“以机器人行驶方向为基准箭头朝机器人左侧为turn_left”标注页面上贴正例图。标注完成后抽20%的框做二次复检重点看转向类。方向识别错了导航决策直接反着走比漏检更危险。5.5 白天训练顺夜间一测就翻车现象白天数据集上mAP超过0.8夜间场景里交通灯漏检、路面标志基本看不见。原因训练图像全是上午拍摄交通灯的亮度、周围环境光、灯罩反光形态与夜间完全不同模型学到的是白天光照下的颜色和形状组合。解决采集阶段把夜间、黄昏独立成子集训练配置里用光度类增强模拟光照变化或者夜间单独训练一个模型做场景切换。我倾向于在数据集层面解决增强模拟出的夜间形态和真实夜间还是有差距。先把夜间子集标好加进训练集再考虑增强参数。6. 进阶用VOC的干净结构搭一个持续迭代的标注闭环当你有了几百张VOC标注、模型能跑出可用精度时最值得做的不是堆epochs而是把整个流程转成一个“模型出初标、人工只改错、增量进训练”的闭环。我的做法是让已训练模型跑一遍新增的未标注片段用高置信度预测直接生成VOC格式的待确认XML导入CVAT后人工只修正错框和漏框确认后再走一次转换脚本进入下一轮训练。VOC格式的价值在这里就体现出来了——它足够简单标注工具、转换脚本、训练框架都能直接吃不需要为某个私有格式写适配。一个小技巧在转换脚本里保留difficult字段的读取逻辑把模型预测置信度在0.3到0.5之间、人工还没确认的框写成difficult1单独存放。增量训练时这些框参与损失计算但不会因为人工漏改一个框就拉偏模型人工确认后再去掉difficult正式进入训练集。这不算什么半监督高深方案但配合VOC字段做起来很顺手几乎不需要额外代码。另一个习惯是每轮训练后把失败样本按类别归档比如单独建night_failures文件夹放模型在夜间漏检的帧下一轮训练前抽出来作为必训样本放回训练集。这比随机加数据更稳能保证每个已知薄弱点都被覆盖。我自己栽过的跟头是早期把精力全放在调mAP上忘记留时间为失败样本做回归验证结果每次精度提升只在旧场景里成立新场景一测就露馅。后来改成“每轮训练完强制看50张失败图并归档”模型的可用性反而比mAP数字涨得更快。路面导航标志识别这个方向VOC格式只是一块垫脚石真正决定上限的是数据覆盖度、标注一致性和迭代闭环这三件事。把这三件做好模型水平和维护成本都会舒服很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表