无人机机载AI部署实战:从模型转换到TensorRT加速

发布时间:2026/11/4 3:55:26
无人机机载AI部署实战:从模型转换到TensorRT加速

你说巧不巧,上一讲我们还在折腾无人机怎么把图传画面送回地面站,这一讲就得干点更“叛逆”的事——把模型直接塞进无人机肚子里,让它自己在天上认东西。机载AI部署,说白了就是把训练好的神经网络模型装进无人机机载计算平台,让飞机在飞行过程中独立完成目标检测、识别、追踪这些视觉任务,不再依赖地面站的算力。

这个能力解决的是无人机行业里一个非常要命的问题:实时性和离线可用。你想,图传链路再快也有延迟,链路一断整个系统就成了瞎子。而模型在机载端跑完后,飞控可以直接根据识别结果做决策,整个闭环算下来端到端延迟能压到几十毫秒以内,链路断了照样能飞。这篇文章面向的是手里已经有一台能飞的无人机、想往上加视觉识别能力的朋友,不需要你有多深的AI理论功底,但需要你对嵌入式Linux、Python和基本的模型训练流程不陌生。我会把从模型选型、格式转换、TensorRT加速到真机上跑通推理的全流程拆开讲,每一步都给出能直接抄作业的方案。

1. 机载AI部署的核心矛盾:为什么非得在板端跑模型

1.1 边缘算力和云端算力的本质区别

很多人第一次接触机载AI会有一个惯性思维:模型在地面用显卡跑不香吗?为什么要挤在无人机那块小板上折腾?这个疑问合情合理,但实际飞几次就明白问题在哪了。首先是延迟账。一个标准的图传链路,从相机采集图像、编码、无线传输、地面解码、推理识别、再把结果编码传回飞机,整个往返链路在信号良好的情况下延迟普遍在100到200毫秒之间,信号差的时候直接奔着500毫秒去了。可无人机飞行速度稍微快一点,比如每秒15米,200毫秒就是3米的位移,等你把识别结果传回去,目标早就不在原来的位置了。

其次是可靠性账。山区巡检、灾区侦察、长距离测绘,这些典型场景里图传信号不稳定是常态,断链也是家常便饭。如果识别决策依赖地面站,链路一断飞控就只能瞎飞,航迹规划和避障能力同步归零。把模型部署在机载端,即使图传全断,飞机本身依旧具备完整的感知和决策能力,这不仅仅是性能问题,而是任务能不能完成的安全底线。

再算带宽账。很多朋友以为图传1080p就够了,但无人机视觉AI真正需要的是原始图像信息。图传链路为了对抗无线干扰,普遍使用H.264/H.265硬编码压缩,一趟1080p@30fps的码流大约占4到6Mbps带宽,可压缩本身就丢弃了大量图像纹理信息,小目标识别精度直接损失。而机载端直接用传感器原始帧做推理,不经过编码压缩,信息完整度根本不是同一层级。

1.2 机载端真正适合跑什么任务

明确边界是机载AI落地第一步。根据我这几年带项目、跟不同行业客户打交道的经验,机载端绝大多数时候只需要三类任务:目标检测、目标跟踪、简单语义分割。目标检测就是“看见什么”,典型如巡检场景里的输电塔缺陷识别、违建屋顶检测、农业田块病虫害区域圈定、灾区人员与车辆的搜救定位;目标跟踪则在检测基础上做到“锁住目标”,让飞机跟随车辆或人员移动;轻量级分割可以做到水体识别、道路提取、着火区域刻画。这些任务用YOLO家族、轻量版SegNet等模型,完全可以在10到30瓦的嵌入式平台上实现实时运行。

那机载端不适合做什么呢?答案也很明确:重型生成式AI、大规模稠密三维重建、超分辨率图像增强这类吃显存和算力的任务,短期内只能留在云端。以现在跑7B参数级大语言模型为例,光是权重加载都超过6GB显存,更别提推理时KV Cache的额外开销,一块Jetson Orin NANO 8GB根本带不起来。我见过不少团队强行在机载端上马重型模型,最后结果是帧率掉到2FPS、机载板发烫降频、飞控供电被拖垮,整机坠毁。这里有个非常朴素却重要的选型原则:能用小模型解决的,绝不用大模型;能放到飞控逻辑层解决的,绝不上神经网络。

