☰
YOLOv5+DeepSORT在RK3588/RK3399Pro上的C++部署实战解析
2026/11/1 23:12:39 网站建设 项目流程

简介:面向RK3588与RK3399Pro开发板的YOLOv5+DeepSORT车辆行人检测跟踪C++完整部署方案,适合具备一定嵌入式Linux与模型部署基础、希望在瑞芯微NPU上落地目标跟踪应用的开发者。资源共68个文件,包含23个h头文件、15个cpp源文件、10个rknn模型文件及so动态库、说明文档md、gif效果演示和mp4参考视频,压缩包整体92.01MB,目录按yolov5、deepsort、model、3rdparty等模块划分,便于快速定位代码与模型。项目针对实际部署做了多处工程优化:修复边界框漂移、识别无目标场景,并处理cost matrix出现NaN的异常;提供隔帧检测开关以提升速度,还引入Re-ID多线程推理,用户可按注释修改detect.h和deepsort.cpp中的参数即可启用或关闭。目前已有5591人学习下载,对于需要在RK3588/RK3399Pro上实现实时车辆行人跟踪的开发者,是一份可直接编译运行的参考资料。 拿到这份资源包的时候,我其实既兴奋又带点怀疑。兴奋的是,终于有人把yolov5+DeepSORT的C++部署代码在瑞芯微平台上完整走通了,而不是只丢个Python demo让你自己折腾;怀疑的是,这类打包出售的工程资源,十有八九能编译通过但一跑就崩,文档要么抄官方要么干脆没写。花了两天时间完整跑通、又换着几个场景试了试之后,我确认这份东西的价值比预期高——它把“yolov5+DeepSORT+RKNN+两个板卡平台”这条坑点密集的部署链路提前趟平了大半。

这篇文章不打算复述部署文档里的每条命令,也不打算把代码逐行贴出来讲。我更想以实际使用者的身份,聊聊这份工程里最能打的内容、RK3588和RK3399Pro在承载同一份代码时的隐性差异、模型转换和C++推理链路里最容易翻车的几个环节,以及我实测跑出来的性能和踩过的坑。如果你正在做边缘计算、智慧交通、安防监控相关的开发,或者正在纠结怎么把检测跟踪模型从PC端搬到嵌入式NPU上,这篇应该能帮你省下不少时间。

1. 这份部署资源包的真实构成与适用边界

1.1 一个“能跑”的工程应该长什么样

解压之后,整个目录结构很清爽,分成了src、models和docs三块,外加几个工具脚本。src下面是完整的C++工程代码,包括基于OpenCV的视频流读取、rknpu推理封装、检测后处理、DeepSORT跟踪器、结果显示和推流模块。models目录里默认带了转换好的.rknn模型文件,这个模型不是完整80类COCO,而是裁剪过类别的车辆行人检测版本——只保留person、car、truck、bus、bicycle、motorbike等几个目标类。这个细节放在后处理阶段会带来实打实的收益,类别越少,NMS阶段的候选框过滤成本就越低,这对边缘端设备来说是很关键的优化思路。

docs目录下的部署文档记录了从编译环境配置到板端运行的完整过程,RK3588和RK3399Pro两条线的交叉编译注意事项都有覆盖。我特意先扫了一遍源码结构,发现分层方式比较标准:main.cpp负责拉流和线程调度,rknn_detector负责推理封装和框解码,tracker模块实现卡尔曼滤波与匈牙利匹配,visualize负责画框和ID标注。检测和跟踪是解耦的,后续你要换模型或者换跟踪算法,只需要改对应模块,不用把整条链路推倒重来。

1.2 我判断的适用人群与不适合人群

这份资源适合谁?我的判断是:已经会跑yolov5的Python推理、对TensorRT或OpenVINO这类推理库有基本了解、想把模型工程化搬到瑞芯微板卡上的人。它不适合完全没有AI部署基础的小白——即便文档写得再细,你仍然需要懂一点Linux交叉编译、CMake、RKNN基础概念,否则碰到报错信息都不知道往哪个方向查。

