ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RV1126 跑通 YOLOv6:模型转换、INT8 量化与板端推理全流程

RV1126 跑通 YOLOv6:模型转换、INT8 量化与板端推理全流程 简介基于rv1126平台的yolov6实时目标检测工程源码面向嵌入式AI开发者与IPC方案二次开发人员解决在瑞芯微RV1126芯片上高效部署yolov6模型并进行实时推理的问题。压缩包共109个文件约3.87MB涵盖src目录下main.cpp、model.cpp、process.cpp、rkmedia.cpp、readfile.cpp等核心模块配套include头文件、lib依赖库以及bin目录下可直接运行的可执行文件与rknn模型也包含CMake构建脚本和Makefile便于交叉编译与移植。代码按模型加载、推理、后处理、RKMedia采集链路清晰分层需在model.h修改模型路径、在rkmedia.h调整分辨率即可适配不同场景适合具备一定RKNN/RV1126开发基础、希望快速跑通目标检测Demo的工程师参考。目前已有124人学习下载整体结构紧凑、依赖明确是少见的开箱即用RV1126目标检测实践资料。1. 为什么「rv1126 yolov6」值得折腾摄像头要跑实时目标检测放服务器延迟不可控普通 ARM 核又跑不动。RV1126 集成的 2 TOPS NPU 正好卡在成本和算力的甜点上量化后的 yolov6s 在 640×640 输入下做出 20 FPS 以上是边缘摄像头方案里被反复验证过的水平。标题里的“开箱即用”说的是源码把模型转换、板端推理、显示输出整条链路都打通了上电就能看到检测框。但真正决定能否复现的是转换时的算子兼容性、量化后的精度损失、以及 CPU 侧后处理能否跟上 NPU 的速度。下文按模型转换、板端推理、性能调优三段展开每一步都带参数和排错思路适合正在评估 RV1126 方案的嵌入式工程师也适合想把 YOLO 系模型压到边缘设备上的人。2. 硬件与软件栈rv1126 的 NPU 算力与 yolov6 模型落地路线2.1 RV1126 的 2 TOPS 算力能扛住多大的模型RV1126 的 NPU 标称 2 TOPS INT8 算力这是理论峰值实际卷积能利用到 70% 到 85% 就算优化得不错。整颗 SoC 还带着四个 Cortex-A7 核主频 1.5 GHz跑完 Linux 和相机链路后能分给后处理的 CPU 资源很少。所以模型选型逻辑很直接能在 NPU 上跑的卷积尽量重把 Backbone 和检测头整体放进去必须在 CPU 上做的解码与 NMS 尽量轻别给 A7 添负担。以 YOLOv6 的 n/s/m 三档来看n 约 4.3M 参数s 约 17Mm 约 34M。RV1126 上的量产方案大多落在 n 和 s 之间。640×640 输入、INT8 量化后s 型号单帧 NPU 推理通常在 30 到 50ms配合后处理整体能到 20 FPS 左右。想要更高帧率优先把输入砍到 416×416 或换 n 型号而不是改模型结构。卷积层的通道数和输入分辨率直接决定 NPU 实际耗时评估顺序排在模型结构之前。提示RV1126 的 NPU 内部按通道拆分并行计算模型通道数过大时拆核搬运的开销会吃掉一部分标称算力。评估余量别拿 2 TOPS 直接做除法以实测帧率为准。2.2 yolov6 的检测头结构为什么输出不是现成的框YOLOv6 用解耦头分类和回归走两条分支回归分支用 DFLDistribution Focal Loss输出边界框偏移量的分布而不是直接的 x1y1x2y2。也就是说模型输出只有分类得分张量和回归分布张量拿不到现成坐标后处理必须自己做。拿到源码仓后先跑一句导出命令把 PyTorch 权重转成 ONNXpython tools/export.py --weights yolov6s.pt --img-size 640 640 --batch 1 --simplify--simplify 用 onnx-simplifier 清掉导出时残留的常量折叠节点。注意不要为了在 PC 上方便推理就加 --end2end 把 NMS 封装进模型NPU 擅长卷积NMS 这类带循环和动态条件的逻辑放进图里要么转换失败要么推理奇慢导出时保持 end2end 选项关闭即可。导出后用 netron 打开 onnx 确认输出节点。常见排布是每个尺度输出两个分支分类分支形状形如 [1, C, H, W]回归分支形如 [1, 64, H, W]也有版本把多尺度结果拼成 [1, 8400, C] 这类合并张量。这里建议保持多输出节点不要为了看着简洁在图上加 concat 和 transposeNPU 上搬运算子的开销比想象中大。2.3 工具链选型rv1126 用 rknn-toolkit 1.x别拿 toolkit2 硬转工具链选错是新手最常卡的地方。RV1126、RV1109、RK1808 属于第一代 RKNN 体系对应 rknn-toolkit 1.7.x 和板端运行时 librknn_api.so后续的 RK3566、RK3568、RK3588、RV1106 才用 RKNN-Toolkit2 和 librknnrt.so。两套工具的模型格式不通用拿 toolkit2 转出的 rknn 文件在 RV1126 上加载会直接报版本错误。芯片转换工具target_platform板端动态库RV1126 / RV1109rknn-toolkit 1.7.xrv1126librknn_api.soRK3568 / RK3588rknn-toolkit2rk3568 / rk3588librknnrt.so转换在 x86 主机的 Ubuntu 上完成装好匹配 Python 版本的 wheel 即可板子侧只需要 rknpu 内核驱动和对应的 .so。拿到标题里的源码包时先看转换脚本 import 的是 rknn.api 还是 toolkit2 的包这决定整套环境怎么搭省去后面一半的排错时间。3. 模型转换与 INT8 量化把 yolov6 变成 rv1126 能加载的 rknn 文件3.1 转换脚本与 rknn.config 参数逐项说明转换脚本骨架如下核心是 config 里的四个参数from rknn.api import RKNN rknn RKNN() ret rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrv1126, quantized_dtypeasymmetric_quantized-u8, ) assert ret 0, rknn config failed ret rknn.load_onnx(modelyolov6s.onnx) assert ret 0, load onnx failed ret rknn.build(do_quantizationTrue, datasetdataset.txt) assert ret 0, rknn build failed rknn.export_rknn(yolov6s.rknn)mean_values 和 std_values 必须和训练对齐。YOLOv6 官方的训练预处理是归一化到 0~1所以 mean 全 0、std 全 255 等价于像素值除以 255如果微调时用了别的归一化这里不改后面推理输出会整体偏移。quantized_dtype 用非对称 uint8 量化RV1126 的 NPU 对 u8 支持最好。target_platform 填 rv1126别填 rv1109两者的算子支持存在差异。参数常用值作用与注意点mean_values[[0,0,0]]输入均值必须与训练一致std_values[[255,255,255]]输入标准差YOLOv6 即 /255target_platformrv1126按芯片填影响算子调度方式quantized_dtypeasymmetric_quantized-u8INT8 量化边端默认选择do_quantizationTrue关闭等于 fp16体积大且部分算子不支持build 之后别忘了 export_rknn经常有人转换完直接拿板子加载报找不到文件才想起没导出。转换全程不需要板子参与但强烈建议用与部署一致的 Python 主版本跑rknn-toolkit 1.7.x 对 Python 3.6/3.8 的支持有差异机器上装了多个 Python 时容易 import 错包。3.2 量化数据集直接决定部署精度images/street_001.jpg images/street_002.jpg images/people_003.jpgdataset.txt 是图片路径列表每行一张。rknn.build 的 dataset 参数指向它工具会逐张读图并统计各层激活值分布量化缩放系数就从这些统计里计算。数量 20 到 50 张足够超过 100 张边际收益很小。关键在于图片内容和真实场景一致做行人检测就放行人密集的街景做工业质检就放合格品和缺陷品混排。校准集全是风景图的话量化尺度会偏向错误分布部署后常见表现是低置信度目标漏检、高置信度目标框偏移。dataset 里的图片分辨率和预处理也要和部署对齐。转换工具会按模型输入自动 resize但如果原图宽高比和 640×640 差太多直接拉伸会改变目标形变量化统计跟着偏。常见做法是先中心裁剪再 resize或者直接用部署侧同一套预处理代码生成校准图保证校准路径和推理路径一致量化出来的 scale 才真实。3.3 算子兼容性与转换报错的快速定位报错特征可能原因处理办法提示 unsupported op失败位置固定图里带了 NMS、GridSample 等 NPU 不支持的算子回到导出环节去掉端到端后处理或裁剪对应子图转换成功板端输出全 0 或全相同输入排布、mean/std 与部署链路不一致对照转换脚本里的预处理参数查输入路径板端加载 rknn 报版本错误toolkit2 转换产物或 rknpu 驱动不匹配换回 1.7.x 工具链核对板侧 librknn_api.so 版本遇到第一个情况先用 netron 定位失败节点在哪个子图里多数是导出时带了端到端逻辑。少数情况是某些注意力实现转成 ONNX 后出现工具不认识的复合算子处理办法是把该模块显式拆成卷积加激活再导出。转换日志里从 ERROR 往上翻能看到具体算子名和输入张量 shape拿这两个信息直接查对应版本 RKNN 的算符支持表命中率最高。4. rv1126 上的实时目标检测闭环采集、NPU 推理与后处理4.1 C API 推理主循环与输入输出缓冲板端工程绝大多数用 C 写推理主循环Python 只留在 x86 转换端。核心调用长这样#include rknn_api.h rknn_context ctx; int ret rknn_init(ctx, yolov6s.rknn, 0, 0, NULL); if (ret 0) return -1; rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(io_num)); rknn_tensor_attr in_attr {0}; in_attr.index 0; rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, in_attr, sizeof(in_attr)); int h in_attr.dims[2], w in_attr.dims[3]; // NCHW 排布 rknn_input in {0}; in.index 0; in.type RKNN_TENSOR_UINT8; in.size w * h * 3; in.fmt RKNN_TENSOR_NHWC_RGB; in.buf frame_rgb; // 已转换好的 RGB 帧 rknn_inputs_set(ctx, 1, in); rknn_run(ctx, NULL); rknn_output outputs[io_num.n_output]; for (int i 0; i io_num.n_output; i) outputs[i].want_float 1; rknn_outputs_get(ctx, io_num.n_output, outputs, NULL); /* 在这里做 DFL 解码 NMS */ rknn_outputs_release(ctx, io_num.n_output, outputs);rknn_init 第三个参数是 flag0 表示默认初始化。输入类型选 UINT8 并配合 NHWC_RGB这样喂进去的是直接从采集链路拿到的像素不需要转 float。编译时链接 -lrknn_api -lpthread头文件用 SDK 里的 rknn_api.h。want_float 置 1 时运行时会替你把 INT8 输出反量化成 float开发期省事量产想省一次数据搬运可以置 0自己按每个输出的 scale 和 zero_point 手工反量化。阶段want_float提供的便利额外开销开发调试1直接用 float 后处理多一次反量化拷贝量产0自行控制反量化时机省一次 buffer 搬运4.2 DFL 解码与 NMS 的 CPU 实现4.2.1 把分布还原成四个偏移量回归分支每个位置输出 64 个通道按 ltrb 分四组每组 16 个值对应一条边的分布。先对每组做 softmax再按索引 0~15 求期望乘 stride 得到真实偏移static void dfl_decode(const float *reg, float stride, float *ltrb) { for (int side 0; side 4; side) { const float *p reg side * 16; float maxv p[0]; for (int i 1; i 16; i) maxv fmaxf(maxv, p[i]); // 防溢出 float sum 0.0f, acc 0.0f; for (int i 0; i 16; i) { float e expf(p[i] - maxv); sum e; acc i * e; } ltrb[side] (acc / sum) * stride; } }减 max 再算 exp 是为了避免 float 直接 softmax 溢出。拿到 ltrb 后用网格坐标乘 stride 得到中心点x1 cx - ltrb[0]y1 cy - ltrb[1]x2 cx ltrb[2]y2 cy ltrb[3]最后裁剪到图像范围内。stride 按模型下采样倍数取三个输出尺度分别对应 8、16、32。4.2.2 小模型配小 NMS别背个大框架YOLOv6 经过置信度阈值筛选后一帧通常只剩几十到几百个框。边端用简单 O(n²) 的贪心抑制就够了A7 上跑几百个框在 1ms 以内不值得引入复杂的向量化 NMS 库。真正要注意的是在解码阶段顺手把类别概率最大值和对应 id 算好NMS 阶段只做排序和抑制避免重复遍历输出张量。阈值的经验值是 conf 0.25、iou 0.45具体按场景调密集小目标场景 iou 可以放到 0.5。4.3 线程模型让后处理和 NPU 推理并行跑NPU 推理 40ms、后处理 15ms串行跑会白白损失帧率。常见做法是两个线程采集线程从 V4L2 取帧并做 NV12 到 RGB 转换推理线程持有 ping-pong 两块输入缓冲本轮把输入 set 进去再 rknn_run取输出后处理后释放。采集线程写空闲 buffer 时推理线程正在读另一块用 pthread_mutex 加条件变量同步。注意 rknn_run 执行期间不要再调用 rknn_inputs_set 改输入同一个 context 不支持并发读写。采集帧率高于推理帧率时弃帧逻辑放在采集线程里做记录上一轮推理完成时间超时未消费就跳过当前帧防止延迟积压。RY1126 内存带宽有限RGB 转换的临时缓冲区建议启动时一次性分配不要在每帧里 malloc。5. 开箱即用之后NPU 耗时剖析、版本核对与线程绑核5.1 单帧各阶段耗时在哪看开发时别信 printf 打出来的“40 FPS”先去板端的 debugfs 看 NPU 负载常见路径是 /sys/kernel/debug/rknpu/load读回来的是最近一秒的使用率再拿 rknn_query 要内部耗时明细rknn_perf_detail perf; rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, perf, sizeof(perf)); printf(npu total: %fs\n, perf.total);perf_detail 里逐层列出了卷积耗时能直接看出瓶颈在 Backbone 还是 Head。这个查询依赖带性能开关的运行时release 构建拿不到明细就只取 total。帧率统计建议在推理循环外套一层时间戳连续记 100 帧的均值、P95 和 P99比单帧计时靠谱得多。P95 和均值差距大说明某个线程的调度出现了周期性毛刺优先查采集线程是否有阻塞式 IO其次查后处理里是否有隐式内存分配。5.2 源码包先对版本再谈开箱即用拿到源码包先别急着交叉编译对三处版本板子内核源码里 rknpu 驱动版本、根文件系统里 librknn_api.so 版本、转换端 rknn-toolkit 1.7.x 版本三者必须配套。驱动和 .so 错版本的表现很有迷惑性有时能加载但输出乱码有时直接段错误。对完版本再按自己的相机分辨率改采集宽高做一次端到端的框与真实物体对齐验证确认量化后的精度回退在可接受范围。再进一步用 pthread_setaffinity_np 把采集线程固定到 CPU0推理和后处理线程分散到 CPU2、CPU3A7 核共享 L2错开调度往往能再压掉 2~3ms 抖动。这三处对齐后源码里的默认参数才算真正贴合你的板子逐帧时间戳里的毛刺会明显减少开箱即用才算是你自己的开箱即用。本文还有配套的精品资源点击获取
返回列表