ARTICLE DETAIL

资讯详情

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

YOLO11车辆检测实战:VOC/COCO/YOLO格式转换与三平台训练

YOLO11车辆检测实战:VOC/COCO/YOLO格式转换与三平台训练 简介一份面向目标检测与车辆检测场景的数据集说明PDF共1个文件大小约7.19MB内容介绍一套托管于百度网盘的真实车辆图片数据集及配套训练说明。该数据集包含城市道路、高速道路、农村道路及遮挡、严重遮挡等丰富场景的高质量车辆图像约1000张划分Auto、Bus、Car、LCV、Motorcycle、Multi-Axle、Tractor、Truck共8个类别可直接服务于交通道路监控场景下的车辆检测项目也可作为监控场景通用车辆检测数据集的补充。配套标签均采用labelimg标注提供VOC(xml)、COCO(json)、YOLO(txt)三种标准格式便于直接导入YOLO等算法训练。PDF内录有标注示例截图与数据集获取方式同时附赠YOLO11一键训练脚本说明覆盖GPU(GPUs)、CPU、Mac(M芯片)三平台并给出博主训练结果日志供参考帮助使用者快速评估数据构成并接入现有训练流程。已有1333人学习下载适合从事车辆检测、智慧交通或相关算法研究的技术人员。1. 拿到“1000张车辆图 三种格式标签 YOLO11训练脚本”先别急着跑训练车辆检测数据集是目标检测里最成熟的落地场景之一停车场统计、路口车流分析、园区安防都会先要一个稳定的“车在哪”结果。标题这套东西本质上把两个痛点一次补齐VOC/COCO/YOLO 三种格式标签以及支持 GPU/CPU/Mac 三平台的 YOLO11 一键训练脚本。适合两类人0 基础想跑通第一个车辆识别模型的新手和已经在做智慧交通项目、想用 1000 张小数据快速验证方案可行性的老手。我的经验是这套方案能不能成不取决于脚本写得多少而取决于数据集和脚本衔接处的格式转换、数据划分、设备选择是否经得住推敲。2. 动手前先拆数据1000张车图里的分布比“三种格式”更决定模型效果很多新手拿到压缩包后第一反应是把 1000 张图直接塞进训练脚本。这里有个隐性前提训练脚本要能用一个 data.yaml 指到 train/val 两个目录并且每张图都有一一对应的标签文件。如果图 1000 张、标签只有 980 份或者 train 里混进了本来该用来做最终测试的图模型效果会莫名其妙地差。我一般先把数据拆成 train/val/test 三个子集再跑一遍标注统计最后才进入格式转换。这一步看着琐碎却是后面所有流程能不能复现的地基。2.1 先做数据集划分images 和 labels 同名同步缺失标签直接剔除如果你的标签已经统一成 YOLO txt那第一步不是转格式而是划分数据集。我用的脚本是复制而不是移动文件因为移动原图一旦切错后面想重新转 COCO 就得重下数据。import random import shutil from pathlib import Path random.seed(7) src_img Path(dataset/images) src_lbl Path(dataset/labels) out_root Path(yolo_split) ratio {train: 0.8, val: 0.1, test: 0.1} all_imgs sorted(src_img.glob(*.jpg)) random.shuffle(all_imgs) pos 0 for split, r in ratio.items(): count int(len(all_imgs) * r) chosen all_imgs[pos:pos count] pos count img_dir out_root / split / images lbl_dir out_root / split / labels img_dir.mkdir(parentsTrue, exist_okTrue) lbl_dir.mkdir(parentsTrue, exist_okTrue) for img_path in chosen: lbl_path src_lbl / (img_path.stem .txt) if not lbl_path.exists(): print(f[warning] 缺少标签跳过: {img_path.name}) continue shutil.copyfile(img_path, img_dir / img_path.name) shutil.copyfile(lbl_path, lbl_dir / lbl_path.name) print(f原图数量: {len(all_imgs)})这个脚本的核心逻辑很简单按 8:1:1 把图片随机打散复制图片时同步复制同名 txt。random.seed(7) 固定随机种子保证每次跑出来都是同一份划分这对后续调参特别重要。两处值得按素材调整如果某一类场景特别少val 可以从 0.1 降到 0.05如果后续要做超参数对比test 建议至少留 50 张否则最终 mAP 没有统计意义。提示YOLO 训练时data.yaml 里 train 和 val 指向的都是 images 目录标签必须放在同级 labels 目录下图片和标签主文件名要完全一致后缀分别是 .jpg/.txt。我见过最隐蔽的坑是视频抽帧得到的数据集连续两帧被拆到了 train 和 test。这种随机拆分会让模型在测试时“作弊”因为相邻帧太像。正确做法是按视频片段或时间段分组先把连续帧作为一个 group再从 group 层面划分。1000 张图如果只来自一段视频宁可不用随机拆分而是每隔多少帧抽一帧作为 test否则最后实拍验证一定会翻车。2.2 标注质量自检统计框面积、长宽比和每张图的框数标签格式正确不代表标注质量过关。1000 张车辆图的人工标注最常见的两块暗雷是框只框车头没框车尾以及小目标图上的框被画得过大。单靠眼睛翻很难发现问题我习惯先跑一个统计脚本把归一化面积、宽高比、每张图框数全打印出来。from pathlib import Path import numpy as np lbl_dir Path(yolo_split/train/labels) area_list, ratio_list, count_list [], [], [] for txt in sorted(lbl_dir.glob(*.txt)): lines [ln.strip() for ln in txt.read_text().splitlines() if ln.strip()] count_list.append(len(lines)) for line in lines: cls, cx, cy, w, h map(float, line.split()) if w 0 or h 0: print(f[warning] 无效框: {txt.name} - {line}) continue area_list.append(w * h) ratio_list.append(max(w, h) / max(min(w, h), 1e-6)) area_list np.array(area_list) ratio_list np.array(ratio_list) print(图像数:, len(count_list)) print(框数:, len(area_list)) print(平均每图框数:, np.mean(count_list)) print(框面积 min/p50/max:, area_list.min(), np.percentile(area_list, 50), area_list.max()) print(宽高比 p90:, np.percentile(ratio_list, 90))这里的 w 和 h 是 YOLO 归一化后的值。框面积等于 w*h如果 p50 小于 0.01说明一半以上的框只占图面的百分之一训练时小目标分支会非常吃力后续训练 imgsz 得加到 800 或 1024。宽高比 p90 超过 2.5说明素材里横向大车多单类检测影响不大但要是和自行车、行人一起做多类检测就需要注意特征提取的感受野匹配问题。每张图超过 15 个框的 txt我基本都会打开原图看一眼高密度框大概率是同一辆车被重复标注了。关于类别数量标题没说但这直接影响脚本里的 nc。1000 张是中小规模数据集我通常建议压缩成单类 vehicle或者按车种拆成 car/truck/bus 三类。参考 BDD100K 这类开放车辆数据集的类别口径没问题但别被 10 个类诱惑1000 张图拆 10 类以后很多类可能只有几十个框类别不均衡会让训练过程像黑匣子一样难调。样本少就靠类别少来补这是小数据集的第一原则。3. VOC/COCO/YOLO 三种格式怎么转坐标参考系和类别编号才是关键标题说的三种格式常见做法是分别给 VOC 的 XML、COCO 的 JSON、YOLO 的 txt。训练脚本主要认 YOLO txt所以第一步要把另外两种格式转成 txt。但转换不只是改文件后缀而是两个底层问题坐标单位以及类别 id 体系。VOC 和 COCO 用的是像素绝对坐标YOLO 用的是归一化的中心点加宽高。类别体系上COCO 经常带背景类 id 0VOC 也有 background 约定YOLO 自定义数据集则一般从 0 开始连续编号。哪个不对齐模型都可能不报错但默默学歪。3.1 VOC XML 转 YOLO txt先做坐标归一化再做类别 id 映射VOC 的 XML 里每个目标是一个object框坐标写在bndbox下xmin/ymin 是左上角xmax/ymax 是右下角。转成 YOLO 后需要的是中心点坐标和归一化宽高。下面这段是常见的最小实现带上越界裁剪和类别映射。import xml.etree.ElementTree as ET from pathlib import Path # 这里把类名和 YOLO 类别 id 对应好0 代表第一类 class_map {car: 0, truck: 1, bus: 2} def voc_to_yolo(xml_path: Path, out_path: Path) - None: root ET.parse(xml_path).getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) xmax min(xmax, img_w) ymax min(ymax, img_h) x_c (xmin xmax) / 2.0 / img_w y_c (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) out_path.write_text(\n.join(lines) \n) for xml_path in Path(voc_annotations).glob(*.xml): voc_to_yolo(xml_path, Path(labels) / (xml_path.stem .txt))代码里两处值得强调。第一xmax 和 ymax 要 clamp 到图片尺寸内。很多标注工具的框会超出边界几个像素YOLO 训练时一旦读到 w/h 大于 1损失函数会把这个框当成一个超大目标轻则损失异常重则 loss 炸掉。第二class_map 必须自己维护不要依赖 XML 里的 name 直接转成数字因为不同批次的 XML 可能一个叫 “car”另一个叫 “Car”大小写不一致就会产生两个类别。转换后我会随机挑几张图用画框脚本叠加显示原图和 txt验证坐标没有整体偏移。这个验证动作看似笨但能省下后面训练失败后排查的时间。如果你想从 YOLO txt 逆转到 VOC公式反过来就行xmin (xc - w/2) * img_wymax (yc h/2) * img_h。但注意 YOLO txt 本身没有图片尺寸必须另读原图宽高这也是为什么我前面一直强调图片和标签要同名同目录少一个都追不回来。3.2 COCO JSON 转 YOLO txt别直接拿 category_id 当训练类别COCO 的 bbox 比 VOC 多一层陷阱它是 [x, y, w, h]x、y 是左上角坐标w、h 是宽高不是右下角。另外json 里的 category_id 往往是原始 id可能从 1 开始也可能中间有空号直接把 category_id 当作 YOLO 类别编号会让模型学到好几个“空类别”背景也会被当成目标的一部分。import json from pathlib import Path def coco_to_yolo(json_path: Path, label_out: Path) - None: data json.loads(json_path.read_text()) categories data[categories] ctg_map {c[id]: i for i, c in enumerate(categories)} img_id_to_meta {} for img in data[images]: img_id_to_meta[img[id]] img label_out.mkdir(parentsTrue, exist_okTrue) anns_by_img {} for ann in data[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_id, anns in anns_by_img.items(): img_meta img_id_to_meta[img_id] img_w, img_h img_meta[width], img_meta[height] lines [] for ann in anns: x, y, w, h ann[bbox] if w 0 or h 0: continue cx (x w / 2.0) / img_w cy (y h / 2.0) / img_h nw min(w, img_w - x) / img_w nh min(h, img_h - y) / img_h lines.append(f{ctg_map[ann[category_id]]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) out_file label_out / (img_meta[file_name].rsplit(., 1)[0] .txt) out_file.write_text(\n.join(lines) \n) coco_to_yolo(Path(annotations.json), Path(labels))关键在ctg_map {c[id]: i for i, c in enumerate(categories)}这一句。它把 json 里不连续的原始 id 重新映射成 0、1、2避免类别空洞。如果转换后标签文件出现超过 10 的类别数字那多半是这一步漏了。还要注意 COCO 的 file_name 可能是相对路径比如images/train/0001.jpg如果直接把 file_name 拼接到 output目录会多出几层。这会让 YOLO 的 labels 目录和 images 目录对不上训练时直接报错找不到标签。我会先Path(file_name).name只取文件名保证标签平铺到同一个 labels 目录。三种格式都备齐的好处是用在验证和部署上COCO 格式方便和公共指标对比VOC 格式方便用传统工具做可视化YOLO txt 是最直接的训练入口。标题说对应三种格式我的落地建议是转换后保留一份转换脚本和一份“原格式文件名-新文件名”的映射表否则下个月再拿到新版标注你又得从零转一遍。4. 用一键训练脚本在 GPU/CPU/Mac 三平台跑通 YOLO11设备识别和参数设置训练脚本要解决的第一个问题不是网络结构调得多花哨而是“训练跑在哪一块硬件上”。GPU 走 CUDAMac 的 Apple Silicon 走 MPS普通电脑退到 CPU。很多新手在自己电脑上能跑换台 Mac 就报 torch.backends 没有 mps换台 Windows 又看 CUDA not available。真正的三平台脚本必须像下面这样先自动探测计算后端再决定训练参数。4.1 环境配置先装 Ultralytics再确认 yolo11 能感知到当前设备YOLO11 的常用训练入口是 Ultralytics 的 Python 库官方维护得比较勤环境和模型是同一个体系0 基础纯小白最稳妥的路径就是先建一个干净的虚拟环境。python -m venv venv_vehicle source venv_vehicle/bin/activate # Windows 用 venv_vehicle\Scripts\activate pip install --upgrade pip pip install ultralytics安装完先做一次设备探测不要直接开始训练python -c import torch; print(cuda:, torch.cuda.is_available()); print(mps:, hasattr(torch.backends, mps) and torch.backends.mps.is_available())如果输出里 cuda 为 False先检查驱动和 torch 版本。Mac 上 cuda 为 False 是正常的只要 mps 是 True 就能用 Apple Silicon 训练。CPU 平台的输出两项都是 False脚本会自动落在 CPU 后端。这里的判断逻辑会直接复用到下面的一键训练脚本里所以先跑通这一段等于给后面的脚本铺路。YOLO11 的模型文件名一般是 yolo11n.pt、yolo11s.ptn 是 nanos 是小模型首次运行会自动下载权重需要联网。4.2 一键训练脚本设备自动选择、data.yaml 自动生成、最小参数入口这三个平台环境差异大我一般把脚本设计成“只传一个 data 目录就能开跑”其他参数都给默认值。脚本会自动检查数据集结构自动写 data.yaml自动选设备。下面是能直接用的一段注释里写了关键设计原因。import argparse from pathlib import Path import torch from ultralytics import YOLO def pick_device(args_device: str | None) - str: GPU 优先其次 Mac MPS最后 CPU。也可以手动覆盖。 if args_device: return args_device if torch.cuda.is_available(): return cuda:0 if hasattr(torch.backends, mps) and torch.backends.mps.is_available(): return mps return cpu def write_data_yaml(data_root: Path, class_names: list[str]) - Path: 自动生成 YOLO 训练需要的 data.yaml names_str str(class_names).replace(, ) yaml_file data_root / data.yaml yaml_file.write_text( fpath: {data_root.as_posix()}\n train: train/images\n val: val/images\n fnc: {len(class_names)}\n fnames: {names_str}\n ) return yaml_file def main() - None: parser argparse.ArgumentParser(description车辆检测 YOLO11 一键训练) parser.add_argument(--data, typePath, requiredTrue, help数据集根目录下含 train/val/images) parser.add_argument(--classes, nargs, default[car, truck, bus], help类别名称顺序对应标签 id) parser.add_argument(--weights, defaultyolo11n.pt, help初始化权重) parser.add_argument(--epochs, typeint, default100) parser.add_argument(--batch, typeint, default16) parser.add_argument(--imgsz, typeint, default640) parser.add_argument(--lr0, typefloat, default0.005) parser.add_argument(--device, defaultNone, help手动指定 cuda:0 / mps / cpu) args parser.parse_args() data_root args.data.resolve() if not (data_root / train / images).exists(): raise ValueError(没找到 train/images 目录请先完成第 2 章的数据划分) yaml_path write_data_yaml(data_root, args.classes) device pick_device(args.device) print(f[vehicle-yolo11] device {device}, dataset {yaml_path}) model YOLO(args.weights) model.train( datastr(yaml_path), epochsargs.epochs, batchargs.batch, imgszargs.imgsz, lr0args.lr0, devicedevice, cacheFalse, projectruns/vehicle_yolo11, namevehicle, exist_okTrue, ) if __name__ __main__: main()这个脚本解决三平台兼容的关键在 pick_device。手动指定--device的优先级最高适合明确要固定跑在 CPU 上做对比的场景没有指定时CUDA 优先于 MPSMPS 优先于 CPU。用hasattr(torch.backends, mps)而不是直接访问is_available()是为了兼容比较老的 torch 版本避免 Mac 上没有 mps 属性时直接报 AttributeError。write_data_yaml 里data_root 用 resolve 换成绝对路径这是避免“在 A 目录启动终端在 B 目录跑训练”时找不到图片的经典问题。classes 的参数顺序必须和标签里的数字对应如果标签里 0 是 car1 是 truck那命令行里就要写--classes car truck。实际使用中单类模型直接--classes vehicle最省心要分车型就按权重从多到少排。模型训练参数里lr0 默认给 0.005 而不是 Ultralytics 默认的 0.01对其他类似数据集更稳妥。小数据集和 YOLO11 这种深层网络学习率稍微大一点就容易在首个 epoch 冲出去出现 loss 瞬间飙高的情况。1000 张图、100 个 epoch、batch 16普通 GPU 上一般十几分钟到一个小时能跑完一轮CPU 上时间会增加几倍所以脚本里没把 device 写死而是让机器自己判断。4.3 三平台运行时该改哪些参数显存、内存和 MPS 的边界同样的脚本在不同平台跑最需要调整的是 batch。GPU 显存不足会直接 OOMMac 的 MPS 内存不足时不会立刻报错而是频繁换页让训练速度看起来像是在原地踏步。我常用的起点如下运行环境推荐模型batch 建议注意事项NVIDIA GPU 显存 6 GB 以上yolo11s.pt16显存紧张时 batch 降到 8普通笔记本 CPUyolo11n.pt48imgsz 可降到 480 加速Mac Apple Silicon MPSyolo11n.pt816训练变慢时优先降 batch不要直接上 yolo11sCPU 平台还有一个容易忽略的参数是 Ultralytics 的 workers默认值在 Linux 上会拉起多个数据加载进程Windows 上偶尔会卡住。真遇到这种情况我一般直接不跑脚本而是先跑yolo detect train ... workers0把数据加载改成单线程定位慢在训练还是慢在读图。Mac 上遇到 MPS 比 CPU 还慢的情况也正常某些算子会回退到 CPU多线程来回切换反而更耗时干脆用--device cpu加上小 batch稳定性优先。5. YOLO11 三平台训练踩坑与排错四个高频问题训练脚本写完后真正占时间的是排错。以下四个问题是我在 GPU、CPU、Mac 三平台都实际撞过墙的场景每条按“现象、原因、解决”拆开讲。5.1 现象loss 一开始就是 NaN或者第一个 epoch 结束就崩成无穷大原因有两类。最常见的是学习率过高0.01 的默认学习率在 1000 张这种小数据集上首轮更新步长过大梯度直接溢出。另一类是标签里有非法框比如 w 或 h 为负、中心点坐标超过 1这种框会让回归损失在一开始就不可导。解决先用第 2 章的质量自检脚本过滤非法框再把 lr0 手动调到 0.005 或 0.002看看 loss 曲线是否回落。如果 lr0 调到 0.001 后 loss 还是炸那就去检查 txt 标签里有没有空行、全 0 的类别 id。5.2 现象Mac 上选 mps 训练一个 epoch 跑得比 CPU 还慢而且内存占用一路飞涨原因YOLO11 的部分算子在 MPS 后端没有完整实现会回退到 CPU造成频繁的同步等待加上 MPS 使用统一内存batch 设太大时显存和系统内存争抢速度反而更差。解决先把 batch 降到 8再观察速度如果还是慢直接--device cpu并且把imgsz降到 512 或 480。小数据集在 CPU 上跑 yolo11nepoch 时间虽然长一点但至少能稳定出结果。不要为了“用上 MPS”而死磕训练产出才重要。5.3 现象验证集 mAP50 很高但拿去拍一段真实路口视频大量漏检原因通常是数据划分泄漏。视频抽帧生成的数据集随机拆分会把同一段连续帧分进 train 和 test模型其实背下来了一部分画面。另外1000 张图如果全来自同一个摄像头视角模型学到的“车”会和背景纹理绑定。解决回到第 2 章的划分逻辑按视频片段或拍摄场景分组test 只放没见过的场景。接下来看 val 输出图把预测框和真实框叠在一起如果框普遍偏小或偏左再考虑 NMS 阈值和回归分支的权重。5.4 现象三种格式转换完YOLO 训练时标签出现不存在的第 4 类或者类别 id 对不上原因VOC/COCO 的类别 id 里带了背景类或者原始 id 不连续。比如 COCO 的类别 id 是 1、3、5如果转换脚本里直接用annotation[category_id]训练脚本会认为共有 5 个类别而标签里只出现 1、3、5类别 0 和 2 就成了空类。解决用第 3 章的ctg_map和class_map做一次重新编号转换完抽 10 张图把 txt 画回图片上确认类别 id 和框位置都正常。这个检查一定要做别指望训练过程替你发现问题YOLO 对“类别编号空洞”这种事很包容它只会默默多出一个错误类别。6. 训练完之后先做一次最小验收用验证指标和可视化决定是否部署训练脚本跑完最后的 best.pt 躺在runs/vehicle_yolo11/vehicle/weights/里。我不建议直接拿去写业务代码至少做一轮最小验收指标、可视化和导出。6.1 不要只盯 mAP50我的一步 val 验收顺序先打开results.png看 val loss 和 train loss 的间距间距过大就是过拟合信号。再看控制台输出的 Precision、Recall、mAP50 和 mAP50-95。1000 张图的单类车辆检测mAP50 在 0.9 左右算正常mAP50-95 如果能到 0.6 以上说明模型对尺度变化比较稳。紧接着看val_batch0_pred.jpg图中会有预测框和真实框叠加框如果整体偏小通常是小目标分辨率不够框如果抖动厉害可能需要调高conf阈值或者换更强的主干。6.2 从 best.pt 导出 ONNX跨平台部署前值得保留的习惯我的习惯是验证完直接导出 ONNX再拿一段没参与训练的实拍视频做一次推理确认。导出代码很简单from ultralytics import YOLO best YOLO(runs/vehicle_yolo11/vehicle/weights/best.pt) best.export(formatonnx, imgsz640)导出的 best.onnx 可以拿到支持 ONNX 的端侧设备或边缘硬件上继续转工具链。做这类导出时我会顺手写一个小的 README记下验证时的 conf 阈值、imgsz、类别顺序。因为隔一个月再回来用这个模型第一个忘的就是类别 id 对应关系。希望这些流程和踩坑总结能帮你在同样的数据集和脚本上少走弯路祝顺利跑出自己的第一个 YOLO11 车辆检测模型。本文还有配套的精品资源点击获取
返回列表