☰
YOLOv8适配MMYOLO并部署大疆无人机机载计算单元的完整实践
2026/10/17 3:40:33 网站建设 项目流程

先交代一下背景:这个项目来自一个无人机巡检方向的算力适配课题,目标很明确——让YOLOv8模型在MMYOLO框架下跑通,并最终落到大疆某行业级无人机机载计算单元上完成实时推理。可能有人会问,YOLOv8本身有官方仓库,直接用Ultralytics不就行了,为什么还要绕一圈去适配MMYOLO?这个问题的答案,恰恰是这个项目最有价值的地方。在开放算力平台、边缘设备和大模型生态多方约束下,算法链路不是“能跑demo”就够的,它需要可复现、可配置、可移植。本文把我从环境初始化到模型迁移、从训练调参到无人机实装的完整过程拆开来讲,踩过的坑和验证过的参数都会列出来,给后面要接类似“开放算力+模型框架迁移+无人机端部署”的朋友一条能直接走的路。

1. 需求拆解与整体设计思路

1.1 标题背后的三层需求

先说我对“AI算力开放-yolov8适配mmyolo大疆无人机”这个标题的理解,它至少包含三层需求,不是一句“把模型部署上去”就能糊弄过去的:

第一层是算力侧。开放算力平台通常不是你在本地随便装个显卡驱动就能跑,它可能有配额限制、有预置镜像、有任务调度。你需要把训练任务设计成能在这种平台上高效利用资源的形式。我们当时拿到的是一个容器化的工作环境,GPU资源需要申请队列,这意味着你不能像本地一样无限重试调参,对缓存、断点续训和镜像里的依赖管理都有要求。

第二层是算法侧。YOLOv8在Ultralytics仓库里跑得好好的,但有的业务平台更熟悉OpenMMLab体系,或者项目后续要直接使用MMDetection的训练流水线、用在检测之外接分割任务。这个适配工作的本质,是把YOLOv8的结构在MMYOLO的代码体系里重新实现一遍,并保证权重可以互相转换、精度不掉太多。

第三层是硬件侧。目标设备是大疆的行业级无人机平台。无人机本体基本不改,但需要往机身上装计算单元,把检测模型跑在边缘GPU上。这里牵扯到的不是单纯“能出画面”,而是“端到端闭环”:模型推理结果要怎么和无人机的云台、图传、地面站通信,延迟能不能压到操作员可接受的范围。

1.2 技术路线选型:为什么绕道MMYOLO

最初团队里也有人提过:Ultralytics仓库原生支持导出ONNX、TensorRT,直接在无人机计算棒上推理不就行了?这个思路没错,但它没解决“训练生态统一”的问题。

我们项目上游实际拿到的是MMDetection风格的数据标注和评测脚本,整个评测流程要嵌入到算力平台已有的调度链路上。如果训练用Ultralytics一套、评测用MMDet一套,中间要维护两套数据格式、两类配置文件、两种回调机制,模型状态文件和fine-tune习惯也完全不同,后期维护成本非常高。所以最终定下来的方向是:在MMYOLO上把YOLOv8复现出来,训练、评测、导出全部走OpenMMLab流水线。

这个路线的好处有几点。首先,MMYOLO的配置系统非常完善,一个yaml文件或者python config就可以把所有训练参数、数据路径、模型结构、优化器参数固定下来,在算力平台上做实验复现非常方便;其次,MMYOLO本身已经实现了YOLO系列很多经典结构,比如C2f、DFL、TaskAlignedAssigner这些组件都有对应实现,迁移的工作量比从零开始小很多;最后,后续如果要在无人机端做量化或者剪枝,MMDeploy的链路可以直接复用,不用自己在ONNX图结构上处理一堆算子兼容问题。

1.3 整体流程规划

我给整个项目拆了四个阶段,对应四份可交付的产物:

  • 阶段一:打通算力平台和MMYOLO环境,完成数据集适配;
  • 阶段二:把Ultralytics YOLOv8s的权重结构在MMYOLO里复现,训练得到能对齐精度的checkpoint;
  • 阶段三:模型导出为ONNX并用TensorRT加速,验证在边缘设备上的推理帧率;
  • 阶段四:把推理程序和无人机机载通信链路整合,完成仿真飞行和实装测试。