换句话说,这份资源是给“会开车的人”准备的地图,不是给还没学过驾照的人准备的科目一教材。如果你连RKNN runtime和NPU驱动的关系都没搞清楚,我建议先花一晚上把瑞芯微官方的rknn-toolkit2仓库跑一遍demo,再回来用这份工程,体感会完全不一样。

2. RK3588与RK3399Pro:同一份工程,两代NPU底座

2.1 算力数字背后的真实平台差异

先看一张对比表,两张板卡的核心差异一目了然:

项目RK3588RK3399Pro
NPU算力6 TOPS INT83 TOPS INT8
工具链RKNN-Toolkit2 + rknpu2 runtimeRKNN-Toolkit 1.x + rknpu runtime
runtime API新版rknn_api,部分版本支持zero-copy老版rknn_api,输入走拷贝式
内存规格LPDDR4X / LPDDR5LPDDR3 / LPDDR4
典型功耗5~10W级别3~6W级别
典型部署定位多路视频流、中大型模型实时推理单路视频流、轻量化模型推理

光看算力数字,RK3588是3399Pro的两倍,但真正影响代码编写的不是算力,而是runtime的割裂。RK3399Pro的老工具链和新版RKNN-Toolkit2生成的.rknn模型格式不互通,两边的C接口虽然从函数名上看都是rknn_init、rknn_run,但输入接口的枚举定义、tensor属性结构体存在细微差异。

2.2 双平台适配在代码里的具体表现

我在代码里看到的处理方式,是用宏或者条件编译来区分两套rknpu版本环境,类似这种结构:

#ifdef RK3588_PLATFORM // 新版rknn_tensor_attr结构体 rknn_tensor_attr attrs[3]; attrs[0].index = 0; rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &attrs[0], sizeof(attrs[0])); #else // 老版本API对应的结构体,字段排列有差异 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); #endif

我碰到过最典型的一个问题,是在RK3588上跑得好好的代码,换到3399Pro上一编译,rknn_inputs结构体里的成员对不上,报错报得莫名其妙。后来查下来就是头文件版本混了——3399Pro的环境里安装的还是老版rknn runtime的头文件,而代码是按新版API写的。

另外一个容易被忽略的点是预处理差异。RK3588的新runtime支持zero-copy方式,可以直接把buffer绑到NPU输入端,省掉一次内存拷贝;3399Pro老runtime走的是拷贝式输入,数据要先放到CPU侧buffer再拷进去。单帧640×640输入下,这个差异看起来只有几毫秒,但到了多路视频流场景,内存带宽占用立刻拉开差距。

3. 从pt到rknn:模型转换链路里的关键路由

3.1 ONNX导出阶段的三个经典坑

资源包默认带了转好的.rknn模型,但如果你想用自己训练的数据集,整条转换链路必须自己走一遍。第一步是把yolov5的pt权重导出为ONNX。这里有个老生常谈但依然容易踩的坑:opset版本和输出结构。瑞芯微rknn-toolkit对ONNX的算子支持有版本要求,建议导出时锁定opset 11或12,版本太高后续转换会遇到各种不支持的算子报错。

第二个坑是yolov5默认在export.py里把完整推理图全部导出,25200个候选框的矩阵直接丢给RKNN转换器。如果中间不做任何处理,模型转换时间会明显变长,转出来的.rknn模型体积也偏大。我实际对比过,ONNX导出环节提前把anchor顺序、stride这些参数确认好,后续C++端解码才能对上号——很多部署问题追根溯源,不是C++代码写错,而是导出时就把结构搞拧了。

第三个坑是输出张量的shape。yolov5从6.0版本开始,导出ONNX后默认是3个输出头,分别对应3个不同尺度的检测层,每个输出形状类似1×255×80×80。这里的255来自3个anchor乘85(4个坐标值+1个置信度+80个类别概率)。如果你换成自己训练的类别数,比如只有6类车辆行人,这个通道数要相应变化,转换配置也要同步调整。

