公司动态

RDK X5部署YOLOv11实战:从环境准备到BPU优化全攻略

📅 2026/9/1 10:37:16
RDK X5部署YOLOv11实战:从环境准备到BPU优化全攻略
简介面向RDK X5开发板提供将自训练YOLOv11模型从训练成果转为板端可运行格式的源码包适合有一定YOLO算法基础、希望在嵌入式平台落地目标检测模型的开发者也适合正在做边缘计算相关课题的学生或工程师。包内共10个文件核心为3个Python脚本分别覆盖ONNX模型导出、板端部署调用与训练辅助另有量化配置YAML、Docker环境搭建脚本、依赖清单及Markdown说明文档整体仅18KB轻量且便于对照学习。跟随源码可完成PT模型到ONNX、修改输出头、检查模型结构、创建量化YAML、生成BIN文件并在RDK X5上部署的完整链路同时包含环境配置、源码修改参考与常见问题说明能帮助读者减少反复踩坑缩短部署周期。已有245人学习下载内容紧凑、目标明确适合作为RDK X5边缘部署YOLOv11的起步模板或项目底座。 把一块RDK X5开发板拿在手里的第一件事我猜大多数人和我一样不是去点灯而是想跑个YOLO看看这块号称10 TOPS算力的板子到底有几斤几两。这篇文章就基于我实际折腾的完整过程给出在RDK X5上部署YOLOv11的可运行源码从环境准备、模型导出、推理脚本到实测性能一次讲透。不管你是刚拿到板子想验证算力还是打算把检测模型落到机器人或者边缘视觉项目里这条链路都可以直接复制。我尽量不堆概念只讲我自己踩过的、验证过的东西。1. 先搞清楚RDK X5的底细再决定怎么跑YOLOv111.1 这不是普通开发板是带BPU的嵌入式AI平台RDK X5是地瓜机器人推出的开发者套件核心是旭日X5 SoC8核Cortex-A55 CPU加一颗自研BPU整板算力大概在10 TOPS这个量级。它跟树莓派最大的区别就在这颗BPU上树莓派跑AI模型靠的是CPU和GPU硬算而RDK X5的BPU是专门为神经网络推理设计的跑BEV、Transformer这类重负载模型才是它的主场。板子自带MIPI CSI摄像头接口可以接RGB或深度相机还有40pin GPIO排针所以很多做机器人和智能相机的团队都拿它当算力核心。这里要泼一盆冷水10 TOPS听起来很猛但这是BPU的整数算力不是说你在Python里随便跑个模型就能自动吃到这个红利。能不能用到BPU取决于模型能不能过地平线工具链的编译和量化。在动手之前你得先把CPU跑通当基准后面再考虑BPU加速。这一步想清楚了后面才不会绕弯。1.2 YOLOv11相比v8和v5到底强在哪里YOLOv11是Ultralytics在2024年9月推出的新一代YOLO系列最大的变化是把v8里的C3k模块换成了C3k2大模型版本引入了C2PSA结构来替代原来的SPPF部分。简单说C3k2又把梯度流重新梳理了一遍在相同参数量下特征提取效率更高C2PSA则是借鉴了Transformer里的自注意力思路让大模型在复杂背景下能抓住更全局的信息。实际部署中我更关注两个点一是相同精度下YOLOv11n的参数量和计算量比v8n还要小一点这对边缘设备非常友好二是它依然是anchor-free的解码方式输出张量结构延续了v8的约定这意味着很多v8时代写好的推理后处理代码稍加适配就能用。YOLOv11家族分成了n/s/m/l/x几个版本在RDK X5这种嵌入式平台上主力肯定是n和sm以上基本只能靠BPU才跑得动CPU想都不要想。2. 环境准备与部署路线一条开箱即用一条留给进阶2.1 先给RDK X5装好基础环境RDK X5官方提供Ubuntu 22.04 的镜像desktop版带桌面环境server版更轻量。我建议如果用SSH远程操作就刷server版省掉桌面占用的内存和CPU把资源全留给推理任务。板子到手后先把系统更新一下然后安装Python依赖sudo apt update sudo apt install -y python3-pip python3-opencv pip3 install --upgrade pip pip3 install onnxruntime opencv-python numpy这里有个容易踩的坑RDK X5是aarch64架构不要自己去官网手动下onnxruntime的wheel包直接用pip3装。pip会自动帮你匹配aarch64版本的轮子手动下载很容易拿错x86_64的包装完一跑直接报Illegal instruction。确认架构的方式很简单uname -m输出是aarch64就对了。2.2 两条部署路线怎么选ONNX Runtime还是BPU工具链部署YOLOv11到RDK X5严格说有两种路线我在动手前把这两条路的优劣列了个表对比项ONNX RuntimeCPU推理地平线BPU工具链上手难度低pip装完就能跑高要装工具链、准备校准数据集单帧延迟百毫秒级到秒级可优化到几十毫秒级算子兼容性基本所有ONNX算子都支持部分算子在量化时掉精度或编译失败适用阶段功能验证、快速原型产品化落地、性能敏感场景我的建议非常明确第一版先走ONNX Runtime把整个检测流程跑通确认模型精度和逻辑正确然后再花时间啃工具链。上来就碰BPU容易被算子报错劝退因为YOLOv11里有些新结构在传统BPU工具链里不一定能一次性编译通过到时候你根本分不清是模型的问题还是工具链的问题。先用CPU版本建立一个可信的基线后面做什么优化都有了参照。3. 可运行源码拆解从一张图片到一组检测框3.1 下载并导出YOLOv11的ONNX模型模型文件可以直接在RDK X5上用ultralytics导出也可以在电脑上导出后SCP传上去。我是在电脑上操作的因为ultralytics第一次跑会自动下载权重国内网络环境下在板子上下载经常不稳定。pip3 install ultralytics yolo export modelyolov11n.pt formatonnx imgsz640导出完成后会得到一个yolov11n.onnx大概20多MB。这里要注意导出时imgsz指定成640如果你的检测目标偏小后面要兼顾小目标优化可以试试640或者768但不要一上来就开1280RDK X5的CPU真的扛不住。3.2 预处理为什么不能直接resize这是很多第一次写YOLO推理脚本的人最容易忽略的环节。YOLOv11训练时的输入是正方形但实际图片几乎不可能是正方形直接把图片cv2.resize成640x640会导致目标变形模型精度会明显下降。正确做法是letterbox也就是保持宽高比缩放图片然后在四周补边padding填充成正方形。另外一个容易被忽视的点是通道顺序。OpenCV读进来的图片是BGR排列而YOLO在训练时用的是RGB如果不做转换模型输出会混乱到怀疑人生。这两个问题一起解决def letterbox(img, new_shape(640, 640), color(114, 114, 114)): shape img.shape[:2] r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) dw (new_shape[1] - new_unpad[0]) / 2 dh (new_shape[0] - new_unpad[1]) / 2 top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) img cv2.copyMakeBorder( img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor ) return img, r, dw, dh这里的r是缩放比例dw和dh是左右和上下的padding宽度。后处理还原坐标时要原封不动地用回来所以这三个值必须从预处理函数里带出来。3.3 推理、解码、NMS搞清楚YOLOv11输出张量的含义把图片送进模型之后YOLOv11的ONNX输出张量默认是[1, 84, 8400]。这个84怎么来的4个坐标值加80个COCO类别得分。8400则是在三种不同尺度特征图上的预测框总数。推理脚本拿到这个张量后要先转置成[1, 8400, 84]按行解析才顺手。坐标的前4个值是cx, cy, w, h也就是中心点x、中心点y、宽度、高度这跟YOLOv5/v8时代完全一致。后面80个值就是每个类别的置信度。后处理步骤就是取类别得分的最大值作为最终置信度同时记录对应的类别id然后过滤掉低于阈值的框最后做NMS去掉重叠框。这里有个小技巧NMS可以直接用OpenCV自带的cv2.dnn.NMSBoxes不用自己手写。它在aarch64的opencv-python里是完整支持的省事又不容易出错。3.4 完整可运行推理脚本下面这个脚本我实测过复制下来改一下ONNX_PATH和图片路径就能跑。COCO的80个类名比较长这里我为了代码可运行先简化成占位符你实际使用时可以从ultralytics源码的constants.py里复制完整列表或者干脆画框时只显示类别id。import time import cv2 import numpy as np import onnxruntime as ort ONNX_PATH yolov11n.onnx INPUT_SIZE 640 CONF_THRESH 0.25 IOU_THRESH 0.45 # 如果只想快速验证先用占位符后续换成完整COCO类名 CLASS_NAMES [fc{i} for i in range(80)] def letterbox(img, new_shape(640, 640), color(114, 114, 114)): shape img.shape[:2] r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) dw (new_shape[1] - new_unpad[0]) / 2 dh (new_shape[0] - new_unpad[1]) / 2 top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) img cv2.copyMakeBorder( img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor ) return img, r, dw, dh def nms(boxes, scores, conf_threshold, iou_threshold): indices cv2.dnn.NMSBoxes( boxes.tolist(), scores.tolist(), conf_threshold, iou_threshold ) if len(indices) 0: return [] return np.array(indices).flatten().tolist() if __name__ __main__: session ort.InferenceSession( ONNX_PATH, providers[CPUExecutionProvider] ) input_name session.get_inputs()[0].name img cv2.imread(test.jpg) origin_h, origin_w img.shape[:2] t0 time.time() # 预处理 inp, r, dw, dh letterbox(img, (INPUT_SIZE, INPUT_SIZE)) # BGR转RGBHWC转CHW归一化到0~1 inp inp[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 t1 time.time() # 推理 outputs session.run(None, {input_name: inp})[0] t2 time.time() # 输出解析兼容 [1,84,8400] 和 [1,8400,84] 两种shape if outputs.shape[1] 84: preds outputs.transpose(0, 2, 1)[0] else: preds outputs[0] boxes_xywh preds[:, :4] class_scores preds[:, 4:] class_ids class_scores.argmax(1) confs class_scores.max(1) keep confs CONF_THRESH boxes_xywh boxes_xywh[keep] class_ids class_ids[keep] confs confs[keep] # 把坐标从letterbox后的坐标系还原回原始图 boxes_xywh[:, 0] (boxes_xywh[:, 0] - dw) / r boxes_xywh[:, 1] (boxes_xywh[:, 1] - dh) / r boxes_xywh[:, 2] / r boxes_xywh[:, 3] / r x1 boxes_xywh[:, 0] - boxes_xywh[:, 2] / 2 y1 boxes_xywh[:, 1] - boxes_xywh[:, 3] / 2 x2 boxes_xywh[:, 0] boxes_xywh[:, 2] / 2 y2 boxes_xywh[:, 1] boxes_xywh[:, 3] / 2 boxes_xyxy np.stack([x1, y1, x2, y2], axis1) indices nms(boxes_xyxy, confs, CONF_THRESH, IOU_THRESH) t3 time.time() for i in indices: x1, y1, x2, y2 boxes_xyxy[i].astype(int) conf confs[i] cls_id class_ids[i] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) label f{CLASS_NAMES[cls_id]} {conf:.2f} cv2.putText( img, label, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2 ) cv2.imwrite(result.jpg, img) print(fpreprocess: {t1-t0:.3f}s) print(finference: {t2-t1:.3f}s) print(fpostprocess: {t3-t2:.3f}s) print(ftotal: {t3-t0:.3f}s)这个脚本我在RDK X5上跑过思路和Ultralytics官方仓库的推理逻辑是一致的但去掉了那些杂七杂八的依赖只留了OpenCV和ONNX Runtime很适合嵌入式环境。4. 实测性能与第一步调优4.1 RDK X5上跑YOLOv11的参考数据我在RDK X5上分别试了YOLOv11n和YOLOv11s两个模型输入分辨率640x640CPU线程数默认FP32精度。参考数据大致如下模型CPU线程数单帧推理耗时是否能实时YOLOv11n4500~800ms量级否仅适合离线处理YOLOv11n8400~650ms量级否提升不明显YOLOv11s41200~1800ms量级否明显吃力注意这个数值只做参考不同系统镜像、电源策略、板子温度都会影响结果。我在跑的时候明显感觉到大核全开之后散热片烫手频率一降性能就往下掉。如果你用的是桌面版系统性能还会再差一截所以再次建议用server版把后台服务都关掉。4.2 不碰BPU也能做的几个提速手段如果你暂时不想碰BPU工具链CPU推理也有几个立竿见影的提速手段设置推理线程数ort.InferenceSession创建时可以传入session_options.intra_op_num_threads我试过4到8线程性能不是线性提升的往往4线程性价比最高。用onnx-simplifier简化模型把一些冗余算子合并掉模型更小推理时也能省一点时间pip3 install onnx-simplifier python3 -m onnxsim yolov11n.onnx yolov11n_sim.onnx降低输入分辨率。从640降到480耗时能下降接近一半但代价是小目标会更容易漏检。这个对很多做小目标优化的项目是个提醒模型结构上再怎么改进输入分辨率不够小目标照样丢。确认onnxruntime版本不要太老。新版本对ARM CPU的调度和算子实现有明显优化我在板子上从1.16升到1.18左右推理耗时能缩短个10%到20%。这些手段都不是银弹组合起来用才能看到整体效果。5. 踩坑记录我在这块板子上遇到的三个实际坑5.1 坑一onnxruntime装错架构一跑就崩我在板子上第一次装onnxruntime图省事直接在桌面浏览器里下载了x86_64的wheel包然后pip install本地文件。装的时候没有报错但一跑InferenceSession就报Illegal instruction (core dumped)。排查了一会儿才反应过来RDK X5是aarch64架构下载错了包。这个问题的根源是我对pip会自动匹配架构这个机制理解不够手动下载反而绕过了pip的自动选择。解决办法很简单pip3 uninstall onnxruntime再重新pip3 install onnxruntime让pip自己去下载正确的版本。5.2 坑二letterbox还原不对检测框全偏到左上角跑通后的第一次验证模型确实检测出了物体但画出来的框全都偏在图片左上角位置明显不对。排查过程是这样的先怀疑是解码公式写错了重新对着YOLOv8的官方坐标还原逻辑逐行比对发现没问题再怀疑是NMS之后索引取错了打印出来也没问题。最后突然想到是不是letterbox的padding没算对回去看预处理函数发现我返回的dw和dh是从letterbox内部直接算出来的浮点数但在还原坐标时我先对box坐标做了归一化又错误地把padding当作像素值减了一边等于两边各减了一次导致x、y整体偏移了大约一个padding的量。这类问题光看代码很难一眼发现最好的排查办法就是打印几个关键中间值原始框坐标、缩放比例r、padding值dw/dh用一张已知目标位置的测试图去对基本能定位到是哪个环节出了问题。5.3 坑三BPU工具链转换YOLOv11时算子不支持这条路我后来也试过用官方工具链把YOLOv11n转成BPU模型时C3k2里面的一些算子量化编译报错需要对模型做结构替换或者走CPU算子回退。折腾了一阵子后我的结论是如果不是对延迟有硬性要求现阶段用ONNX Runtime做CPU推理完全够用如果一定要跑BPU建议先去翻工具链的算子支持列表确认C3k2和C2PSA相关的算子都在支持范围里再动手别等编译报错才回头查。后来我在实际项目里换了个思路YOLOv11的C3k2模块和YOLOv8的C3k在结构上非常接近拿v8的模型做BPU量化部署基本没障碍精度损失也可控。如果你的场景对模型结构没有硬性要求不妨先试试YOLOv8在BPU上的表现整体流程顺很多。写在最后先把CPU跑通再谈BPU优化我在RDK X5上部署YOLOv11这一圈下来最大的体会是嵌入式部署最忌讳一步到位的心态。先用ONNX Runtime把流程跑通确认预处理的letterbox、后处理的解码和NMS逻辑都正确再考虑BPU工具链和性能优化这样每一步都有明确的对照基线。另外提一个后续可以扩展的方向把推理循环改成摄像头视频流输入配合板子上的MIPI CSI接口做实时检测再通过40pin GPIO输出检测结果控制舵机或者报警器这其实就是一个小型视觉机器人的雏形了。RDK X5的40pin引脚图一定要提前下载到本地我第一次接线的时候把I2C和串口的引脚位置看反了排查了半天才发现是硬件层面接错了。这套源码和思路希望能帮你少走几步弯路。如果你在板子上跑出来的性能跟我参考数据差异很大先别急着怀疑模型看看系统是不是桌面版、散热有没有做好、后台服务是不是占着CPU这几个因素对A55这种架构的影响比想象中大得多。本文还有配套的精品资源点击获取