这四个阶段看起来是串行,实际执行时会有很多并行:比如阶段二还在调精度时,阶段三就可以先用官方权重转一版ONNX用来验证流程通畅;阶段四的通信模块和模型推理本来就可以分开开发,最后再做联调。这种“流水线并行”的心态对这类多端项目非常重要,否则一个环节卡住,整个团队都会空转。

2. 算力平台环境准备与依赖管理

2.1 算力开放平台的初始检查

我们申请的开放算力平台给的是一个预装GPU驱动的Ubuntu容器镜像,PyTorch版本有1.13和2.0两个可选。这点很重要,因为MMYOLO、MMDetection和PyTorch版本之间有严格对应关系,版本不匹配常常会碰到“编译失败找不到so”或者“某些算子没有实现”的诡异报错。

拿到环境后,我建议先别急着装包,先把下面几条信息记下来:

nvidia-smi # 看GPU型号、驱动版本、显存 python -c "import torch; print(torch.__version__)" # 看预装torch版本 cat /etc/os-release # 看系统版本

我们当时的平台是8卡V100的环境,单卡显存32GB,预装PyTorch 2.0.1。这里有个小经验:如果平台允许创建自己的conda环境,尽量新建一个干净环境,不要在原镜像里直接装包。因为平台镜像是共享的,你装一半包挂掉了或者把别的依赖顶掉了,恢复成本很高。我们用的是miniconda新建env,把MMYOLO相关的依赖全部锁在这个环境里。

2.2 MMYOLO安装细节

MMYOLO目前依赖MMEngine、MMCV和MMDetection三件套,版本必须对齐。我们用了一组经过验证的稳定版本组合,安装命令如下:

pip install openmim mim install mmcv>=2.0.0,<2.2.0 mim install mmdet>=3.0.0,<3.3.0 mim install mmengine>=0.6.0,<1.0.0 git clone https://github.com/open-mmlab/mmyolo.git cd mmyolo pip install -e .

这里踩过的一个坑是:mmcv的预编译包不一定和平台CUDA版本匹配。如果你用pip直接安装mmcv,它默认用的是编译好的通用版本,有时会因为CUDA版本不一致出现“libcudart.so: undefined symbol”之类的运行时错误。解决方法是改用mim安装,它会从官方索引拉取对应CUDA版本的预编译包。

另外要注意,算力平台上通常不能随便装软链接,因为有的目录是只读的。我们的做法是把整个mmyolo仓库放到工作目录下编译安装,数据、配置文件、checkpoint都放在同一个workspace里,形成一份可迁移的项目目录。

2.3 数据集准备与标注转换

项目里用的是自采的巡检目标数据集,主要是行人、车辆和特定设施,大概有12000张标注图。从原始标注到MMYOLO能用的格式,中间经历了一次转换。

Ultralytics使用的数据格式是每个标注对应一个txt文件,内容是“class_id x_center y_center width height”,坐标都归一化到0~1。而MMYOLO也支持这种格式,但它的数据集配置方式更接近COCO那样的JSON标注。我们做了两手准备:转成了COCO格式,因为在评测时方便用COCO API算mAP,观察各类别的精度变化更直观。

转换流程不复杂,关键要注意坐标归一化方式:

# coco格式的bbox是像素坐标 # yolo的txt是归一化相对坐标 # 转换时要把x_center/y_center换算成左上角和宽高 x_top_left = (x_center - width / 2) * image_width y_top_left = (y_center - height / 2) * image_height bbox = [x_top_left, y_top_left, width * image_width, height * image_height]

如果原始标注里存在超出图像边界的框,转换时需要做clip处理,不然后面训练时loss会忽高忽低,测出来的mAP数据也是虚的。处理完后最好随机挑几百张图可视化一遍标注结果,确认框没有整体偏移。

3. YOLOv8在MMYOLO体系下的结构适配

3.1 看懂Ultralytics和MMYOLO之间的结构差异

要做模型迁移,第一步是搞清楚两边对同一个网络的实现有什么差别。YOLOv8的主干网络和Ultralytics代码里的实现是紧耦合的,但在MMYOLO里,这些组件都被拆成接口模块,需要自己拼装。

