ARTICLE DETAIL

资讯详情

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

RV1126跑通YOLOv6实时检测:工程结构与调优实践

RV1126跑通YOLOv6实时检测:工程结构与调优实践 简介基于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.cppsensor采集与缓冲池的衔接方式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输入下三个尺度的输出经过解码后会产生大量候选框如果直接全部做置信度过滤和NMSCPU会被拖住。常见做法是先按置信度阈值做mask过滤只保留概率高于阈值的那部分框进入NMS。process.cpp里的阈值参数可以调整把阈值从0.25提到0.5能明显减少参与NMS的框数量推理帧率会有可感知的提升。3. cd build make install交叉编译安装的完整路径3.1 CMake工程下的交叉编译器探测机制源码包在build目录里保留了CMake生成的构建信息。CMakeCCompilerId.c、CMakeDetermineCompilerABI_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 installmake负责把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 ./mainLD_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.25MODEL_PATH是rknn模型在板端的绝对路径运行时被readfile.cpp读取。如果模型和可执行文件放在一起也可以直接使用相对路径但IPC场景下程序往往由systemd或脚本拉起工作目录不固定建议始终使用绝对路径。INPUT_WIDTH和INPUT_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 3SENSOR_WIDTH和SENSOR_HEIGHT是sensor模组实际输出的分辨率需要和驱动能力对齐。OUTPUT_WIDTH和OUTPUT_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万像素而模型输入只有640x640ISP缩放的压力很大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这类启动绑定就能让帧率曲线平稳不少。本文还有配套的精品资源点击获取
返回列表