RV1126跑通YOLOv6实时检测:工程结构与调优实践
2026/9/16 13:08:20 网站建设 项目流程

简介:基于rv1126芯片实现的yolov6实时目标检测源码工程,面向嵌入式IPC开发及RKNN部署技术人员,解决低算力平台实时检测落地的需求。压缩包共109个文件,主要以C++源码、头文件、CMake工程文件为主,同时附带RKNN模型、可执行文件与依赖库,整体约3.87MB,目录按src、include、lib、bin划分,结构清晰,方便定位与二次开发。源码中src目录包含main.cpp、model.cpp、process.cpp、rkmedia.cpp、readfile.cpp等,分别对应主流程、模型加载推理、后处理、媒体采集及文件读取模块,通过修改model.h和rkmedia.h即可切换模型与分辨率,工程化程度较高。目前已有124人学习下载,适合具备C++基础并希望绕开环境坑、快速在RV1126上跑通YOLOv6的开发者,是一份可直接编译运行并参照改造的实战代码。

1. RV1126上跑YOLOv6实时检测,这套工程好在哪

RV1126是一颗集成NPU的IPC SoC,算力在2 TOPS上下,跑轻量级检测网络不轻松,但也不至于捉襟见肘。YOLOv6这种单阶段检测器在这类平台上的部署难点,从来不是把权重转成rknn,而是把视频采集、NPU推理、结果后处理三条链路捏成一个不互相阻塞的闭环。这套基于rv1126实现的目标检测源码,已经把main函数、模型加载、后处理函数、rkmedia视频通路拆成了独立模块,bin目录下放好可执行文件与rknn模型,lib目录里是librtsp.a、libeasymedia.so这类运行依赖,你把bin和lib整体拷贝到开发板上就能直接出现检测框。对于想快速拿下一版IPC目标检测demo的工程师,它省掉了从零搭RKNN推理框架的大量时间;对于做了几年的老手,这套代码里模块边界的切法、模型输出解析的位置、分辨率和缓冲数的取舍,也都值得对照自己手头的工程重新梳理一遍。

2. 检测工程的代码链路:rkmedia流、RKNN推理、后处理的四层分工

2.1 入口main.cpp与实际工作线程的关系

这一版的main.cpp解决的是初始化顺序问题,而不是把逻辑都堆在主函数里。实际运行时,rkmedia的采集通路需要先把sensor的帧稳定拉起来,NPU才能拿到连续输入;rknn的上下文必须先创建成功,推理循环才敢启动。常见做法是先初始化rkmedia的视频输入通道,分配好缓冲池,再去调用model.cpp里的模型加载函数,如果上下文创建失败就直接退出,避免跑到第一帧推理时才暴露驱动缺失的问题。

main函数里通常还会挂一个轻量的统计逻辑。我一般会加一个全局的帧号计数器,每处理满30帧就打印一次平均推理耗时和当帧检测框数量。这样在板端第一次跑起来时,能立刻看出是视频链路在掉帧,还是后处理在大目标上卡住。这个工程里main.cpp的初始化路径比较简单,但它把rkmedia和model两个模块的先后顺序约束清楚,这对排查问题很有价值。

2.2 rkmedia.cpp:sensor采集与缓冲池的衔接方式

rkmedia.cpp是和RV1126视频硬件绑定最深的模块。RV1126的媒体通路由rkmedia库统一封装,VI视频输入、VENC编码、VO显示都以虚拟通道的形式暴露。这套源码里rkmedia.cpp承担的是VI通道配置、帧缓冲池申请、以及把sensor帧交给推理线程的中转工作。

一个很容易被忽略的点是,rkmedia默认拿到的帧格式通常是NV12,而YOLOv6的rknn模型输入往往是RGB,中间必须有一层格式转换和缩放。如果板端sensor输出1080p,模型输入只需要640x640,缩放和裁剪就发生在rkmedia.cpp这段链路里。缓冲池的数量也在这里定义,太少会导致sensor侧丢弃,检测帧率不稳定;太多则IP化的码流延迟会明显变大。这个工程里缓冲数是可以直接改的宏,建议先用默认值跑通,再根据实测帧率微调。

