尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

YOLOv8实战:VisDrone2019无人机小目标检测从训练到部署

发布时间:2026/9/25 21:37:49

资讯中心
01
ARTICLE

YOLOv8实战:VisDrone2019无人机小目标检测从训练到部署

YOLOv8实战:VisDrone2019无人机小目标检测从训练到部署
最近把无人机视角的目标检测任务重新捡起来发现很多人在拿到 VisDrone2019 数据集之后的第一反应都是直接拿 YOLOv8 默认参数跑一遍然后 mAP50 上不去就开始怀疑模型不行。其实问题往往不在模型而在数据本身。VisDrone2019 是无人机俯拍场景下的大规模目标检测基准目标尺寸小、分布密集、遮挡严重和平时做 COCO 那种水平视角完全是两种玩法。这篇文章我完整记录一次基于 YOLOv8 训练 VisDrone2019-DET 子集的流程重点说透环境怎么搭、数据格式怎么转、训练参数怎么调最后再给到导出部署的建议适合刚接触无人机视觉、正在为小目标检测发愁的玩家。1. VisDrone2019到底难在哪先用数据视角把“无人机场景”看透1.1 官方数据集结构里我们到底该用哪一部分VisDrone2019 不是一个单一文件包它本身包含多个子任务常见的有 DET目标检测、VID视频目标检测、SOT单目标跟踪、MOT多目标跟踪。大家平时说“训练 Visdrone2019”默认指的是 VisDrone2019-DET也就是全帧图像上的目标检测任务。整个 DET 数据集分成了 train、val、test-dev 三部分训练集图像在 6400 张左右验证集在 500 多张测试集大约 1600 张。注意这个量级训练集不算大之前我们在 COCO 上动辄十几万张图到了这里整个数据集可能还不如 COCO 一个类别多。所以训练 VisDrone 时过拟合风险比 COCO 高不少后面很多参数设置都要围绕“数据量有限”这一点来考虑。另外官方包的目录结构一般是这样VisDrone2019-DET/ ├── Annotations/ │ ├── train/ │ │ ├── 0000001_00001_d_0000001.txt │ │ ├── ... │ ├── val/ │ └── test-dev/ ├── Sequences/ │ ├── train/ │ ├── val/ │ └── test-dev/图像文件在 Sequences 目录下每个图像对应的标注是 Annotations 目录下同名 txt 文件。很多第一次接触的人容易在目录结构上出问题下完数据后不知道把路径指到哪里后面转换格式也就跟着出错。1.2 10个类别与原始标注的“怪习惯”VisDrone2019-DET 标注了 10 个目标类分别是 pedestrian行人、people人群、bicycle自行车、car小汽车、van面包车、truck卡车、tricycle三轮车、awning-tricycle遮阳三轮车、bus公交车、motor摩托车。原始标注文件和 YOLO 系列用的格式差别很大它一行一条记录用逗号分隔一共 8 列bbox_left,bbox_top,bbox_width,bbox_height,score,category,occlusion,truncation比如一行真实的标注可能是493,142,302,159,1,4,1,0这里 bbox_left、bbox_top 是目标框左上角坐标bbox_width、bbox_height 是宽高后面四个字段很多人会忽略掉但里面有个隐藏的关键点score 那一列。VisDrone 的 score 字段不是检测置信度而是“这个框是否参与训练”。score 为 1 表示是真实目标框score 为 0 表示这个区域是标注方认为的复杂区域或背景干扰区建议训练时当作忽略区处理。麻烦的是同一张图里一个目标被多个框框住的情况也存在转换时如果不过滤 score训练数据会混入大量负样本信息导致模型被搞糊涂。occlusion 表示遮挡程度truncation 表示截断程度这两个在普通检测任务里一般不直接用但它们能辅助分析模型漏检原因。VisDrone 里类别编号从 1 开始1 到 10 对应上面说的 10 个类11 是 others12 是 ignore。转 YOLO 格式时通常只保留 1 到 10把 11、12 丢掉。1.3 三个绕不开的难点小目标、高俯拍、密集遮挡第一是目标尺寸普遍极小。无人机飞行高度决定了目标在画面里只占一小块区域很多目标在原始分辨率下也就 20×20 像素左右放到 640 分辨率训练后可能只剩几个像素。这一点直接决定了训练时的 imgsz 不能无脑用默认值。第二是高俯拍导致的视角变化。水平视角下汽车、行人有一个相对统一的“侧面”外观无人机俯拍时人只剩一个头顶汽车变成车顶俯视图同一个类在不同高度、不同角度下外观差异非常大。这会让模型学习“类内一致性”更困难单纯堆数据量还不够。第三是密集遮挡与类别混淆。停车场里一排车紧密排列路边行人和骑电动车的人距离极近还有遮阳三轮车这种外观上可能和小汽车混淆的类别。VisDrone 的标注里“pedestrian”和“people”定义还很微妙人群聚集时边界框互相重叠转换格式后如果不注意负样本训练过程会非常不稳定。这也是为什么我建议训练前先把数据翻来覆去多看几遍。很多人直接跑训练直到结果不好才开始怀疑数据其实问题在数据准备阶段就已经埋下了。2. YOLOv8训练环境Ubuntu 20.04从CPU到GPU的一次到位配置2.1 版本搭配Python、PyTorch、Ultralytics和CUDA先对齐YOLOv8 的 Python 实现来自 Ultralytics 团队安装时最怕的是 torch、torchvision、ultralytics 三方版本互相打架。我这次在 Ubuntu 20.04 上搭环境推荐直接用 conda 做一个独立环境避免把系统 Python 弄乱。基础搭配参考组件推荐版本Ubuntu20.04 / 22.04Python3.10PyTorch2.0.1 或 2.1.xtorchvision与 torch 对应CUDA11.8 或 12.1Ultralytics8.1.xOpenCV4.8之所以推荐 Python 3.10是因为它对 torch 的 wheel 支持最全3.11、3.12 在部分版本上也能用但没必要给自己增加不确定性。2.2 纯CPU环境先把完整训练流程跑通如果你手头没有 N 卡比如只有一台普通笔记本或者云上的 CPU 服务器也能把训练流程完整跑起来只是速度慢。VisDrone 全量训练在 CPU 上非常痛苦但你可以缩小数据量验证流程。配置方法conda create -n yolov8 python3.10 -y conda activate yolov8 pip install ultralytics这一步会把 ultralytics 以及它依赖的 torch、torchvision 一起装上。不过默认安装的 torch 可能带 CUDA 版本体积比较大。想装纯 CPU 版本可以这样pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics装完后验证python -c import torch; print(torch.__version__)如果输出正常就可以直接用 CPU 跑一个小轮次的训练目的不是出精度而是确认数据转换、代码链路没有问题。我自己测试过在纯 CPU 上用 imgsz640、batch8、训练 3 个 epoch、只取 200 张图大概也要几十分钟到一两个小时。所以 CPU 环境更适合做“流程验证”不适合完整训练。2.3 GPU环境CUDA安装和PyTorch的隐性坑有显卡的话先把驱动确认好。Ubuntu 20.04 上输入nvidia-smi能看到显卡列表和显存信息说明驱动正常。注意 nvidia-smi 右上角显示的 CUDA Version 是“驱动支持的最高 CUDA 版本”不代表你已经装了 CUDA Toolkit。PyTorch 是自带 CUDA runtime 的你只需要保证驱动版本够新然后安装对应版本的 PyTorch 即可。如果驱动没装好先装驱动sudo apt update sudo apt install ubuntu-drivers-common sudo ubuntu-drivers autoinstall sudo reboot驱动装完后创建环境并安装 ultralyticsconda create -n yolov8 python3.10 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics常见报错之一是安装完 torch 后用torch.cuda.is_available()返回 False。原因一般是驱动太旧或者 PyTorch 版本和驱动支持的 CUDA 版本不匹配。遇到这种情况先看 nvidia-smi 里的驱动版本再看你装的 PyTorch 对应的是 cu118 还是 cu121换一个匹配的 wheel 重新装即可。还有一个容易忽略的点conda 环境里nvidia-smi可能不可用但不影响 torch 调用 GPU。验证用 Python 命令更靠谱python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))输出类似True NVIDIA GeForce RTX 3090这就说明环境跑通了。3. 数据准备是重头戏VisDrone标注转YOLO格式与数据集划分3.1 原始标注转YOLO格式前先想清楚哪些框要留YOLOv8 训练期望的标注是每个图像对应一个同名的 txt 文件文件里每一行是class_id x_center y_center width height注意所有坐标都归一化到 0 到 1 之间。VisDrone 原始标注是像素坐标和原始类别编号所以必须做转换。转换时最影响结果的决策是如何处理 score 为 0 的框、类别 11 和 12。我在实验里采用的做法是score 为 0 的框直接丢弃。理论上它们可以转为“忽略区域”供模型训练时参考但 YOLOv8 的标准训练流程不支持直接传入忽略框强行保留只会让模型把负样本当正样本学。类别 11others、12ignore直接丢掉。类别 1 到 10 保留转换时 class_id 减 1变成 0 到 9。这样做的代价是忽略了部分难例比如一个目标周围恰好有 ignore 区域时模型会在这个区域附近出现误检。但实测下来对初次训练来说这个方案最稳后续想提升再考虑用割草机式的工具去处理 ignore 区域。3.2 一个可直接用的批量转换脚本我写了一个比较保守的转换脚本按上面说的策略批量处理整个数据集from pathlib import Path import cv2 def visdrone_to_yolo(ann_file: Path, image_dir: Path, output_dir: Path): image_file image_dir / (ann_file.stem .jpg) if not image_file.exists(): print(fmissing image: {image_file}) return img cv2.imread(str(image_file)) if img is None: print(fcannot read: {image_file}) return h, w img.shape[:2] out_lines [] with ann_file.open(r, encodingutf-8) as f: for line in f: parts line.strip().split(,) if len(parts) 6: continue left, top, bw, bh map(float, parts[:4]) score float(parts[4]) category int(parts[5]) if score 1: continue if category 1 or category 10: continue cx (left bw / 2.0) / w cy (top bh / 2.0) / h nw bw / w nh bh / h out_lines.append( f{category - 1} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n ) out_path output_dir / (ann_file.stem .txt) out_path.write_text(.join(out_lines), encodingutf-8) if __name__ __main__: base Path(/path/to/VisDrone2019-DET) for split in [train, val]: ann_dir base / Annotations / split img_dir base / Sequences / split out_dir base / labels / split out_dir.mkdir(parentsTrue, exist_okTrue) for ann_file in sorted(ann_dir.glob(*.txt)): visdrone_to_yolo(ann_file, img_dir, out_dir)脚本有几个细节需要注意。第一图像的宽高不是写死的而是用 cv2 读图后动态获取因为 VisDrone 图片尺寸虽然大部分统一但也有个别尺寸不同的情况。第二坐标归一化时用(left bw / 2.0) / w不是left / w。第三输出目录建议放在数据集根目录下的 labels/train、labels/val这样 data.yaml 写起来清爽。跑完脚本后随便打开一个输出 txt 看一眼比如原图分辨率是 2000×1500一个目标框的像素坐标是 (493, 142, 302, 159)转出来大概是3 0.322000 0.221000 0.151000 0.106000这中间任何一步输出异常都要回到上一级查原始 txt。3.3 数据划分原子图方式但注意验证集别用训练集图像VisDrone 官方已经划分好了 train 和 val所以不需要自己重新随机划分。但很多人喜欢把所有图混在一起再重新划分这其实有风险VisDrone 的序列图存在很强的时域相关性同一个航拍片段里前后帧非常相似如果 train 和 val 里混入了同一个片段的图像验证集指标会虚高部署到新场景后立刻现原形。所以最好直接用官方划分除非你有非常明确的自定义场景。转换完目录后在数据集根目录写一个 data.yamlpath: /path/to/VisDrone2019-DET train: images/train val: images/val nc: 10 names: 0: pedestrian 1: people 2: bicycle 3: car 4: van 5: truck 6: tricycle 7: awning-tricycle 8: bus 9: motor这里 images/train 是相对 path 的路径对应官方的 Sequences/train。如果你把图像目录名字改了这里要同步改。我第一次训练时图省事只把 labels 建好忘了把 images 目录复制到预期路径YOLOv8 报了一个“dataset not found”就退出了。这类问题主要是路径语义没对清楚。4. 训练实战从能跑通到效果提升的参数调整全记录4.1 一条可复现的基础训练命令环境装好、数据转好之后训练其实只有一条命令yolo detect train data/path/to/VisDrone2019-DET/data.yaml \ modelyolov8m.pt \ epochs100 \ imgsz1280 \ batch16 \ namevisdrone_yolov8m_1280如果想在 Python 脚本里控制训练流程也可以from ultralytics import YOLO model YOLO(yolov8m.pt) model.train( data/path/to/VisDrone2019-DET/data.yaml, epochs100, imgsz1280, batch16, namevisdrone_yolov8m_1280, )这里modelyolov8m.pt非常重要它表示从 COCO 预训练权重初始化而不是从零开始。很多人会把 model 参数写成yolov8m.yaml那样就是随机初始化在 VisDrone 这么小的数据集上效果会差很多。预训练权重提供了基础的图像特征提取能力后续迁移到无人机视角会快得多。4.2 imgsz怎么选640、960还是1280VisDrone 原始图像普遍是 2000×1500 左右目标框又小如果训练分辨率设为默认的 640相当于把原始图缩小到三分之一再输入网络很多小目标会在下采样过程中直接消失。我建议至少从 1280 起步。但分辨率不是越大越好imgsz 每翻一倍计算量大约是四倍。1280 尺寸下batch16 对显存的压力很大24GB 显存都未必能稳定跑起来。如果你的卡是 8GB 或 12GB可以考虑两个方案一个是把 batch 降到 8 或 4另一个是先用 960 跑通再用 1280 做精调。这里有个常见的操作误区想提升精度不一定是全程用 1280可以先用 960 训练前期最后二三十个 epoch 用 1280 继续训练。Ultralytics 也支持训练过程中切换 imgsz但只建议对同一份数据和 labels 操作。我自己跑下来纯 1280 从头训练比“960 加 1280 段训”更稳因为前者整体收敛轨迹更一致。4.3 batch和learning rate要一起调YOLOv8 默认的 batch16但在 VisDrone 大分辨率下经常跑不动。很多人直接把 batch 调到 4 或者 8却忘了学习率结果模型收敛变慢。YOLOv8 的优化器是自适应学习率策略batch 变小后每个 step 看到的样本变少梯度更不稳定应适当降低学习率。一个比较实用的搭配显卡显存imgszbatchlr0备注8GB640160.01快速验证流程12GB96080.005中等精度训练24GB128080.003高精度训练24GB1280160.002完整训练epochs 我建议至少 100。VisDrone 训练图才 6000 多张100 epoch 相当于模型把每张图学了 100 遍基本够用。如果你看到 val 指标还在稳步上升可以加长到 150 或 200但要注意过拟合。4.4 预训练权重选型s、m、l到底该用哪个YOLOv8 系列有 n、s、m、l、x 五个规模我在 VisDrone 上推荐从 m 起步。原因是 s 和 n 的参数量有限特征提取能力较弱面对小目标和密集场景容易欠拟合l 和 x 精度更高但训练时间和显存要求也高得多。如果你的卡是 8GB又想跑 1280建议先试 m 加 batch4而不是直接上 l。如果时间允许用同一份数据分别跑 s、m、l 各一个短训对比你会发现 m 到 l 的提升是实打实的但这个提升值不值得多花两三倍训练时间得看你的部署平台。无人机机载设备往往算力有限最后训练阶段选 m 在精度和部署成本之间更平衡。4.5 数据增强设置旋转和mosaic是无人机场景的宝藏YOLOv8 默认开了 mosaic、随机平移、缩放、翻转等增强但默认不一定适合无人机视角。我最想调的是两个参数degrees默认是 0意为不随机旋转。无人机飞行姿态变化大俯拍画面里目标方向是很随意的把 degrees 开到 30 或 45 能显著提升模型对任意方向目标的适应能力。mosaic默认 1.0。mosaic 把四张图拼接成一张训练图很大程度上缓解了 VisDrone 小目标占比过高的问题。但 mosaic 增强会在最后 10 个 epoch 自动关闭因为这阶段拼图会干扰收敛。我自己用的 train 参数里会加degrees45, mosaic1.0, fliplr0.5, scale0.5,注意 scale 不要开太大无人机俯拍的尺度变化确实很大但过度缩放会让原有分辨率优势消失。实测比较稳的是 scale0.5 左右再大对小目标反而不友善。5. 训练结果怎么看loss曲线、mAP指标和失败案例分析5.1 正常收敛的loss曲线长什么样训练结果默认保存在项目目录下的runs/detect/visdrone_yolov8m_1280/里面有一个 results.csv记录了每个 epoch 的 train_loss、val_loss、mAP 等指标。用 pandas 读出来画个图几行代码搞定import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/visdrone_yolov8m_1280/results.csv) plt.figure(figsize(12, 6)) plt.plot(df[epoch], df[train/box_loss], labeltrain box_loss) plt.plot(df[epoch], df[val/box_loss], labelval box_loss) plt.legend() plt.show()正常收敛一般有两个特征一是 train 和 val 的 box_loss 同时下降且尾部趋缓二是 val 的 mAP50 曲线在后期缓慢上升而不是大起大落。如果 val loss 在某个 epoch 后开始反弹而 train loss 还在降基本可以判断过拟合下一轮就考虑减小模型规模、增大数据增强或提前停止。VisDrone 场景里最常见的异常是 val mAP50 一直卡在 0.2 以下这种时候先别急着调模型回到数据层面检查 labels 数量和类别分布。我把 labels 可视化打开后经常发现某些类别的标注框数量极少比如 bus 只有几百个框模型很难学出来。5.2 按类别看指标而不是只看总mAP训练结束后 YOLOv8 会输出每类的 P、R、mAP50我习惯于把 Pedestrian、People、Car 这类高频类别和 Tricycle、Awning-tricycle 这类低频类别分开看。高频类别 AP 高、低频类别 AP 低这是正常现象但如果 Car 的 AP 也掉到 0.3 以下那多半是数据问题比如忽略框没处理好或者类别间混淆严重。VisDrone 类别里最容易混淆的是 pedestrian 和 people二者的标注标准本身就模糊另外 awning-tricycle遮阳三轮车在俯拍视角下辨识度不高经常和三轮车、小汽车混在一起。面对这种情况一个可操作的思路是合并相近类别把 pedestrianpeople 合成 person或者把 tricycleawning-tricycle 合成一个 tricycle 类类别数少了每个类样本量变大mAP 往往能拉上来。当然这是业务取舍不是标准方案。5.3 从混淆矩阵和val_batch里找问题YOLOv8 默认会生成一张confusion_matrix.png这张图非常有价值。它展示真实类别和预测类别的对应关系对角线越亮说明分类越准对角线外的亮点说明存在误检。我在 VisDrone 上看到最多的误检来源是背景background被预测成 car 或 pedestrian原因是无人机俯拍场景里地面纹理、屋顶边缘、阴影区域与真实目标非常像尤其低分辨率下更难区分。建议训练完不要直接关程序打开 val 批次的预测图多看几轮。肉眼扫一遍就能发现大量重复检测、框尺寸不合理、把密集人群拆成多个小框等问题。很多时候改进方向不是神经网络结构而是后处理参数比如 NMS 的 iou 阈值。YOLOv8 默认iou0.7对密集场景可以试试把conf0.25调高到0.3或0.4能明显减少误检。我还发现一个很实操的技巧把 results.csv 里每个 epoch 的metrics/precision(B)和metrics/recall(B)画在同一个图上观察二者交叉位置。如果 recall 一直很高但 precision 上不去说明模型把太多背景判成目标反过来precision 高但 recall 低说明目标漏检严重需要提高输入分辨率或增加正样本增强。6. 部署到无人机或边缘设备导出ONNX/TensorRT与后续优化方向6.1 训练出best.pt之后导出要看部署端格式训练产生的best.pt是 PyTorch 权重不能直接跑到大多数边缘设备上。把模型转成 ONNX 是做中间格式迁移的第一站yolo export modelruns/detect/visdrone_yolov8m_1280/weights/best.pt \ formatonnx \ imgsz1280 \ dynamicTrue加dynamicTrue的好处是允许推理时输入不同尺寸图像但会牺牲一定性能。如果你确定部署时的输入尺寸固定建议不开启这样 ONNX 文件更小、推理更快。如果目标设备是 NVIDIA Jetson 系列或者带 TensorRT 的板卡可以一步到位导出 engine 格式yolo export modelruns/detect/visdrone_yolov8m_1280/weights/best.pt \ formatengine \ imgsz1280 \ halfTruehalfTrue使用 FP16 精度显存减半、速度提升精度损失在 VisDrone 这种小目标场景下会有一些但多数情况可以接受。如果板卡是 RK3588 这类 SoC则直接用 ONNX 转 RKNN 即可。注意不同部署端对 NMS 的支持不同ONNX 导出时如果带了内置 NMS板端处理更方便但也更依赖算子兼容性。6.2 小目标场景下再往前走的两个方向训练完不等于结束。从实测来看VisDrone 上的精度瓶颈大部分来自小目标两个常见优化方向值得尝试。第一个是测试时增强TTA。YOLOv8 内置了 TTA推理时对图像做多尺度翻转预测再融合结果能稳定提升小目标 recall代价是推理时间成倍增加。无人机实时场景下未必可用但做离线分析时很香yolo detect predict modelbest.pt sourcexxx.jpg imgsz1280 augmentTrue第二个是切片推理也叫裁剪推理。把大图切分成若干小块对每一块分别做检测再把结果合并回原图坐标。它的本质等效于把 imgsz 继续放大小目标在切片里会变得“更大”。我测试过 640 分辨率原图推理和 1280 分辨率切片推理后者在car和person类别上 recall 提升非常明显。缺点是推理耗时增加密集目标多时还可能重复检测同一个物体需要在合并阶段做二次 NMS。6.3 个人习惯训练记录、配置管理、分类别实验最后分享一个我自己的习惯做法。VisDrone 训练跑一次成本不低所以每次实验我会在数据集根目录创建一个 experiment 配置文件把 data.yaml、训练命令、参数、备注都存成一份文本。这样回头复盘时不会出现“这个模型当时是怎么训的”这种让所有人头疼的问题。train 和 val 目录做好版本管理最好原始镜像打包后不再乱动labels 转换脚本也固定下来一旦重新生成必须保证同一份图像同一份标注只生成一次结果。现在这个流程跑通一次之后换其他数据集基本就是改 data.yaml 的事你会发现自己对“小目标”“密集场景”“俯拍视角”这些词的理解比只看论文时要深得多。这也是我把这套流程完整记下来的原因数据整理花的时间永远不算浪费模型是最后一步而不是第一步。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。