ARTICLE DETAIL

资讯详情

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

基于YOLOv8的VisDrone2019无人机小目标检测完整实战指南

基于YOLOv8的VisDrone2019无人机小目标检测完整实战指南 无人机视角的目标检测跟咱们平时在地面拍的图片完全不是一回事。你从几百米高空往下看地面上的行人、车辆、三轮车都成了几十个像素的小点密密麻麻挤在一起再加上光照忽明忽暗、目标互相遮挡用常规目标检测模型直接套上去效果往往惨不忍睹。Visdrone2019就是这个领域极具代表性的公开数据集基于YOLOv8在这套数据上训练一版能用的检测模型是我个人认为性价比最高的切入方式。这篇文章我会把整个流程完整走一遍——环境搭建、数据集处理、训练参数怎么调、损失曲线怎么看、常见坑怎么避全都交代清楚适合正在做无人机视觉相关毕业设计、竞赛或者工程项目的朋友参考。1. 环境准备Ubuntu 20.04 上搭建 YOLOv8 运行环境1.1 环境规划与版本选择动手之前先把环境捋清楚这一步看似基础但后面所有排查都跟它有关。我这次用的是 Ubuntu 20.04 系统Python 版本 3.8显卡是 GTX 1660 Ti6GB 显存这个组合目前跑 YOLOv8 非常典型既有性价比又不会太寒酸。先说结论YOLOv8 官方仓库 ultralytics 对 PyTorch 的版本要求不算苛刻PyTorch 1.8 以上都能跑但我实测下来 2.0 以上的版本省心很多主要是 AMP自动混合精度和 DDP分布式数据并行相关的坑少。CUDA 版本建议直接用 11.8因为 PyTorch 官方预编译包对 11.8 的支持最稳11.7、12.1 虽然也能装但某些显卡驱动组合下会出现诡异的算子报错。如果你只有 CPU 环境也别灰心YOLOv8 的 CPU 推理和训练都能跑只是速度感人。我拿一块 i5-12400 的机器试过训练 Visdrone 这种规模的训练集一个 epoch 大概要多花 5 到 10 倍时间。用 CPU 跑通流程、验证代码、做小规模实验完全没问题真要上大训练量再找 GPU 机器就行。装的时候注意别用太新的 Python3.8 到 3.10 都行3.11 在某些依赖上会有兼容性问题。1.2 安装步骤与验证环境安装这块我建议用虚拟环境避免把系统 Python 搞乱。具体步骤我走下来最顺的顺序是这样的# 创建虚拟环境Python 版本可以按你机器上已有的选 conda create -n yolov8 python3.8 -y conda activate yolov8 # 安装 PyTorch注意是 cu118 后缀的版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics 及其依赖 pip install ultralytics装完之后别急着跑训练先验证一下 CUDA 是否真的可用。这一步能筛掉一半的“环境装好了但 GPU 没用上”的假象python -c import torch; print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))如果输出True和你的显卡型号说明 CUDA 链路是通的。如果输出False大概率是 PyTorch 版本和驱动不匹配去 NVIDIA 官网查一下驱动支持的 CUDA 版本再装对应版本的 PyTorch。接下来验证 YOLOv8 是否正常工作我习惯直接跑一次官方预训练模型的推理顺便把权重下载下来备用yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg这一步如果顺利会在runs/detect/predict目录下生成标注好检测框的结果图。很多人在这一步会遇到ModuleNotFoundError或者网络下载超时前者检查是不是在虚拟环境里装的后者手动把权重文件下载下来放到weights目录改成本地路径加载。我特别想提醒一点yolov8n.pt 是官方给的 COCO 预训练权重只有 80 个类跟 Visdrone 的 10 个类完全对不上。但预训练权重依然非常有用它学会的底层特征——边缘、纹理、形状——在小目标检测上可以迁移过来。后面训练的时候指定pretrainedTrue模型会加载这个 COCO 权重作为初始化而不是从零开始训练收敛速度快很多mAP 也往往更高。所以这个权重文件别删后面要用。2. Visdrone2019 数据集深度解析与预处理2.1 数据集特点与类别体系Visdrone2019 是我见过的“最不友好”的目标检测数据集之一但也是最值得折腾的。先看它的类别体系官方定义了 12 个类别包括类别ID类别名称说明0ignored regions忽略区域不参与训练1pedestrian站立行人2people非站立的人坐着的、骑在车上的3bicycle自行车4car小汽车5van面包车/厢式货车6truck卡车7tricycle三轮车8awning-tricycle带遮阳篷的三轮车9bus公交车10motor摩托车11others其他这个类别定义本身就藏着一个坑pedestrian和people的区别非常微妙一个要求“直立行走”一个是“非直立状态”。实际标注的时候边界本来就模糊模型学着学着就容易把两者混淆。我第一次训练完观察推理结果发现很多站立的行人被标成了people这就是类别定义模糊导致的。解决思路有两个一是干脆合并这两个类别二是保留但接受一定的混淆率。我建议如果做得是通用检测任务直接合并省心如果是要跟官方评测对比指标那就得保留原始定义。再看数据集规模Visdrone2019 目标检测任务提供了 6471 张训练图片和 548 张验证图片原始图片分辨率普遍在 2000×1500 左右部分图片更大。这带来两个直接问题一是图片尺寸大直接训练显存爆炸二是目标相对尺寸极小的比例惊人。我统计过Visdrone 里大量目标的边界框不超过 32×32 像素放在 640×640 的输入图上很多目标甚至只占 10×10 像素这对检测器来说是地狱级难度。2.2 标签格式转换从 Visdrone 格式到 YOLO 格式Visdrone 的标注格式跟 YOLO 完全不同这也是新手最容易卡住的地方。Visdrone 官方给的是这种格式每个目标一行bbox_left,bbox_top,bbox_width,bbox_height,score,object_category,truncation,occlusion其中score字段在训练集里恒为 0truncation表示截断程度0 完整、1 轻微截断、2 严重截断occlusion表示遮挡程度0 无遮挡、1 轻微、2 严重、3 大面积。这四个字段后三个在训练时可以忽略但score0是 YOLO 格式转换的一个重要参照官方有些标注框本身是标记着被忽略的这类框在训练时要过滤掉。YOLO 格式要求每行是class_id x_center y_center width height四个坐标值都是归一化到 0~1 的浮点数以图片宽高为基准。转换脚本的核心逻辑用 Python 实现我贴一个精简版本import os def visdrone_to_yolo(txt_path, img_w, img_h, classes_of_interest): txt_path: Visdrone 标注文件路径 img_w, img_h: 对应图片的宽和高 classes_of_interest: 需要保留的类别ID列表 yolo_lines [] with open(txt_path, r) as f: for line in f: parts line.strip().split(,) if len(parts) 8: continue bbox_left, bbox_top, bbox_width, bbox_height map(float, parts[:4]) score int(parts[4]) category int(parts[5]) # 过滤掉 score 为 0 的框这些是被忽略的 if score 0: continue # 只保留我们关心的类别 if category not in classes_of_interest: continue # 过滤掉空标注宽高为0的情况 if bbox_width 0 or bbox_height 0: continue # 计算归一化中心坐标和宽高 x_center (bbox_left bbox_width / 2) / img_w y_center (bbox_top bbox_height / 2) / img_h width bbox_width / img_w height bbox_height / img_h # 避免归一化后出现 0 或超过 1 的值某些标注可能越界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) width min(max(width, 0), 1) height min(max(height, 0), 1) yolo_lines.append(f{category} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return \n.join(yolo_lines)这里有几个看似不起眼但很关键的细节。首先是score 0的过滤官方文档里说明这个字段在训练集里为 0但标注文件里仍然存在一些框是专门标出来用于“忽略区域”的。如果不做过滤这些框会作为负样本的干扰项混进训练。其次是对越界框的处理Visdrone 的标注偶尔会出现坐标超出图片边界的情况这在 YOLO 训练时会导致 loss 异常我在脚本里做了 min-max 截断。最后是宽高为 0 的异常框必须直接剔除。类别映射这里我再多说一句。Visdrone 的类别 ID 从 0 到 11但类别 0 是ignored regions直接不参与训练。实际处理时我会把类别映射重排一下让pedestrian从 0 开始这样 YOLO 数据集的类别 ID 是连续且可用的。我常用的映射表是pedestrian0, people1, bicycle2, car3, van4, truck5, tricycle6, awning-tricycle7, bus8, motor9。注意这一步别弄错否则训练出来的模型类别语义就全乱了。2.3 训练集/验证集划分与目录结构Visdrone 官方已经划分好了训练集和验证集直接照用是最省事的做法。我习惯把处理后的数据整理成 YOLOv8 的标准目录结构VisDrone-YOLO/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── ... │ └── val/ │ ├── 0002.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── ... │ └── val/ │ ├── 0002.txt │ └── ...图片和标签文件必须同名同前缀否则 YOLOv8 会报找不到标签文件的错误。原始 Visdrone 图片文件是.jpg标注文件是.txt转换时保持文件名一致就行。我遇到过不少人直接把原始 Visdrone 目录丢给 YOLOv8 训练报一堆标签不匹配的错。其实 YOLOv8 也提供了一定程度的数据集格式自适应能力但对 Visdrone 这种非标准格式支持有限官方强烈建议转成上述统一格式。实际处理时我建议写个小脚本遍历整个文件夹一次性完成转换、重命名、分流别手动操作太多次不然很容易搞乱。这里贴一下目录构建的核心步骤示意import os import shutil # 原始 Visdrone 目录结构 src_images_train VisDrone2019-DET-train/images src_labels_train VisDrone2019-DET-train/annotations # 目标 YOLO 目录结构 dst_images_train VisDrone-YOLO/images/train dst_labels_train VisDrone-YOLO/labels/train os.makedirs(dst_images_train, exist_okTrue) os.makedirs(dst_labels_train, exist_okTrue) # 复制图片 for img_file in os.listdir(src_images_train): if img_file.endswith(.jpg): shutil.copy(os.path.join(src_images_train, img_file), os.path.join(dst_images_train, img_file)) # 转换并写入标签 for label_file in os.listdir(src_labels_train): if not label_file.endswith(.txt): continue img_file label_file.replace(.txt, .jpg) # 判断对应图片是否存在 if not os.path.exists(os.path.join(src_images_train, img_file)): print(f警告: {label_file} 没有对应的图片) continue # 获取图片尺寸 img_w, img_h get_image_size(os.path.join(src_images_train, img_file)) # 执行标注转换 yolo_content visdrone_to_yolo( os.path.join(src_labels_train, label_file), img_w, img_h, classes_of_interest[1,2,3,4,5,6,7,8,9,10] # 排除 0 和 11 ) with open(os.path.join(dst_labels_train, label_file), w) as f: f.write(yolo_content)验证集的处理一模一样只是路径不同。处理完后记得扫一遍看有没有空的标注文件可能某些图片里所有目标都被过滤掉了这些空 txt 文件保留没问题YOLOv8 会把它当作纯背景处理。3. 训练配置与参数选择3.1 自定义 YAML 配置文件YOLOv8 用 YAML 文件来确定“训练什么数据”这是整个训练过程的起点。Visdrone 数据集的 YAML 配置很简单关键是指对路径path: /home/user/VisDrone-YOLO 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: motorpath这个字段我用的是绝对路径。虽然 YOLOv8 支持相对路径但在不同工作目录下启动训练时相对路径解析容易出错换成绝对路径一劳永逸。train和val是相对于path的路径所以在这里填写images/train和images/val而不是完整路径。nc是类别数量这里一定是 10不是 12因为我已经把ignored regions和others排除掉了。names列表必须跟转换脚本里的类别 ID 一一对应顺序反了训练不会报错但结果会让你怀疑人生。我当时就是靠这个踩过坑模型检测出的car实际是bus排查半天才发现是 names 顺序错位。3.2 关键超参数含义与选择逻辑YOLOv8 的训练参数很多但不是每个都要懂关键是搞明白直接影响训练质量的几个。我从最重要的开始说模型大小modelYOLOv8 提供 n、s、m、l、x 五个版本从轻到重。我用 GTX 1660 Ti 跑n 版训练速度最快但精度垫底s 版是性价比之选m 版在我的 6G 显存上训练已经有点勉强了。建议根据你有没有 GPU、显存多大来定显存 4GB 以下用 n4-8GB 用 s8GB 以上可以尝试 m。l 和 x 在我这种消费级显卡上基本不要想训练只能拿来跑推理或者直接用别人训练好的权重做微调。输入尺寸imgsz这个是 Visdrone 训练里最重要的参数之一。默认 640 对小目标非常不友好我做了对比实验同样 100 个 epochimgsz640 的模型在 Visdrone 验证集上 mAP50 大概 0.32而 imgsz1280 的能到 0.41 左右。但大尺寸带来的显存开销是二次方的1280 的输入在 GTX 1660 Ti 上只能跑 batch4而且速度很慢。我建议先 640 跑通全流程再在时间充裕时尝试 960 或 1280 作为进阶优化。batch sizebatchbatch size 直接影响梯度估计的稳定性和训练速度。我这里直接给出一个快速估算法batch size × imgsz² × 3RGB三通道× 4字节数≈ 一个 batch 的显存占用。以 GTX 1660 Ti 6GB 为例imgsz640 时 batch16 大约占用 4.7GB再加上模型参数和中间变量的开销6GB 显存会非常紧凑。实测我在 6GB 显存上训练imgsz640 时 batch16 已经是极限偶尔会出现 OOM显存不足。稳妥一点的方案是 batch8跑得慢一点但不会中途崩溃。学习率lr0YOLOv8 默认学习率是 0.01配合 batch size 有一个自动缩放逻辑lr lr0 * batch / 64。所以如果 batch8实际学习率是 0.00125。这个自动缩放对大多数情况是友好的不需要手动调。但如果你发现训练早期 loss 震荡剧烈可以把 lr0 调低到 0.005 试试。训练轮数epochsVisdrone 这种大规模数据集100 个 epoch 是底线150 个 epoch 是比较合理的设置。我见过有些人只跑 50 个 epoch 就下结论说模型不收敛这是明显的训练不充分。在我的 1660 Ti 上imgsz640、batch8每个 epoch 大概 5-8 分钟100 个 epoch 接近 10 小时夜里挂机跑是常态。下面我把常用参数整理成一个速查表方便你设置训练命令时对照参考参数名默认值我的推荐值说明modelyolov8s.ptyolov8s.pt预训练权重或模型结构data必填Visdrone.yaml数据集配置epochs100150训练轮数imgsz640640进阶 960/1280输入图片尺寸batch168批大小显存小就降lr00.010.01初始学习率自动缩放patience5050早停耐心值devicenull0GPU 编号workers84数据加载线程数pretrainedTrueTrue是否加载预训练权重cos_lrFalseTrue是否使用余弦退火学习率augmentTrueTrue是否启用数据增强cacheFalseFalse是否缓存数据到内存3.3 数据增强策略解析YOLOv8 默认开启了一整套数据增强包括马赛克Mosaic、随机平移、缩放、翻转、色彩抖动等。这些增强对 Visdrone 这种高分辨率小目标数据集效果两极分化严重。Mosaic 增强是 YOLOv8 默认开启的大杀器它把 4 张图拼成一张能显著提升模型对小目标的鲁棒性。但 Mosaic 也有副作用它对小目标多的数据集不算友好因为拼接时小目标被缩得更小、信息更容易丢失。实测下来Visdrone 训练时 Mosaic 在最后 10 个 epoch 会被自动关闭这是 YOLOv8 的默认行为叫做close_mosaic这个设计很聪明让模型在最后阶段适应真实分布。如果你想手动控制可以通过mosaic0.5这样的参数降低 Mosaic 概率或者干脆设成 0 试试看效果。翻转增强对 Visdrone 要小心无人机俯瞰视角下目标本身是旋转性很强的但 YOLOv8 默认只做左右翻转这个没问题。上下翻转我个人建议不开因为无人机数据里目标方向跟图像坐标系的关系已经被训练集固定了强行加翻转反而引入噪声。我在实验中发现对于 Visdrone 这种数据关闭一些过于激进的颜色增强比如 HSV 变化会有帮助。无人机图片的颜色分布相对稳定过度的色彩抖动会让模型学习到错误的颜色不变性。具体做法是在训练时有选择地调整参数不必照搬默认配置。4. 模型训练实操与损失函数曲线分析4.1 启动训练与中途断点恢复万事俱备直接启动训练。我的训练命令长这样yolo train \ modelyolov8s.pt \ dataVisdrone.yaml \ epochs150 \ imgsz640 \ batch8 \ device0 \ workers4 \ projectdrone_visdrone \ nameexp1这里几个细节解释一下。project和name组合决定了结果保存路径上面配置的结果目录是drone_visdrone/exp1。如果你不想每次都手写这些参数可以把它们写到一个自定义的.yaml配置文件里YOLOv8 支持通过cfg参数直接加载。启动之后终端会实时打印每个 batch 的 loss 情况和训练进度估计剩余时间会动态更新。第一次跑的时候看到 ETA 有 10 多个小时别慌这是正常的挂在后台跑就行。训练过程中途断了是家常便饭断电、显存溢出、系统重启都可能打断训练。YOLOv8 在训练时会自动保存last.pt最后一个 epoch 的权重和best.pt验证集指标最好的权重默认每 10 个 epoch 自动保存一次检查点。如果训练中途断了直接指定断点权重继续训练yolo train \ modelruns/detect/exp1/weights/last.pt \ dataVisdrone.yaml \ epochs150 \ imgsz640 \ batch8 \ resumeTrueresumeTrue会从last.pt记录的状态恢复包括优化器状态、学习率调度器等都在权重文件里存着续训不会破坏之前的学习进度epoch 计数也会从上次停下的位置继续。这个机制在长时间训练时非常实用相当于免费给了个自动存档。4.2 训练日志与损失函数曲线解读YOLOv8 会把训练过程中的各类指标记录到runs/detect/exp1/results.csv里面每一行是一个 epoch 的统计数据。我习惯用 pandas 读取这个文件然后用 matplotlib 把关键指标画成曲线可视化效果会直观很多。核心代码可以参考下面的import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/exp1/results.csv) # 查看有哪些列 print(df.columns.tolist()) # 画出训练损失曲线 plt.figure(figsize(12, 4)) plt.subplot(1, 3, 1) plt.plot(df[epoch], df[train/box_loss], labelbox_loss) plt.xlabel(epoch) plt.ylabel(box_loss) plt.title(Box Loss) plt.grid(True) plt.subplot(1, 3, 2) plt.plot(df[epoch], df[train/cls_loss], labelcls_loss) plt.xlabel(epoch) plt.ylabel(cls_loss) plt.title(Cls Loss) plt.grid(True) plt.subplot(1, 3, 3) plt.plot(df[epoch], df[train/dfl_loss], labeldfl_loss) plt.xlabel(epoch) plt.ylabel(dfl_loss) plt.title(DFL Loss) plt.grid(True) plt.tight_layout() plt.savefig(loss_curves.png, dpi150) plt.show()YOLOv8 的损失主要由三部分构成box_loss边界框回归损失、cls_loss分类损失、dfl_loss分布焦点损失。看这三条曲线核心是判断模型是否在正常收敛。正常的曲线应该是前期快速下降中后期缓慢趋平波动幅度逐渐减小。如果出现 loss 反弹或者剧烈震荡就要留意是不是学习率太大或者数据里有脏标注。绘制验证集指标同样重要特别关注metrics/mAP50(B)和metrics/mAP50-95(B)这两条。mAP50 是 IoU 阈值 0.5 下的平均精度mAP50-95 是 0.5 到 0.95 区间多个 IoU 阈值的平均精度。对小目标检测来说mAP50-95 往往比 mAP50 低不少因为小目标框很难跟标签精确重叠。如果发现 mAP50 在涨但 mAP50-95 不怎么动说明模型的定位精度不够这时候加大 imgsz 或调整损失权重可能比盲目的多训练更有效。loss 曲线不只是用来“看结果好不好”更重要的是辅助判断训练状态。我常用的判断逻辑是这样的训练 loss 下降但验证 mAP 不涨过拟合信号可以提前停止或加强数据增强训练和验证 loss 同步下降很流畅健康状态继续跑训练 loss 和验证 loss 之间差距越来越大泛化能力在退化考虑降低模型容量或加正则化loss 出现nan这是最致命的几乎一定是数据里有极端异常值或者学习率设置过大。排查方法下文会讲。4.3 推理验证与可视化效果训练结束后第一件事就是拿验证集图片做推理直观看看模型效果。YOLOv8 推理命令很简单yolo predict \ modelruns/detect/exp1/weights/best.pt \ source/path/to/VisDrone-YOLO/images/val/0001.jpg \ save_txtTrue \ save_confTruesave_txtTrue会把检测结果保存成 txt 文件save_confTrue会附带置信度分数这两个选项在做后续精度分析时很关键。推理完打开保存的图片我第一眼看的是小目标有没有被检出来大目标有没有重复框密集区域有没有漏检。Visdrone 的场景往往密集一个画面里几十辆车的聚簇漏检和误检几乎是肉眼可见的。如果发现模型对某几个类别特别差比如awning-tricycle这类稀有的类别那就要去看训练集里这个类的样本数量。很多类别之间的性能差异根源不是模型不行而是数据不均衡。Visdrone 数据集里car类别样本数量远超awning-tricycle模型自然对前者学得更好。下文的常见问题排查里我会专门讲这类类别不均衡问题的处理方式。5. 常见问题与排查技巧实录5.1 训练过程中的高频报错与解决方案我把自己跑 Visdrone 训练实际遇到过的报错整理成了速查表基本都是实操里最高频的每一项我都简述现象、原因、解决方法错误现象可能原因解决方案CUDA out of memorybatch 或 imgsz 太大降低 batch或减小 imgsz或开启 AMP混合精度Assertion label shape error标签文件格式错误可能是类别 ID 不匹配或用逗号分隔重新检查转换脚本确认 YOLO 格式No labels found in train images标签目录与图片目录不匹配或者所有标签都被过滤光了检查数据目录结构遍历一下标签文件是否为空NaN in loss数据中含异常标注或学习率过大先用 CPU 小 batch 测试训练排查是否是数据问题再考虑降学习率训练速度越来越慢数据缓存占用越来越大或系统内存不足关闭 cache 选项或清理系统内存Class name mismatchdata yaml 里 names 与标注类别数不一致核对 nc 和 names 列表长度是否相等Download timeout for weights网络问题导致预训练权重下载失败手动下载权重并放到本地路径加载AttributeError: NoneType object has no attribute shape图片读取失败可能是图片损坏或路径错误检查图片文件是否能正常打开确认路径可用这里特别展开讲一下 NaN loss 的排查这是最折磨人的问题。我遇到过两次一次是数据里有一张图片的标注文件里出现了一个坐标值异常大的框归一化后超过了 10计算 loss 时直接溢出了另一次是学习率设置过高配合大 batch 导致梯度爆炸。排查方法是先用小 batch、低学习率跑 5 个 epoch如果 loss 正常了说明是学习率问题如果依然出现 NaN那就遍历数据集找出有问题的标签文件。遍历方法可以加载每张图片和标签手动计算标签坐标是否在合理范围内0~1 之间这个脚本写起来不复杂但能省很多排查时间。5.2 数据集层面的隐藏陷阱Visdrone 数据集的“隐藏坑”比我预想的多这里列几个必须注意的类别不均衡。前面提到awning-tricycle样本量稀少这个影响是真实存在的。处理方式有几种最简单的是增加这个类的图片增强权重让模型“看到”更多次进阶一点是过采样复制该类的样本加入训练集或者干脆做数据扩充利用 YOLOv8 内置的马赛克增强让模型在拼接图像中多接触这些稀有类别。我实际试用后发现增大该类的增强权重效果最明显操作也最简单。小目标密度过高。Visdrone 是出了名的“小目标杀手”有些训练图片里同一画面上有超过 200 个目标目标间互相重叠遮挡标签框之间 IoU 极高。这种高密度场景会让 NMS非极大值抑制失效推理时很多目标被抑制掉了。应对这种问题最直接的方法是提高 NMS 的 IoU 阈值在推理时传递iou0.3试试看召回率有没有提升。另一种思路是训练时用更大的 imgsz把小目标放大一些让模型学得更好。光照和环境变化。无人机在不同时间、不同高度拍摄图片的光照分布差异巨大。Visdrone 训练集里有大量黑夜、黄昏、清晨的图片色彩偏暗偏蓝小目标在暗光下几乎跟背景融为一体。解决思路是做亮度相关的数据增强或者干脆把训练集中的暗光图片单独抽取出来做一次直方图均衡化的预处理。我在调优时用HSV增强把饱和度变化范围调大了一点确实有帮助。重复标注问题。Visdrone 有些序列是从视频中抽帧来的相邻几帧的目标几乎一模一样。这种时间上的冗余如果没有处理好会让模型对特定场景过拟合验证集上虚高但换了新场景就垮掉。如果严谨一点可以做一个去重——基于图像相似度剔除几乎相同的图片但操作起来比较耗时。我实际做法是按序列号划分数据确保同一视频序列的帧全部在训练集或全部在验证集避免时间重叠带来的指标虚高。5.3 调优方向与改进思路如果你已经完整跑通了一遍训练流程接下来的调优就是我上面反复提到的几个方向。我最推荐的一条路线是先解决数据问题再调模型参数。数据层面的优先级最高。先把类别映射检查一遍、把脏标签清理干净、把类别不均衡处理好这些基础工作没做好后面在模型上的所有努力都会被削弱。然后是输入尺度和增强策略imgsz640 换到 1280 能带来的提升比我试过的任何模型结构改动都明显但代价是训练时间翻几倍。增强策略方面我建议跑一组对比实验默认增强 vs 关闭 Mosaic vs 降低 HSV 变化强度在 Visdrone 验证集上各跑 50 个 epoch取 mAP 最优的一组继续加长训练。模型层面YOLOv8 本身已经做得比较均衡了我通常不会在初版训练时做结构改动先把基线跑出来再说。后续如果需要进一步提升可以考虑在 YOLOv8 的 head 部分做改进比如添加针对小目标的检测头或者换用 P2 层特征。这些属于进阶玩法Visdrone 上很多竞赛方案也证明了有效但不是新手该第一步接触的。拉长了说这套训练流程跑通了之后你还能顺带做一些后续验证把训练好的权重导出成 ONNX 或 TensorRT 格式部署到板子上等另一块 GPU 空下来做多卡并行训练加速或者直接把这套流程套到其他无人机航拍数据上。6. 部署与实战经验总结6.1 模型导出与部署思路训练不是终点模型最后是要拿来用的。YOLOv8 的部署思路很成熟官方提供了一整套模型导出功能支持 ONNX、TensorRT、OpenVINO 等格式。在无人机边缘设备上部署时最常见的是导出 ONNX 再转 TensorRT在 Jetson 系列板卡上跑推理。导出命令极其简洁yolo export modelruns/detect/exp1/weights/best.pt formatonnx导出后会生成一个best.onnx文件尺寸大约几十 MB精度损失可以忽略。再转 TensorRT 的话推荐用trtexec工具或 ultralytics 自带的formatengine导出选项。实测在 Jetson Orin 系列设备上YOLOv8s 的 TensorRT FP16 推理速度能做到 30 FPS 以上满足实时无人机检测的需求。如果部署目标平台是瑞芯微 RK3588 这类国产芯片转换流程会稍微复杂一些但思路是一致的先 ONNX 再转换成芯片平台自己的推理格式。6.2 我在实战中的几个经验体会最后把自己这段时间折腾 Visdrone 和 YOLOv8 的一些体会摆一摆。Visdrone2019 数据集真的不适合拿来入门目标检测它是为进阶玩家准备的材料。如果你是刚开始接触 YOLOv8建议先用 COCO 数据集或者自己用手机拍的照片把基本流程跑通再上 Visdrone 也不迟。但反过来一旦你在 Visdrone 上能训练出不差的模型说明你已经跨过了数据处理、参数调试、问题排查这几道槛后面再碰任何数据集都会轻松得多。我反复强调的“先跑通再调优”思路放到所有 AI 项目里都成立。第一次训练的目标不是拿高分而是让整个链路顺畅数据能读、模型能训、loss 能降、结果能出。哪怕第一次的 mAP 只有 0.2也比调参卡壳一整天强。在这个基础之上每个 epoch 的损失曲线、每个验证指标的变化都会变成你调优的养料。最后再分享一个小技巧。跑长训练的时候给实验目录起名时把参数和日期写进去比如visdrone_s_640_b8_20250101一个月后回来看结果时你绝对不会后悔当初多花了十秒写清楚名字。做实验最怕的不是失败而是忘了当初怎么失败的。养成记录实验的习惯跟写好代码注释一样重要这对我来说是整个实操过程中最有价值的一件事。
返回列表