YOLOv8核心模块有三块:骨干网络CSPDarknet的变体、C2f模块、以及检测头里的DFL(Distribution Focal Loss)结构。其中C2f模块是YOLOv8相对YOLOv5改进的主要地方,它把原本的C3模块换成了跨阶段部分连接加两组卷积汇聚,能在保持轻量化的同时提升梯度流。MMYOLO对这部分是有原生支持的,在mmyolo.models.backbones的YOLOv8CSPDarknet里直接实现了。检测头方面,MMYOLO也提供了YOLOv8Head,里面包含DFL和TaskAlignedAssigner标签分配逻辑。

但别高兴得太早。虽然组件都在,你需要对照Ultralytics仓库里的模型参数,把每一层的输出channel数、depth_multiple、width_multiple这些超参数在MMYOLO的config里对齐。YOLOv8s对应的depth_multiple=0.33,width_multiple=0.5,如果这些参数对不上,实际网络大小就变了,权重加载时shape不匹配,损失的是精度或者说白白浪费算力。

3.2 权重转换路线

权重转换是整个适配过程中最容易被卡住的一步。网上能搜到不少开源转换脚本,但真正跑到你的版本组合上,可能因为状态字典的key命名不一样直接报错。我实践下来,最稳妥的是如下两步走:

第一步,从Ultralytics导出模型的state_dict:

import torch from ultralytics import YOLO model = YOLO("yolov8s.pt") state_dict = model.model.state_dict() # key名示例: model.0.conv.weight / model.2.cv1.conv.weight ... # 需要保存成我们对照之后的重命名映射 torch.save(state_dict, "ultralytics_yolov8s_state.pt")

第二步,在MMYOLO里加载时做key映射。因为MMYOLO网络模块的命名通常是backbone.stage1.0.conv.weight这样,Ultralytics则是model.0.conv.weight,纯数字索引到语义化命名的映射关系需要写一个dict。这里没有捷径,建议把所有key打印出来逐行对照。

我另一个建议是:如果时间允许,不要只靠加载官方权重来做迁移,先在MMYOLO里用小学习率、短迭代地把权重“热起来”再微调。这一方面能验证网络结构拼装得对不对(如果结构有问题,loss会在前几步就出NaN),另一方面能让BN层的running_mean/var适应新的数据分布,避免直接推理时部分类别精度崩掉。

3.3 配置文件编写要点

MMYOLO的好处是配置即代码,整个实验的逻辑都在一个config文件里。我把关键的配置项列在这里,照着这个结构填基本不会错:

_base_ = "mmyolo/configs/yolov8/yolov8_s_syncbn_fast_8xb16-500e_coco.py" data_root = "data/relative/path" # class数一定要和数据集对应 num_classes = 3 model = dict( backbone=dict(arch="P5", deepened_factor=0.33, widened_factor=0.5), bbox_head=dict( head_module=dict(num_classes=num_classes), prior_generator=dict(base_sizes=base_sizes), # 根据数据集调整anchor尺寸 ), ) train_dataloader = dict(batch_size=16, num_workers=8, dataset=dict(data_root=data_root)) val_dataloader = dict(dataset=dict(data_root=data_root, ann_file="annotations/val.json"))

其中最容易忽略的是prior_generator的base_sizes。YOLOv8本质上是anchor-free的,但MMYOLO里依然用anchor的“尺寸先验”来初始化预测层。如果这个值明显不符合你的目标尺度分布,训练前期收敛会变慢。我的做法是先用数据集的真实宽高统计出一个先验集合,再填进配置里。

另一个细节是学习率调度。MMYOLO的默认训练策略是500 epochs,8卡batch 128,这在我们的算力配额下根本跑不完。我把epoch数压到300,对应地把warmup迭代数和学习率衰减的milestone也改掉,保证大约160个epoch时学习率先衰减一轮,让模型快速收敛到可用的精度,后续如果精度不够再加上去。

4. 训练细节与精度验证

4.1 训练过程中的观察重点

训练过程中我主要盯三样东西:loss曲线、mAP曲线、单类的precision/recall。这里有个容易犯的错:新手容易只看总loss降没降,忽略了mAP的变化。有时候总loss降得很平滑,但mAP却在震荡,这通常意味着标签分配出现了问题,比如正负样本不平衡、或者分类loss和回归loss的权重配比不合理。