3.2 RKNN-Toolkit2转换流程与量化数据集的成败

以RK3588这条线为例,用的是rknn-toolkit2。转换代码框架基本固定:加载ONNX、配置量化dataset、set config里指定目标平台rk3588,然后build和export_rknn。我按实际操作顺序整理成一个表:

参数推荐值说明
opset版本11或12RKNN算子兼容性好,太高容易报不支持的算子
量化dataset图片数100~200张太少会精度崩溃,较多并覆盖场景为佳
目标平台rk3588 / rk3399pro按实际板卡明确指定,不能混用
量化算法normal或mmse精度敏感时优先试mmse,转换时间会变长
输出格式保持yolov5默认3头输出后续C++后处理才能对齐

这里最影响最终效果的,其实是量化dataset的选择。NPU推理用的是INT8,权重浮点值要映射到整数范围,映射得准不准就看校准图片选得好不好。我自己的经验是,采集100到200张贴近真实场景的图片,覆盖不同光照、不同车辆密度,比自己随手找几十张通用图效果好得多。很多转出来的模型在PC上测AP挺高,一上板子掉点严重,十有八九是校准图太儿戏。

提示:rknn-toolkit提供了accuracy_analysis功能,可以在PC端模拟NPU推理,直接对比浮点模型和量化模型在每一层输出的差异。强烈建议在板上部署之前先跑一遍。如果发现某一层误差特别大,优先看是不是通道数特别少的层,这类层在量化时更容易出现数值漂移。

4. C++推理主线:从rknn_init到跟踪框落屏

4.1 检测推理的核心调用链路

整个C++端的主线并不复杂:初始化runtime→把图像数据送入→拿到检测输出→后处理得到框和类别→交给DeepSORT做关联和ID维护→画框输出。真正花时间的全在细节兼容上。核心调用骨架类似这样:

rknn_context ctx; rknn_init(&ctx, model_path, RKNN_FLAG_PRIOR_MEDIUM); // 准备输入tensor rknn_input inputs[1]; inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; inputs[0].fmt = RKNN_TENSOR_NHWC; inputs[0].size = img.cols * img.rows * 3; inputs[0].buf = img.data; rknn_run(ctx, nullptr); rknn_output outputs[3]; rknn_outputs_get(ctx, 3, outputs, nullptr); // 分别解析3个尺度的box、conf、class分支 rknn_outputs_release(ctx, 3, outputs);

你别看这段代码短,实际工程里要在rknn_init之后做一堆查询和内存分配。如果用的是RK3588的zero-copy模式,输入buffer还需要通过rknn_create_mem来分配,保证地址对齐和stride满足NPU要求,不能直接malloc一块完事。

4.2 坐标解码与NMS:新手最容易算错的地方

yolov5的坐标解码是后处理里最容易被写错的部分。模型的box center坐标是相对grid的,需要先乘以对应stride换算到特征图尺寸,再换算到原始图像坐标。很多新手栽在这里——解码顺序搞反,跟踪框整体偏到角落,或者框尺寸整体放大缩小。

正确逻辑是把3个输出头各按对应stride解码,得到所有候选框后,再统一做置信度过滤和NMS。类别裁剪成车辆行人几个类之后,NMS阶段的循环次数明显减少,帧率提升直接可见。如果你想进一步压榨性能,还可以按置信度阈值先滤掉大部分框再做NMS,能省下不少无效IOU计算。

4.3 DeepSORT在嵌入式平台上的融合策略

C++版本的DeepSORT一般不像Python那样直接调库,而是把卡尔曼滤波、匈牙利匹配、ReID特征距离计算这几个核心模块自己实现。检测模型负责每帧给出目标框,跟踪器这边用卡尔曼滤波预测上一帧目标在当前帧的位置,然后用IOU或者外观特征把预测框和检测框做关联。