2.3 model.cpp和readfile.cpp:模型加载与推理的边界

readfile.cpp单独存在,是为了把“从文件系统把模型二进制读取到内存”这件事收敛到一个函数里。RV1126上模型以单个.rknn文件存在,可能是几百KB到几MB,读取逻辑不复杂,但路径错或权限不对时,会导致rknn上下文初始化失败。把读文件独立出来之后,启动加载阶段出了问题,只需要看这一个文件的报错,不用在业务逻辑里反复找。

model.cpp则紧贴RKNN API做封装,负责模型加载、输入输出内存申请、推理执行三个环节。这里最需要管理好的是输出内存的生命周期。YOLOv6这类单阶段检测器,输出不止一张特征图,而是三个不同尺度的分支,每个分支对应不同的感受野。model.cpp的推理函数需要按分支数量准备输出buffer,并在下一次推理前复用内存,而不是频繁分配释放,否则内存碎片会让长时间运行的检测进程越跑越慢。

模块文件主要职责与硬件关系最需要关注的变量
main.cpp初始化顺序、主循环帧号计数
rkmedia.cppsensor采集、格式转换、缓冲池VPU/Sensor缓冲数和分辨率
model.cppRKNN上下文、推理执行NPU输出分支数
process.cpp解析输出、NMS、置信度过滤置信度阈值
readfile.cpp读取rknn模型文件文件系统模型路径

2.4 process.cpp里的后处理:从三个输出分支到最终检测框

process.cpp负责把RKNN输出的原始tensor转换成可以用来画框的坐标。YOLOv6输出结构在不同导出版本下排布可能不同,有的是[x1,y1,x2,y2,obj,cls],有的是[cx,cy,w,h,obj,cls],这套源码在process.cpp里做了对应的分支解析。如果你换了其他YOLOv6模型,需要先对照rknn-toolkit导出时的配置,再确认解析顺序是否一致。

后处理的计算量在这个平台上不可忽视。640x640输入下,三个尺度的输出经过解码后会产生大量候选框,如果直接全部做置信度过滤和NMS,CPU会被拖住。常见做法是先按置信度阈值做mask过滤,只保留概率高于阈值的那部分框进入NMS。process.cpp里的阈值参数可以调整,把阈值从0.25提到0.5,能明显减少参与NMS的框数量,推理帧率会有可感知的提升。

3. cd build && make install:交叉编译安装的完整路径

3.1 CMake工程下的交叉编译器探测机制

源码包在build目录里保留了CMake生成的构建信息。CMakeCCompilerId.cCMakeDetermineCompilerABI_C.bin这类文件是CMake在探测编译器时自动生成的中间产物,它们的作用是确认工具链能正常编译并链接出可执行文件,不需要手动改动。真正要确认的是CMakeCCompiler.cmake里记录的编译器路径是否指向RV1126的aarch64工具链,如果指向x86的gcc,编译出的二进制搬到板端会直接报格式错误。

执行构建前,建议先把工具链路径加到bash环境中:

export PATH=/opt/rv1126-toolchain/bin:$PATH mkdir -p build && cd build cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain/rv1126.toolchain.cmake ..

这里CMAKE_TOOLCHAIN_FILE指定的是交叉工具链描述文件,如果工程里没有提供,可以自己写一个只包含编译器路径和系统根目录的toolchain文件。cmake执行成功后,CMakeDetermineCompilerABI_CXX.bin会重新生成,说明CMake对C++编译器的探测已经通过。这一步只要能看到-- Check for working CXX compiler: ... works的输出,说明工具链环境没有问题。

3.2 make install实际生成的文件

工具链确认无误后,编译安装是一步到位:

cd build make install

make负责把src下的main.cpp、model.cpp、process.cpp、rkmedia.cpp、readfile.cpp编译链接成可执行文件,链接时用到lib目录下的librtsp.a和libeasymedia.so。make install阶段则执行cmake_install.cmake里描述的复制规则,把可执行文件和运行依赖统一放到install目录下。如果只改动了process.cpp里的阈值,单独执行make就能完成增量编译,install环节在文件内容没变化时会跳过复制。

