公司动态

电动车摩托车违规检测数据集全流程构建:从数据采集到YOLO训练

📅 2026/9/1 7:42:55
电动车摩托车违规检测数据集全流程构建:从数据采集到YOLO训练
简介本资源是一套面向计算机视觉初学者与YOLO目标检测实践者的专用数据集聚焦电动车、摩托车骑行人员违规行为识别场景适用于交通监管、智能安防等实际项目开发与课程实训。数据集严格遵循YOLOv5目录结构组织开箱即用支持直接接入训练流程共1883个文件包含940张高质量JPG图像涵盖不戴头盔、使用手机等5类典型违规行为、941个对应TXT标签文件按YOLO格式标注、1个开箱可视化的Python脚本随机加载图像并绘制带类别标签的边界框无需修改即可运行及1张说明图。压缩包大小为56.63MB结构清晰、标注规范、类别明确配套classes.txt文件完整定义5个违规类别。目前已有531人学习下载特别适合需要快速验证模型效果、理解YOLO数据格式、开展小规模交通违规检测实验的开发者与学生。 做目标检测项目的人应该都有同感模型训练本身反而不是最花时间的真正让人头疼的是数据集。尤其是像电动车、摩托车违规检测这种场景网上现成数据集很少公开数据基本没有完全匹配的只能自己攒、自己标、自己清洗。我最近正好完整跑了一遍这个流程从收集原始图片到标注再到划分数据集、写类别文件、做可视化校验最后拿YOLO训练出可用的模型。这篇内容就围绕“电动车、摩托车违规目标图像检测”这套数据集展开把我实际踩过的坑、总结的套路和可直接复用的脚本思路全部拆开讲适合正在做交通违规识别、车辆目标检测或者打算从零整理自己数据集的同学参考。之所以写这个项目是因为电动车和摩托车的违规行为在城市交通治理里非常典型比如不戴头盔、违规载人、逆行、加装遮阳伞甚至闯红灯。这些场景如果单纯靠人工监控效率太低所以用YOLO做自动检测是很有落地价值的。但要检测“违规”首先得能稳定检测出“车”和“人”以及“人头是否戴了头盔”这类细粒度特征。这套数据集就是围绕这个需求构建的。1. 项目整体设计从“要检测什么”到“数据从哪来”1.1 需求拆解电动车、摩托车违规要识别哪些目标很多刚开始做这个方向的人都会犯一个错误就是一上来就想直接识别“违规行为”。比如想一次性输出“这个人没戴头盔”“这辆车超载了”。但实际上YOLO这类目标检测模型本质上是“定位分类”它并不知道“超载”是什么概念它只知道画面里有哪些物体、物体在哪、物体属于哪个类别。违规行为的判断是需要后处理逻辑或者规则推理来完成的。所以我的做法是先把“违规检测”拆成语义更清晰的子任务检测电动车包含电动自行车、电摩检测摩托车检测骑行人头头戴头盔 / 未戴头盔分别作为两类检测乘员判断是否违规载人时需要数人头检测车身特征如遮阳伞、雨棚这类违规加装物把这些拆完之后类别文件里的每一个类别就非常明确。我实际采用的类别定义是helmet、no-helmet、rider、passenger、motorcycle、e-bike、umbrella。其中helmet和no-helmet是用于后续违规识别的关键rider和passenger用于计数motorcycle和e-bike用于区分车辆类型umbrella专门用来识别加装遮阳伞这种常见的违规改装。注意类别的命名不是随便起的它会影响class文件里的顺序而class文件的顺序又直接决定YOLO标签文件中类别ID对应关系。这个顺序一旦训练中途再改之前的标注就全部作废了。所以在标注之前先把类别清单定死不要边标边改。1.2 数据来源与采集方案的取舍数据来源是数据集建设里最现实的问题。可选方案有这么几种各有利弊第一种从公开数据集中筛选。像BDD100K、UA-DETRAC、Aeroscapes这类自动驾驶或者航拍数据集里会有不少包含两轮车的图片。优点是省去大量采集时间缺点是需要人工逐张筛选因为公开数据集里摩托车、电动车的占比通常不高而且很多是远距离小目标直接拿来用效果并不好。第二种从视频里抽帧。这是我最推荐的方式。找一些城市路口的监控视频素材或者自己拍摄的骑行画面用ffmpeg每隔几秒抽一帧就能得到大量连续但又有差异性的图片。抽帧的好处是场景丰富、角度多样而且可以控制时间覆盖比如分别抽早高峰、中午、傍晚、夜间的画面这样模型对光照变化的鲁棒性会好很多。第三种自己用手机或相机补拍。针对模型经常漏检的特殊角度、特殊车型比如后视角、逆光、雨天带雨披的画面定向补拍。这部分图片量不用太大但针对性极强往往能明显改善模型的短板。我当时的实际比例大约是公开数据集筛选40%视频抽帧50%补拍10%。最终整理了大概9000多张图片其中有效标注目标数超过2万个类别集中在e-bike、helmet、no-helmet、rider这几类上。1.3 标注策略单框还是多类哪些类该合并标注阶段最重要的一件事是确定“框住什么”。有些目标检测新手会把人、车一起框在一个大框里比如把骑手和电动车框成一个rider目标这样虽然省事但模型很难学到“头盔和头”的对应关系后续判断是否戴头盔就成了无源之水。我建议的分类标注规则是rider框住骑乘人员的头部肩部区域但不包括头盔顶部以上空白区域尽量贴合人头轮廓helmet在骑手头部位置单独再画一个框框内只包含头盔no-helmet同样是头部位置但由于没有头盔框内是裸露头发或头部motorcycle/e-bike框住整车注意区分燃油摩托车和电动自行车特征上排气筒、车身线条差异比较明显passenger后座乘员的人头区域和rider分离开umbrella遮阳伞外形比较特殊从上方伸出来很容易框这里有一个非常关键的细节helmet和no-helmet与rider是重叠的关系也就是同一位置会有两个框。YOLO允许一个图像区域被多个不同类别的框同时覆盖训练时并不会因此报错因为不同的ground truth框会分配给不同的anchor或者不同的样本分支。这也是“违规检测”方案能跑通的前提先检测人的位置再检测头是否戴了头盔二者是并行输出。我在实际标注中遇到的另一个问题是是否要把“头盔”类别和“骑手”类别合并成一个“戴头盔骑手”类别。比如直接把骑手框标注为rider-with-helmet和rider-without-helmet两个类别。这种做法确实能简化后处理但缺点也很明显类别数量翻倍且当后座乘员不戴头盔、而骑手戴头盔时会出现两个类别同时叠加反而容易造成混淆。所以最终我没有采用这种合并方案而是坚持“部位独立检测后处理联合判断”的方式。2. 数据集目录结构与语义划分好的数据长什么样2.1 目录组织images/labels 加 train/val/test 三件套当我拿到一套“划分好的数据集”我第一件事就是检查目录结构。YOLO系列无论是YOLOv5、YOLOv8还是YOLOv9/v10/v11都有一套约定俗成的数据组织方式。我构建的目录结构如下dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt ├── data.yaml ├── visualize.py └── split_dataset.pyimages和labels一一对应同名图片文件和同名标签文件的文件名保持一致只是扩展名不同。图片是.jpg标签是.txt。train/val/test三个子目录的比例我采用的是8:1:1也就是8000张训练、1000张验证、1000张测试。这里有个行业惯例需要特别说明验证集和测试集不是一回事。验证集用来在训练过程中监控模型效果、调整超参数测试集是训练完全结束之后用来模拟真实场景评估最终模型的。很多人偷懒只分train和val但做违规检测这种落地项目时我强烈建议留一份test因为要检验模型在完全没见过的路口、天气、车型上的表现否则容易出现“训练时指标挺好一到现场就崩”的情况。提示在划分数据集的时候不要只按文件名随机切分。最好是按“来源分组”切分比如来自同一条视频的帧全部划入同一个集合。否则同一个视频相邻帧会被同时塞进train和val模型相当于提前看到了验证集的图像内容验证分数虚高。2.2 classes.txt 文件里类别顺序为什么不能乱很多人在使用别人数据集时常犯一个错误直接改class文件里类别的名字比如把helmet改成head_helmet却没意识到改了名字之后标签文件里每个框的类别ID含义就变了。classes.txt这个文件本身只是一个“类别名称列表”每一行写一个类别名写在前面的类别ID就是0后面依次是1、2、3...我在项目中使用的classes.txt内容如下helmet no-helmet rider passenger motorcycle e-bike umbrella对应的标签文件中第一列的0就代表helmet1代表no-helmet2代表rider依此类推。模型训练时读的是ID不是读名称。所以如果你把helmet和no-helmet在class文件里的顺序调换那之前所有标注为helmet的标签在模型看来全都变成了no-helmet模型等于学了一个完全错误的东西。顺便说一句输出class文件的顺序在训练时要和data.yaml里的names列表保持一致。以YOLOv8为例data.yaml里的配置是这样的path: dataset train: images/train val: images/val test: images/test names: 0: helmet 1: no-helmet 2: rider 3: passenger 4: motorcycle 5: e-bike 6: umbrella细心的同学会发现这样写和classes.txt是同一个顺序。我的经验是class文件和data.yaml里的names顺序必须统一且一旦固定就不要再改。2.3 标签格式YOLO txt 每一行的五个数字到底代表什么YOLO标签文件的每一行对应一个目标框格式是class_id x_center y_center width height这里的x_center、y_center、width、height全部是归一化到[0,1]之间的小数而不是像素值。归一化的方式是像素坐标除以图片的宽高。举个例子一张宽1920、高1080的图片某个头盔框的左上角是(960, 200)右下角是(1180, 320)那中心点坐标就是((9601180)/2/1920, (200320)/2/1080)即(0.5573, 0.2407)宽为(1180-960)/19200.1146高为(320-200)/10800.1111。因此标签文件里的一行完整内容就是0 0.5573 0.2407 0.1146 0.1111这里第一位的0表示helmet类别。为什么要归一化因为不同图片尺寸不一样如果不归一化模型训练时就无法统一处理。所以当你用LabelImg或者X-AnyLabeling这类标注工具导出时一定要选择YOLO格式导出而不是Pascal VOC或COCO。如果标注工具只支持VOC格式的XML也可以后续写脚本来做坐标转换这个我在后面第4部分会给出可用的转换代码。还有一个需要警惕的坑标签文件中坐标值必须严格在0到1之间但如果目标贴着图片边缘某些标注工具会生成超出范围的值比如x_center width/2 1。YOLO训练时遇到这种坐标会报警甚至导致训练中断所以预处理时最好加一轮越界检查把所有超出[0,1]的值裁剪到有效范围内。3. 数据划分与可视化脚本思路3.1 数据划分脚本按比例还是按来源分组数据划分我写了一个独立的Python脚本核心逻辑并不复杂但要处理两个关键点一是同名文件要同步移动二是按来源分组避免数据泄露。下面是split_dataset.py的核心逻辑保留主要流程大家可以按需修改import os import random import shutil random.seed(42) image_dir images_all label_dir labels_all train_ratio 0.8 val_ratio 0.1 # 假设文件名格式为source_video_000001.jpg # 按source分组避免同一视频的帧被切到不同集合 sources {} for img_name in os.listdir(image_dir): if not img_name.endswith(.jpg): continue source_key img_name.rsplit(_, 1)[0] # 以最后一段序号前的内容作为分组key sources.setdefault(source_key, []).append(img_name) all_groups list(sources.values()) random.shuffle(all_groups) train_count int(len(all_groups) * train_ratio) val_count int(len(all_groups) * val_ratio) train_groups all_groups[:train_count] val_groups all_groups[train_count:train_count val_count] test_groups all_groups[train_count val_count:] def move_group(group, split): for img_name in group: base os.path.splitext(img_name)[0] shutil.move(os.path.join(image_dir, img_name), os.path.join(dataset, images, split, img_name)) shutil.move(os.path.join(label_dir, base .txt), os.path.join(dataset, labels, split, base .txt)) for split, groups in [(train, train_groups), (val, val_groups), (test, test_groups)]: for g in groups: move_group(g, split)按来源分组划分的效果在视频抽帧数据上体现得尤其明显。如果不分组同一个路口、同一批车辆、几乎相同的背景会同时出现在训练集和验证集模型其实“见过”验证集里的场景最终在验证集上跑个0.98的mAP看着漂亮一上线立刻掉到0.6。这是我在实际项目中吃过亏才明白的。3.2 类别统计与样本均衡划分好数据之后下一个必要操作是统计各类别数量确认样本均衡性。我用一个简单的统计脚本import os label_dir dataset/labels/train class_count {} total_boxes 0 for label_file in os.listdir(label_dir): path os.path.join(label_dir, label_file) with open(path, r) as f: for line in f: cls_id int(line.strip().split()[0]) class_count[cls_id] class_count.get(cls_id, 0) 1 total_boxes 1 classes [helmet, no-helmet, rider, passenger, motorcycle, e-bike, umbrella] for cls_id in sorted(class_count.keys()): print(f{classes[cls_id]:12s} {class_count[cls_id]:6d} {class_count[cls_id]/total_boxes*100:.2f}%)我在实际统计中发现一个典型问题umbrella类别只有几百个目标而e-bike有上万个目标。如果直接训练模型对遮阳伞的检测能力会非常差。解决方案有几个一是单独对样本少的类别做数据增强把遮阳伞图片旋转、翻转、亮度变化后复制多份二是收集更多包含遮阳伞的图片三是在YOLO训练配置里增加每个类别的loss权重让少样本类别的错误产生更大的惩罚。实际最有效的是前两种第三种只能作为补充手段。3.3 可视化脚本画框、查错、看到模型眼中的数据划分完数据后不要急着训练先用可视化脚本把标签画到图片上看一遍。我写的visualize.py主要功能是从标注文件中读取类别和坐标然后在原图上画出边界框和类别名称输出到预览目录。import cv2 import os image_dir dataset/images/train label_dir dataset/labels/train output_dir dataset/visual_check os.makedirs(output_dir, exist_okTrue) classes [helmet, no-helmet, rider, passenger, motorcycle, e-bike, umbrella] colors [(0, 255, 0), (0, 0, 255), (255, 0, 0), (255, 255, 0), (255, 0, 255), (0, 255, 255), (128, 128, 0)] for label_file in os.listdir(label_dir): if not label_file.endswith(.txt): continue base os.path.splitext(label_file)[0] img_path os.path.join(image_dir, base .jpg) img cv2.imread(img_path) if img is None: print(fMissing image: {img_path}) continue h, w img.shape[:2] with open(os.path.join(label_dir, label_file), r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls_id % len(colors)], 2) cv2.putText(img, classes[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[cls_id % len(colors)], 2) cv2.imwrite(os.path.join(output_dir, base _vis.jpg), img) if output_dir.count % 100 0: print(fProcessed {output_dir.count} images)这样一组可视化图片出来后你才能真正发现标注里的问题。我印象最深刻的是有一次可视化检查时发现一张没有任何目标的图片它的标签文件却是空的这其实没问题YOLO允许空标签文件代表图片中无目标。但有些图片被标注了目标可视化后却发现框的位置画在完全错误的地方比如头盔框跑到了行人肩膀上这类错误如果不通过可视化检查很难在训练时发现。4. 实操过程从原始图片到可训练数据集全流程4.1 环境准备与目录初始化先说明环境我是在Ubuntu 20.04上用Python 3.8完成整个流程的不需要特别高的硬件配置数据集整理阶段用CPU就够了可视化只需要OpenCV。标注工具我推荐在Windows上用LabelImg或者X-AnyLabeling因为图形化界面比较友好在Linux服务器上整理数据时直接用Python脚本和命令行。目录初始化的命令比较简单但我建议一次建好mkdir -p dataset/{images/{train,val,test},labels/{train,val,test}} mkdir -p original/{images,labels}这里的original是存放原始图片和原始标注文件的目录后续清洗、转换都在副本上进行不要直接在原图上改动。这个习惯帮我避免了很多次误操作。4.2 标注转换与清洗如果你的标注工具直接导出YOLO格式可以跳过转换环节。但如果工具只支持Pascal VOC的XML格式就需要一个转换脚本。核心是把XML里的xmin, ymin, xmax, ymax像素坐标转换为归一化的中心点坐标和宽高。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, out_txt, class_map): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) with open(out_txt, w) as f: for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) width min(max(width, 0.0), 1.0 - x_center) if x_center 1.0 else 0.0 height min(max(height, 0.0), 1.0 - y_center) if y_center 1.0 else 0.0 f.write(f{class_map[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n)这里最需要注意的是最后那几行越界裁剪。我在转换时遇到的典型情况是标注对象非常贴近图片边缘XML里的坐标换算后x_center width/2 1。如果不裁剪YOLO训练时会警告甚至忽略这些框造成目标丢失。裁剪之后虽然框的位置略有偏差但至少目标不会整个消失。4.3 生成配置文件 data.yaml数据准备好之后在dataset目录下创建data.yaml。这里有个很多人没注意的细节path字段的写法。如果你用YOLOv8的CLI训练最好用相对路径方便代码在不同机器间迁移。我一般写成这样path: /absolute/path/to/dataset # 换机后记得修改或者用相对路径 train: images/train val: images/val test: images/test nc: 7 names: 0: helmet 1: no-helmet 2: rider 3: passenger 4: motorcycle 5: e-bike 6: umbrellanc是类别总数必须和names里的数量一致否则YOLO直接报错。我建议每次修改data.yaml后先运行一条简单的命令验证数据集是否能被正确读取比如用YOLOv8的val模式加载一次模型让它检查数据格式然后再开始训练。这一步看似费时但能避免你在训练跑到一半时才发现配置错误。4.4 训练前的自检清单在正式开始训练之前我建议跑一遍下面这个自检清单它就是我的“踩坑经验浓缩版”检查图片和标签文件数量是否一致遍历images/train下所有jpg确认每个jpg都有对应的txt检查标签文件是否有空行、非五列数据、类别ID越界比如只有7类但出现了ID为7或更大的值可视化抽查至少200张图片重点看有无画错位置、漏标、框过于松垮的情况确认classes.txt和data.yaml的类别顺序一致确认验证集和测试集中的图像来源与训练集不重叠检查是否存在大量宽高比极端的图片比如超宽全景图这类图在YOLO训练时会被缩放变形影响效果我每次新建数据集都会把这个清单写成一个checklist脚本自动跑一遍输出“未发现异常”或者报错信息。人的记忆是不可靠的脚本检查才是可控的。5. 常见问题与排查技巧实录5.1 标签丢失或图片尺寸不匹配症状训练时提示image dataset/labels/train/xxx.txt不存在或者读标签时报错。排查思路极少数情况是图片本身没标注但更多时候是文件名不匹配。比如一张图叫img_001.jpg标签却叫img_001.txt看起来没问题但如果你在Windows上做过文件传输可能会有隐藏扩展名问题实际文件名变成img_001.jpg和img_001.txt这样大小写不一致。我的做法是统一把文件名全小写化并在划分脚本里做同步校验。还有一个隐蔽问题图片格式可能是.jpeg、.png而标签找的是.jpg所以脚本里最好做后缀归一化。5.2 类别ID错位导致训练结果混乱症状训练能跑通但val时输出结果里helmet的精度极低同时no-helmet精度很高且混淆矩阵一团糟。排查思路很大概率是class文件顺序和标注ID不一致。比如标注软件导出时界面上类别顺序是helmet在下、no-helmet在上但它导出时可能按字母序重新排了ID。或者你在标注中途删除了一个类别后续所有类别的ID都往前挪了一位但老标签没跟着更新。解决办法是重新导出所有标注或者写脚本按“名称到ID”映射批量重建标签。我后来为了避免这个问题在可视化脚本里额外加了一个“展示标签名称而不是纯数字”的功能这样一眼就能看出ID和名称是否对应。5.3 可视化脚本画框位置错乱症状画出来的框偏移严重要么偏左上要么框和目标对不上。排查思路大部分情况是坐标换算公式错了。YOLO存的是中心点坐标而画框需要左上角和右下角坐标所以是x_min (x_center - width/2) * img_width y_min (y_center - height/2) * img_height x_max (x_center width/2) * img_width y_max (y_center height/2) * img_height注意一定要乘以原图的宽高而不是直接使用归一化后的坐标。另一个容易忽略的是OpenCV的cv2.rectangle接收的坐标必须是整数如果直接传入浮点数OpenCV不会报错但画出来的框位置会错乱所以需要先int()转换。5.4 小目标检测效果差症状电动车、摩托车在画面远处时目标很小模型漏检率很高尤其helmet这种更小的目标几乎全部漏掉。这部分其实不是数据集的格式问题而是数据构成问题。我在实际项目中的经验是对这类小目标集中的场景可以尝试把原图切割成多个patch或者使用YOLO的imgsz参数增大推理分辨率比如训练和推理都用1280而不是默认的640。另外要注意的是如果图片中目标太小标注框高度甚至小于10个像素这种目标“人眼能看出模型很难学”建议通过裁剪放大处理之后再加进数据集。5.5 类别不均衡导致遮阳伞类别权重过低症状umbrella类别在训练中的loss很小但验证时的召回率非常低。排查思路回归到第3.2节提到的统计问题。我最终是通过两轮训练解决的第一轮用完整数据集训练一个基础模型然后专门收集umbrella相关的误检图片做第二轮增量训练。这个方法比起单纯调loss权重效果更直接。增量训练时把新收集的图片追加到训练集中再用之前的权重作为预训练权重接着训练几十个epoch就能明显改善。类别不均衡还有一个隐蔽危害模型对高频类别比如rider、e-bike会偏向于“宁可多检也不错杀”导致大量低频类别的误报。所以我建议训练完成后不能用单一的mAP指标来评估一定要单独看每个类别的precision和recall尤其是低频类别。6. 训练与验证确保数据集真正可用数据集整理的最终目的是训练出一个能用的模型所以训练阶段的验证非常关键。我用YOLOv8为例命令很简单yolo detect train datadataset/data.yaml modelyolov8s.pt epochs100 imgsz640 batch16训练完成后用测试集验证yolo detect val datadataset/data.yaml modelruns/detect/train/weights/best.pt splittest这里的splittest很重要很多人默认会去验证val集但我们的目的是看看模型在新场景上的泛化能力所以用test集更接近真实部署场景。我在第一次用这套数据训练时val集的mAP50达到0.91看起来很漂亮但test集只有0.78。说明数据划分时的分组策略起了作用val集里有一部分和训练集场景很接近的视频所以指标虚高。后来我严格按来源分组后val和test的差距缩小到了0.05以内。训练时还可以开启YOLO的数据增强参数比如hsv_h0.015, hsv_s0.7, hsv_v0.4, degrees10, translate0.1, scale0.5, fliplr0.5。这些增强对交通场景尤其有用因为同一辆电动车可能在画面中出现在不同角度、不同光照下。增强参数不需要全部拉满适度即可过度增强会把模型搞飘。训练完成之后我建议做一次面向实际场景的“抗干扰测试”把测试图片加入一些常见的干扰元素比如夜晚灯光、雨水条纹、运动模糊看看模型是否还能稳定输出。如果效果不佳那就需要补充对应场景的训练样本而不是盲目加训练轮数。我在实操中发现夜间场景的漏检率是白天的3倍以上这个不能靠调参解决只能靠补充夜间的数据或者做专门的夜图像增强例如将图片亮度增强后推理。最后关于模型部署YOLO训练完导出的best.pt可以直接用yolo export modelbest.pt formatonnx导出为ONNX格式方便在服务端或者嵌入式设备上部署。但在部署前一定记得用同样的预处理参数输入图片的尺寸、归一化方式都要和训练时保持一致否则推理效果会打折扣。7. 我踩过的最值钱的几个坑做了这么多次数据集相关的项目有几条经验想单独拎出来讲因为它们不是“报错型问题”而是“效果不好但又不报错”的隐性陷阱。第一不要迷信公开数据集的标注。我从公开数据集里筛出来的图片看似标注完整但很多标注框是“车框”和“人头框”一起给的根本没有区分是否戴头盔。如果要用于违规检测必须手工重新标注。这个工作量非常大所以有个心理准备不要指望“拿来即用”。第二标注的一致性比标注的精细度更重要。举个例子车的框只要不偏太多就行但头盔和头部的边界必须严格一致。同一个头盔框如果第一张标到头盔边缘第二张多标了一圈空气第三张少标了头盔前沿模型会学到很多噪声。所以我在标注规范里写得很死helmet框的上边界就是头盔顶部下边界就是头盔下沿和额头交界处左右边界就是头盔可见部分的最大外沿。第三数据集的“背景多样性”比“目标多样性”更容易被忽略。很多公开图像里的电动车都在同一种城市背景里比如干净的人行道、灰色马路、白天光照。但真实场景里的背景可能是商场门口、农贸市场、学校门口可能是逆光、雨天、夜间。模型如果只见过一种背景换个环境就容易失准。所以我补拍数据时特意选择不同天气、不同时段、不同街道背景这比单纯增加目标数量的收益大得多。第四可视化校验要贯穿整个数据集生命周期。不是只做一次就完了。每次从外部拿新的图片进来都要重新跑一遍可视化每次调整标注规范也要可视化对比。否则可能不知不觉混入了脏数据而脏数据对模型的影响是“一颗老鼠屎坏一锅汤”。这套“电动车、摩托车违规目标图像检测”数据集做完之后我最大的感受是目标检测的模型结构再先进没有一套可靠的数据集支撑最终效果都是空中楼阁。而数据集的可靠性恰恰取决于那些看起来琐碎的细节class文件顺序、标签坐标是否正确、训练验证集是否隔离、可视化是否检查到位。把这些细节做到位训练出一个能落到交通违规检测场景里的YOLO模型就是水到渠成的事了。本文还有配套的精品资源点击获取