新闻详情

新闻详情

首页 / 资讯中心 / 详情

RK3588上YOLOv5 C++部署:交叉编译与RKNN推理完整指南

发布时间:2026/9/28 19:20:02来源:尧图网络
RK3588上YOLOv5 C++部署:交叉编译与RKNN推理完整指南
1. 为什么非要交叉编译RK3588上板的第一道坎1.1 先想清楚为什么在你的电脑上编译的C程序板子跑不了很多读者看到交叉编译四个字就开始头疼其实这个东西的底层逻辑特别简单。我们现在用香橙派5RK3588跑YOLOv5日常工作台是x86架构的Ubuntu主机而板子是ARM64架构AArch64的Linux系统。x86和ARM是两套底层指令集你在x86上用默认gcc编出来的ELF文件拿到ARM板子上就是一堆不认识的机器码表现出来就是直接报Exec format error或者运气好点能加载但一跑到特定指令就Segmentation fault。类比一下你写了一份中文讲稿想讲给只会英文的人听。你直接在台下用中文念对方听不懂你需要先把稿子翻译成英文再把英文念给他听。交叉编译器就是那个翻译官——它运行在你的x86主机上但生成的是ARM板子能执行的机器码。这个翻译过程在嵌入式开发里是标配因为板载编译太慢了——香橙派5虽然性能不弱但你要在板子上现场编OpenCV、RKNN这些大工程那酸爽程度不建议体验。1.2 工具链选型aarch64交叉编译器到底该用哪个RK3588是ARMv8-A架构必须用AArch64交叉编译器。这里有个经验别用Ubuntu apt源里那个gcc-aarch64-linux-gnu也不是不能用只是版本对齐比较麻烦。我更推荐直接用Rockchip打包好的交叉编译工具链常见的有gcc-arm-10.3-2021.07-aarch64-aarch64-none-linux-gnu这一套或者Linaro GCC 7.5、9.2都行。如果你手头是野火、正点原子这类RK3568开发板SDK里自带的交叉编译器比如prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3实际在RK3588上也能直接用逻辑完全一致。下载解压后把它加到PATH环境变量里export PATH/opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH验证一下aarch64-none-linux-gnu-gcc --version能看到版本号就说明工具链没问题。注意你后面写CMakeLists的时候编译器路径必须精确指向这个bin目录下的gcc和g不能靠系统默认的gcc否则编出来又是x86的。1.3 主机环境的基本要求主机建议Ubuntu 20.04或18.0464位x86。我这次用的是Ubuntu 20.04配合RKNN-Toolkit2的2.2.0版本。这里提前说一句RKNN的工具链版本、板子固件里的runtime版本、librknnrt.so版本这三者必须能对得上否则后面哭的地方多着呢。具体怎么对齐第2节详细讲。2. 一堆文件先理清RKNN Runtime头文件、动态库和工具链的版本匹配2.1 RKNN的PC端转换、板端推理分工逻辑先建立一个大框架。你用YOLOv5训练出来的模型是PyTorch的 .pt 权重这个权重不能直接丢给NPU。整个部署流程是PC端用rknn-toolkit2这个Python工具把.pt - .onnx - .rknn做一个带量化或不带量化的模型转换。转换过程中会指定目标平台是rk3588同时把图像的归一化均值、标准差、输入分辨率等预处理摊到模型里。板端加载.rknn文件的是librknnrt.so这个runtime库它负责与RK3588的NPU驱动通信把推理任务派发到NPU上执行。所以你现在的主要任务是写一段C代码在板上加载 .rknn 模型把一张图片喂进去把NPU输出的张量拿回来再自己写后处理解码置信度过滤NMS最后把框画出来。这里一百度搜基本都是Python的RKNN推理脚本但生产环境上板多数还是C因为内存可控、启动快、没有Python解释器的开销。2.2 rknn_api.h和librknnrt.so从哪来交叉编译需要两块料声明API的rknn_api.h和链接用的librknnrt.so。这两个文件一般在RK SDK的rknpu2/runtime目录下或RKNN-Toolkit2安装包里自带的示例工程里能找到。我的做法是建了一个项目目录把依赖都收拢到一起~/rknn_yolov5_demo/ ├── build/ ├── include/ │ └── rknn_api.h ├── lib/ │ └── librknnrt.so ├── model/ │ └── yolov5s_relu.rknn ├── src/ │ └── main.cpp ├── CMakeLists.txt └── test.jpg然后把rknn_api.h复制到include/librknnrt.so复制到lib/。这里有个关键点librknnrt.so的版本必须和你板子固件里的NPU驱动匹配。判断方法是看release notes或者直接在板子上跑ls /usr/lib/librknnrt.so看版本信息。我踩过的坑就是PC端rknn-toolkit2升到2.3板子固件还是老runtimeAPI调用直接报RKNN_ERR_MODEL_INVALID所以版本思路是第一位的。2.3 三个版本对照PC工具链、runtime、模型转换的平台参数做一个简单对照记忆环节版本/参数对应关系rknn-toolkit2PC转换工具2.2.0决定.rknn模型的格式规范librknnrt.so板端runtime2.2.0必须和转换工具同代或兼容模型转换时config的target_platformrk3588和实际板子芯片一致不要填错成rk3568板子固件NPU驱动随固件建议固件升级到支持runtime对应版本注意在模型转换时如果用rknn.config(target_platformrk3588, mean_values[[0,0,0]], std_values[[1,1,1]])我一般把RGB三通道均值设成0、方差设成1让归一化在代码里用letterbox和除以255的方式自己控制。这样后处理的时候对图像数值心更有数。3. NPU调用流程拆开看YOLOv5推理C代码的骨架3.1 初始化rknni_handle加载模型从最基础的API调用开始。C端的第一个动作是rknn_init把 .rknn 模型文件读进来拿到一个rknn_context的句柄之后所有操作都通过这个句柄和NPU打交道。rknn_context ctx; int ret rknn_init(ctx, model_path, 0, 0, NULL); if (ret 0) { printf(rknn_init failed: %d\n, ret); return -1; }这个函数如果返回负数多半是模型文件有问题或runtime版本对不上。对应报错码可以查rknn_api.h头文件里rknn_error_code的枚举定义比如RKNN_ERR_MODEL_INVALID -6。拿到句柄后建议立刻用rknn_query把模型输入输出的信息打出来。这是后面写后处理最重要的参考因为不同版本的YOLOv5导出方式不同输出张量的shape可能差很大。rknn_input_output_num io_num; ret rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(io_num)); printf(input num: %d, output num: %d\n, io_num.n_input, io_num.n_output); rknn_tensor_attr input_attr[io_num.n_input]; memset(input_attr, 0, sizeof(input_attr)); for (uint32_t i 0; i io_num.n_input; i) { input_attr[i].index i; ret rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, input_attr[i], sizeof(input_attr[i])); printf(input %d: shape[%d,%d,%d,%d], type%d\n, i, input_attr[i].dims[0], input_attr[i].dims[1], input_attr[i].dims[2], input_attr[i].dims[3], input_attr[i].type); }以我们用640x640输入导出的YOLOv5s为例正常打印出来 input 的shape是[1, 640, 640, 3]注意RKNN的输入默认是NHWC布局最后一位是通道数和OpenCV的HWC数据布局正好对应可以直接memcpy不用做mixchw,这一点对性能很重要。3.2 输入张量设置与推理图像预处理resize、letterbox、转RGB做好之后填充结构体喂给NPUrknn_input inputs[1]; memset(inputs, 0, sizeof(inputs)); inputs[0].index 0; // 把内存中连续排列的RGB像素传进去 inputs[0].buf img_data; inputs[0].size width * height * 3; inputs[0].pass_through 0; // 0表示需要NPU对数据做归一化等预设处理 inputs[0].type RKNN_TENSOR_UINT8; inputs[0].fmt RKNN_TENSOR_NHWC; ret rknn_inputs_set(ctx, 1, inputs); ret rknn_run(ctx, NULL);注意pass_through的选择如果你在模型转换时把均值方差写进config了这里设为0即可如果设成1表示原始数据不做任何处理直接丢给模型那预处理就完全靠自己。我建议新手用pass_through0省心。3.3 获取输出张量推理完成后用rknn_outputs_get把输出取回来rknn_output outputs[3]; // YOLOv5s一般是3个输出 memset(outputs, 0, sizeof(outputs)); for (int i 0; i 3; i) { outputs[i].want_float 1; // 让runtime输出float数据方便后处理 } ret rknn_outputs_get(ctx, 3, outputs, NULL); // 处理完释放 rknn_outputs_release(ctx, 3, outputs);这里want_float1有个取舍模型如果做了INT8量化输出默认是INT8数据如果设成1让runtime帮你反量化转成float会多一点点转换开销但后处理写起来舒服得多。做高帧率部署时可以设成want_float0自己写反量化逻辑收益其实不小。新手先别急着搞这层优化先把流程跑通。4. 从张量到目标框YOLOv5后处理的全套换算逻辑4.1 YOLOv5s的三个尺度输出代表什么如果你用的是官方YOLOv5 6.0/6.1版本导出的模型输入640x640输出是3个blob分别对应三种感受野80x80的feature map负责检测小目标40x40的feature map负责检测中等目标20x20的feature map负责检测大目标每个feature map上的每个格子预置3个anchor框。所以光看原始张量80x80x3x85、40x40x3x85、20x20x3x85。85这个数字怎么来的4 1 80前4位是bounding box的xywh第5位是objectness置信度后面80位是COCO数据集80个类别的概率。三个尺度加起来一共 64001600400 8400 个候选框。在RKNN导出的模型里这三个输出可能被压成[1, 6400, 85]、[1, 1600, 85]、[1, 400, 85]或者保持原始[1, 80, 80, 255]、[1, 40, 40, 255]、[1, 20, 20, 255]的形状255 3x85因为anchor的3个先验信息放在了channel维度。我建议用前面提到的rknn_query把dims打印出来确认所见即所得不要猜。4.2 坐标反解与letterbox还原接下来的核心是把每个格子的xywh换算回原图的坐标。YOLOv5检测头输出的xywh是相对feature map尺寸的逻辑坐标需要做sigmoid、乘以anchor、乘以stride等一系列操作。官方给出的公式是x_center (sigmoid(x) * 2 - 0.5 grid_x) * stride y_center (sigmoid(y) * 2 - 0.5 grid_y) * stride w (sigmoid(w) * 2) ^ 2 * anchor_w h (sigmoid(h) * 2) ^ 2 * anchor_h这里grid_x、grid_y是当前格子在整个feature map中的位置stride是多少倍下采样80x80对应stride840x40对应stride1620x20对应stride32。换算出来的坐标是针对640x640输入图的。但我们原始图片经过letterbox填充后才变成640x640所以还得反向操作一次先算出缩放比例和padding偏移然后把框从这个letterbox后的坐标系还原到原图坐标系。float scale min(640.0f / img_w, 640.0f / img_h); float pad_x (640 - img_w * scale) / 2.0f; float pad_y (640 - img_h * scale) / 2.0f; // 假设model_x、model_y是640坐标系下的中心点坐标 float orig_x (model_x - pad_x) / scale; float orig_y (model_y - pad_y) / scale;这个还原我经常看到有人漏写结果就是检测框整体偏移到左上方边界框位置差一截。你如果发现框的位置总是不对先检查这段映射。4.3 置信度过滤与NMS的C实现所有候选框解码完成后按流程过滤先丢objectness低于阈值的框一般conf_thresh 0.25。对剩下的框取80个类别分数里的最大类别和最大得分作为该框的最终得分。最终得分再低于阈值的直接丢掉。剩下框按得分从高到低排序进行NMS非极大值抑制。NMS逻辑不复杂从得分最高的框开始依次计算它和其他框的IoUIoU大于阈值的比如0.45就认为重叠太多直接删除。C里我用一个简单的vector实现struct DetBox { float x1, y1, x2, y2; float score; int class_id; }; vectorDetBox nms(vectorDetBox boxes, float iou_thresh) { sort(boxes.begin(), boxes.end(), [](const DetBox a, const DetBox b) { return a.score b.score; }); vectorbool removed(boxes.size(), false); vectorDetBox result; for (size_t i 0; i boxes.size(); i) { if (removed[i]) continue; result.push_back(boxes[i]); for (size_t j i 1; j boxes.size(); j) { if (removed[j]) continue; float inter intersection_area(boxes[i], boxes[j]); float uni union_area(boxes[i], boxes[j]); if (inter / uni iou_thresh) removed[j] true; } } return result; }提示YOLOv5标准做法是先按类别过滤再对每个类别分别做NMS以避免不同类别重叠目标被误删。实际部署时如果类别不多直接全类别一起NMS也能跑效果差异很小。我是按类别循环做的逻辑上更严谨。5. CMake交叉编译配置少走弯路的做法和排雷5.1 完整可用的CMakeLists.txt我最开始图省事直接写g命令行交叉编译依赖一多就崩了。后来老老实实用CMake一次配置完后患少很多。这个工程的核心依赖是librknnrt.so和OpenCVCMakeLists长这样cmake_minimum_required(VERSION 3.10) project(yolov5_demo CXX) set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(TOOLCHAIN_DIR /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu) set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-none-linux-gnu-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-none-linux-gnu-g) # 本工程自带头文件 include_directories(${CMAKE_SOURCE_DIR}/include) link_directories(${CMAKE_SOURCE_DIR}/lib) # OpenCV头文件和库的路径从板子拷回来的 set(OpenCV_DIR /opt/rk3588_opencv/lib/cmake/opencv4) find_package(OpenCV REQUIRED) add_executable(yolov5_demo src/main.cpp) target_link_libraries(yolov5_demo ${OpenCV_LIBS} rknnrt pthread )这里唯一的难点是OpenCV的交叉编译。想从源码编一个ARM版OpenCV至少得花一两个小时而且容易出幺蛾子。我的做法是从板子上直接拷香橙派5烧写的Ubuntu 20.04系统镜像里已经带好OpenCV在板子上执行# 在板子上执行 find /usr -name libopencv* 2/dev/null find /usr -name opencv4 -type d 2/dev/null然后把板子上/usr/include/opencv4整个目录和/usr/lib/aarch64-linux-gnu下的libopencv_world.so*拷回工作台注意不要拷全部只拷libopencv_world*就行Ubuntu里OpenCV是合并成一个world库的放到/opt/rk3588_opencv下。这套路屡试不爽省掉交叉编译OpenCV的巨大工程量。5.2 产物验证链接的是什么架构心里要有数编译完成后做两个验证# 在宿主机上 file build/yolov5_demo输出应该包含ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV)。如果显示x86-64说明编译器又没指对回去查CMakeCache。再检查动态库依赖aarch64-none-linux-gnu-readelf -d build/yolov5_demo | grep NEEDED正常应该看到librknnrt.so和libopencv_world.so.xxx。如果发现依赖了x86的库路径排查link_directories是否写错。5.3 踩坑ldd不能查交叉编译产物这里特别提醒别在宿主机上用ldd查ARM交叉编译产物的依赖宿主机的ldd是x86的只会给你报not a dynamic executable。用aarch64-none-linux-gnu-readelf -d或者把程序拷到板子上用板子的ldd查。这个错误我见得太多了论坛里天天有人问为什么ldd报错就是架构没搞明白。6. 上板运行实测部署文件、性能调参与报错对照6.1 部署文件清单把下面三个文件上传到香橙派5的同一个目录例如/root/yolov5build/yolov5_demo交叉编译好的可执行程序model/yolov5s_relu.rknn模型转换后的RKNN文件test.jpg测试图片建议找一张经典的多目标图比如网上常见的人、自行车、车厢混合场景用scp或者adb推过去scp yolov5_demo root192.168.x.x:/root/yolov5/ scp yolov5s_relu.rknn root192.168.x.x:/root/yolov5/ scp test.jpg root192.168.x.x:/root/yolov5/6.2 设置库路径并运行板子上的librknnrt.so一般已经在/usr/lib下但如果你的板子固件比较新或比较旧可能路径不一样。运行前统一设置export LD_LIBRARY_PATH/root/yolov5:/usr/lib:$LD_LIBRARY_PATH chmod x ./yolov5_demo ./yolov5_demo test.jpg跑通后控制台会打印模型输入输出信息、推理耗时、检测到的目标类别和置信度同时程序会把画好框的结果保存成result.jpg把它拉回宿主机看一眼确认框的位置和类别是对的。我第一次跑通时看到的是person 0.87, bicycle 0.62, car 0.54框也贴得挺准那一刻的成就感确实是Python部署给不了的。6.3 性能数据与调参方向实测下来在香橙派5上跑INT8量化的YOLOv5s640x640输入单线程普通调用不含前后处理的前提下NPU推理时间大约在40ms级别整体帧率大概15帧左右。如果你把所有预处理、后处理都优化一遍可以压到20帧以上。几个已经验证有效的优化点NPU核心调度RK3588的NPU有3个核心用rknn_set_core_mask(ctx, RKNN_NPU_CORE_0_1_2)让三个核一起干活比默认单核提升明显。want_float改0输出拿INT8后再自行反量化省掉runtime侧的float转换。多线程流水线让前处理、NPU推理、后处理三段跑在不同线程上这是把板子帧率拉起来的关键套路。确认模型推理类型如果用的不是量化模型而是FP16推理时间会明显变长目标检测这种场景能量化尽量量化精度损失一般能控制在3%以内。6.4 常见报错对照表最后放一张排查表都是我实际踩过或者身边人反复问过的问题现象根因解决error while loading shared libraries: librknnrt.so动态库路径没被找到export LD_LIBRARY_PATH或把.so放进/usr/librknn_init failed: -6模型和目标平台的runtime版本不匹配检查rknn-toolkit2与板端runtime版本一致性Segmentation fault输出buffer大小估计错误或坐标越界用rknn_query打印dims严格按shape分配内存Exec format error可执行文件不是ARM架构file命令检查编译产物确认交叉编译器路径检测框歪斜/偏移letterbox坐标逆映射漏写补上pad和scale的反算逻辑全部框都消失了置信度阈值太高或模型没有正常量化把conf_thresh放到0.1测试同时检查前处理是否做对这些坑里最阴的是Segmentation fault基本都出在rknn_output的size了你没按实际shape取或者只分配了几百字节就memcpy几万字节。建议拿到输出前先printf(output size: %d\n, outputs[i].size)把真实size打出来心里有数了再去动指针。这个交叉编译部署流程其实不止YOLOv5能用。后面你要是换成YOLOv8或者自己训练的自定义模型只要还是rknn导出的.rknn模型这一步的C推理框架和CMake配置几乎原封不动只改后处理部分就行。这也是我在一开始花了力气写通用推理骨架的原因——一次跑通之后换模型都顺手。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Radar Kubernetes UI快速上手:6种安装方式全解析,kubectl radar一条命令玩转集群仪表盘 2026/9/28 20:17:00