提示:判断一个任务适不适合机载端,就用三个问题过一遍:算力能否实时、功耗是否可承受、断链后是否仍需要该功能。三个问题全是“是”,果断部署;有一个“否”,就老老实实回到云端。

2. 平台选型与硬件避坑:算力、功耗、重量不可能三角

2.1 主力机载计算平台横向对比

机载计算平台的选择直接决定后面所有工作的痛苦程度。我把市面上真正适合无人机搭载的主流方案整理成了一张表,参数都按实际操作经验做过校准,有些标称值水分很大,我也在表里用实际情况做了修正。

平台标称算力实际持续功耗内存生态成熟度适合场景实测手感
Jetson Orin NANO 8GB20 TOPS INT8 / 约10 TOPS FP167-15W8GB LPDDR5极高,JetPack一整套主流机载视觉任务,YOLOv5/v8实时性价比之王,热密度高必须加主动散热
Jetson NX Orin 16GB100 TOPS INT815-40W16GB极高多路相机+复杂模型+轻量大模型算力充裕但供电和散热压力大
树莓派5 8GB纯CPU约0.2 TOPS,算力需外挂NPU5-12W8GB高,资料巨多学习验证、低速轻任务裸跑YOLO只能1-3FPS,必须搭配Hailo-8等AI加速卡
瑞芯微RK35886 TOPS INT85-10W8-32GB中上,RKNN工具链量产低成本项目、轻量模型成本低、板子小,但工具链坑比Jetson多
英特尔NUC视GPU而定,可挂A770等30W起16-32GB高大载重大型机重量功耗过于奢侈,一般飞行器不推荐

如果是第一次做机载AI项目,我的建议永远是无脑选Jetson Orin NANO 8GB。这板子最大优势不是算力参数好看,而是JetPack把CUDA、TensorRT、cuDNN、OpenCV全部预编译打包好了,省去最折磨人的环境配置环节。我自己带过的项目里,Jetson系列占了七成以上,剩下两成是RK3588做量产降本,还有一成是树莓派挂加速卡的低成本验证。给一个性能锚点:Jetson Orin NANO在FP16精度下跑YOLOv5s,640x640输入,TensorRT优化后大概能到35到55FPS,完全满足无人机实时视觉需求。

2.2 供电、散热和机身集成的三个大坑

选好计算板只是万里长征第一步,接下来三个坑几乎人人都会踩一遍。

第一个坑是供电。Jetson Orin NANO标称7到15瓦,但这是持续均值,推理峰值瞬间能冲到20瓦以上。很多朋友图省事直接用USB-C供电,结果电机一推油门电压跌落,板子直接重启,无人机在空中变成一块砖。正确做法是从动力电池单独分出一路,经过降压稳压模块给计算板供电,跟飞控、舵机、图传共用电源会导致严重的纹波干扰。我实际使用的方案是:动力电池出来接一个独立的DC-DC降压模块,输出5V/5A给计算板,再在计算板电源输入端并联两个470uF的电解电容做储能缓冲,这样即使电机大电流抽电时电压跌了,计算板也能靠电容撑过那几十毫秒的瞬态。无人机上的电压跌落窗口非常短,但足以让一个Linux系统重启。

第二个坑是散热。Jetson Orin NANO的热设计密度相当高,满负载运行先把散热器烤到烫手,然后SoC温度到85度就开始降频,性能直接打六折。天上飞的时候气流还能帮助散热,但地面调试阶段没有大风量环境,板子降频是一定的。我的做法是主动风冷加散热片全覆盖导热硅脂填充,起飞前用tegrastats确认温度维持在65度以下再放飞机。不要指望被动散热,实测被动散热跑YOLOv5s三分钟就贴着降频线走。

第三个坑是重量和安装位置。计算板加摄像头加连接线、散热模组,总体重量普遍在150到300克之间,这对一台轴距450毫米、起飞重量2公斤左右的四轴来说不是小数字。安装时要把计算板放在飞机重心附近,否则飞控调参能让你怀疑人生。另外务必注意减震,飞控有减震球、计算板也得有,因为模型推理对图像质量敏感,高频振动会让画面出现果冻效应,识别精度骤降。我在计算板固定板上用四颗M3尼龙柱加硅胶垫圈做了简单减震,实测对识别稳定性提升非常明显。