YOLOv8用的是TaskAlignedAssigner,它按“分类分数和IOU的乘积”来选正样本。如果你发现mAP震荡但loss平稳,可以先检查训练配置里的loss_cls、loss_bbox、loss_dfl权重。默认配置里分类损失权重是0.5、回归损失是7.5,DFL是1.5,这个配比是COCO上调过的。我们数据集目标大而稀疏,手动把回归权重稍微压到6.0,分类提到0.8,最终测试集精度反而稳了一些。

另外,推荐在训练时随时保存最佳checkpoint,不仅看验证集的mAP,也要记录每个类别的AP50和AP75。因为无人机巡检场景里,小目标召回率往往是最难提的。如果某个类别AP75一直上不去,可能就是标注框不够精确或者该类别图像分辨率太低,这时候调模型参数不如回去检查数据质量。

4.2 精度对标记录

训练完成后,我们做了一次严格的精度对比:同一份验证集,分别跑Ultralytics官方权重和MMYOLO复现的checkpoint。最后结果很接近:

模型mAP50mAP50-95参数量
Ultralytics YOLOv8s官方权重(未微调)0.8120.63811.1M
MMYOLO迁移权重(加载官方预训练,微调200ep)0.8060.62511.1M
MMYOLO从零训练(300ep)0.7830.60711.1M

这个结果说明两个问题。第一,只要结构对齐,MMYOLO和Ultralytics的精度差距完全可以控制在1%以内,在可接受范围。第二,加载官方权重再做微调确实有效,尤其是在我们数据集和COCO域差异没有特别大的前提下,从零训练需要多花不少钱才能追平精度。

4.3 剪枝与量化的初步尝试

为了上无人机端,模型体积和推理速度也要提前做准备。我们在做完精度对齐后,尝试了一版轻量化方案:用MMYOLO的稀疏化训练加通道剪枝(DepGraph),再叠加TensorRT推理时的INT8量化。

稀疏化的原理是在训练过程中对BN的gamma值施加L1正则,拉向0,让一部分通道的贡献变得很小,然后再根据重要度裁剪掉这些通道。实际操作中,我设置了scheduler.step每一轮对模型剪枝比例逐步提升,最终剪掉30%的通道,mAP50只掉了0.8个百分点,但模型体积减小了近40%。

不过要提醒一句:剪枝对部署很有用,但对训练框架是有侵入性的,调试成本不低。如果在没把握的情况下,可以先不动剪枝,优先把TensorRT的FP16推理跑通。FP16是零风险的加速手段,剪枝量化可以作为后续优化项。

5. 无人机端部署与端到端联调

5.1 机载计算单元选型与硬件连接

大疆行业级无人机挂载第三方计算单元,在行业内已经是常规操作。我们在选型时以算力、功耗、尺寸为主要约束,最终选择了一款内置GPU模块的机载计算机,通过无人机支架固定,使用转接板供电和通信。

这里最关键的参数是供电稳定性。无人机平台能给外部设备提供的电流有上限,机载计算单元满载启动时的浪涌电流很容易导致系统掉电重启。我们实测过:在推理程序刚加载模型进入GPU、功率瞬时飙升时,电压波动能到几百毫伏。所以选电源模块时,不仅要看额定功率,还要留至少30%的功率余量。

通信协议上,大疆平台通过配套开放SDK提供串口和网络通道,我们用的是标准的MAVLink协议栈,以机载计算机作为地测链路中间节点。图像数据从无人机相机采集后,通过内部传输进入机载GPU显存做推理,结果叠加在视频流上,再经图传链路回传。整个过程避开了无人机飞控系统的改动,只把机载计算单元当作一个“感知外设”挂在平台上,降低了改装风险。

5.2 模型导出与TensorRT加速流程

模型训练的最终目标是进入TensorRT engine,所以中间要过ONNX这个“通用语言”。

导出流程:

# 1. 用MMYOLO的export脚本导出ONNX python tools/export_onnx.py \ configs/xxx/yolov8s_custom.py \ checkpoints/yolov8s_custom.pth \ --output-file yolo8s_custom.onnx # 2. 用onnxsim简化图结构 python -m onnxsim yolo8s_custom.onnx yolo8s_custom_sim.onnx # 3. 用trtexec转成TensorRT engine trtexec --onnx=yolo8s_custom_sim.onnx \ --saveEngine=yolo8s_custom.engine \ --fp16 \ --workspace=2048 \ --batch=1

导出时容易踩的坑有几个:一是ONNX导出尺寸必须是固定尺寸,如果需要在640和1280两种输入分辨率之间动态切换,要导两次;二是TensorRT对不同算子的优化策略差异很大,如果你的网络里有自定义算子,可能要写plugin插件这一层,遇到这种情况建议优先把模型里的自定义层改成标准算子(比如把一些自定义激活函数换成ReLU或SiLU,Sebastian官方支持度更高)。因为MMYOLO的YOLOv8实现都是基于标准算子,这块我们没遇到太多阻碍。

推理阶段的代码结构是这样的:

// 伪代码示例 auto engine = loadEngine("yolo8s_custom.engine"); auto context = engine->createExecutionContext(); // 用cudaMalloc分配输入输出显存 float* input_buffer; float* output_buffer; cudaMalloc(&input_buffer, batch * 3 * 640 * 640 * sizeof(float)); cudaMalloc(&output_buffer, batch * output_size * sizeof(float)); // 图像预处理: resize + normalize + BGR2RGB preprocess(frame, input_buffer); // 推理 context->enqueueV2(&buffers, stream, nullptr); // 后处理: 解析 [batch, 84, 8400] 格式的检测结果 postprocess(output_buffer, results);

这里有个值得注意的细节:YOLOv8导出到ONNX后,输出通常是一个维度为[1, 84, 8400]的tensor,其中84对应4(box坐标预测)+80(COCO类)+?最好不要在代码里硬编码这些数字,应该从模型配置或输出shape里动态读取。否则换一个类别数就要改代码,后续维护很痛苦。

5.3 端到端处理链路设计

机载端推理程序不要只做一个“跑模型”的孤岛,需要把整个链路串起来。我实际实现的是这样一个数据流:

相机采集 → 硬解码(GPU硬解H.264/H.265) → 图像resize到640x640 → TensorRT推理 → 后处理输出检测框 → 把检测结果通过串口封装成MAVLink自定义消息 → 地面站接收并叠加显示

这个链路里最容易出问题的环节是图像采集和解码。大疆相机的原始视频流常见是H.265编码,机载GPU模块如果没有对应的硬解能力,CPU软解会直接占满计算资源,导致推理帧率下降。所以在选机载计算单元时,建议优先选带视频硬解引擎的型号。

另一个设计取舍是:到底在机端做完整视频流分析,还是把视频流回传地面站再分析?我们的场景因为要保证低延迟告警,所以选择了机端分析,只把结构化检测结果(类别、置信度、坐标)传回地面站。这样数据量从每帧几MB压缩到几十字节,图传链路的压力小很多,也避免远程推理延迟被链路抖动放大。

实测下来,YOLOv8s在640x640输入下,FP16模式跑在机载GPU模块上,推理单帧耗时大约23毫秒,加上解码和预处理,整链路端到端延迟约在180毫秒,剔除图传本身的延迟,检测算法引入的延迟在60~70毫秒。这个水平对巡检类应用已经完全够用,地面站操作员几乎感觉不到框和画面的错位。

5.4 实装测试注意事项

实装前我们做了三轮测试:先在地面用固定摄像机模拟视频流,再在机库内悬停测试,最后才到外场起飞测试。

外场测试有几个雷区值得提:

第一,强光环境对模型鲁棒性影响明显,逆光时航拍画面过曝,检测率会显著下降。我们加了一层基于直方图均衡的预处理,在机端推理前会自动判断图像是否过亮或过暗,做一次自适应增强,实测能提升逆光场景下的召回率。

第二,机载设备温度。GPU模块满载运行时温度升得很快,机载环境又不像地面设备有风扇直吹。我们的机载计算机虽然自带风扇散热的改装套件,但外场在35度气温下长时间运行,还是会触发降频。建议在程序里加上温度监控,温度超过阈值自动把推理频率从全速下调到三档运行,防止设备过热断电影响整个测试。

