1. 机载AI部署到底在解决什么问题
把模型装到无人机上,这句话听起来简单,但真正动过手的人都知道,这里面的坑比训练模型本身多得多。训练一个YOLOv8模型,只要数据集标注到位、显卡够用、超参调得差不多,基本上都能跑出一个能看的mAP。但当你把这个模型从训练服务器搬到无人机机载计算平台上的时候,你会发现完全是另一回事——推理速度掉到没法看、内存直接爆掉、精度莫名其妙下降、甚至模型根本加载不了。
这一讲的核心目标很明确:把在PC上训练好的YOLOv8模型,经过格式转换、量化压缩、推理引擎适配之后,部署到无人机的机载计算平台上,实现实时目标检测。涉及的关键技术链路是PyTorch的.pt权重文件先导出为ONNX通用格式,再根据目标硬件平台选择TensorRT、RKNN或NCNN等推理框架进行加速部署。
适合谁来参考?如果你已经跟着前面的课程走完了数据采集、标注、模型训练这几步,手里有一个训练好的YOLOv8权重文件,现在需要把它真正跑在无人机上,那这一讲就是为你准备的。如果你还没走到这一步,建议先把前面的环节补齐,因为部署阶段遇到的很多问题,根源其实在训练阶段就埋下了。
我个人的经验是,机载AI部署这件事,三分靠模型,七分靠工程。模型结构本身固然重要,但真正决定你能不能跑通的,是格式转换的细节、量化策略的选择、推理引擎的版本匹配、以及内存和算力的精细调度。下面我会把整条链路拆开,把每个环节的关键点和踩过的坑都讲清楚。
2. 整体部署链路设计与方案选型
2.1 从训练到部署的完整链路拆解
先理清整条链路。一个典型的机载AI部署流程是这样的:
训练阶段产出.pt文件 → 导出ONNX → 针对目标平台做图优化和量化 → 转换为平台专用推理格式 → 集成到机载程序中调用推理 → 前后处理对接飞控
每一步都有它的存在理由。.pt是PyTorch的原生格式,只能在PyTorch环境下加载,机载平台通常不会装完整的PyTorch,太重了。ONNX是一个中间通用格式,相当于一个“翻译层”,让不同框架之间可以互通。但ONNX本身推理效率一般,所以还需要进一步转成目标平台的原生推理格式,比如NVIDIA平台用TensorRT,瑞芯微RK3588用RKNN,通用ARM平台可以用NCNN。
为什么不在机载上直接跑PyTorch?我试过,在Jetson Nano上装PyTorch跑YOLOv8n,单帧推理时间大概在300-500ms,根本达不到实时要求。换成TensorRT FP16之后,同样的模型能压到30-50ms,差距是数量级的。所以格式转换和推理加速这一步,不是可选项,是必选项。
2.2 目标硬件平台的选择逻辑
机载计算平台的选择直接决定了你后面走哪条技术路线。目前主流的选择大概有这么几类:
| 平台 | 典型型号 | 算力 | 功耗 | 适用场景 | 推理框架 |
|---|---|---|---|---|---|
| NVIDIA Jetson | Orin NX / Xavier NX / Nano | 20-100 TOPS | 10-25W | 中大型无人机,多路感知 | TensorRT |
| 瑞芯微 | RK3588 / RK3566 | 6 TOPS (NPU) | 5-15W | 中小型无人机,性价比路线 | RKNN |
| 华为昇腾 | Atlas 200I DK | 8 TOPS | 10W | 特定生态项目 | CANN |
| 通用ARM | 树莓派4B / 香橙派 | 无NPU | 3-8W | 验证阶段,低帧率场景 | NCNN / ONNX Runtime |
选平台的核心考量是三个:算力够不够、功耗能不能接受、重量和体积放不放得下。无人机对重量极其敏感,每多100克可能就少飞3-5分钟。所以很多时候不是选最强的,而是选最合适的。
我个人的建议是,如果你刚开始做机载AI部署,先用Jetson Orin NX或者RK3588开发板在地面调通整条链路,确认模型精度和速度都达标之后,再考虑集成到飞控系统里。不要一上来就往飞机上装,调试阶段反复拆装会让你崩溃。
2.3 推理框架的选型对比
TensorRT和RKNN是两条最常见的路线,它们的核心差异在于:
TensorRT是NVIDIA的推理加速库,对CUDA生态支持最好,FP16和INT8量化都很成熟,支持层融合、kernel自动调优等高级优化。缺点是只能在NVIDIA平台上用,而且版本兼容性比较敏感——TensorRT 8.x和10.x的API差异不小,GTX 1070这种老卡在TensorRT 10.x上可能遇到算力等级不支持的问题。
RKNN是瑞芯微的NPU推理框架,专门为RK3588这类带NPU的芯片设计。它的优势是功耗低、成本低,INT8量化后推理速度很快。缺点是对算子支持不如TensorRT全面,某些自定义算子可能需要手动实现或者替换。
选哪个?看你的硬件。如果用的是Jetson系列,毫无疑问走TensorRT。如果用RK3588,就走RKNN。如果只是想在树莓派上验证一下,NCNN或者ONNX Runtime就够了。
3. 模型导出与格式转换的实操细节
3.1 PyTorch转ONNX的关键参数与避坑
从.pt到ONNX这一步,看起来就一行代码的事,但参数没设对,后面全是坑。YOLOv8的导出命令大概长这样:
yolo export model=best.pt format=onnx imgsz=640 opset=12 simplify=True dynamic=False逐个参数说:
imgsz=640:这个必须和训练时的输入尺寸一致。如果你训练用的是640x640,导出时改成320,模型结构不会报错,但精度会掉得厉害。我见过有人导出时忘了改,部署完发现mAP掉了20个点,排查了一天才发现是尺寸不匹配。
opset=12:ONNX的算子集版本。YOLOv8一般用11或12就够了。opset太高的话,某些推理框架可能不支持;太低的话,某些算子可能没有。12是一个比较安全的中间值。
simplify=True:这个选项会调用onnx-simplifier对计算图做简化,去掉冗余节点,对后续转换很有帮助。但要注意,simplify有时候会引入一些奇怪的算子融合,导致某些推理框架不认。如果转TensorRT时报错,可以先试试simplify=False。
dynamic=False:是否使用动态输入尺寸。机载部署一般用固定尺寸,设False可以让推理引擎做更好的优化。如果你确实需要动态尺寸,设True,但TensorRT对动态shape的支持会带来额外的性能开销。
注意:导出ONNX之后,务必用onnxruntime跑一遍推理,和PyTorch的输出做对比。如果输出差异很大,说明导出过程有问题,不要急着往下走。
3.2 ONNX模型检查与图优化
导出完ONNX,别急着转TensorRT或RKNN,先做两件事:
第一,用Netron可视化检查计算图。打开Netron,加载ONNX文件,看看输入输出的shape对不对,有没有奇怪的算子,有没有多余的节点。我遇到过导出后多了一个无用的Reshape节点,导致RKNN转换失败的情况。
第二,用onnxruntime做精度对齐。写一个简单的脚本,用同一张图片分别跑PyTorch和ONNX,对比输出的差异。如果差异在1e-3以内,说明导出没问题。如果差异很大,检查是不是opset版本不对,或者某些算子导出时行为不一致。
import onnxruntime as ort import numpy as np sess = ort.InferenceSession("best.onnx") input_name = sess.get_inputs()[0].name dummy = np.random.randn(1, 3, 640, 640).astype(np.float32) output = sess.run(None, {input_name: dummy}) print(output[0].shape)3.3 ONNX转TensorRT的完整流程
TensorRT的转换有两种方式:trtexec命令行工具和Python API。trtexec最方便,适合快速验证:
trtexec --onnx=best.onnx --saveEngine=best.trt --fp16 --workspace=2048--fp16:启用FP16精度。Jetson Orin和Xavier都支持FP16,速度比FP32快一倍左右,精度损失通常在1%以内。如果你的平台支持INT8,还可以加--int8,但需要校准数据集。
--workspace=2048:分配给TensorRT的工作空间大小,单位MB。太小的话某些层可能无法使用最优kernel。2048一般够用,如果模型特别大可以加到4096。
--shapes:如果ONNX是动态shape,需要在这里指定具体尺寸,比如--shapes=input:1x3x640x640。
转完之后,用trtexec跑一下benchmark:
trtexec --loadEngine=best.trt --batch=1 --iterations=100 --duration=10看输出的Throughput和Latency。如果Latency在几十毫秒以内,基本就达标了。
3.4 ONNX转RKNN的注意事项
RK3588的转换流程稍微复杂一点,需要用到RKNN-Toolkit2。核心步骤是:
from rknn.api import RKNN rknn = RKNN() rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') rknn.load_onnx(model='best.onnx') rknn.build(do_quantization=True, dataset='calibration.txt') rknn.export_rknn('best.rknn')do_quantization=True:启用INT8量化。RK3588的NPU对INT8有专门加速,FP16也能跑但速度差不少。量化需要提供一个校准数据集,通常从训练集里随机抽100-200张图就够了。
mean_values和std_values:归一化参数。这个必须和训练时的预处理一致,否则精度会崩。YOLOv8默认是0-1归一化,所以mean=0,std=255。
注意:RKNN对某些算子的支持有限,比如SiLU激活函数在旧版本可能不支持。如果转换报错,可以尝试把SiLU换成ReLU,或者升级RKNN-Toolkit2到最新版本。
4. 量化策略与精度速度平衡
4.1 FP16、INT8量化的取舍逻辑
量化是机载部署里最核心的优化手段,没有之一。FP32转FP16,模型体积减半,推理速度提升30%-50%,精度损失通常不到1%。FP16转INT8,模型体积再减半,速度再提升50%-100%,但精度损失可能到2%-5%,取决于校准数据的质量。
什么时候用FP16?大多数场景下FP16就够了。Jetson Orin NX跑YOLOv8n FP16,640x640输入,大概能到15-20ms一帧,也就是50-60 FPS,完全够用。RK3588跑INT8大概也是这个水平。
什么时候必须上INT8?当你的模型比较大(比如YOLOv8m或l),或者平台算力有限(比如Jetson Nano),FP16跑不到实时的时候,就得考虑INT8。但INT8的精度损失需要仔细评估,特别是对小目标检测,量化后小目标的召回率可能会明显下降。
4.2 INT8量化校准数据集的制作
INT8量化的核心是校准,校准数据的质量直接决定量化后的精度。制作校准数据集的要点:
数量:100-500张就够了,不需要太多。太少的话统计不充分,太多的话转换时间很长。
分布:校准数据要覆盖实际推理时可能遇到的各种场景。如果你的无人机白天和晚上都要飞,校准集里就得有白天和晚上的图。如果只有白天,那晚上推理时精度会崩。
格式:RKNN的校准集是一个txt文件,每行是一张图片的路径。TensorRT的校准集可以用INT8 calibrator,需要实现一个校准器类,或者直接用trtexec的--calib参数指定缓存文件。
我一般会从训练集里按类别分层抽样,确保每个类别的样本数量均衡。如果某个类别特别少,可以适当过采样。
4.3 量化后精度验证与回退策略
量化完必须做精度验证。方法很简单:用同一批测试集,分别跑量化前和量化后的模型,对比mAP。
如果mAP掉了超过3个点,说明量化有问题。排查方向:
- 校准数据集是否覆盖了测试集的分布
- 量化时是否某些层被强制跳过(比如检测头)
- 是否可以用混合量化,对敏感层保持FP16
TensorRT支持逐层精度设置,可以把检测头保持FP16,主干网络用INT8。RKNN也支持混合量化,在config里设置quantized_dtype和quantized_algorithm。
实操心得:如果INT8量化后精度实在调不回来,果断回退到FP16。多花几毫秒换几个点的mAP,在大多数应用里是划算的。
5. 机载推理程序集成与性能调优
5.1 推理引擎的C++集成要点
模型转好了,接下来要把它集成到机载程序里。机载程序通常是C++写的,因为要对接飞控、传感器、通信模块,Python在实时性和资源占用上不太适合。
TensorRT的C++集成大概流程是:反序列化engine → 创建context → 分配显存 → 绑定输入输出 → 执行推理。关键点是显存分配要提前做好,不要在推理循环里反复申请释放,否则会有严重的性能抖动。
RKNN的C++集成类似,用rknn_api.h里的接口,初始化 → 查询输入输出属性 → 设置输入 → 运行 → 获取输出。
前后处理这块,YOLOv8的输出是[1, 84, 8400]这样的格式,需要做解码、NMS、坐标映射。这些操作在CPU上做就行,但要注意优化,比如用SIMD指令加速,或者把NMS放到GPU上做。
5.2 内存与算力的精细调度
机载平台的内存和算力都是有限的,必须精打细算。
内存方面:TensorRT engine加载后会占用一部分显存,输入输出buffer又占一部分。如果同时跑多个模型(比如检测+跟踪),显存很容易不够。解决办法是复用buffer,或者用CUDA stream做异步推理。
算力方面:CPU和GPU/NPU的分工要明确。图像预处理(resize、归一化)可以在CPU上做,也可以放到GPU上做。如果CPU负载已经很高,就把预处理也放到GPU上,用CUDA kernel实现。
功耗方面:Jetson平台可以设置功耗模式,比如nvpmodel -m 0是最大性能,-m 1是平衡模式。如果无人机续航紧张,可以适当降频,用性能换续航。
5.3 推理帧率与飞控周期的匹配
无人机的飞控周期通常是400Hz(IMU采样率),但视觉推理不需要这么快。一般视觉检测跑到20-30 FPS就够用了,因为目标运动不会那么快。
关键是要做好时间同步。推理结果打上时间戳,飞控根据时间戳做延迟补偿。如果推理延迟是50ms,无人机以10m/s飞行,那目标位置已经变了0.5米,不做补偿的话控制精度会很差。
我一般会在推理程序里维护一个环形buffer,存最近几帧的检测结果,飞控根据当前时间戳插值取用。这样即使推理帧率有波动,控制也不会抖。
6. 常见问题与排查技巧实录
6.1 模型转换失败类问题
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| ONNX导出报错 | opset版本不兼容 | 检查PyTorch和ONNX版本 | 降低opset到11或12 |
| TensorRT转换失败 | 存在不支持的算子 | 用Netron查看计算图 | 替换算子或升级TensorRT |
| RKNN转换失败 | 算子不支持或shape不对 | 查看RKNN日志 | 简化模型或修改输入shape |
| 转换后推理结果全零 | 输入预处理不一致 | 对比预处理代码 | 统一归一化参数 |
| 量化后精度暴跌 | 校准集分布不对 | 检查校准集覆盖 | 重新制作校准集 |
6.2 推理性能不达标类问题
帧率远低于预期:先确认是不是用了FP16或INT8。如果用了还是慢,检查是不是某些层回退到了FP32。TensorRT可以用--verbose看每层的精度。另外检查是不是CPU预处理成了瓶颈,用top看一下CPU占用。
推理延迟波动大:通常是内存分配问题。检查是不是在推理循环里动态申请了内存。另外Jetson平台如果开了多个进程,GPU资源竞争也会导致延迟波动。
首次推理特别慢:这是正常的,TensorRT和RKNN都有初始化开销。解决办法是在程序启动时先跑几次warmup推理,把engine预热好。
6.3 机载环境特有的坑
供电不稳导致推理中断:无人机电池电压会随着放电下降,如果推理平台对电压敏感,可能在低电量时重启。解决办法是加稳压模块,或者设置低电量保护阈值。
振动导致接触不良:无人机的振动很大,SD卡、USB接口、排线都可能松动。我遇到过推理程序跑着跑着突然读不到模型文件,排查半天发现是SD卡松了。解决办法是用带锁的接口,或者用胶固定。
温度过高降频:Jetson Orin NX满载功耗能到25W,散热不好的话温度很快上到80度以上,然后自动降频。解决办法是加散热片和风扇,或者限制推理频率。
IMU采样率不足的影响:如果IMU采样率达不到200Hz,飞控的姿态估计会变差,视觉检测的坐标变换也会受影响。视觉和IMU的融合对时间同步要求很高,IMU采样率不够的话,融合效果会明显下降。这个问题的根源在硬件选型,部署阶段能做的有限,主要是做好时间戳对齐和插值补偿。
6.4 实操避坑清单
- 导出ONNX前,先在PC上用onnxruntime验证精度,确认无误再往下走
- TensorRT转换时,workspace给足,不要省那点显存
- RKNN量化时,校准集一定要覆盖实际场景的分布
- 机载程序里,推理线程和飞控线程要分开,用共享内存或消息队列通信
- 首次上机测试,先用地面电源,确认稳定后再用电池
- 保留一个FP16的engine作为备份,INT8出问题时可以快速切换
- 推理结果一定要做时间戳标记,飞控端做延迟补偿
- 定期检查散热和供电,这两个是机载环境最常见的故障源
7. 从部署到飞行的最后一公里
模型跑通了,帧率达标了,精度也验证了,但离真正在无人机上稳定运行还有一段距离。这段距离里最关键的是系统集成测试。
我一般的做法是分三步走:第一步,地面静态测试,无人机放在桌上,推理程序跑起来,看能不能稳定输出检测结果,连续跑30分钟不出错。第二步,地面动态测试,无人机拿在手里模拟飞行姿态变化,看检测结果跟不跟得上。第三步,低空悬停测试,无人机起飞到2-3米高度悬停,看视觉检测在实际飞行振动和气流下的表现。
每一步都可能暴露新问题。比如地面静态测试没问题,一悬停就发现检测框抖动厉害,那是因为振动导致图像模糊,模型对模糊图像的鲁棒性不够。解决办法是在训练时加入运动模糊增强,或者在推理前做图像稳定。
还有一个容易被忽略的点是模型的热启动时间。TensorRT engine第一次加载需要几秒钟,如果无人机起飞后才加载模型,这几秒钟里视觉系统是盲的。所以一定要在起飞前完成模型加载和warmup,确保起飞瞬间视觉系统就处于就绪状态。
最后说一个我踩过的坑:不要在地面站和机载程序之间传原始图像。我一开始为了调试方便,把推理用的图像通过图传传回地面站显示,结果图传带宽被占满,飞控遥测数据延迟严重。后来改成只传检测结果和压缩后的小图,问题就解决了。机载部署的每一个资源决策,都要考虑对飞控主链路的影响,视觉系统再重要,也不能抢飞控的资源。