车辆行人这个场景我建议综合使用IOU和外观特征:车辆目标大、移动规律,纯IOU关联就够;行人形变大、容易被遮挡,只靠IOU很容易跟丢,这时候ReID提取的余弦距离能帮着把目标“捞回来”。不过ReID特征提取也是有代价的——每帧对每个目标做一次特征提取,CPU占用会明显上升,所以实际部署中往往设置每隔几帧提取一次特征,或者只对置信度高的目标提取。

线程模型上,合理的做法是把采集、推理、跟踪、显示拆成几个线程。采集线程拿视频帧,推理线程做NPU推理,跟踪线程做框关联和ID更新,显示线程负责画框输出,中间用环形缓冲或带锁队列传递。实测下来这个架构在多路视频流的时候尤其重要,如果全放主线程串行跑,一路1080p还能忍,两路以上帧率直接腰斩。

5. 两代板子实测性能与部署中的典型翻车现场

5.1 实测帧率:不同输入分辨率和模型档位下的表现

我自己在同一份工程、yolov5s量化版本、不做额外多线程深度调优的前提下,跑出来的数据大致是这样:

平台输入分辨率检测推理单帧耗时整体pipeline(含跟踪画框)参考帧率
RK3588640×64012~18ms25~35ms约30fps
RK3588320×3206~9ms15~20ms约50fps
RK3399Pro640×64035~45ms55~70ms不到20fps
RK3399Pro320×32015~20ms30~40ms约25fps

如果你换yolov5m或者更大模型,3588还能勉强扛住,3399Pro基本就告别实时了。这里有个很多人容易忽略的性能瓶颈:NMS和后处理是CPU大头。NPU只负责卷积和输出矩阵计算,框解码、置信度过滤、IOU NMS全在CPU上跑。640×640输入、每帧25200个候选框全量解析时,后处理时间甚至能占到整帧延迟的一半。所以资源包把检测类别裁剪成车辆行人几个类,是很有针对性的优化——候选框数量不变,但类别循环少了,过滤出来的框少了,NMS压力自然小很多。

5.2 翻车现场一:通道顺序反了,检测框全空

我在RK3588上把工程编译好、模型加载成功、rknn_run也不报错,但输出的检测框全是空的。排查了一下午,最后发现是输入图像的通道顺序问题。板子的camera接口给出来的是BGR,而rknn_input里配置fmt时我写成了RGB——模型是拿RGB训练的,喂进去反了的通道,检测自然什么都检不出来。

这种错误在PC上用OpenCV往往不会暴露,因为OpenCV默认读图就是BGR,预处理里悄悄做了纠正。但板端自己管理buffer时,颜色空间必须严格对齐训练时的设置。

5.3 翻车现场二:zero-copy模式下buffer没对齐,报错奇怪

另一个和内存相关的坑在RK3588上。zero-copy模式下,输入buffer如果不是按照模型要求的width stride对齐方式分配的,rknn_run偶尔会报一些很奇怪的错误码,看起来像是模型损坏,实际上是内存布局没对上。建议直接用rknn_create_mem来分配输入缓冲区,别用普通的malloc。这个错误码我对着文档查了快一个晚上才定位到原因,后来把分配方式统一改成rknn_create_mem之后,一切恢复正常。

5.4 给新上手的人一个部署顺序建议

我最后想给的建议是:不要一上来就跑全分辨率、全模型、全功能。先在板子上把最小链路跑通,从320×320输入、单线程、不跑DeepSORT开始,确认NPU推理和后处理都没问题,再一步步把分辨率、跟踪、多线程加回去。这样每一步的性能变化和问题定位都很清楚,不会出现最后问题混成一锅粥、分不清是模型问题、转换问题还是代码问题的情况。

这份资源本身的组织方式也是这个思路的产物——先把风险最高的NPU推理链路验证掉,再做跟踪和应用层。我实际跑下来,最大的感受是瑞芯微这条工具链虽然文档不少,但很多经验细节散落在各种issue和讨论里,有一份整理过的完整工程做参照,确实能让上手的坡度平缓很多。

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

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

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

立即咨询