Radar Kubernetes UI快速上手:6种安装方式全解析,kubectl radar一条命令玩转集群仪表盘

Radar Kubernetes UI快速上手:6种安装方式全解析,kubectl radar一条命令玩转集群仪表盘 【免费下载链接】radar The missing open-source Kubernetes UI with a built-in MCP server for AI agents. See whats broken, why, and what changed. Issues, Topology, event timelin…

阅读更多 →
深入理解MySQL_经典问题总结1 2026/9/28 20:16:59

深入理解MySQL_经典问题总结1

✅MySQL存储引擎有哪些,有什么区别?MySQL主要的存储引擎有InnoDB,MyISAM,Memory,Archive,NDB。InnoDB是默认的存储引擎,支持事务和外键,通过聚簇索引根据主键快速访问数据&#xff0…

阅读更多 →
12.RK3588 的 6TOPS NPU 到底能做什么?边缘 AI 落地场景盘点 2026/9/28 20:16:59

12.RK3588 的 6TOPS NPU 到底能做什么?边缘 AI 落地场景盘点

RK3588 的 6TOPS NPU 到底能做什么?边缘 AI 落地场景盘点摘要:6TOPS 听起来很美,但"标称算力"和"实际可用算力"之间隔着模型量化、带宽和工程优化。本文盘点 RK3588 NPU 在工业与安防场景中已经验证可落地的典型应用&…

阅读更多 →
达摩院用平扫 CT 查食管癌:不插管的 AI,难点到底在哪 2026/9/28 20:16:58

达摩院用平扫 CT 查食管癌:不插管的 AI,难点到底在哪

先把事实摆上:阿里达摩院联合四川省肿瘤医院、中山大学肿瘤防治中心等机构,发布了食管癌筛查 AI 模型 DAMO EAGLE。它不需要插管做内镜,也不需要注射造影剂,仅凭一张常规的胸部平扫 CT,就能识别食管癌以及早期的癌前病…

阅读更多 →
书霸AI降重清单|www.shubaai.com 2026/9/28 20:16:57

书霸AI降重清单|www.shubaai.com

论文写完,并不等于可以直接提交。很多同学最后卡住的地方,往往是重复率、AIGC检测结果,或文档格式没有处理妥当。书霸AI的“降重/降AIGC”功能,可以把这些检查集中到一个流程中完成。使用前,建议先准备好论文原稿&…

阅读更多 →
二手车价格预测Python实战:期末机器学习作业全流程指南 2026/9/28 20:16:51

二手车价格预测Python实战:期末机器学习作业全流程指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