这里值得多说一句,当构建树上存在同名so时,链接顺序可能优先命中宿主系统的库。如果你在x86的Linux上编译,系统里恰好装了同名的libeasymedia.so,链接时会选x86版本,生成的文件无法在RV1126上运行。排查方法是在编译完成后执行file bin/可执行文件,确认架构是ARM aarch64而不是x86-64

3.3 把bin与lib同步到开发板并修正权限

编译安装完成后,移植的主体就是bin和lib两个目录。常见做法是不动打包脚本,直接通过adb或scp推到板端可写分区:

adb push bin /oem/ adb push lib /oem/ adb shell chmod +x /oem/bin/main adb shell sync

推送到/oem分区是个稳妥选择,这个分区在断电重启后不丢文件,适合放可执行程序和模型。chmod +x是必须的一步,拷贝过程经常会丢失可执行权限位,不加会直接遇到权限拒绝。执行测试时先手动运行一次,确认库路径能被找到:

export LD_LIBRARY_PATH=/oem/lib:$LD_LIBRARY_PATH cd /oem/bin && ./main

LD_LIBRARY_PATH的作用是把lib目录加入动态库搜索路径,这一步在快速验证阶段最省事。如果想固化成长期运行方案,可以把这行环境变量写进板端的系统启动脚本中,确保开机后服务能自动加载到库文件。

4. 修改模型路径与分辨率:model.h和rkmedia.h里的关键参数

4.1 model.h里的模型路径与输入尺寸配置

model.h里定义的是模型相关常量。以这个工程的结构来看,至少包含以下内容:

#define MODEL_PATH "/oem/model/yolov6.rknn" #define INPUT_WIDTH 640 #define INPUT_HEIGHT 640 #define CLASS_NUM 80 #define CONF_THRESH 0.25

MODEL_PATH是rknn模型在板端的绝对路径,运行时被readfile.cpp读取。如果模型和可执行文件放在一起,也可以直接使用相对路径,但IPC场景下程序往往由systemd或脚本拉起,工作目录不固定,建议始终使用绝对路径。INPUT_WIDTHINPUT_HEIGHT必须和rknn模型导出时的输入尺寸一致,不一致时RKNN驱动在部分版本下不会报错,而是直接输出乱数据,这类问题藏在运行过程中非常难查。

CLASS_NUM是模型训练时的类别数。如果你用的YOLOv6模型是在COCO上训练的,保持80即可;如果是自己训练的特定场景模型,比如只识别3种缺陷,这个值必须改成3,否则process.cpp解析分类概率时会越界,检测框置信度会全面失真。CONF_THRESH对应后处理里的第一层过滤阈值,这个值对帧率的影响最直接。

4.2 rkmedia.h里的分辨率与缓冲配置

rkmedia.h定义的是视频采集链路的参数:

#define SENSOR_WIDTH 1920 #define SENSOR_HEIGHT 1080 #define OUTPUT_WIDTH 640 #define OUTPUT_HEIGHT 640 #define BUFFER_COUNT 3

SENSOR_WIDTHSENSOR_HEIGHT是sensor模组实际输出的分辨率,需要和驱动能力对齐。OUTPUT_WIDTHOUTPUT_HEIGHT是送入推理链路前的目标尺寸。这个工程里通常做法是让OUTPUT_*对焦模型输入尺寸,这样rkmedia缩放输出的帧可以直接作为RKNN输入,省掉一次多余的图像缩放操作。BUFFER_COUNT是缓冲队列长度,IPC场景下至少保持2个,追求更稳定的帧率可以改成4,但内存占用会相应增加,多路码流同时开启时尤其要注意。

rkmedia.h里还有一个容易被忽视的点是像素格式定义。RV1126的sensor多数直接输出NV12,如果定义成其他格式,画面上会出现明显的颜色错乱,检测率会断崖式下降。确认方法是在rkmedia.h里找到格式相关宏,和外接sensor数据手册上的输出格式对照一眼,颜色偏色时优先检查这里。

4.3 修改生效后的启动验证

参数修改后重新编译并推送,验证的标准是板端能连续输出检测框同时RTSP码流不中断。快速自检命令:

dmesg | grep -i "rknn\|v4l2"