提示:签订单项目如果要选机载平台,一定要确认整机电流余量。以4S 14.8V电池、25A持续放电能力为例,计算板占5A,飞控和图传占3A左右,剩余电流才是动力系统的余量,不要让动力部分陷入电流饥渴,否则飞行动力不足比AI跑不动更致命。

3. 模型轻量化与格式转换:从PyTorch到TensorRT的必经之路

3.1 把训练好的PyTorch模型导出为ONNX

训好的模型装进机载平台之前,必须先过一道格式转换的关卡。绝大多数团队训练用PyTorch,但机载端真正跑得飞快的推理引擎是TensorRT,TensorRT不能直接吃PyTorch的权重,需要先把PyTorch模型导出为ONNX中间格式,再由TensorRT进行编译优化。这个过程看着简单,但坑密度极高。

以YOLOv5为例,官方仓库自带导出脚本,导出ONNX的命令基本是下面这样:

cd yolov5 python export.py --weights yolov5s.pt --include onnx --opset 12 --img 640 --batch 1

这里有两个细节需要特别注意。第一个是opset版本,ONNX的算子集版本太新,TensorRT不支持;太旧,某些PyTorch算子的对应关系又映射不上去。Jetson Orin NANO上的TensorRT 8.5.x对应ONNX opset 12到16都还兼容良好,我倾向于固定到12,稳定性最舒服。第二个是batch size,推理时固定batch为1,导出时也固定为1,别用动态维度。动态shape表面上看增加了灵活性,实际在TensorRT引擎生成时会导致优化不彻底,推理性能下降10%以上。航拍场景里输入分辨率本来就是固定的640或1280,动态维度换来的灵活性毫无意义。

导出后的ONNX文件还需要做一次简化,能清掉不少冗余Shape节点和恒等算子。我用的是官方onnxsim:

pip install onnxsim onnxsim yolov5s.onnx yolov5s_sim.onnx

简化后的模型在TensorRT编译时更高效,engine生成时间能缩短30%左右。这里有个经验:不要试图在导出阶段就把NMS后处理放进模型图里,NMS非极大值抑制的TensorRT算子支持比较有限,而且灵活性极差,我自己尝试过把自定义NMS层写进ONNX里,最后调试成本远超在板端用Python或C++手写NMS的成本,完全没必要。

3.2 TensorRT引擎生成与FP16/INT8量化

拿到ONNX文件后,下一步就是用TensorRT把它编译成特定硬件平台的engine文件。TensorRT会针对显卡架构做算子融合、内存复用、精度选择等优化,这是推理性能的关键来源。JetPack里自带trtexec工具,命令行直接就能完成转换:

/usr/src/tensorrt/bin/trtexec \ --onnx=yolov5s_sim.onnx \ --saveEngine=yolov5s_fp16.engine \ --fp16 \ --minShapes=images:1x3x640x640 \ --optShapes=images:1x3x640x640 \ --maxShapes=images:1x3x640x640

FP16是嵌入式AI部署的默认精度,精度损失几乎无损,但推理速度比FP32快一倍还多。如果还想继续压榨性能,可以做INT8量化。INT8量化的原理是把网络权重和激活值从FP32映射到INT8整数范围,推理时用整数运算替代浮点运算,配合TensorRT的INT8内核,性能比FP16又能提升30%到50%。但INT8量化不是白给的,它需要准备一个校准数据集,TensorRT会对校准数据做统计,算出各层激活值的动态范围,从而定出最优的量化尺度。

校准数据这步是INT8量化最刁钻的地方。如果随便丢几张自然图片进去,量化后精度可能惨不忍睹。我自己的做法是从实际任务场景里抽帧,采集500张左右覆盖不同光照、不同目标姿态、不同背景复杂度,再抽成模型输入尺寸的tensor,全部喂给TensorRT校准器。这一步会直接影响最终精度,校准集越贴近真实推理数据,量化后的精度损失就越小。

下面是同一份YOLOv5s权重在Jetson Orin NANO上不同精度跑的实测数据,输入640x640,以COCO mAP为基准:

