news 2026/9/1 6:56:52

无人机目标检测实战:基于YOLOv5与ONNX Runtime的边缘部署全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
无人机目标检测实战:基于YOLOv5与ONNX Runtime的边缘部署全流程

简介:这是一份面向无人机图像目标检测研究者和开发者的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], dtype=np.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.yaml

python 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,固定batch=1,输入尺寸固定为640x640:

python 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输出直接就是过滤后的检测框、分数和类别ID,C++端省了大量工作。

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_unique<Ort::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::vector<Detection> 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_ptr<Ort::Session> session_; std::string input_name_, output_name_; std::vector<int64_t> input_shape_; };

这里的预处理有一个容易被忽视的细节:归一化方式必须和训练时完全一致。YOLOv5训练时将像素值除以255后归一化到0到1区间,推理时也必须做同样处理。差别还有RGB和BGR的顺序,OpenCV默认读入是BGR,YOLO训练时用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上跑,一定要上TensorRT,CPU推理虽然能跑,但一旦画面里目标数量变多,帧率波动会非常明显,给下游控制带来隐患。TensorRT导出也很简单,用官方trtexec工具一行命令:

trtexec --onnx=best_sim.onnx --fp16 --saveEngine=best.engine --workspace=1024

在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 与飞控的时序配合问题

当检测结果需要输入飞控做跟随或者避障时,两个系统之间的时序协同是必须面对的。最直接的方法是走MAVLinkMAV_CMD或者自定义的USER_DATA消息,将目标位置的像素坐标、归一化距离、类别ID打包发送给飞控。地面站用QGroundControl或Mission Planner可以直接调试。

这里最关键的教训是:飞控的控制频率一般是50Hz以上,目标检测只能跑到20到30 FPS,中间的时间差必须处理。我的做法是检测线程输出一个带预测性质的目标位置,利用上一帧和当前帧的像素位移做一个简单的线性插值,预测下一时刻的位置。这个方法不需要卡尔曼滤波那么复杂,但能把控制闭环的延迟从80ms降到40ms左右,稳定性的提升非常明显。

写在最后的一个实用技巧

最后分享一个我从调试中获得的小技巧:在做无人机目标检测实飞测试时,一定要同时录制飞行日志和检测日志。飞行日志记录飞控的IMU数据、姿态、GPS和时间戳;检测日志记录每一帧的检测结果、推理耗时和置信度。如果现场出了问题,回放时可以精确对齐某一帧图像和当时的飞行姿态,排查是控制问题还是检测问题非常高效。

我遇到过一类诡异情况:同一段飞行录像,在地面回放时检测一切正常,但在飞机上实时处理时就会丢失目标。后来对日志才发现,飞机转弯时机身震动导致画面出现滚转模糊,模型在模糊帧上的置信度会掉到阈值以下。回放录像如果不做运动模糊模拟,根本复现不出来。这类隐含的物理耦合问题,不多录日志、不对齐分析,基本不可能找到根因。所以日志对齐这个东西,别偷懒,它能帮你省下几周的无头排查时间。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 6:56:45

基于 PX4 6X 的无人机机载机械臂设计1:机械结构与 3D 打印件设计

一、 前言本文章是基于 Pixhawk 6X (PX4) 飞控平台搭建的一套高空墙面接触式检测无人机。 无人机在执行墙面检测时&#xff0c;机身需要与墙壁保持安全飞行距离&#xff0c;这就需要一套灵活、轻量且具备缓冲能力的机载机械臂来实现前端触达。本文作为该系列的第一篇&#xff0…

作者头像 李华
网站建设 2026/9/1 6:55:14

Ubuntu20.04下PL-VINS源码配置完整指南:从依赖到跑通EuRoC

简介&#xff1a;一份针对Ubuntu20.04与OpenCV4环境的PL-VINS源码包&#xff0c;面向从事机器人、无人驾驶、无人机等领域的感知定位研究者与开发者&#xff0c;重点解决点线特征视觉惯性导航系统在配置中的OpenCV4适配难题。资源压缩包仅6KB&#xff0c;共包含3个文件&#xf…

作者头像 李华
网站建设 2026/9/1 6:54:05

STM32F030+DS18B20多点测温:单总线协议与工程实践详解

简介&#xff1a;面向物联网嵌入式开发者的STM32F030多点温度采集系统完整代码包&#xff0c;适用于需要构建低功耗远程温湿度监控方案的工程师与学生。资源以源文件为主体&#xff0c;包含10个C源文件与10个头文件&#xff0c;覆盖DHT20传感器驱动、BC260Y-CN模块NB-IoT通信、…

作者头像 李华
网站建设 2026/9/1 6:53:37

基于SpringBoot的牛奶销售系统的设计与实现(源码+文档+部署+讲解)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/1 6:49:46

护理实训操作 AI 考评系统:让护理技能考核标准化、精准化

护理实操是医护教学的核心环节&#xff0c;直接决定从业者的临床服务能力与患者就医安全。长期以来&#xff0c;国内护理实训考核高度依赖人工阅卷、教师现场点评的传统模式。这种模式不仅耗时耗力、效率低下&#xff0c;还存在评分标准主观、细节把控不严、批量考核难落地等问…

作者头像 李华
网站建设 2026/9/1 6:48:29

电商用户行为分析全流程:从流量到留存的可运行Python源码

简介&#xff1a;这份电商用户行为分析源码包面向大数据分析、电商数据分析方向的毕业生及研究者&#xff0c;围绕淘宝用户2017年11月25日至12月3日超1亿条行为记录&#xff0c;完整呈现从数据导入、清洗、异常值处理到Hive分析、可视化展示的毕设流程。压缩包共20个文件&#…

作者头像 李华