第三,通信链路断连怎么处理。无人机飞行中可能飞过信号遮挡区域,图传和数传会有几秒中断。地面站程序必须能处理“中间结果缺失”的情况,不能一断连就崩溃。我们在地面站里做了超时重连和缓存逻辑,断连恢复后会先发一条复位指令,让机端程序重新开始推送检测结果。

6. 常见问题与排查实录

把整个项目过程中遇到的高频问题整理成一张速查表,按阶段归类,遇到类似报错可以直接对着查。

6.1 训练与转换阶段的坑

现象可能原因解决方案
训练初期loss直接变NaN学习率过大或BN层初始化异常把warmup迭代数增加,检查配置文件里norm_cfg是否设置momentum=0.03
加载Ultralytics权重时shape mismatch结构参数(depth_multiple等)没对齐逐层打印shape,对比两个模型的state_dict
加载权重后mAP反而比随机初始化低BN层的统计量不匹配新数据先用小学习率热启动50个epoch,再正常训练
COCO格式转换后标注框偏移坐标归一化/反归一化时混用了中心点和左上角可视化标注结果,一条条核对坐标公式
同一模型mAP在不同平台结果不同图像缩放插值算法或归一化均值/方差不同统一预处理逻辑,训练和部署必须完全一致

6.2 部署阶段的坑

现象可能原因解决方案
trtexec导出engine时报错算子不支持网络中存在自定义层查看报错日志定位到具体算子,改成MMYOLO自带的标准算子
推理结果比训练时差很多导出时尺寸或batch和实际不一致固定输入尺寸和batch,用同一份图像在torch和trt上逐像素对比输出
显存占用高导致偶尔超限后处理里反复做cudaMalloc内存池复用,只分配一次buffer,后处理在CPU上做或者开固定显存空间
图像帧率比预期低很多解码环节把GPU算力抢走了换支持硬解的视频流管线,或者降低解码分辨率

这里想多说一句“逐像素对比输出”这个排查方法,它是部署周期里最值钱的手段。具体做法是:用同一张测试图,在PyTorch里跑一遍模型,再在TensorRT里跑一遍,把两者的输出tensor保存下来做逐元素比较,误差一般在1e-3级别。如果某个通道差异特别大,重点检查那个通道对应的输入预处理是否一致。很多时候问题不是出在模型转换,而是出在你训练时输入的归一化方式和部署时的不一样,这种bug靠肉眼根本看不出来。

6.3 无人机联调的坑

现象可能原因解决方案
无人机起飞后检测框延迟明显增大计算单元降频或图像解码链路被挤压检查温度日志,优化任务优先级;必要时降低推理频率保证稳定性
图传画面和检测框明显错位视频流存在缓存队列延迟改用时间戳对齐,后处理时记录图像帧时间,叠加时按时间戳匹配
串口接收检测结果周期丢失MAVLink消息长度和波特率不匹配增大串口缓冲区,检测结果拆包控制在单包内,避免跨包传输
悬停时框体抖动明显云台增稳和检测帧率不匹配对输出框做简单的时间域滤波(如滑动平均)

这里没有列完所有问题,但你可以发现一个共性:绝大多数部署问题都不是出在“模型”本身,而是出在“链路”和“工程化”。模型在服务器上跑得好好的,一旦挪到带功耗限制、强振动、强光干扰的边缘场景,数据流、时间戳、资源争用都会变成新的大坑。

结尾

这个项目做下来,我个人最大的体会是:AI算力适配这个事,真正难的不是算法本身的复现,而是多框架、多硬件、多链路之间的协同。YOLOv8在Ultralytics仓库里就是一套代码的事,但要让它跑进MMYOLO、跑上开放算力平台、再跑到大疆无人机的计算单元上,任何一环的工程细节都能让整个交付延后几周。建议后边接手类似任务的人,前期多花时间把环境依赖锁死,把配置文件的每个参数都搞清楚,中间一定不要跳过“权重加载后小学习率热启动”那一步,后期部署阶段也要坚持用逐像素对比去确认模型没有烧歪。最后再分享一个小技巧:每次改完配置或代码,先把改动的git commit信息写得明明白白,多端协同的项目里,版本管理做得好的人能省下别人几倍的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询