
简介面向道路机器人视觉感知任务提供一份基于YOLOv11格式的路面导航标志标注数据集适合自动驾驶、移动机器人、目标检测方向的学习者与开发者在模型训练、验证及算法效果对比阶段使用。数据覆盖交通灯、马路、左右转、黄线、人行道、机器人等多类常见路标图片源自路口视频帧的截取场景贴近实际行驶环境可直接用于监督学习。压缩包共815个文件其中jpg图片与txt标注文件各407个保持一一对应关系txt标注内容采用标准YOLO格式记录目标类别与归一化坐标另含1个yaml配置文件用于声明类别信息整体大小仅5.52MB便于快速下载和本地部署。已有746人学习使用。借助这部分已标注数据可免去自行采集与标注的繁琐直接开展YOLOv11模型的训练与超参数调优适合课程设计、毕业设计或路面导航识别项目的预训练数据准备也便于按需拆分训练集与验证集。1. 路面导航标志识别不是“多一个模型”而是道路机器人的视觉底线一台轮式机器人要自己穿过园区、街道或厂区它首先要搞清楚三件事前面能不能走、往哪拐、现在该不该停。这三件事对应的就是交通灯、左右转标志、黄线、人行道和路面边界。这些对象密集、尺寸小、视角低正好是通用检测模型最不擅长的场景。YOLOv11 是 Ultralytics 在 YOLOv8 之后推出的检测系列backbone 和 C3k2 模块的改动让它在小目标上比前代更稳训练和部署流程又完全兼容旧习惯所以很多做道路机器人的团队直接把它当作视觉导航的基线模型。这篇文章按我实际做过的路径来写从类别定义、数据标注、YOLO 格式转换到训练参数、推理输出再到翻车复盘的避坑点和小目标优化适合准备自己标注数据、从头训练 YOLOv11 模型的工程师照着复现。2. 把路面导航标志拆成检测任务类别定义、数据标注与 YOLO 格式转换2.1 七个类目怎么定类别定错后面全是返工标题里列的目标是交通灯、马路、左右转、黄线、人行道、机器人。我第一次做这个项目时直接把“马路”单独建了一个类结果模型怎么训都飘——因为“马路”在图像里经常占掉半个画面检测框巨大而 YOLO 的损失函数对边框回归的尺度很敏感几个大框会把整个 batch 的梯度带走小目标反而学不动。后来我把“马路”拆成了“路面区域”和“车道线”两类路面区域用语义分割的思路去处理YOLOv11 只负责检测车道线。如果你们只做导航我的建议是保留traffic_light、left_turn、right_turn、yellow_line、crosswalk、robot六个检测类路面边界用分割或固定相机标定解决别混进检测任务里。类别映射建议写成常量文件所有脚本共享。类名用英文小写加下划线训练时省得编码问题转格式时也避免中文路径的麻烦。robot这一类是针对“识别路面上的其他机器人”设计的实测中它和行人、自行车、小型电动车会互相干扰后面避坑章我专门讲。如果你是在真实园区跑建议再考虑加一个pedestrian类因为 YOLOv11 的 COCO 预训练权重里虽然有 person但你自己数据里不出现行人模型很容易把行人认成 robot。2.2 标注规范小目标、细长目标和遮挡是三个坎路面标志里最难标的是黄线和人行道。黄线通常是细长条宽可能只有 10 到 20 个像素标成水平矩形会让长宽比极端到 1:20 以上YOLOv11 的 anchor-free 检测头虽然对长宽比容忍度高但回归时仍然容易抖动。我的做法是黄线分段标注每段长度控制在 200 像素以内宁可一段路上标三个框也不要一个超长框。人行道相反它是一整块纹理区域边界模糊标注标准不统一会让模型学得很痛苦。我一般规定人行道只标完整的斑马纹矩形区域不标单条白线这样类别语义稳定。标注工具没有强制要求LabelImg、X-anylabeling 都可以关键是输出格式统一。我习惯直接从 LabelImg 导出的 VOC XML 开始因为它的 bndbox 格式最简单后面转 YOLO 格式时解析逻辑一目了然。真正决定模型上限的不是工具是两类数据比例交通灯样本量至少要占总样本的 25% 以上因为它在画面里最小机器人和左右转标志 10% 以上即可太少就做离线增强补。建议每张图里的目标数量不要超过 15 个太多小目标挤在一起训练初期梯度会被密集小框淹没。2.3 VOC 转 YOLO 格式转换脚本与四个边界坑YOLOv11 训练需要的是 txt 标签每一行是class cx cy w h全部归一化到 0 到 1。下面这个脚本是我一直留着的转换器能直接处理 VOC 格式的 XMLimport xml.etree.ElementTree as ET import os CLASS_MAP { traffic_light: 0, road: 1, left_turn: 2, right_turn: 3, yellow_line: 4, crosswalk: 5, robot: 6, } def convert_voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # YOLO 格式是归一化的中心点加宽高 cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h # 越界或负值的框直接裁剪到 [0, 1]不能直接丢 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_name os.path.basename(xml_path).replace(.xml, .txt) with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) if __name__ __main__: convert_voc_to_yolo(data/annotations/0001.xml, data/labels/)这段逻辑的关键是cx、cy、bw、bh的计算必须除以原图宽高而不是除以某个固定尺寸。很多新手在这里直接写死 640导致训练时标签全部错位模型还能收敛但预测框全部偏移这种翻车最隐蔽。另一个细节是bndbox的子节点在 XML 里有大小写混用的情况脚本里我统一用小写find如果你的标注工具输出的是xMax记得先做一次字段名规整。四个坑按出现频率排序第一XML 里的filename和实际图片名不一致训练时 image 和 label 对不上模型丢样本第二object里有difficult1的样本不处理的话会把难样本强行拉进训练低置信度预测全是这种框第三一张图里有两个完全相同的类名但标注了不同语义比如远车和近车都被标成 robot模型学的是尺寸而不是形状第四转换后用脚本统计一下每个 txt 的行数和 XML 里object数量对不上就说明有解析丢失。数据做完后我会顺手跑一个校验函数把所有 txt 里类别 id 超范围、坐标值越界的行打印出来这一步能省掉后面几个小时的无效训练。3. 用 YOLOv11 训练自己的模型环境配置、模型选型与调参清单3.1 0 基础环境配置从 conda 到跑通第一次验证YOLOv11 的环境配置比很多人想象中简单它不需要自己编译 CUDA 算子Ultralytics 包把前后处理都封装好了。先建一个干净的 conda 环境Python 版本不用追新3.10 就够稳。pip install ultralytics会自动带上 torch但这里有个问题pip 默认装的 torch 是 CPU 版还是 CUDA 版取决于你机器上的 pip 源建议先装 torch 再装 ultralytics顺序反了可能装到 CPU 版训练慢到怀疑人生。conda create -n yolo11 python3.10 -y conda activate yolo11 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install -U ultralytics nvidia-sminvidia-smi是训练前必跑的一条命令确认驱动能看到显卡同时看一下显存大小。如果显示 CUDA 版本和驱动不匹配先更新驱动不要硬调 torch 版本这是最省时间的做法。装完后跑一个最小验证yolo detect predict modelyolo11n.pt sourcehttps://ultralytics.com/images/bus.jpg能输出一张带框的图片环境就算通了。第一次跑会下载权重如果下载慢去 Ultralytics 的 release 页面手动下载 yolo11n.pt 放到当前目录命令行会自动识别本地文件。这一步过了后面训练就是改参数的事不再是环境的事。3.2 模型选型yolo11n、s、m、l 怎么挑YOLOv11 官方提供了 n、s、m、l、x 五个尺度参数量和精度递增。道路机器人是端侧设备我一般只在 n 和 s 之间选m 留给离线分析用。如果你的机器人用的是 Jetson Orin Nano 这类设备yolo11s 在 TensorRT FP16 下能做到 30 到 50 FPS精度比 n 高不少如果用的是树莓派或更低端的板子老老实实选 n然后用第 6 章的小目标优化手段去补精度。模型参数体量精度倾向推理速度适合场景yolo11n最小一般小目标弱最快低算力端侧、实时性优先yolo11s较小中等能扛住常规小目标快大多数道路机器人yolo11m中等较高中等离线分析、高精度验收yolo11l/x大高慢服务器端、不做端侧部署我的经验是第一次先把 yolo11s 跑通拿到 baseline 后再决定要不要降级到 n。很多人上来就选 l训了一晚上发现部署端根本跑不动白白浪费时间。另外注意YOLOv11 的权重文件名是yolo11s.pt不是yolov11s.pt少写一个 v 会导致找不到文件我见过不下三次这种低级翻车。3.3 训练超参数imgsz、epochs、batch 与类权重训练命令用 Ultralytics 的 CLI 就能完成但参数要按任务改不能直接套 COCO 的默认值。路面标志是小目标为主的任务imgsz我会直接干到 1280前提是显存够。imgsz640时交通灯可能只有 8 乘 8 像素特征图上的响应基本是噪声模型只能靠猜提到 1280 后同样的目标有 16 乘 16 以上像素学习难度完全不是一个量级。显存不够就降低 batch也不要轻易降分辨率。yolo detect train \ dataroad_nav.yaml \ modelyolo11s.pt \ imgsz1280 \ epochs150 \ batch8 \ patience30 \ optimizerAdamW \ lr00.001 \ device0dataroad_nav.yaml是数据集配置文件我通常会写成这样path: /home/user/road_nav_dataset train: images/train val: images/val names: 0: traffic_light 1: road 2: left_turn 3: right_turn 4: yellow_line 5: crosswalk 6: robotepochs150是路面标志的合理区间这类任务 100 轮左右基本收敛150 轮留了余量。patience30表示验证集 mAP 连续 30 轮不涨就早停省时间。优化器我推荐AdamW而不是默认的 SGD小数据集上 AdamW 收敛更稳对学习率的敏感度低。如果类别严重不平衡比如 robot 只有几百个框而 traffic_light 有几千个框可以在 yaml 里加class_weights或者在训练命令里用--class_weight balanced让模型对少样本类别给更高的损失权重。训练过程中我只看两个曲线metrics/mAP50-95和train/box_loss。mAP50-95 持续上升说明在学如果 loss 降了但 mAP 不涨大概率是过拟合或数据里有脏标注先回去查数据而不是加训练轮数。4. 让机器人真正“看到”推理脚本、结果标记与结构化输出4.1 加载模型并保存推理结果不只是画框训练完的best.pt就是交付物。推理时我习惯写一个独立脚本不用命令行因为机器人端需要的是结构化结果不是一张画了框的图。下面这段是核心流程同时覆盖了热词里反复出现的“保存推理结果”需求from ultralytics import YOLO import json model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcetest/, imgsz1280, conf0.35, iou0.5, saveTrue, save_txtTrue, save_confTrue, save_cropTrue, line_width3, ) warnings [] for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) if conf 0.5: warnings.append({ class: r.names[cls_id], confidence: conf, xyxy: box.xyxy[0].tolist(), }) with open(nav_output.json, w, encodingutf-8) as f: json.dump(warnings, f, ensure_asciiFalse, indent2)这段代码里最有用的参数是save_txtTrue和save_cropTrue。save_txt会把每个检测框的类别和坐标写成 txt方便事后和标注数据做比对save_crop会把每个检测目标单独裁剪成图片我用来建立难例库每天跑完机器人后翻一遍把误检的 crop 收进训练集这个习惯比任何调参都有效。conf0.35是低阈值因为后面还有一层逻辑把置信度大于 0.5 的框作为强警告两个阈值分开设避免一次调太狠把真目标漏掉。box.xyxy[0].tolist()转成浮点列表是为了 JSON 序列化机器人控制端拿到这个结构后不需要知道模型细节只需要读class和confidence字段就能做决策。我的经验是输出里务必带上xyxy原图坐标因为后续做目标跟踪或避障时需要把像素坐标换算到相机系或机器人坐标系没有原图坐标就只能重新推理浪费算力。4.2 导航决策的阈值策略导航指令来自多帧投票不来自单帧道路机器人和普通视觉检测最大的不同是“误检代价不对称”把红灯看成绿灯会出安全事故把绿灯看成红灯只会让机器人停一下。所以我在控制端做了一层时间维度的投票连续 3 帧都检测到traffic_light且置信度高于 0.6才认为当前路口有灯要切换红灯状态需要连续 2 帧置信度高于 0.7。这个逻辑写在机器人主程序里不写在模型里模型永远只做单帧检测决策交由状态机处理。左右转的判定同理。left_turn和right_turn标志在远距离时外观高度相似单帧很容易混淆。我一般要求机器人到路口前 5 米内才开始读取转向标志同时结合全局地图的预设方向做交叉验证。模型只负责给出“我看到了一个转向标志”和对应置信度到底转不转由路径规划模块说了算。把决策权和感知权分开是这类项目能落地的关键。4.3 端侧部署的取舍ONNX 导出与帧率预算如果机器人主控是 x86 工控机直接用 PyTorch 推理没问题但如果是 Jetson 或 ARM 板我建议导出 ONNX 后用 TensorRT 加速。导出命令很简单yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz1280 dynamicFalse导出后先用onnxruntime测一遍输入输出的 shape确认和训练时的预处理一致。这里有个坑训练时如果用了imgsz1280导出时也必须是 1280否则模型会 silently 自动 resize精度掉一截还不报错。TensorRT 构建 engine 时建议开 FP16路面标志检测对精度要求高FP8 在当前硬件上容易翻车不值得为那 20% 的速度提升牺牲红灯检测的可靠性。帧率预算要提前算假设机器人运动速度是 1.5m/s相机帧率 15 FPS那么每帧之间移动 0.1 米。检测延迟 200ms 意味着 0.3 米的控制滞后在路口场景这个距离可能直接压到人行道上。所以与其堆帧率不如把决策频率降下来让模型跑 10 FPS控制端用多帧投票保证稳定性这是我觉得性价比最高的部署方案。5. 路面标志识别避坑指南5 个翻车现场的复盘5.1 黄线永远漏检Mosaic 增强把细长目标切没了现象训练时 loss 正常下降但验证集的yellow_line类别召回率长期在 20% 以下可视化结果里黄线框基本没出过。原因Ultralytics 默认开启 Mosaic 增强每张训练图由 4 张图拼接而成。细长的黄线在拼接过程中被裁断、缩放到只剩几个像素正样本形同虚设。模型根本没在训练里见过完整的黄色车道线自然学不出来。解决训练前 30 个 epoch 关闭 Mosaic让模型先见完整的细长目标训练后期再打开 Mosaic 提升泛化性。CLI 里加mosaic0.0即可也可以把复制粘贴增强开到 0.5把少数黄线样本复制到其他图上多露几次脸。我这么改之后黄线召回率从 20% 涨到 75% 以上这个参数几乎成了同类任务的必修课。5.2 左右转标志互相混淆类别语义太像分辨率吃不住现象left_turn和right_turn的混淆矩阵里互相误判率超过 30%远距离几乎全错。原因两个类别在 1280 分辨率下也只有 30 乘 30 像素左右箭头方向的特征在特征图里只剩高维语义模糊区。加上标注时有些图里左转箭头被阴影遮了一半标注员自己都判断错了模型学到的是错误的箭头方向。解决三管齐下。第一把转向标志的标注框收紧只标箭头区域不标整个牌子减少背景干扰第二增加旋转增强degrees10让箭头在不同倾角下都能被识别第三如果场景里转向标志固定出现在路口附近可以把远处检测阈值提高只信任近距离的检测结果。把这两个类的样本数提到各 1000 张以上混淆率能压到 10% 以内。5.3 夜间路灯被当成交通灯训练集只有白天模型学会了“亮的东西”现象夜间测试时模型把一排路灯全部标成traffic_light甚至把反光的玻璃幕墙也框出来。原因数据集里 95% 是白天样本交通灯在白天和路灯外观差异明显但到了晚上交通灯亮起来路灯也亮起来模型学的亮度特征直接失效。解决收集夜间真实数据是最有效的没有捷径。如果暂时补不了数据先用图像增强模拟hsv_h0.1, hsv_s0.5, hsv_v0.4拉大色相和饱和度变化再配合gamma校正模拟暗光。但注意这些只是缓解最终还是要落地采集。我吃过这个亏之后每条测试路线都要求白天、傍晚、夜间各采集三遍数据采集成本高但相比模型上线后夜间接连误报导致的停驶这才叫省钱。5.4 远处的小汽车被识别成 robot正样本太少特征没学够现象模型把远处的两厢轿车和三轮车都标成robot置信度还不低。原因robot类别只有 200 多个样本而且都是近处、完整、背景干净的机器人。YOLOv11 在小样本类别上会退化成按轮廓匹配而两厢车的轮廓和机器人确实相似。解决先把 robot 样本扩到 800 个以上角度覆盖前后左右四个方向距离覆盖 3 米到 20 米。如果采集不到就从机器人本身的外观入手很多园区机器人的顶部有反光标识或特定色块标注时把这些特征标注进去模型就把注意力从“轮廓”转移到“特征”上。还有一个土办法在机器人外壳加一个高对比度的矩形 ARUCO 码单独作为robot_marker类训练检测鲁棒性远超纯外观识别。5.5 验证集 mAP 波动大模型像在“薛定谔的收敛”现象训练最后 30 轮 mAP 曲线大幅震荡上一轮 0.72下一轮 0.61再下一轮又回到 0.70说不清模型到底好没好。原因验证集太小只有 100 张图且每张图的场景差异巨大某一轮随机增强产生的数据分布变动就足以让 mAP 剧烈波动。另外训练时没有固定随机种子前后两轮实验的验证结果不可比。解决验证集固定到 300 张以上并按场景分层抽样确保白天、夜间、晴天、雨天都有。训练命令里加seed42保证实验可复现。如果验证集实在扩不了就接受波动以训练结束前 20 轮的 mAP 均值作为模型评估指标不要盯着单轮看。这是典型的“数据不足导致指标虚高或虚低”的案例先修数据再修模型。6. 小目标优化与验证方法把模型从“能跑”逼到“能上路”6.1 小目标优化的 3 个实用手段路面标志检测的核心矛盾就是目标小。除了把imgsz拉到 1280我常用的三个手段是切图推理、多尺度训练、注意力增强。切图推理是把 1280 的原图切成四个 640 的块分别检测再合并结果相当于用算力换精度适合离线分析。多尺度训练不需要改代码Ultralytics 的mosaic和scale参数本身就带多尺度把scale0.5调到scale0.9就能让模型看到更多尺寸的目标。注意力增强我自己试过给 backbone 加 hcanet 的通道注意力模块对小目标有提升但改动模型结构后部署时要跟着改导出逻辑建议在 baseline 稳定后再做不要一上来就改结构。6.2 验证不是看一张图混淆矩阵和 PR 曲线才是验收标准每次训完模型我会把验证集跑一遍完整评估重点不是总 mAP而是每个类别的召回率。交通灯漏检是安全事故robot 误检是导航干扰这两个类别的 PR 曲线我单独画出来看。Ultralytics 训练完会在runs/detect/train/下自动生成confusion_matrix.png和PR_curve.png不需要额外写代码。我只看三件事confusion matrix 的正对角线是否够亮、左右转之间的互相误判是否低于 10%、交通灯的召回率在置信度 0.5 时是否高于 90%。三条都满足再谈部署否则继续回数据循环。6.3 我留给自己的一条工程纪律每次改参数前先冻结数据我做这个项目最大的教训是“数据和参数不能同时改”。第一次优化时我一边补了 200 张夜间数据一边把imgsz从 640 提到 1280模型确实变好了但完全不知道是谁的功劳后续想复现都没法复现。现在我的习惯是先固定数据集把参数调到最优再固定参数迭代数据。一次只动一个变量实验记录表里写清改动项哪怕只是改了一个mosaic概率也要记。这条纪律让我的每个模型都比前一个版本明明白白地好而不是靠玄学。模型的终点不是训练完成而是机器人在真实路面上连续跑一周不掉线。我习惯在每个周五把当天机器人采集的数据拿回来做一次增量验证发现问题马上补标。这套流程走下来YOLOv11 作为路面导航标志识别的基座是完全够用的——它把标注、训练、部署的门槛压得很低剩下的功夫全在数据质量和工程细节上。希望这篇笔记能帮你少走我走过的弯路。本文还有配套的精品资源点击获取