ARTICLE DETAIL

资讯详情

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

无人机车辆检测实战:VOC/COCO/YOLO标签转换与YOLO11三端一键训练

无人机车辆检测实战:VOC/COCO/YOLO标签转换与YOLO11三端一键训练 简介面向无人机场景车辆检测的实战数据集适合从事目标检测算法训练、智能交通与航拍视觉分析的开发者与研究者使用。数据集包含1000张真实场景高质量图片覆盖城市道路行驶车辆、道边停车、停车场、小区车辆以及车辆遮挡、严重遮挡等多种复杂情形类别划分为轿车car、货车van和巴士bus三类可用于无人机车辆检测项目或作为通用检测数据集的场景补充。资源包为1个PDF文件约2MB内含数据集基本情况介绍与获取方式标注采用labelimg完成质量较高并提供VOC(xml)、COCO(json)、YOLO(txt)三种主流格式可直接投入YOLO等算法训练。随附YOLO11一键训练脚本支持GPU(GPUs)、CPU及Mac芯片多平台方案并给出博主训练结果日志供参考。目前已有261人学习便于快速验证模型效果并复现训练流程。1. 无人机视角下的车辆检测1000 张图、三种标签格式与 YOLO11 一键训练到底怎么落地拿到「无人机场景-目标检测-车辆检测数据集-1000张图-对应VOC/COCO/YOLO三种格式标签支持GPU(GPUs)/CPU/Mac三平台YOLO11一键训练脚本」这个标题多数人的第一反应是数据集我有了脚本我也能写但为什么跑出来的 mAP 总比论文低一截问题往往不在模型而在数据标签的坐标系转换和训练入口的环境判断。无人机航拍视角下的车辆目标有几个鲜明特征尺度小、密集、遮挡严重、背景随高度剧烈变化。1000 张图不算多但如果标签格式对齐、训练脚本能在 GPU、CPU、Mac 三端自动切换设备这套流程完全可以在单机跑出一个可用的车辆检测基线。这篇内容面向已经会用 Python 和 PyTorch、但还没把无人机车辆检测完整跑通的人从标签格式差异讲到 YOLO11 一键脚本的设备判断逻辑再到实际训练时最容易翻车的几个参数。2. 三种标签格式的坐标系差异与转换脚本2.1 VOC、COCO、YOLO 的坐标定义到底差在哪VOC 格式的标注文件是 XML每个目标用bndbox记录xmin、ymin、xmax、ymax这四个值是绝对像素坐标原点在图像左上角。COCO 格式用 JSONbbox字段是[x, y, width, height]同样是绝对像素但注意它是左上角坐标加宽高不是右下角。YOLO 格式用.txt每行class_id x_center y_center width height全部是归一化到 0 到 1 之间的相对值中心点坐标加宽高。这三种格式在无人机车辆检测里最容易出问题的地方是VOC 转 YOLO 时忘记做归一化或者归一化时除了图像宽高而不是标注范围。另一个高频错误是 COCO 的bbox宽高在转换时被误当成右下角坐标。我一般会在转换脚本里加一步断言检查转换后的中心点是否落在 0 到 1 之间宽高是否为正且不超过 1。2.2 用 Python 把 VOC 批量转成 YOLO 和 COCO下面这个脚本处理 VOC 的 XML 目录输出 YOLO 的.txt和 COCO 的单个 JSON。假设图像是.jpgXML 和图像在同一目录下按文件名对应。import os import json import xml.etree.ElementTree as ET from PIL import Image # 类别映射无人机车辆检测通常只有 car / truck / bus 等 CLASS_MAP {car: 0, truck: 1, bus: 2, van: 3} def voc_to_yolo_and_coco(xml_dir, img_dir, out_yolo_dir, out_coco_json): os.makedirs(out_yolo_dir, exist_okTrue) coco_images [] coco_annotations [] ann_id 1 for xml_file in sorted(os.listdir(xml_dir)): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() filename root.find(filename).text img_path os.path.join(img_dir, filename) with Image.open(img_path) as im: w, h im.size coco_images.append({id: len(coco_images) 1, file_name: filename, width: w, height: h}) yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: continue cls_id CLASS_MAP[cls_name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 裁剪到图像边界无人机图像边缘常有截断目标 xmin max(0, min(xmin, w - 1)) ymin max(0, min(ymin, h - 1)) xmax max(0, min(xmax, w - 1)) ymax max(0, min(ymax, h - 1)) xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 过滤掉宽高为 0 的无效框 if bw 0 or bh 0: continue yolo_lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) coco_annotations.append({ id: ann_id, image_id: len(coco_images), category_id: cls_id, bbox: [xmin, ymin, xmax - xmin, ymax - ymin], area: (xmax - xmin) * (ymax - ymin), iscrowd: 0 }) ann_id 1 txt_name os.path.splitext(filename)[0] .txt with open(os.path.join(out_yolo_dir, txt_name), w) as f: f.write(\n.join(yolo_lines)) coco_out { images: coco_images, annotations: coco_annotations, categories: [{id: v, name: k} for k, v in CLASS_MAP.items()] } with open(out_coco_json, w) as f: json.dump(coco_out, f, indent2) # 调用示例 voc_to_yolo_and_coco( xml_dir./drone_vehicle/Annotations, img_dir./drone_vehicle/JPEGImages, out_yolo_dir./drone_vehicle/labels_yolo, out_coco_json./drone_vehicle/annotations_coco.json )这段代码的关键逻辑有三处。第一CLASS_MAP必须和后续 YOLO11 训练时的data.yaml里的names顺序完全一致否则类别索引会错位。第二裁剪到图像边界这一步在无人机图像里不能省因为航拍目标经常被图像边缘截断VOC 标注里可能出现xmax等于图像宽度甚至超出 1 像素的情况。第三过滤宽高为 0 的框这类框在 COCO 评估里会直接报错在 YOLO 训练里会产生 NaN 损失。参数方面xc、yc、bw、bh保留 6 位小数足够YOLO11 官方实现内部会再做一次浮点解析精度损失可以忽略。COCO JSON 里的area字段用于评估时的面积分组如果只做训练可以不算但建议保留方便后续用 pycocotools 做验证。2.3 转换后必须做的三项校验转换脚本跑完不代表数据就能用。我一般会做三个检查。第一随机抽 20 张图用 OpenCV 把 YOLO 格式的框画回图像上肉眼确认框的位置和大小没有整体偏移。第二统计每个类别的实例数量如果某个类别少于 50 个实例训练时大概率欠拟合需要考虑合并类别或补充数据。第三检查图像宽高比分布无人机图像如果同时存在 4:3 和 16:9 两种比例YOLO11 默认的imgsz640会做 letterbox 填充小目标在填充后可能进一步缩小这时候要考虑把imgsz提到 960 或 1280。import cv2 import numpy as np def visualize_yolo_label(img_path, label_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh map(float, parts) 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), (0, 255, 0), 2) cv2.putText(img, class_names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(check_vis.jpg, img) visualize_yolo_label( ./drone_vehicle/JPEGImages/000001.jpg, ./drone_vehicle/labels_yolo/000001.txt, [car, truck, bus, van] )这个可视化脚本只依赖 OpenCV跑一张图就能看出转换是否正确。如果框整体偏左上或偏右下多半是归一化时用错了宽高或者 VOC 的xmax被误当成了宽。3. YOLO11 一键训练脚本GPU、CPU、Mac 三端设备判断与参数配置3.1 为什么需要一键脚本而不是直接调 ultralytics 命令YOLO11 的官方训练入口是yolo detect train或 Python 里的YOLO(yolo11n.pt).train(...)。但在实际项目里训练环境往往不统一实验室服务器有 NVIDIA GPU个人笔记本是 Mac 的 MPS还有同事只有 CPU。如果每次训练都手动改device参数很容易出现「在 GPU 上跑通、在 Mac 上报错」的情况。一键脚本的核心价值不是省几条命令而是把设备判断、路径检查、数据配置校验和训练参数默认值封装成一个可复用的入口。我一般会把脚本分成三层环境探测层、数据校验层、训练启动层。环境探测层负责判断当前机器有没有 CUDA、有没有 MPS、都没有就回退到 CPU。数据校验层检查data.yaml里的路径是否存在、类别数是否和标签里的最大 class_id 一致。训练启动层根据设备类型调整batch、workers和amp参数。3.2 设备探测与训练参数自动适配下面这个脚本用torch探测设备然后根据设备类型设置不同的默认参数。注意 Mac 的 MPS 后端对某些算子支持不完整YOLO11 在 MPS 上训练时需要把amp关掉否则可能出现梯度为 NaN。import os import sys import yaml import torch from ultralytics import YOLO def detect_device(): if torch.cuda.is_available(): return cuda, torch.cuda.device_count() if hasattr(torch.backends, mps) and torch.backends.mps.is_available(): return mps, 1 return cpu, 1 def build_train_args(device, gpu_count): args { data: ./drone_vehicle/data.yaml, epochs: 100, imgsz: 960, patience: 20, save: True, pretrained: True, optimizer: AdamW, lr0: 0.001, lrf: 0.01, cos_lr: True, close_mosaic: 10, val: True, plots: True, } if device cuda: args[device] list(range(gpu_count)) if gpu_count 1 else 0 args[batch] 16 * max(1, gpu_count) args[workers] 8 args[amp] True elif device mps: args[device] mps args[batch] 8 args[workers] 4 args[amp] False # MPS 上 AMP 不稳定 else: args[device] cpu args[batch] 4 args[workers] 2 args[amp] False return args def check_data_yaml(yaml_path): with open(yaml_path) as f: cfg yaml.safe_load(f) for key in [train, val, names]: if key not in cfg: raise ValueError(fdata.yaml 缺少字段: {key}) train_path cfg[train] if not os.path.exists(train_path): raise FileNotFoundError(f训练集路径不存在: {train_path}) return cfg def main(): device, gpu_count detect_device() print(f检测到设备: {device}, GPU 数量: {gpu_count}) cfg check_data_yaml(./drone_vehicle/data.yaml) print(f类别: {cfg[names]}) args build_train_args(device, gpu_count) model YOLO(yolo11s.pt) model.train(**args) if __name__ __main__: main()设备探测的逻辑很直接torch.cuda.is_available()为真就用 CUDA否则检查 MPS最后回退 CPU。参数适配里几个关键点值得展开。imgsz设成 960 而不是默认的 640是因为无人机车辆目标普遍偏小640 分辨率下很多车辆只有十几个像素YOLO11 的 P3 特征图经过 8 倍下采样后几乎无法保留有效信息。batch在单卡 CUDA 上给 16MPS 给 8CPU 给 4这个数值不是绝对的取决于显存或内存大小如果训练时出现 OOM优先降batch而不是降imgsz。amp在 CUDA 上开启可以省显存、提速但在 MPS 和 CPU 上必须关掉这是血泪经验开着 AMP 在 Mac 上跑几十个 iteration 后 loss 直接变 NaN。close_mosaic设成 10 表示最后 10 个 epoch 关闭 mosaic 增强。Mosaic 对小目标检测有帮助但训练末期关闭可以让模型更适应真实分布这个技巧在 YOLOv5 时代就有YOLO11 依然适用。3.3 data.yaml 的写法与类别数对齐YOLO11 的data.yaml结构很简单但无人机车辆检测里最容易错的是names的顺序和nc的对应关系。下面是一个四类车辆检测的示例。path: ./drone_vehicle train: images/train val: images/val test: images/test nc: 4 names: 0: car 1: truck 2: bus 3: van注意train和val写的是相对路径相对于path字段。如果直接写绝对路径也可以但换机器时容易失效。nc必须等于names的条目数且names的键必须从 0 开始连续。如果 VOC 转换时CLASS_MAP里 car 是 0、truck 是 1这里就必须一致。我见过有人把names写成列表[car, truck, bus, van]YOLO11 也能解析但键值对形式更不容易出错。数据集划分方面1000 张图建议按 7:2:1 分 train、val、test。如果某些类别的实例数太少可以考虑分层抽样保证每个 split 里都有所有类别。划分脚本可以用sklearn.model_selection.train_test_split对文件名列表做一次随机划分然后按划分结果把图像和标签分别复制到对应目录。3.4 训练启动与日志观察脚本跑起来后控制台会输出每个 epoch 的 box_loss、cls_loss、dfl_loss 和 mAP50、mAP50-95。无人机车辆检测里前 10 个 epoch 的 box_loss 下降速度是关键指标。如果 box_loss 在前 5 个 epoch 几乎不降大概率是学习率太低或者标签有问题。我一般会把lr0从 0.001 提到 0.01 试一次如果 loss 震荡严重再降回来。训练日志里还有一个容易被忽略的字段是instances表示当前 batch 里的目标数量。如果这个值长期低于 5说明数据集中小目标占比过高或者imgsz太小导致部分目标在数据加载时被过滤掉了。YOLO11 默认会过滤掉宽或高小于 2 像素的框如果无人机图像里车辆只有 3 到 4 个像素这个过滤阈值需要调整但更根本的解决办法是提高输入分辨率。# 如果不想用 Python 脚本也可以直接用命令行但设备参数需要手动指定 yolo detect train data./drone_vehicle/data.yaml modelyolo11s.pt epochs100 imgsz960 batch16 device0 ampTrue命令行方式适合快速验证但跨平台时还是建议用前面的 Python 脚本因为设备判断逻辑已经封装好了。4. 无人机车辆检测训练中的避坑与排查4.1 现象训练 loss 正常下降但 mAP 始终为 0原因通常有两个。第一验证集的标签路径在data.yaml里写错了YOLO11 加载不到验证标签评估时把所有预测都当成假阳性。第二类别索引错位比如训练标签里 car 是 0但data.yaml里 car 是 1模型学到的类别和评估时的类别对不上。解决办法是先用第 2 章的可视化脚本检查验证集标签再打印data.yaml的names和标签文件里的最大 class_id 做对比。4.2 现象Mac 上训练几个 epoch 后 loss 变成 NaN这是 MPS 后端加 AMP 的经典翻车场景。MPS 对torch.cuda.amp的替代实现不完整某些算子在半精度下会溢出。解决办法是在build_train_args里对 MPS 设备强制ampFalse同时把lr0降到 0.0005 左右。如果关掉 AMP 后仍然 NaN检查数据里有没有宽高为 0 的框这类框在计算 DFL loss 时会产生无穷大。4.3 现象GPU 显存足够但训练速度极慢常见原因是workers设得太小数据加载成了瓶颈。在 CUDA 设备上workers建议设为 CPU 核心数的 1 到 2 倍但不要超过 16否则进程调度开销反而拖慢速度。另一个原因是图像尺寸太大imgsz1280比imgsz640的计算量大约翻两番如果 GPU 是入门级优先保证batch能跑满再考虑提imgsz。4.4 现象验证集 mAP 比训练集低很多无人机车辆检测里如果训练集和验证集的拍摄高度、光照条件差异大过拟合会非常明显。解决办法是在data.yaml里把val指向和训练集同分布的图像或者用mosaic、mixup、copy_paste等增强手段提高泛化。YOLO11 默认开启 mosaic但mixup和copy_paste需要手动在train_args里加注意copy_paste对实例分割任务更有效纯检测任务收益有限。4.5 现象CPU 训练时内存爆满CPU 训练没有显存限制但内存容易被数据加载和模型副本吃满。batch4、workers2是保守配置如果内存仍然不够把imgsz降到 640或者用yolo11n.pt而不是yolo11s.pt。另外CPU 训练时amp必须关掉半精度在 CPU 上不仅不加速还会因为类型转换增加内存开销。5. 从 1000 张图到可用模型小目标增强与验证技巧1000 张图在无人机车辆检测里属于小样本直接训 YOLO11s 很容易过拟合。我一般会做两件事一是用imgsz960配合close_mosaic10让模型在训练末期看到更多真实尺度的小目标二是在验证阶段用conf0.001而不是默认的 0.25因为小目标的置信度普遍偏低默认阈值会漏掉大量正确预测。下面这个验证脚本可以输出不同conf阈值下的 mAP 曲线帮助判断模型的实际可用区间。from ultralytics import YOLO model YOLO(./runs/detect/train/weights/best.pt) for conf in [0.001, 0.01, 0.05, 0.1, 0.25]: metrics model.val(data./drone_vehicle/data.yaml, confconf, iou0.5) print(fconf{conf}, mAP50{metrics.box.map50:.4f}, mAP50-95{metrics.box.map:.4f})跑完这个循环如果conf0.001时 mAP50 比conf0.25高 10 个点以上说明模型对小目标的置信度校准不好可以考虑在训练时加label_smoothing或者用focal_loss替代默认的 BCE loss。YOLO11 的损失函数在ultralytics/utils/loss.py里改起来需要一定代码量更简单的做法是增加小目标在训练集中的比例或者用copy_paste把车辆实例复制到更多背景上。另一个实用技巧是测试时增强TTA。YOLO11 的val接口支持augmentTrue会对每张图做多尺度翻转推理再合并结果对小目标检测通常有 1 到 3 个点的 mAP 提升代价是推理时间翻倍。如果部署环境对延迟不敏感这个开关值得打开。metrics model.val(data./drone_vehicle/data.yaml, augmentTrue, conf0.01) print(metrics.box.map50)最后说一个我踩过的坑无人机图像里的车辆方向任意YOLO11 默认的水平框在密集场景下重叠严重NMS 后容易漏检。如果数据里车辆朝向一致比如高速公路场景可以尝试旋转框检测但 YOLO11 官方对旋转框的支持不如水平框成熟需要改数据格式和损失函数。对于 1000 张图的规模我建议先把水平框做到 mAP50 在 0.7 以上再考虑是否值得投入旋转框。希望帮到你。本文还有配套的精品资源点击获取
返回列表