ARTICLE DETAIL

资讯详情

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

夜间无人机车辆检测:YOLO11数据集预处理与跨平台训练实战

夜间无人机车辆检测:YOLO11数据集预处理与跨平台训练实战 简介无人机场景夜间车辆检测数据集是一份聚焦无人机夜间视角下车辆识别任务的高质量图像资源面向目标检测算法开发与计算机视觉学习者。数据集包含1000张真实夜间场景图片覆盖城市道路行驶、道边停车、停车场、小区以及车辆遮挡等复杂情况全部统一标注为car单个类别便于直接开展YOLO等模型的训练与评估。资源以PDF形式打包内含数据集缩略图、labelimg标注截图、数据集基本情况及网盘获取方式共1个PDF文件大小约4.13MB适合需要先了解数据构成再获取完整数据的用户。此外资源提供VOC、COCO、YOLO三种常见格式标注并附赠支持GPU、CPU及MacM芯片平台的YOLO11一键训练脚本和博主训练结果日志使不同硬件条件下的使用者都能快速启动训练。目前已有373人学习适合无人机场景车辆检测项目作为数据补充与技术参考。1. 夜间无人机车辆检测数据集的底子决定你的YOLO11训练顺不顺准备做“无人机场景-目标检测-夜间车辆检测数据集”这套流程时最容易翻车的往往不是YOLO11本身而是数据和脚本的适配。这个数据集包含1000张夜间无人机俯拍图并给了VOC/COCO/YOLO三种格式标签还配了一份支持GPU(GPUs)/CPU/Mac三平台的一键训练脚本看起来已经很完整但拿到之后直接开训仍会踩到不少隐蔽的坑。夜间俯视视角下车辆在整张图里的面积占比很小低照度又让边界模糊路灯、树影、屋顶都可能成为误检来源。适合谁来用一类是刚接触YOLO11想立刻跑通“标签格式→训练脚本→验证评估”全流程的人另一类已经在做夜间目标检测或低空遥感检测需要一套规范化的数据和可复现的训练基线。我拿到这种数据集不会花太多时间在安装环境上而会把精力放在三件事确认三个格式的标签是否对齐、搞清夜间的增强策略、根据硬件选训练参数。下面从数据体检开始一步一步把脚本真正变成能训练出模型的工具。2. 先摸数据底细1000张夜间无人机图像的划分与标签核对标题里写了1000张图但训练脚本不会替你做数据体检。所有后续报错追到最后大半是标签路径找不到、坐标越界、或者训练集和验证集信息泄漏。这一章先解决最基础的三件事看目标尺度分布、划分数据集、核对三份标签。2.1 夜间无人机车辆检测为什么容易翻车无人机俯视视角让车辆变成典型的小目标一辆车在1920乘1080的图像里可能只有几十像素宽占图像面积不到百分之一。YOLO11通过多层特征图金字塔做预测小目标主要落在高分辨率浅层特征上而浅层特征语义信息弱容易把车灯、路灯混在一起。夜间低照度进一步降低对比度不亮灯的静止车辆几乎和沥青路面融为一体。这类问题不能直接套白天预训练权重常见做法是先统计一下训练集里目标到底有多小再决定输入分辨率。我习惯用下面这段脚本快速看YOLO标签的尺度分布from pathlib import Path stats [] for txt in Path(labels/train).glob(*.txt): for line in txt.read_text().splitlines(): _, cx, cy, w, h map(float, line.split()) stats.append((w, h)) stats.sort(keylambda x: x[0] * x[1]) for w, h in stats[:20]: print(f归一化w{w:.3f}, h{h:.3f}, 面积占比{w*h*100:.3f}%)这段脚本把训练集YOLO标签里的所有框读取出来按面积排序打印最小的20个。面积占比指归一化后w乘h例如0.02乘0.02就是0.04%。如果发现大量车辆目标面积占比低于0.05%那么优先提高imgsz到960或1280比把模型从yolo11s换到yolo11l更有效。因为夜间小目标在高分辨率特征图上还剩几个像素下采样太狠就彻底丢了。接下来还要给数据做夜间场景增强。光照变化是夜间检测的主要干扰源我会在训练管线里挂上针对夜间的Albumentations增强代码和参数如下import albumentations as A night_aug A.Compose([ A.RandomBrightnessContrast(brightness_limit(-0.25, 0.15), contrast_limit(-0.2, 0.2), p0.8), A.MotionBlur(blur_limit(3, 7), allow_shiftedTrue, p0.3), A.RandomGamma(gamma_limit(80, 120), p0.4), A.HueSaturationValue(hue_shift_limit(-10, 10), sat_shift_limit(-20, 20), val_shift_limit(-20, 20), p0.5), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]))参数说明亮度对比度的区间故意不对称夜间原图偏暗被进一步压暗是常态运动模糊模拟无人机低空飞行时的快门拖影gamma偏移模拟不同路灯色温。不要加太强的Cutout或RandomRain小目标被切掉一个角后会整框漏检。2.2 先做两件事图像数量核对与8:1:1划分打开数据集第一步是数图片数标签。1000张图如果有999张有标签另一张缺标签YOLO训练时可能不报错但验证指标会莫名其妙地低一点。先跑一个最简单的核对脚本from pathlib import Path data_root Path(dataset) imgs sorted((data_root / images).glob(*.jpg)) print(图片总数:, len(imgs)) for split in [train, val, test]: img_dir data_root / images / split label_dir data_root / labels / split imgs list(img_dir.glob(*.jpg)) txts list(label_dir.glob(*.txt)) print(f{split}: images{len(imgs)}, labels{len(txts)})如果数量对不上先检查文件名是否完全同名不要用image1.jpg对image1.JPG这种大小写不一致的文件名Linux上会直接视为不同文件。数量没问题后我一般按8:1:1划分训练、验证、测试随机种子固定住保证可复现import random, shutil from pathlib import Path imgs sorted(Path(dataset/images).glob(*.jpg)) random.seed(42) random.shuffle(imgs) n len(imgs) for split, subset in [(train, imgs[:int(n*0.8)]), (val, imgs[int(n*0.8):int(n*0.9)]), (test, imgs[int(n*0.9):])]: out_dir Path(dataset/split) / split out_dir.mkdir(parentsTrue, exist_okTrue) for p in subset: shutil.copy2(p, out_dir / p.name) print(split, len(subset))逻辑说明先把图片路径全部读出来固定随机种子打乱然后按比例切三段。这里只是把图片复制出来标签后续可以按同样的文件名去labels目录取。如果图片扩展名是PNG把glob(*.jpg)改成glob(*.png)即可。这个脚本能跑通的最少代码就是这些不需要引入其他框架。但无人机的连续帧是强相关的直接随机划分可能在训练集和验证集里出现近乎重复的相邻帧验证指标虚高。更严谨的做法是按拍摄架次或序列ID分组。假设文件名前缀代表不同架次例如FL01_0001.jpg、FL02_0001.jpg则可以用GroupShuffleSplitfrom sklearn.model_selection import GroupShuffleSplit paths sorted(Path(dataset/images).glob(*.jpg)) groups [p.name.split(_)[0] for p in paths] gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(paths, groupsgroups))这个代码的核心是让同一个前缀的所有帧只出现在一侧。如果嫌多装一个sklearn依赖也可以按前缀手动排序后切分效果接近。测试集建议单独从另一个架次里抽不要和训练验证共用序列。2.3 标签文件清点VOC/COCO/YOLO三份是否对得上这个数据集给三种格式标签但最终训练用YOLO格式。我的做法是先给YOLO格式做体检因为坐标越界和类别号越界会在训练时产生隐性影响比如loss变成NaN、AP曲线断崖。from pathlib import Path labels_dir Path(dataset/labels) class_names [car, truck, bus, motorcycle] # 以实际data.yaml为准 bad 0 for txt in labels_dir.glob(*.txt): img_path Path(dataset/images) / (txt.stem .jpg) if not img_path.exists(): print(缺图像:, txt.name) bad 1 continue for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: print(行格式错误:, txt.name, line) bad 1 continue cls int(parts[0]) cx, cy, w, h map(float, parts[1:]) if cls len(class_names): print(类别号越界:, txt.name, cls) bad 1 if not (0 cx 1 and 0 cy 1 and w 0 and h 0): print(归一化坐标越界:, txt.name, line) bad 1 print(检查完成问题数:, bad)逻辑说明YOLO标签每行是“类别 中心x 中心y 宽度 高度”其中归一化坐标都应落在0到1之间。空txt不一定是错误背景图确实可以没有目标但要确认不是转换漏了。COCO格式则重点检查“有图无标注”import json coco json.loads(open(instances_val.json).read()) ann_img_ids {ann[image_id] for ann in coco[annotations]} all_img_ids {img[id] for img in coco[images]} print(有图但完全无标注的id:, all_img_ids - ann_img_ids)如果该批图每张都应有车辆并存在大量无标注id说明COCO转换时漏了。VOC格式通常看XML里是否包含difficult1这类框在训练时不能简单保留我在第3章会说怎么处理。3. VOC/COCO/YOLO标签互转转换脚本与四个边界坑三种格式并存的价值在于交叉验证。当你发现VOC里某个框在YOLO里不存在或者COCO里某张图整体缺标注时就要动手重转。这一章把互转时容易踩的坑拆开讲照着脚本改类别名就能用。3.1 三种格式差异坐标单位、类别归属和文件结构先看一张总表后面所有转换都在围绕这张表展开格式文件形式坐标单位一个文件包含多少目标VOCXML像素左上角右下角一个XML含多个objectCOCOJSON像素左上角宽高一个JSON含所有图YOLOTXT归一化中心点宽高一个TXT对应一张图一行一个目标互转的核心只有两件事坐标单位换算和类别字符串到数字ID的映射。第一件用除法第二件需要写死一张对照表。不建议用类别名的字母顺序动态生成ID不同工具的排序规则不一样一旦换了环境就会整体错位。我一般固定用[car, truck, bus, motorcycle]这种顺序并在dataset.yaml里保持同样顺序。3.2 VOC转YOLO读取XML的bndbox做归一化VOC的XML里bndbox给的是xmin, ymin, xmax, ymax单位像素。转YOLO时要先算中心再除以图像宽高import xml.etree.ElementTree as ET from pathlib import Path class_names [car, truck, bus, motorcycle] def voc_to_yolo(xml_path, out_txt, img_w, img_h): root ET.parse(xml_path).getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: 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) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_names.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) Path(out_txt).write_text(\n.join(lines))逻辑说明VOC的框是左上角加右下角中心点就是两点相加除以二除以图像宽高后得到0到1的相对值。img_w, img_h务必来自真实图像解码不要直接用XML里的size无人机图像经常被处理软件旋转或重保存XML记录的长宽可能和OpenCV读出的不一致。注意转换完的txt文件名必须与jpg完全同名后缀一致放在labels/目录下Ultralytics才能自动对应。3.3 COCO转YOLO从JSON的annotations提取每帧框COCO的bbox字段是[x, y, width, height]这个x、y是左上角坐标不是VOC的右下角。很多人在这里少加半个宽度导致中心点整体偏左上。import json from pathlib import Path coco json.loads(Path(instances_train.json).read_text()) images {img[id]: img for img in coco[images]} cat_map {cat[id]: cat[name] for cat in coco[categories]} class_names [car, truck, bus, motorcycle] for ann in coco[annotations]: img images[ann[image_id]] img_w, img_h img[width], img[height] x, y, w, h ann[bbox] if w 0 or h 0: continue cx (x w / 2) / img_w cy (y h / 2) / img_h cx min(max(cx, 0), 1) cy min(max(cy, 0), 1) cls_id class_names.index(cat_map[ann[category_id]]) out Path(labels) / (img[file_name].rsplit(., 1)[0] .txt) with out.open(a) as f: f.write(f{cls_id} {cx:.6f} {cy:.6f} {min(w/img_w, 1):.6f} {min(h/img_h, 1):.6f}\n)参数说明ann[bbox]里的w、h已经是宽高所以中心x等于x加w的一半。末尾用min(...,1)夹紧是为了防止标注线超出图像边界导致训练出现NaN。COCO的类别ID不连续且常从1开始所以要先建立cat_id - name映射再换成YOLO从0开始的索引。3.4 转换后一定要做反向验证代码跑完不代表坐标对。我会随机抽几张图把YOLO标签画回原图肉眼看框的位置是否贴合车辆主体import cv2 from pathlib import Path img cv2.imread(sample.jpg) h, w img.shape[:2] for line in Path(sample.txt).read_text().splitlines(): _, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(sample_check.jpg, img)这段代码把归一化中心点换算回像素坐标直接画框。夜间图如果框总偏在车灯一侧说明标签本身就有人为偏移再训练也只是拟合错误偏移。转换后如果要从YOLO转回VOC同样要做坐标夹紧xmin max(0, xmin)否则VOC工具不接受负数坐标。边界坑里最容易被忽略的是VOC的difficult字段。VOC标注常把被遮挡、难以辨认的目标标成difficult1转换时如果不过滤等于让模型把极难样本当普通正例学后期loss一直降不下来。常见做法是跳过difficult目标或者在转换时单独输出一个ignore目录。COCO的iscrowd目标同理也应该剔除。4. YOLO11一键训练脚本GPU/CPU/Mac三平台适配与常见问题排查数据体检和标签转换做完训练脚本就是最后一道门槛。支持GPU(GPUs)/CPU/Mac三平台的一键脚本核心是设备自动选择和参数自动适配。这一章给一份可落地的最小脚本然后把最容易踩的五个坑按现象、原因、解决办法拆开。4.1 一键训练脚本的最小实现我习惯用Python写训练入口而不是shell因为Windows、Linux、Mac的shell命令差异太大Python能统一处理。脚本只要做两件事自动选择设备然后把训练参数传给Ultralytics YOLO11。import argparse import torch from ultralytics import YOLO def pick_device(): if torch.cuda.is_available(): return cuda:0 # 多卡可传0,1,2 if getattr(torch.backends, mps, None) and torch.backends.mps.is_available(): return mps # Apple Silicon Mac return cpu def main(): parser argparse.ArgumentParser() parser.add_argument(--data, defaultdataset.yaml) parser.add_argument(--model, defaultyolo11n.pt) parser.add_argument(--epochs, typeint, default100) parser.add_argument(--batch, typeint, defaultNone) parser.add_argument(--imgsz, typeint, default640) parser.add_argument(--device, defaultNone) args parser.parse_args() device args.device or pick_device() model YOLO(args.model) results model.train( dataargs.data, epochsargs.epochs, batchargs.batch, imgszargs.imgsz, devicedevice, projectruns/drone_night, namefyolo11_{device.replace(:, )}, exist_okTrue, patience30 ) print(best.pt:, results.save_dir) if __name__ __main__: main()逻辑说明pick_device()先判断CUDA再判断Apple Silicon的MPS最后落到CPU。Ultralytics收到devicemps会走PyTorch的MPS后端。Intel Mac没有MPS会自动走到CPU这是正常行为。如果机器有多个GPU把cuda:0改成0,1即可Ultralytics会做数据并行分发但batch要随卡数放大。参数说明--model是YOLO11预训练权重名yolo11n.pt最小yolo11s.pt推荐给夜间小目标。--batch默认None表示让Ultralytics自动探测但在CPU和MPS上自动值往往偏大建议手动设成4或8。--patience设30表示连续30轮验证指标不涨就早停可以避免小数据集过拟合。4.2 dataset.yaml结构与训练入口Ultralytics训练时只认YOLO格式标签dataset.yaml里的path填数据集根目录train和val填图片子目录。它会在同级labels目录下寻找对应的txt标签目录不对就会报“no labels found”。path: /abs/path/to/dataset train: images/train val: images/val test: images/test nc: 4 names: [car, truck, bus, motorcycle]目录结构要保持默认对应关系dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── dataset.yaml如果标签目录叫annotations而不是labels最简单的方式不是改Ultralytics源码而是在训练前做一个软链接或者写一行Python把目录改成标准结构。我一般会在训练脚本里自动生成dataset.yaml顺便检查目录是否存在from pathlib import Path root Path(dataset).resolve() yaml_text f path: {root} train: images/train val: images/val test: images/test nc: 4 names: [car, truck, bus, motorcycle] Path(dataset.yaml).write_text(yaml_text)这个脚本的优点是路径自动转成绝对路径避免用户从不同工作目录启动训练时找不到数据。之后运行一行命令python train.py --data dataset.yaml --model yolo11s.pt --epochs 80 --imgsz 640GPU机器会调用CUDAMac会自动走MPS没有独立显卡的笔记本会退到CPU继续跑至少能验证完整流程。4.3 避坑三平台上的系列常见问题这里的每条记录都是我实际踩过的现象、原因、解决按三段说方便排查。问题1Mac上训练到一半直接退出现象进程报RuntimeError: MPS backend out of memory有时是整个系统内存被打满。原因MPS使用统一内存batch设太大或Ultralytics开了图像缓存内存会瞬间翻倍另外Mac上部分自定义算子回退CPURAM占用更高。解决把batch调成4设置cacheFalseworkers降到2模型换成yolo11n.pt可以临时绕过去。MPS能用但别把它当满血GPU对待训练分辨率也建议从640起步。问题2NVIDIA GPU显存充足训练速度却和CPU差不多现象nvidia-smi能看到进程但每个epoch耗时几分钟。原因PyTorch的CUDA版本和驱动不匹配导致计算回退到CPU或者worker过多数据增强已把CPU吃满GPU在空等。解决先跑python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))返回False就重装对应CUDA版PyTorch返回True就把workers降到4并检查训练日志里的device显示是cuda:0还是cpu。问题3loss降得很好验证集mAP却很低现象训练集mAP50到0.9验证集只有0.4或者AP50高但AP75很低。原因一个是划分泄漏无人机连续帧同时进了训练和验证一个是过拟合1000张图的量对yolo11s偏少训练后期开始背训练集。解决按2.2的GroupShuffleSplit重新划分epochs降到60patience设30打开Mosaic和MixUp增强。夜间车辆框对IoU很敏感AP75低通常是框回归不稳把imgsz从640提到896会直接改善。问题4训练一开始就刷WARNING no labels found现象日志大量出现no labels found in dataset/images/train第一轮loss为0。原因标签目录和图片目录不是兄弟关系或者txt是空的。解决确认数据集根目录下有labels/train每张图对应同名txttxt为空可以但文件必须存在。另一个隐藏原因是Windows下txt被存成UTF-8带BOM格式Ultralytics读第一行会多一个不可见字符用脚本把所有txt转成无BOM的UTF-8。问题5用--batch -1在Mac上跑报CUDA out of memory现象机器没有CUDA报错却指向CUDA。原因Ultralytics的自动batch探测优先调设备显存在MPS上不够保守会给出一个远超内存的大值。解决Mac和CPU机器上不要用-1把batch显式写成4或8。GPU机器上也可以先设16试一轮再根据显存余量往上加不要盲目信任自动值。5. 从训练到可用推理可视化与三平台导出技巧训练结束不是终点best.pt能不能用要看验证集可视化结果和部署导出。我固定跑三个动作半小时内就能判断这套数据训练得到的模型是否可靠。5.1 验证集可视化先盯住误检而不是精度from ultralytics import YOLO model YOLO(runs/drone_night/yolo11s/weights/best.pt) results model.predict(sourcedata/images/test, conf0.25, imgsz896, saveTrue)跑完到runs/detect/predict看结果图。夜间场景重点看两类误检路灯和车灯被当成车树冠阴影和屋顶边缘被当成车。这两类问题在mAP曲线里不明显可视化一眼就能看出。如果误检集中在车灯就在增强里加强亮度抖动如果集中在阴影需要补充对应的负样本。5.2 导出三平台格式一行命令做部署前置验证没问题后我会考虑导出。GPU服务器导出TensorRTCPU导出ONNXMac导出CoreMLyolo export modelruns/drone_night/yolo11s/weights/best.pt formatengine halfTrue device0 yolo export modelruns/drone_night/yolo11s/weights/best.pt formatonnx dynamicTrue yolo export modelruns/drone_night/yolo11s/weights/best.pt formatcoreml参数说明halfTrue只对TensorRT有效需要显卡支持FP16ONNX的dynamicTrue让输入尺寸不固定方便后续自定义imgszCoreML导出后在M系列Mac上会走ANE加速但第一次调用最好先跑一次预热。有一个容易忽略的细节导出前要把imgsz固定成训练时用的尺寸。如果训练用896导出用640夜间小目标会掉得比想象多。我最开始贪快训练用1280导出用640夜里车灯密集的场景漏检接近三分之一。这个教训让我后来每次导出都强制检查输入尺寸和训练分辨率一致。做夜间无人机车辆检测数据、标签、设备和导出没有多少玄学空间只要按数据体检、格式互转、三平台训练、可视化验证这条线走多数翻车都能提前拦住。推荐你也把自己的数据按这套流程过一遍先求稳定再求速度。希望帮到你。本文还有配套的精品资源点击获取
返回列表