精度推理延迟(单帧)帧率mAP相对FP32损失适用场景
FP32约28ms约35FPS基准追求精度的离线分析
FP16约18ms约55FPS不到0.5%绝大多数实时场景,推荐

如果你非要上INT8,建议先在FP16模型上跑通全流程,留一个回滚开关,一旦INT8精度不合格能随时退回FP16。

注意:TensorRT生成的engine文件是跟硬件绑定的,Jetson Orin NANO上生成的engine不能直接用到别的Jetson型号上,换了板子必须重新生成。这个细节项目交付的时候特别容易踩雷,批量出货前一定要统一硬件版本。

4. 实战:把YOLOv5s部署到Jetson Orin NANO

4.1 环境初始化与依赖安装

Jetson Orin NANO的环境准备比一般Linux板子省心太多。第一次开机用SDK Manager刷入JetPack 5.1.2或更新的版本,系统会自带CUDA 11.4、TensorRT 8.5.3、cuDNN 8.6和OpenCV 4.7。这些核心组件版本之间有严格的配套关系,我踩过最狠的坑就是自作主张用pip把CUDA或TensorRT升了级,结果整个JetPack生态土崩瓦解,各种so库链接错误,最后只能重刷系统。JetPack自带的组件版本是英伟达测试过的黄金组合,不要去动它。

需要手动装的只有Python推理相关依赖:

sudo apt update sudo apt install python3-pip libopenblas-dev libopenmpi-dev sudo pip3 install torch==2.1.0 torchvision==0.16.0 tensorrt==8.5.3 numpy==1.21.6

这里有个细节值得说明:Jetson的PyTorch版本不能随便pip装,因为官方PyTorch包默认是给x86_64平台编的,直接装会报Illegal instruction错误或干脆不支持CUDA。必须用NVIDIA为JetPack预编译的PyTorch轮子包。JetPack 5.x对应PyTorch 2.1.0的安装命令,官网有专门页面说明,按它的指引走准没错。

用pycuda来管理GPU显存分配也可以,但个人建议优先用tensorrt自身的python绑定,它能让你少写不少底层代码。安装方式就一句话:

sudo pip3 install pycuda==2022.2

4.2 在板端手写推理代码

环境就绪之后,下面这段代码是我在实际项目中沉淀下来的TensorRT推理模板,适合YOLOv5s这种单输入单输出的检测模型。核心思路只有一句话:预分配GPU显存,把数据搬运和对齐成本降到最低。

import numpy as np import tensorrt as trt import cv2 class TRTInference: def __init__(self, engine_path, input_shape=(640, 640)): self.logger = trt.Logger(trt.Logger.WARNING) with open(engine_path, "rb") as f: runtime = trt.Runtime(self.logger) self.engine = runtime.deserialize_cuda_engine(f.read()) self.context = self.engine.create_execution_context() self.input_shape = input_shape # 绑定buffer self.inputs = [] self.outputs = [] self.bindings = [] for binding in self.engine: shape = self.engine.get_binding_shape(binding) size = trt.volume(shape) * 1 dtype = trt.nptype(self.engine.get_binding_dtype(binding)) buffer = np.zeros(size, dtype=dtype) self.bindings.append(buffer.ctypes.data) if self.engine.binding_is_input(binding): self.inputs.append(buffer) else: self.outputs.append(buffer) def preprocess(self, img): # letterbox保持宽高比填充到640 h, w = img.shape[:2] scale = min(self.input_shape[0] / h, self.input_shape[1] / w) nw, nh = int(w * scale), int(h * scale) img_resized = cv2.resize(img, (nw, nh)) canvas = np.full((self.input_shape[1], self.input_shape[0], 3), 114, dtype=np.float32) x_off = (self.input_shape[1] - nw) // 2 y_off = (self.input_shape[0] - nh) // 2 canvas[y_off:y_off+nh, x_off:x_off+nw] = img_resized # BGR转RGB,归一化到0-1 canvas = canvas[:, :, ::-1].transpose(2, 0, 1) / 255.0 return np.ascontiguousarray(canvas) def infer(self, img): input_tensor = self.preprocess(img) self.inputs[0] = input_tensor.ravel() self.context.execute_v2(bindings=self.bindings) # 输出形状 1x25200x85 output = self.outputs[0].reshape(1, 25200, 85) return output