如果dmesg里有v4l2相关报错,说明sensor初始化参数未被驱动接受,回查rkmedia.h里的SENSOR_WIDTH和像素格式定义。如果rknn相关日志出现维度不匹配,直接核对model.h里的输入尺寸和类别数。这两个命令查完,能定位绝大多数启动即崩的问题。如果程序能启动但检测结果是空白,优先检查模型路径是否正确,MODEL_PATH指向了不存在的文件,部分RKNN版本只会记录一条warning然后继续运行,输出全零数据。

另外还有一种情况:程序起来了,画面也正常,但一执行到推理就段错误。多数是rkmedia.h里的缓冲数和实际申请内存不一致导致的。RV1126上rkmedia的缓冲池是在初始化阶段一次性申请的,后处理线程访问越界时不会立即崩溃,而是过几十帧才触发一次段错误,这种问题可以通过把BUFFER_COUNT调小并配合日志定位。

5. 针对RV1126的实时性调试技巧:帧率统计与故障定位

5.1 在main循环里增加一组耗时统计输出

这套源码默认情况下不一定带帧率打印,我建议在main.cpp的推理循环里补一段耗时代码,用来量化NPU和CPU分别消耗的时间:

static int frame_count = 0; static struct timespec last_ts; struct timespec now_ts; clock_gettime(CLOCK_MONOTONIC, &now_ts); frame_count++; if (frame_count % 30 == 0) { double interval = (now_ts.tv_sec - last_ts.tv_sec) + (now_ts.tv_nsec - last_ts.tv_nsec) / 1e9; printf("[%s] total_fps: %.2f\n", __func__, 30.0 / interval); last_ts = now_ts; }

这段代码把30帧作为一个统计窗口,输出的是综合帧率,包含采集、推理、后处理和显示整个链路的耗时。CLOCK_MONOTONIC取的是系统单调时钟,不受校时等高精度影响,比gettimeofday更适合做性能统计。如果你想把NPU单独拆出来看,可以在model.cpp的推理调用前后各打一个时间戳,两次tick之间就是纯推理时间。

5.2 帧率不达标时优先检查的两个方向

当综合帧率明显低于预期,先区分是NPU慢还是CPU慢。看top命令的输出,如果CPU占用率很高而rknn相关进程的CPU不高,说明瓶颈在后处理。此时把CONF_THRESH调高到0.5,观察帧率是否有明显提升,如果提升很大,就说明候选框过滤环节占用了过多CPU。反过来,如果调阈值后帧率毫无变化,瓶颈在模型推理本身,需要考虑降低输入分辨率,比如从640降到512。

第二处要查的是rkmedia.cpp里的视频链路。如果sensor输出是400万像素,而模型输入只有640x640,ISP缩放的压力很大,VPU带宽会被大量占用。可以先降采样到1080p观察帧率变化,损伤很小但收益明显。另一个要排查的是并发通道,IPC设备通常要同时跑RTSP推流,如果VENC编码通道也在工作,VPU会和NPU抢内存带宽,这种情况下可以把编码帧率调低一半再做对比。

故障现象定位方向调整措施
启动段错误lib路径或rpath用LD_LIBRARY_PATH指定板端lib目录
帧率下跌但CPU不高NPU推理耗时降低模型输入分辨率
帧率低且CPU高后处理候选框过多提高CONF_THRESH或核对CLASS_NUM
画面偏色导致漏检像素格式定义对齐sensor输出的NV12格式
RTSP黑屏但检测正常librtsp.a与SDK版本不匹配用lib目录下的原始版本重新链接

最后一个验证小技巧:跑稳定性时保持检测画面里目标的数目恒定,如果检测框数量波动很大,帧率抖动也大,说明可能是复杂场景拖慢了后处理。用top确认RV1126的CPU核有没有被打满,如果发现rtsp服务和main进程在抢同一个核,可以用sched_setaffinity把推理线程绑定到一个空闲核上,让RTSP线程独占另一个核。这个技巧在双核场景下收益最明显。实测中简单加taskset -c 1 ./main这类启动绑定,就能让帧率曲线平稳不少。

本文还有配套的精品资源,点击获取

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

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

立即咨询