
简介这是一份面向无人机图像目标检测研究者和开发者的Drone-YOLO算法代码包基于YOLOv8改进专门解决航拍图像中目标尺寸小、分布密集、图像分辨率大等检测难题。核心改进包括颈部采用三层PAFPN结构与夹层融合模块以及使用RepVGG模块作为下采样层能够增强多尺度特征学习能力在VisDrone2019数据集上mAP0.5表现优于多数基线适合环境监测、交通监控、农业巡检等实时检测场景。资源为zip压缩包共3个文件包含inscode环境配置、html可视化预览页面以及gitignore配置文件整体仅7KB轻量易用。已有123人学习下载代码结构精简便于研究者快速复现实验、查看检测效果同时兼顾嵌入式硬件上的实时推理能力方便在此基础上进行二次改进与部署到实际无人机平台。 无人机视觉感知这几年是真的火但也是真的难做。地面跑的模型一上无人机结果就是漏检、误检、卡顿轮着来。我自己在Jetson Orin Nano上调试过好几套方案踩了无数坑之后终于稳定了一套流程。这次把完整的做法整理出来项目代号就叫Drone-YOLO核心链路是数据集准备 - 模型训练 - ONNX导出 - C部署 - 机上实测。整个流程我用的是YOLOv5作为基础框架部署端用ONNX Runtime做CPU推理后续也做了TensorRT加速。这套方案不挑具体硬件只要你手里有一台支持CUDA的NVIDIA边缘设备或者普通x86工控机基本都能照着跑通。这篇内容适合三类人第一类是刚接触无人机目标检测、想快速搭一个能跑的原型的开发者第二类是已经在本地训练过目标检测模型、但没上过机的算法工程师第三类是想把检测结果接到飞控或者地面站做联动控制的嵌入式玩家。我会把每一步的选型逻辑、参数理由、以及我在实际调优中发现的坑都写清楚尽量让你少走弯路。1. 无人机目标检测的第一性原理先搞清楚难在哪1.1 为什么地面跑得通的模型上天就崩很多人第一次把训练好的YOLO模型放到无人机上结果发现效果和电脑上完全不是一个量级。这不是模型本身的问题而是无人机视觉场景天然比地面场景更苛刻。第一是视角差异。地面目标检测数据集里的图片绝大多数是平视或者轻微俯视角度拍的。无人机是纯俯视视角车顶、人的头顶、树冠这些视角的视觉特征和侧面完全不同。模型在训练时没见过这样的特征分布推理时自然就认不出来。第二是目标尺度极小。无人机在100米高度巡航时一辆轿车在1080P画面里可能只占20到30个像素常规目标检测算法在小目标上本来就偏弱。第三是运动模糊和光照突变。无人机在运动中抖动、云台震动、逆光飞行都会让画面质量下降这是地面固定摄像头很少遇到的情况。所以直接用现成模型上机基本都会翻车除非你的目标大且环境简单。1.2 Drone-YOLO的解题思路Drone-YOLO的解题思路可以概括为三点用无人机视角数据重新训练、用轻量化模型保证实时性、用ONNX中间层解耦训练和部署。第一点数据决定上限。我选择了VisDrone公开数据集作为起点加上少量自采的飞行视频做补充。很多人在这一步就图省事直接拿COCO预训练权重去飞我只能说效果很感人。第二点模型选型上基线用的是YOLOv5s参数量只有7.2M左右在Jetson Orin Nano上做FP16推理可以稳定跑到40到60 FPS。如果对精度要求更高可以换YOLOv5m或者YOLOv8m但代价就是帧率掉一半。第三点部署链路用ONNX作为中间格式训练在PyTorch里完成部署用ONNX Runtime加载两边完全解耦Debug起来非常方便。后续如果想换TensorRT引擎ONNX也是必须经过的一步。2. 数据集与训练模型效果的上限在生产数据这一步就定了2.1 数据源选型与标注格式转换我用的主数据集是VisDrone它包含288个视频片段、超过26万帧图像标注了10个类别行人、人、轿车、货车、公交车、卡车、三轮车、遮阳三轮车、自行车、摩托车。这个数据集的好处是纯无人机俯视视角类别贴近实际应用缺点也很明显——标注质量参差不齐小目标极多直接拿原始标注训练会非常痛苦。VisDrone原始标注格式和YOLO要求的格式不一样。VisDrone标注框的顺序是x1, y1, x2, y2左上角坐标和右下角坐标而YOLO需要的是归一化后的中心点坐标cx, cy, w, h取值0到1。这里有个隐藏坑VisDrone的坐标是按原图分辨率算的一旦你在训练时改了输入尺寸必须先映射回原图坐标系再归一化不能直接把原始坐标除以640。我记得我第一次转换的时候忘了这一步训练出来模型预测的框全部偏移查了半天才发现是坐标被重复缩放了。转换脚本核心逻辑如下import os import numpy as np def visdrone2yolo(txt_path, img_w, img_h, out_path): with open(txt_path, r) as f: lines f.readlines() yolo_lines [] for line in lines: parts line.strip().split(,) # 前4个是坐标第5个是类别最后一个是是否截断/忽略 bbox np.array(parts[:4], dtypenp.float32) cls_id int(parts[4]) if cls_id 1 or cls_id 10: # VisDrone类别从1开始忽略0号 continue x1, y1, x2, y2 bbox x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h width (x2 - x1) / img_w height (y2 - y1) / img_h # 过滤掉过小或越界的框 if width 0 or height 0 or width 1 or height 1: continue yolo_lines.append(f{cls_id - 1} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(out_path, w) as f: f.write(\n.join(yolo_lines))另外我在项目里也准备了一份自定义数据采集脚本用大疆Mavic 3E在多个高度30米、60米、100米拍摄了园区道路和停车场画面再用开源的labelImg标注成YOLO格式。自采数据不需要很多300到500张就行目的不是增加数据量而是让模型适应你实际使用的场景。2.2 训练参数调整与数据增强策略在模型训练上我用的命令和参数如下。命令用的是YOLOv5官方仓库这里给的是主要参数完整配置还需要准备VisDrone.yamlpython train.py --data VisDrone.yaml --weights yolov5s.pt --img 640 \ --batch 16 --epochs 100 --device 0 --cache \ --hyp hyp.scratch-low.yaml --multi-scale几个关键参数的选择逻辑我说明一下--img 640常规配置兼顾精度和速度。如果目标极小且算力允许可以升到1280但推理时间会翻一倍以上不建议一上来就用。--multi-scale随机在0.5到1.5倍范围内缩放输入图模拟不同飞行高度下的目标尺度变化。无人机在不同高度飞行时目标像素尺寸差异巨大这个增强非常管用。--batch 16在显存允许范围内尽量大。Batch太小会导致BN层统计量不稳定模型收敛慢。--hyp hyp.scratch-low.yaml低数据增强配置适合中大规模数据集。如果自采数据多可以考虑hyp.scratch-high.yaml更强的马赛克增强和颜色扰动会提升泛化能力但训练时间也会延长。还有一个我强烈建议做的操作在训练后期关闭马赛克增强。马赛克增强在提升模型鲁棒性上效果显著但它引入的样本分布和真实场景差异较大如果一直开到最后一轮模型可能学不到细腻的小目标特征。我在YOLOv5里是这样处理的前90轮开启马赛克最后10轮把mosaic概率设为0用正常的几何和颜色增强精调。这个操作在验证集mAP上大概能提升1到2个百分点非常值得。3. 边缘端部署ONNX导出到C推理全流程3.1 模型导出与优化训练完成后接下来就是把PyTorch权重转成ONNX。这一步看起来简单但有几个细节不注意会埋下大雷。最核心的问题是动态batch和动态shape的处理。如果你在导出时声明了动态维度ONNX Runtime在跑第一帧时会重新做图优化导致单帧耗时暴增这在实时视频流里是不可接受的。我的做法是导出为固定shape的ONNX固定batch1输入尺寸固定为640x640python export.py --weights runs/train/exp/weights/best.pt \ --include onnx --opset 11 --batch-size 1 \ --img 640 640 --dynamic False导出后建议用onnx-simplifier做一次图结构优化去掉一些冗余的Reshape和Transpose节点这样可以让推理速度提升5%到10%python -m onnxsim best.onnx best_sim.onnx --overwrite-input-shape 1:3:640:640另外强烈建议在导出前在模型里直接集成NMS非极大值抑制逻辑。YOLOv5官方导出的ONNX是不包含NMS的输出三个特征层的原始预测需要后处理。如果你在Python里做NMS无所谓但到C里自己写NMS又麻烦又容易出bug。我在实际项目中更喜欢直接用--include onnx,end2end导出端到端版本这样ONNX输出直接就是过滤后的检测框、分数和类别IDC端省了大量工作。3.2 ONNX Runtime C推理实现C端我用的是ONNX Runtime原因是它对ONNX的支持最完整、交叉编译友好度最高。核心推理逻辑大概长这样#include onnxruntime_cxx_api.h #include opencv2/opencv.hpp class YoloDetector { public: YoloDetector(const std::string model_path) { env_ Ort::Env(ORT_LOGGING_LEVEL_WARNING, drone-yolo); session_options_.SetIntraOpNumThreads(4); session_options_.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); session_options_.SetOptimizedModelFilePath(optimized_model.onnx); session_ std::make_uniqueOrt::Session(env_, model_path.c_str(), session_options_); // 获取输入输出信息 Ort::AllocatorWithDefaultOptions allocator; input_name_ session_-GetInputNameAllocated(0, allocator).get(); output_name_ session_-GetOutputNameAllocated(0, allocator).get(); input_shape_ {-1, 3, 640, 640}; // NCHW } std::vectorDetection detect(const cv::Mat img) { // 1. 预处理resize、归一化、BGR2RGB、CHW // 2. 创建输入Tensor // 3. session_-Run() 推理 // 4. 解析输出端到端模式下是[1, num_dets, 6] } private: Ort::Env env_; Ort::SessionOptions session_options_; std::unique_ptrOrt::Session session_; std::string input_name_, output_name_; std::vectorint64_t input_shape_; };这里的预处理有一个容易被忽视的细节归一化方式必须和训练时完全一致。YOLOv5训练时将像素值除以255后归一化到0到1区间推理时也必须做同样处理。差别还有RGB和BGR的顺序OpenCV默认读入是BGRYOLO训练时用RGB所以必须转换。如果这两个细节错了模型输出的置信度会整体低一截你也很难排查因为它不是完全不能检测只是效果变差了。3.3 实测数据与精度速度取舍我在三个平台上做了推理实测结果如下平台推理后端输入分辨率FPS备注桌面RTX 3060ONNX Runtime CPU640x6408-10 FPSCPU推理仅供调试桌面RTX 3060TensorRT FP16640x640180 FPS精度几乎无损Jetson Orin Nano 8GBONNX Runtime CPU640x64018-22 FPS可用但偏慢Jetson Orin Nano 8GBTensorRT FP16640x64045-60 FPS四线程建议模式这个表给了大家一个清晰参考如果你在Jetson上跑一定要上TensorRTCPU推理虽然能跑但一旦画面里目标数量变多帧率波动会非常明显给下游控制带来隐患。TensorRT导出也很简单用官方trtexec工具一行命令trtexec --onnxbest_sim.onnx --fp16 --saveEnginebest.engine --workspace1024在C端加载engine文件用nvinfer1的API这个过程网上教程很多我不展开写。我自己的经验就是一开始先在CPU/ONNX Runtime上把逻辑跑通确认检测效果没问题之后再切TensorRT做加速这样可以避免后处理逻辑和推理引擎同时排查时的复杂度爆炸。4. 常见问题与排错实录4.1 小目标漏检严重怎么办小目标检测是无人机视觉里最头疼的问题。我实测下来漏检源头其实有两大类模型训练问题和输入分辨率问题。训练方面如果VisDrone数据集里大量目标小于16x16像素模型确实很难学到有效特征。我建议先用--img 1280训练一版看看验证集上小目标AP是否有明显提升。如果有说明你的场景确实需要高分辨率输入如果提升不大那么问题可能出在特征提取层这时候可以尝试改用YOLOv5的P2输出层在yaml配置中增加P2: [4, -1]来增强小目标检测能力。推理方面在算力有限的情况下可以尝试切片推理SAHI把640x640的输入图切成四个320x320的小块分别推理再把结果合并回原图。这样等效于用更高分辨率看图但算力消耗只增加了一倍左右在Jetson上依然可以保持在20 FPS以上很多情况下比直接换大模型更划算。4.2 推理延迟和抖动如何处理无人机上如果检测结果是被用于实时跟踪或避障延迟和帧率抖动比静态精度更致命。我的处理经验有三条第一不要用阻塞式推理。把相机采集、模型推理、飞控通信放到三个独立线程用环形缓冲队列或者带时间戳的共享内存传递数据。推理线程永远只处理最新帧如果处理不过来就丢帧绝对不能让相机采集线程阻塞等待。第二开启固定工作线程数。ONNX Runtime的SetIntraOpNumThreads我设置为4实测比默认值在Jetson上更有规律性延迟波动明显减小。TensorRT侧则用cudaStreamSynchronize控制流同步。第三推理输出加时间戳缓存。检测结果一定要和图像时间戳绑定不能只用当前时间。因为队列里可能有积压如果用程序当前时间来关联数据飞行控制和地面站就会看到错乱的目标位置。4.3 模型在真实场景泛化差VisDrone训练出来的模型在数据集上mAP不错一飞出去就各种检测不到。这个问题的根源是训练集和部署集分布不同。VisDrone是2018年之前的拍摄数据画面质量和大疆现在的相机差太多而且场景集中在城市街区。我的解决办法是数据混合训练7成VisDrone数据3成自采数据混合成一个数据集。自采数据要覆盖不同时间白天、黄昏、不同天气晴天、阴天、不同方向逆光、顺光。如果实在采集不到大量数据也可以用图像风格迁移做数据增强把VisDrone的图风格迁移到你实际部署的光照和空气质量条件下这个方法我用过效果不错。还有一个容易踩的坑是类别数量不一致。如果你的场景只需要检测人和车就别把VisDrone全部10个类别都保留把无关类别删掉这样模型容量可以集中到关心类别上精度会更好。我最终训练时只保留了行人、轿车、公交车、卡车四个类别mAP比全类别模型提升明显。4.4 与飞控的时序配合问题当检测结果需要输入飞控做跟随或者避障时两个系统之间的时序协同是必须面对的。最直接的方法是走MAVLink的MAV_CMD或者自定义的USER_DATA消息将目标位置的像素坐标、归一化距离、类别ID打包发送给飞控。地面站用QGroundControl或Mission Planner可以直接调试。这里最关键的教训是飞控的控制频率一般是50Hz以上目标检测只能跑到20到30 FPS中间的时间差必须处理。我的做法是检测线程输出一个带预测性质的目标位置利用上一帧和当前帧的像素位移做一个简单的线性插值预测下一时刻的位置。这个方法不需要卡尔曼滤波那么复杂但能把控制闭环的延迟从80ms降到40ms左右稳定性的提升非常明显。写在最后的一个实用技巧最后分享一个我从调试中获得的小技巧在做无人机目标检测实飞测试时一定要同时录制飞行日志和检测日志。飞行日志记录飞控的IMU数据、姿态、GPS和时间戳检测日志记录每一帧的检测结果、推理耗时和置信度。如果现场出了问题回放时可以精确对齐某一帧图像和当时的飞行姿态排查是控制问题还是检测问题非常高效。我遇到过一类诡异情况同一段飞行录像在地面回放时检测一切正常但在飞机上实时处理时就会丢失目标。后来对日志才发现飞机转弯时机身震动导致画面出现滚转模糊模型在模糊帧上的置信度会掉到阈值以下。回放录像如果不做运动模糊模拟根本复现不出来。这类隐含的物理耦合问题不多录日志、不对齐分析基本不可能找到根因。所以日志对齐这个东西别偷懒它能帮你省下几周的无头排查时间。本文还有配套的精品资源点击获取