预处理里letterbox填充那步看起来不起眼,但特别容易出错。直接把原图拉伸到640x640虽然省事,但会把目标的宽高比给拉变形,小目标检测精度直接崩。正确的做法是保持宽高比缩放到640以内,用灰色像素填充剩余区域,最后在NMS阶段把检测框坐标再映射回原图。很多入门玩家在前处理上偷懒,最后模型AP掉到一半还找不到原因,问题往往就出在这。

后处理阶段因为涉及置信度过滤和NMS,代码量稍多,但思路很直接:先把anchor解码得到的25200个候选框按置信度排序,筛掉低于0.25的框,再做NMS去除重叠框,然后把框坐标按前面letterbox的比例逆映射回原始画面。这一步我建议直接在Python里用numpy实现,别去接什么第三方后处理库,搞明白原理比省那么几毫秒重要得多。

4.3 把识别结果喂给飞控

模型跑通之后,接下来是最有成就感也最容易搞砸的一环:让识别结果驱动飞控动作。这块的技术栈选择取决于你的飞控是什么。如果用的是PX4或ArduPilot,它们都原生支持MAVLink协议,最稳妥的方案是计算板通过UART串口跟飞控通信。我推荐在计算板上直接写一个轻量级MAVLink消息生成器,把检测到的目标框中心点坐标、目标类别、置信度打包进MAVLink的自定义消息或标准消息里发给飞控。

一个简化版实现,通过串口发送JSON格式的目标信息,飞控端解析后驱动云台或航线跟随:

import serial, json, time ser = serial.Serial('/dev/ttyTHS1', 115200, timeout=0.1) while True: # 假设infer()每帧返回目标列表[(x, y, w, h, cls, conf), ...] targets = infer(frame) msg = { 'num': len(targets), 'targets': [ {'x': t[0], 'y': t[1], 'w': t[2], 'h': t[3], 'cls': t[4], 'conf': float(t[5])} for t in targets[:5] ], 'ts': time.time() } ser.write((json.dumps(msg) + '\n').encode()) time.sleep(0.05) # 20Hz下检测结果

这里有个细节要提醒:串口波特率别贪高,115200足够传20Hz的目标信息了,再高会增加误码率。飞控端接到目标信息后,可以通过PX4的offboard模式发布速度控制指令,让飞机自动对准目标;也可以驱动云台舵机保持目标居中,这个逻辑对于陪伴拍摄、巡逻侦察类任务非常实用。

提示:机载AI和飞控是两套系统,必须做松耦合。我给很多团队写过的架构都是:AI节点只管出检测结果,飞控逻辑层决定怎么飞。千万别干“检测结果直接覆盖飞控指令”这种事,一旦模型误识别或推理卡顿,整机安全性没有兜底。

5. 常见问题与排查实录:机载部署最容易翻车的五个瞬间

5.1 帧率上不去,卡在个位数

这是机载部署反馈频率最高的问题,没有之一。帧率上不去的原因通常分布在三个层面。第一个是JetPack默认的电源模式不是最大性能模式,需要手动切换:

sudo nvpmodel -m 0 # 切换到最大性能模式 sudo jetson_clocks # 强制最高频率,不自动降频

第二个层面是预处理吃掉了太多CPU时间。cv2.resize和letterbox这些都是CPU操作,如果分辨率高还不用GPU解码,CPU满载会让推理线程得不到调度。优化方向是用Jetson的硬件解码器,从相机直接拿NV12格式的帧,在GPU上做NVIDIA的DLA预处理,但这套方案复杂度陡增。对大多数项目来说,把相机分辨率控制在1080p以内、用GPU做resize和归一化,已经能显著改善帧率。

第三个层面是Python解释器的GIL限制。如果你用了多线程在同一个Python进程里做相机采集和推理,GIL会限制它们不能真正并行。我的解决方案是改成双进程,一个进程负责相机的采集、推流,另一个进程专注推理,进程间用共享内存传递帧数据,实测吞吐量提升了一个数量级。

5.2 INT8量化后精度崩溃

