简介:这是一份面向无人机夜间车辆检测项目开发者的配套数据集获取指南,以PDF形式呈现。资料依托真实夜间无人机场景的1000张高质量车辆图片,覆盖城市道路行驶、道边停车、停车场、小区及严重遮挡等丰富情境,统一标注为car类别,并整理为VOC、COCO、YOLO三种标准格式,可直接用于YOLO等算法训练。同时附带YOLO11一键训练脚本,支持GPU、CPU、Mac(M芯片)多平台运行,并给出博主训练结果日志作参考。资源共1个文件(PDF),大小4.13MB,已有373人学习。通过该PDF可获取百度网盘中的完整数据集及详细说明,适合作为夜间无人机车辆检测项目的数据补充与实战训练参考。
1. 夜间无人机车辆检测:1000张图加三格式标签,这个数据集能让你少走两周弯路
夜间无人机视角下的车辆检测,和白天地面视角完全是两个问题。相机噪点多、车灯过曝、车身和沥青路面几乎一个灰度,白天跑得好好的模型到晚上经常漏检。这份数据集就是冲这个场景来的:1000张真实夜间无人机视角图片,覆盖城市道路行驶、道边停车、停车场、小区、车辆遮挡和严重遮挡几类典型情况,不区分轿车、SUV或货车,统一标成一个car类别。配套给了 VOC(xml)、COCO(json)、YOLO(txt) 三种格式标签,外加一套支持 GPU(GPUs)、CPU、Mac(M芯片) 三平台跑的 YOLO11 一键训练脚本。适合正在做无人机夜间巡检、安防监控、智慧交通项目的人,也适合想把现有检测模型往夜间场景扩的工程团队——拿到手可以直接开训,不用再从零标数据。
2. 三种标注格式与类别设计:VOC/COCO/YOLO 的结构差异和选用逻辑
2.1 为什么只保留 car 一个类别:类别合并的工程考量
这个数据集最反直觉的地方,是它故意不区分轿车、SUV、货车,全部框成car。第一次用的人可能会觉得这是偷懒,实际做夜间检测项目时你会发现,这个决定很务实。
夜间无人机俯视视角下,轿车和 SUV 的轮廓差异本来就小,车顶反光又经常遮掉局部特征,强行分三类只会让标注噪声变大、类别间特征重叠严重,模型训练时反而容易在轿车和 SUV 之间反复摇摆。货车虽然长一点,但夜间车灯和车身灰度特征并不稳定。合并成单一car类别之后,模型只需要回答「这里有没有车」,不需要回答「这是什么车」,对巡检、计数、违停检测这类业务场景完全够用,训练收敛也快得多。
另外一个实际好处是标注一致性。供数据集的人用的是 labelimg,画框的人如果要在三种车型间反复判断,1000 张图很容易标出前后矛盾的结果。单类别把标注难度拉低,质量反而更容易保证。你在自己的项目里如果遇到类别噪声大的情况,也可以考虑做类似的合并操作——先保证检测率,再谈细分类。
2.2 VOC 的 XML 与 COCO 的 JSON 怎么读:标注字段逐个拆
三种格式里,VOC 的 XML 最好读,结构直白,一个文件对应一张图。labelimg 标注后自动生成 XML,核心内容就是<object>节点下的<name>和<bndbox>。
<annotation> <folder>night_vehicle</folder> <filename>DJI_00123.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>car</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>824</xmin> <ymin>412</ymin> <xmax>1106</xmax> <ymax>655</ymax> </bndbox> </object> </annotation>注意<truncated>和<difficult>这两个字段。truncated=1 表示目标在图像边缘被截断,difficult=1 表示难识别目标。这个数据集的遮挡车场景里,部分车辆的框就是 truncated=1。训练时如果发现模型对边缘车辆漏检严重,可以统计一下这两个字段的分布,决定是保留还是清洗。
COCO 格式是一个大 JSON 文件搞定所有图片,核心是images、annotations、categories三段。images里存宽高和文件名,annotations里每条记录对应一个目标框,bbox字段的格式是[x, y, width, height],注意是左上角坐标加宽高,不是中心点。
import json with open("night_vehicle_coco/annotations.json", "r", encoding="utf-8") as f: coco = json.load(f) cat_id_map = {c["id"]: c["name"] for c in coco["categories"]} print("类别映射:", cat_id_map) # 统计每张图的标注数量 from collections import Counter img_ann_cnt = Counter() for ann in coco["annotations"]: img_ann_cnt[ann["image_id"]] += 1 # 找出标注数最多的前 5 张图 top5 = img_ann_cnt.most_common(5) print("标注最多的前5张图:", top5)这段脚本用来快速检查 COCO 标注有没有漏掉整张图的情况。返回的top5里如果出现某张图标注数量异常多,比如超过 30,大概率是这张图里有大量小目标,或者是标框重叠严重,需要打开原图复查。初次拿到任何 COCO 格式数据集,第一步建议先跑这个统计,确认categories里的类别 id 和实际标注对得上。
2.3 YOLO 的 TXT 最省心但最容易错:归一化坐标的换算逻辑
YOLO 格式在三种标注里最简洁,每个 txt 文件一行一个目标,五个数字分别是class_id, x_center, y_center, width, height,全部是相对图片宽高的归一化值。这个格式的问题在于,一旦从 VOC 或 COCO 转过来,坐标换算错一步,框就全偏了。
0 0.589583 0.495370 0.218750 0.169444 0 0.425000 0.740741 0.153125 0.219444第一行的0.589583表示目标中心点的 x 坐标占图片宽度的 58.96%,0.218750是框宽占图片宽度的 21.87%。从 VOC 的像素坐标转成 YOLO 归一化,公式是:x_center = (xmin + xmax) / 2 / width,box_width = (xmax - xmin) / width。很多转换脚本出错就出在一个地方——分母用了原图宽,但实际跑了resize或letterbox预处理,导致归一化坐标和目标在训练图上的实际位置不一致。
读取 YOLO txt 时有一个高频坑:txt 文件里第一行第一列是class_id,不是类别名。labelimg 导出 YOLO 格式时,需要先设置类别列表,如果设置顺序和训练脚本里的data.yaml类别顺序不一致,模型会把所有框都当成错误类别。后面避坑章节会专门讲这个。
2.4 格式转换的常见做法与校验方法
拿到数据集后,不管最终训练用什么框架,都建议先把三种格式全部读取一遍,交叉校验坐标能不能对上。常见做法是写一个统一的标注解析函数,把 VOC、COCO、YOLO 都转成同一个中间结构,再对比边界框面积差异。
import xml.etree.ElementTree as ET def read_voc(xml_path): tree = ET.parse(xml_path) root = tree.getroot() boxes = [] for obj in root.iter("object"): name = obj.find("name").text bnd = obj.find("bndbox") xmin = int(float(bnd.find("xmin").text)) ymin = int(float(bnd.find("ymin").text)) xmax = int(float(bnd.find("xmax").text)) ymax = int(float(bnd.find("ymax").text)) boxes.append((name, xmin, ymin, xmax, ymax)) return boxes def voc_to_yolo_line(box, img_w, img_h, class_id): name, xmin, ymin, xmax, ymax = box x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h return f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}"read_voc返回的是像素坐标的边界框,voc_to_yolo_line负责转成归一化格式。这里两个函数分开写是有意的——校验的时候先读 VOC 原始框,再读 YOLO 转回去的结果,对比xmin和xmax,误差超过 2 个像素就要查转换脚本。这类误差通常来自浮点精度丢失,如果超过 5 个像素,基本可以断定是归一化时把宽高搞反了。
如果是自己从零转格式,最省事的路线是先转成 COCO JSON,再用现成的转换工具转 YOLO。COCO 是中间格式里字段最完整的,转成 VOC 或 YOLO 都不容易丢信息。反过来从 YOLO 直接转 COCO 反而容易出问题,因为 YOLO 里没有 category 名称,只有 id,id 映射表一旦错位就全错。这个数据集里三种格式都配好了,你不需要自己做转换,但理解这条链路能帮你快速判断标签文件是否被误改过。
3. YOLO11 一键训练脚本拆解:GPU/CPU/Mac 三平台环境配置与参数说明
3.1 环境准备:ultralytics 版本与依赖的选择
数据集的训练脚本基于 YOLO11,也就是 ultralytics 库维护的 yolo11 系列模型。环境准备本身不复杂,但三平台各有各的坑,先列一个对照表再细说。
| 平台 | 训练设备 | 关键依赖 | 显存要求 |
|---|---|---|---|
| GPU 服务器 | NVIDIA 显卡 | torch + CUDA 版 | 单卡建议 8G 以上 |
| CPU 笔记本 | 无显卡 | torch CPU 版 | 无,但速度慢 10~20 倍 |
| Mac M 芯片 | MPS 后端 | torch 官方 Mac 版 | 统一内存,16G 起步 |
实际项目里,GPU 平台基本是 Linux + NVIDIA 驱动,装好 CUDA 后直接用pip install ultralytics就行。CPU 平台的坑在于 torch 默认装的是 CUDA 版,即使没有 NVIDIA 显卡也会把 CUDA 相关库拉进来,影响启动速度,更推荐用pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu单独装 CPU 版。Mac M 芯片最顺的是pip install ultralytics,torch 会自动带上 MPS 后端支持,不需要额外配置。
这个数据集配套的脚本里,训练入口通常会封装一个参数解析层,自动判断当前平台的可用设备。当你拿到脚本先别急着跑,打开看一眼device参数是怎么传的——很多人直接在 Mac 上跑 GPU 平台的命令,结果模型一直在 CPU 上龟速训练,白等几个小时。
3.2 三平台训练脚本的核心差异:device 参数与 MPS 后端
一键训练脚本最核心的部分就是 device 参数的三平台适配。常见做法是写一个自动检测函数,检查torch.cuda.is_available()和torch.backends.mps.is_available(),按优先级选择训练设备。
import torch def auto_device(): if torch.cuda.is_available(): device = "0" # 使用第一张显卡 print(f"使用 GPU: {torch.cuda.get_device_name(0)}") elif torch.backends.mps.is_available(): device = "mps" print("使用 Mac MPS 后端") else: device = "cpu" print("使用 CPU,训练速度会慢很多") return device # 关键参数说明: # device="0" 是 ultralytics 的写法,代表第一张显卡 # device="0,1" 代表两张显卡并行数据并行 # device="mps" 走 Apple Silicon 的 Metal 后端 # device="cpu" 纯 CPU 训练,只适合验证流程auto_device返回的字符串直接传给 ultralytics 的model.train(device=device)。要注意 MPS 后端虽然能用,但有些操作符还没完全支持,比如某些版本的torch.nn.Upsample在 MPS 上有实现问题。如果训练中途报not implemented的算子错误,最直接的办法是回退到 CPU 跑通全流程,确认不是数据集问题后再考虑换 GPU 平台。
GPU 多卡场景里,脚本通常会有--device 0,1的写法,ultralytics 会自动做数据并行。这里有个经验值——两张卡训练时 batch size 可以翻倍,但学习率也要相应调大lr=0.01 * 卡数,否则收敛速度没有提升,反而因为 batch 变大导致精度波动。
3.3 关键超参数怎么给:imgsz、batch、epochs、patience 的经验值
训练脚本里预设的超参数,直接决定你第一次训练跑出来的结果。这个数据集场景是夜间小目标检测,参数不能照搬白天 COCO 的默认值。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| imgsz | 640 | 无人机俯瞰图目标较小,640 是速度和精度的平衡点,目标太小时可以试 960,但训练时间翻倍约 2.5 倍 |
| batch | GPU 16 / CPU 8 / Mac 8 | 显存不够先降 batch,不要降 imgsz |
| epochs | 100 | 1000 张图单类别,100 轮足够收敛,再多容易过拟合 |
| patience | 20 | 验证集 mAP 连续 20 轮不涨就早停,防止无效训练浪费时间 |
| lr0 | 0.01 | 预训练权重迁移,0.01 是安全值,0.001 更保守但收敛慢 |
| workers | 4~8 | 数据加载线程数,Windows 下注意不要超过 CPU 核心数 |
batch 参数是翻车重灾区。很多人喜欢一次性把显存吃满,但夜间图像噪点多,梯度本身波动大,batch 太大反而不利于模型学习车灯这类高对比度特征。我一般先按batch=16跑一轮,显存不够就降到 8 或 4。如果 batch 降到 4 还爆显存,再看是不是cache=True参数把图片缓存到显存里了,关掉改成cache=False就能省出好几个 G。
epochs=100 看起来不多,但这个数据集只有 1000 张图、单类别,目标边界简单,基本 60~80 轮就收敛了。如果训练到 80 轮mAP50还在明显上升,说明模型容量不够或者学习率太保守,优先调的是lr0而不是继续加 epochs。注意 patience=20 的意义——早停机制会在验证集指标连续 20 轮不刷新时自动截断训练,如果你用 GPU 排队训练,这个参数能省下不少机时。
3.4 训练日志与结果验证:跑完看什么
数据集附带博主训练结果日志,这个日志值得仔细看,因为里面记录了别人在这个数据集上的真实表现。跑完train.py后,ultralytics 会在runs/detect/train/目录下生成一组结果文件。
runs/detect/train/ ├── weights/ │ ├── best.pt │ └── last.pt ├── results.csv ├── results.png ├── confusion_matrix.png ├── val_batch0_labels.jpg ├── val_batch0_pred.jpg └── args.yaml优先看results.png里的mAP50和val/box_loss曲线。mAP50 曲线在训练后期应该是平缓上升的,val loss 不能持续上升——如果 val loss 从某个 epoch 开始回头往上走,而 mAP 还在涨或停滞,说明过拟合了,需要加weight_decay或增大mosaic概率。confusion_matrix.png里关注car类别所在行列的值,如果大量把背景判成 car,说明夜间场景的误检率高,要检查是不是图片里车灯反光被当成了车。
日志文件如果只给了一部分训练轮次的截图,你也可以用来反推数据质量。比如 mAP50 起步值很高(超过 0.5),说明预训练权重和夜间场景的分布比较接近;起步值很低(低于 0.2),则说明场景迁移较大,需要更多的训练轮次或者做图像增强。
4. 避坑:夜间小目标检测的五个常见问题与排查记录
4.1 误把白天车辆检测的预训练权重直接拿来续训
现象:用yolo11n.pt或yolo11s.pt这种在 COCO 上预训练的权重直接训练这个夜间数据集,前几轮 loss 下降很快,但到 30 轮以后 mAP50 卡在 0.3 左右上不去。
原因:COCO 里白天车辆图像的特征分布和夜间无人机俯视差异很大。预训练权重里的浅层特征(边缘、纹理)还能用,但深层语义特征全是白天的,夜间图像经过几次下采样后,车身灰度信息和暗部噪声混在一起,深层特征基本失效。模型需要大量轮次重新学习夜间特征,100 轮并不够。
解决:改用yolo11m.pt或yolo11l.pt起步,增大模型容量来容纳夜间特征。同时把lr0从 0.01 调到 0.005,让模型在迁移初期慢一点,别把预训练权重里的通用特征冲掉。如果手头有之前跑过的夜间检测模型,哪怕场景不完全一致,优先用它做预训练权重,比用 COCO 权重效果好得多。
4.2 labelimg 导出 YOLO 格式时类别编号错位
现象:用脚本训练时报错class index out of range,或者训练能启动但 loss 不降,检查标签发现car类别的 id 标成了 1,而data.yaml里只有 1 个类别 id=0。
原因:labelimg 在导出 YOLO 格式前,需要先加载classes.txt或者手动输入类别列表。很多人直接新建标注,类别列表顺序是["car", "truck"]或从上一个项目残留下来,导致导出的 txt 里 class_id 不是从 0 开始的连续编号。单类别数据集如果 class_id=1,训练脚本直接把样本都忽略掉。
解决:拿到标签后先跑一遍类别统计脚本,检查所有 txt 文件里的 class_id 最小值是否为 0、最大值是否小于类别总数。数据集的 YOLO 标签如果是从 VOC 转换的,转换脚本没有做类别映射偏移,也会出现这个问题。任何情况下都不要信任 labelimg 的默认导出,开工前强制检查类别 id 分布。
4.3 Mac 上用 MPS 训练突然 OOM 或者算子不支持
现象:Mac 上device="mps"启动训练,前 100 个 iteration 正常,中途报RuntimeError: MPS does not support this operation,或者直接被杀进程提示内存不足。
原因:MPS 后端对个别算子支持不完整,尤其是带动态形状的操作,比如某些版本的torchvision.ops.nms在 MPS 上的实现有 bug。另外 YOLO11 训练过程中的cache机制会把图片缓存到统一内存里,1000 张 1080p 图像很快吃满 8G 内存。
解决:先用cache=False关掉图像缓存,这是 Mac 爆内存的头号原因。算子报错的话,第一步查 torch 版本,pip install --upgrade torch torchvision升到最新版大概率能解决。如果升级后还报错,直接device="cpu"训练——Mac 的 CPU 训练虽然慢,但稳定,1000 张图、100 轮、640 分辨率,约 4 到 8 小时能跑完,比排查 MPS 算子问题省时间。
4.4 夜间图像对比度低导致 mAP 虚高但实际漏检
现象:训练日志里 mAP50 到了 0.85,看起来很好,但在无人机实测视频上漏检率很高,尤其是停在树荫下或者深色柏油路上的车辆。
原因:夜间图像对比度低,车和背景的灰度值接近。测试集里的图片和训练集同源,分布非常接近,mAP 指标虚高。实际部署时的拍摄角度、高度、光线条件稍有变化,模型立刻失灵。这不是数据集的问题,是训练策略的问题——没有对夜间场景做足够的数据增强,模型学到的可能是特定灰度分布,而不是车辆的语义特征。
解决:训练时开hsv_h=0.05, hsv_s=0.5, hsv_v=0.3这类颜色增强参数,模拟不同光线下的夜间环境。关键是有意识地留出一部分图片不参与训练,专门当验证集,这些验证图要和训练图在场景上尽量不同。如果验证集 mAP 比训练集低超过 5 个百分点,说明模型过拟合到了特定场景。
4.5 把遮挡车辆标注删除导致模型学不会遮挡场景
现象:训练时把严重遮挡的车辆标注过滤掉,因为觉得这些框「不干净」。结果模型在遮挡场景下不仅漏检,连正常的停车检测也变差了。
原因:遮挡车辆的样本虽然标签框不准,但它提供了「车辆部分可见」的重要特征。删掉这些样本等于告诉模型——车辆必须是完整出现的。夜间场景遮挡本来就多,树冠、路灯杆、其他车辆都会挡住车身,模型没见过部分车辆特征,自然学不会。
解决:保留遮挡标注,重点要看的是标注框是否包含足够的车身像素。如果标注框里 80% 以上是树或建筑,这种框确实该清洗;但框里能明显看到车灯、车顶边缘或车身轮廓的,应该保留。数据集的「车辆严重遮挡」场景就是这个用途——补充模型对部分可见目标的识别能力。清洗标准是框内前景占比,不是遮挡面积。
5. 验证模型效果:从训练日志到夜间场景实测的完整闭环
5.1 用混淆矩阵和训练曲线判断过拟合与漏检方向
训练结束后,不要只看总 mAP,要把results.png里的曲线图分开读。val/box_loss和val/cls_loss是判断过拟合的两个关键指标。如果 box_loss 在训练后期持续下降但 val_loss 开始回升,说明模型开始死记训练样本的精确框位置,泛化能力反而下降。此时最划算的操作是直接使用早停前那个 epoch 的权重,而不是 last.pt。
混淆矩阵里有一块单独的「background」行和列,这是夜间误检的重灾区。如果car列里混入大量 background 分值,说明模型把车灯、路面反光当成车辆。一个有效的补救办法是在推理时把conf阈值从默认的 0.25 提高到 0.45,误检会显著下降,但要注意 mAP 也会同步降低,需要根据业务场景权衡。
5.2 用小批量夜间视频帧做可视化验证
训练日志指标再漂亮,也只是在标注数据上的表现。真实场景里无人机飞行高度、拍摄角度、云台稳定性都会影响检测效果。常见做法是从实测视频里抽帧,抽出 100~200 张没有标注的图,跑一遍推理,把结果可视化出来。
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="night_frames/", conf=0.35, # 检测置信度阈值,夜间建议 0.3~0.4 iou=0.5, # NMS 的 IoU 阈值 save=True, # 保存可视化结果 imgsz=640, # 推理尺寸,与训练时保持一致 device="0", # GPU / CPU / mps 按需修改 ) # 检查结果中是否有漏检和误检 for r in results: boxes = r.boxes if len(boxes) == 0: print(f"漏检帧: {r.path}")这个脚本有几个值得留意的点。imgsz必须和训练时保持一致,否则小目标的尺度分布会变化,导致检测不稳定。conf阈值在夜间场景建议先设 0.35 跑一遍,看误检和漏检的比例,再上下微调。可视化结果直接看框的贴合程度——如果大量框比实际车身大一圈,说明回归分支没有学好,需要回训练阶段调box_loss的权重或者增加高分辨率训练轮次。
5.3 推理速度与部署参数的取舍
验证完精度之后,还要测推理速度。GPU 上 YOLO11s 跑 640 分辨率大概 2~4ms 一帧,CPU 上可能在 100ms 以上,Mac 的 MPS 介于两者之间。如果你做的巡检项目要求实时处理,就要确定模型尺寸的选择逻辑。
yolo11n(nano)和 yolo11s(small)在夜间小目标上的差异没有白天那么明显,因为夜间目标本身特征少,模型层数深一些收益有限。实测中如果发现 yolo11s 和 yolo11n 的 mAP 只差 2~3 个点,直接上 yolo11n 换推理速度。如果差到 8 个点以上,说明夜间特征确实复杂,值得保住精度。
从那以后我每次拿到夜间检测数据集,都强制先做一遍标签格式校验和类别 id 统计,再跑 20 轮短训练看曲线走向,最后才上完整训练。这套流程看起来多花几个小时,但能避掉上面绝大多数坑。希望帮到你。
本文还有配套的精品资源,点击获取