量化后精度下跌超过两个点这很正常,但如果跌得离谱,比如mAP直接暴跌20%,几乎可以肯定是校准集出了问题。量化校准集必须覆盖目标可能出现的所有尺度和光照条件,航拍俯视视角出来的目标跟COCO数据集里那些自然图片的分布完全是两码事,用COCO标注图片做校准集,实测大概率翻车。

修复方法不外乎两个方向:一是重新采集校准集,确保里面包含至少三分之一是实际任务场景的原始截图;二是把敏感层保留为FP16精度,让TensorRT只量化那些对精度影响小的层。TensorRT 8.5之后已经支持逐层精度控制,如果校准集已经尽量贴近真实分布但精度仍不满意,就用这个方式定点补偿。

5.3 推理线程卡死、整机掉电复位

天上飞的时候推理线程突然卡死是最吓人的故障。我排查这类问题有固定套路:先看dmesg输出里有没有 GPU 相关的Xid错误,再看/var/log/syslog里有没有OOM kill记录,最后对比tegrastats截图的温度曲线和供电电压记录。掉电复位最常见的原因有两个:一是供电瞬间电流超过稳压模块的限流阈值,二是计算板过热触发了硬件保护。

给一个非常实用的小技巧:在代码里定期把核心状态写入一个轻量级日志文件,时间戳、帧号、显存占用、温度、最近一次推理耗时各占一行。飞机落地后就算炸了,这个日志文件就是第一手的事故分析材料。我们内部管这叫黑匣子机制,虽然实现非常简单,但救过不止一次项目交付的场子。

下面是一张实战排查速查表,覆盖了机载AI最常见的坑:

症状常见根因快速验证方法处理方式
推理帧率过低电源模式未切换、CPU预处理瓶颈、GIL限制运行nvpmodel -m 0和jetson_clocks后再测切电源模式、GPU解码、改多进程
检测框偏移明显letterbox后未做坐标逆映射或用错scale打印letterbox的scale和pad值与后处理对照统一维护一组坐标变换参数,前后处理共用
整机掉电重启动力电跟计算板电源共路、电流瞬跌万用表测稳压模组输出,观察跌落到多少V独立DC-DC降压模组、并联大电容
温度飙升导致降频被动散热不够、导热硅脂没填满tegrastats观察温度向上突破80度的时间点主动风冷、散热片全覆盖、起飞前预热
ONNX算子转换报错opset版本过新、自定义OP不被支持用onnxsim简化,检查算子列表降版本到opset 12,替换不支持的自定义层

5.4 串口通信丢帧和乱码

计算板跟飞控之间的串口通信看着简单,实际调起来也是一地鸡毛。我遇到最多的问题是数据线用错了TX/RX交叉顺序,其次是地线没共地导致的串口电平漂移。解决方法是先用一个最简单的心跳程序验证链路,计算板每秒发一个固定字符,飞控收到后原样回传,确认链路稳定之后再上正式协议。

MAVLink格式的消息如果出现校验失败,别急着怀疑计算板代码,先检查波特率两侧是否一致、是否启用了硬件流控但有流控线没连。PX4默认的TELEM串口不开硬件流控,计算板侧也一定别开,开了会导致消息全部被卡住。这个坑我埋了两次,现在每次写完串口通信第一件事都是关流控。

写在最后的一点个人体会

做了这么多年机载AI方面的落地,我最想跟后来者分享的经验其实跟神经网络关系不大,反而是系统工程的事。模型从训练机搬到真机上,性能能压到什么程度、帧率能跑到多少,这些固然重要,但一只眼睛要始终盯着电压裕量、散热能力和串口可靠性,因为飞机这东西不是线缆插好就能一直稳跑的物理系统。我现在的开发流程是:地面端把模型在机载板上连续跑30分钟,同时开着全速推理,确认温度和电源都不会出幺蛾子,再上飞机做系留测试,最后才松开绳索做小范围自由飞行。这个过程看起来很保守,但被我锁掉的故障至少两位数。

最后再分享一个实操小技巧:机载端部署完成之后,把训练好的模型文件、TensorRT engine、推理脚本、环境依赖清单全部放进同一个目录,连同SHA256校验码一起保存在机载板里。后面不管谁接手这个项目、或者飞机掉水里需要重刷系统,一套命令就能完整恢复部署环境。这个习惯帮我和团队省下的返工时间,比我写过的任何一行推理代码都值钱。