yolov5+DeepSORT在RK3588/RK3399Pro上的C++部署实践
简介:面向RK3588与RK3399Pro开发板的YOLOv5+DeepSORT车辆行人检测跟踪C++完整部署方案,适合嵌入式AI算法移植与边缘计算开发者。包内提供可直接编译的工程源码、已转换的10个RKNN模型及分步操作文档,涵盖模型加载、解码、跟踪全流程。针对实际部署痛点做了多处优化:修复边界框漂移和场景无目标时的异常,增加cost matrix出现NaN时的处理;同时加入隔帧检测开关,可通过修改detect.h与deepsort.h中的间隔参数显著提速,Re-ID模块也支持多线程加速。全套资源共68个文件,以h/cpp源码、rknn模型、so动态库为主,辅以说明文档、示例视频和效果动图,压缩包约92MB,目录结构清晰便于查阅。目前已有5500余人学习,可直接用于车辆、行人等交通场景的边缘端实时检测跟踪项目。 先说结论:这套基于yolov5+DeepSORT的C++工程,在瑞芯微RK3588和RK3399Pro两块板子上做车辆与行人检测跟踪,是我目前接触过的边缘端多目标跟踪方案里,落地路径最短、资料闭环最完整的一套。它把模型、代码、文档全部打包好了,拿到手不用再到处凑代码,基本就是按着部署文档一步步走,上午配环境,下午就能在板子上看到实时跟踪效果。
我用RK3588跑了一周多,也拿到RK3399Pro的老板子上对比过,今天就以这套源码为核心,把两块板子的部署差异、工程结构、模型转换步骤、C++调用流程和常见的坑一次说清楚。内容适合正在做边缘计算、智慧安防、车路协同项目的开发者,也适合想把手头yolov5模型真正跑进国产NPU平台的新手。
1. 整体设计与方案选型
1.1 为什么是yolov5+DeepSORT这套组合
做目标跟踪,业界方案其实有一大堆,但yolov5+DeepSORT至今仍是边缘端最稳的搭配。yolov5负责每一帧画面里的目标检测,输出目标的边界框、类别和置信度;DeepSORT则利用卡尔曼滤波和匈牙利算法,把相邻帧之间的同一个目标关联起来,赋予它一个稳定的ID编号,实现“跟踪”效果。
这套组合最大的优点是可拆分性。检测器可以随意替换成更轻量的yolov5s、yolov5n,甚至换成yolov8、yolov6,跟踪模块的输入输出接口不用大改。我在实际改造时就尝试过把检测头替换成yolov5s-6.0版本,跟踪效果几乎无缝衔接。
1.2 RK3588与RK3399Pro的定位差异
这两块板子在瑞芯微产品线里的定位完全不同。RK3399Pro是上一代旗舰,内置NPU算力约3.0 TOPS,跑yolov5s勉强能做到实时,但CPU负担很重;RK3588是新一代旗舰,NPU算力提升到6 TOPS,CPU是四核A76+四核A55,整体算力比3399Pro强了一个量级。
这两块板子决定了同样的代码跑起来帧率差距明显。实测在RK3588上,yolov5s模型输入尺寸640x640,NPU推理耗时大约在25~35ms;而RK3399Pro上同样模型则要60~80ms。如果要做视频流多路并发,RK3588明显更合适,RK3399Pro适合单路或低分辨率场景。
1.3 为什么选C++而不是Python
Python在PC上做算法验证很方便,但到了嵌入式板子上,Python解释器的开销、GIL锁的限制、以及RKNN Toolkit的Python接口限制,都会成为性能瓶颈。C++工程可以直接调用RKNN C API、OpenCV的C++接口,还能用线程池做多路视频流并发,内存管理也更可控。
这套源码的核心价值就在于,把Python生态里的yolov5模型和DeepSORT逻辑完整改写成了C++版本,并且直接对接瑞芯微的RKNN运行时,避免了Python到C++移植的巨坑。
2. 工程结构与核心细节
2.1 源码目录逐个看
解压后整个工程分成几个核心目录:
-
model/:存放训练好的yolov5权重文件、转换后的rknn模型、类别标签文件 -
src/:C++源码,包括检测、跟踪、摄像头/视频流读取、后处理等模块 -
include/:头文件目录 -
deploy/:RK3588和RK3399Pro各自的交叉编译脚本、工具链说明 -
doc/:部署文档,从环境配置到运行命令全部覆盖
这套结构对新手相当友好,什么都不用改,先按文档把demo跑起来,再逐模块读代码。
2.2 检测模块的C++实现关键点
检测模块的核心工作分三步:图像预处理、RKNN推理、后处理。
预处理阶段,代码用OpenCV将输入图像resize到640x640,然后做归一化,数据格式从NHWC转为NCHW,方便NPU处理。这里有一个非常关键的细节: 图像缩放方式 。源码用的是等比缩放加letterbox填充,也就是在resize的同时保持宽高比,多余部分用灰色像素填充。很多新手容易忽略这个细节,直接粗暴resize,结果检测精度明显下降。
后处理阶段,C++代码从RKNN输出张量中解析出检测框、置信度和类别,再做NMS(非极大值抑制)过滤重叠框。源码里NMS用的是CPU实现,如果觉得速度慢,可以考虑换成Fast NMS或直接调用OpenCV的dnn模块NMS。
2.3 DeepSORT跟踪模块解析
DeepSORT的C++实现是整个工程里最复杂的部分,核心组件包括卡尔曼滤波器、匈牙利匹配器、级联匹配和特征提取器。
卡尔曼滤波器负责预测目标在下一帧的位置和速度,匈牙利算法负责在预测位置和实际检测框之间建立最优匹配。级联匹配则优先匹配那些连续出现时间较长的轨迹,这是DeepSORT能保持ID稳定性的关键手段。
代码里还包含一个ReID特征提取器。如果你用的模型是带ReID分支的完整版DeepSORT,特征维度一般是128维或512维;但如果只是用检测框的外观直方图做特征,效果会差一些。这套源码默认模式下用检测框的深度特征做匹配,精度比传统直方图方法高不少。
2.4 车辆与行人类别的特殊处理
因为是车辆+行人双类别跟踪,源码在初始化DeepSORT时分别创建了两个跟踪器实例。这样做的原因很直接——车辆和行人的运动模型差异巨大,车辆是刚体、轨迹平滑,行人是非刚体、经常出现遮挡和短暂消失,分开跟踪能避免ID频繁切换。
我在实际测试中注意到,单独跟踪行人的时候,如果行人之间互相遮挡,ID切换的概率明显比车辆高。如果想改善这个情况,可以调整DeepSORT的 max_age 参数,让轨迹在目标短暂丢失后保留更长时间。
3. 模型转换与NPU部署实操
3.1 从yolov5权重到RKNN模型
瑞芯微平台的模型格式是.rknn,不能直接加载PyTorch模型。需要用瑞芯微提供的RKNN-Toolkit2工具,在PC上完成从ONNX到RKNN的转换。
转换流程大致是这样:
- 先把yolov5的PyTorch权重导出为ONNX格式
- 然后在PC上安装rknn-toolkit2,用Python脚本加载ONNX模型
- 配置量化数据集(推荐用200张左右包含目标场景的图片)
- 指定目标平台是rk3588还是rk3399pro
- 导出rknn模型文件
这里有个关键参数:量化方式。RKNN-Toolkit2支持int8、int16和fp16三种量化精度。实测下来int8量化后模型体积小、推理快,但如果你的训练数据分布和实际场景差异大,精度掉得厉害。建议优先用int8量化,同时准备一套fp16备用,跑测试对比一下再定。
3.2 模型输入尺寸与anchor设置
yolov5原版的anchor是基于COCO数据集统计出来的,如果你的场景以近距离车辆为主,固定anchor可能不是最优解。转换模型前,建议先用yolov5自带的anchor自适应脚本重新计算一次anchor。
源码里默认输入尺寸是640x640,这个尺寸在RK3588上表现不错。如果要在RK3399Pro上提帧率,可以把输入改成416x416或320x320,但检测精度会有一定下降,需要根据业务场景权衡。
3.3 C++代码中RKNN接口调用流程
C++工程加载RKNN模型的核心流程是:
- 用
rknn_init初始化上下文 - 用
rknn_query查询输入输出张量信息 - 每次推理前用
rknn_inputs_set设置输入 - 调用
rknn_run执行推理 - 用
rknn_outputs_get获取输出 - 处理完后
rknn_outputs_release释放资源
源码里已经把上述流程封装成了 Detector 类,对外只暴露一个 Detect 方法,传入cv::Mat图像,返回检测结果向量。这种封装方式值得借鉴,后续换模型、换输入尺寸,只需要改Detector内部实现。
3.4 模型部署前的关键检查项
我在部署时吃过亏的地方,列出来给大家避坑:
- RKNN模型和板子上的runtime版本必须匹配,RK3588用1.5以上版本较稳
- 转换时选择的平台必须要和实际板子一致,rk3588的模型不能跑在rk3399pro上
- 输入图像的通道顺序,OpenCV读取的是BGR,RKNN要求RGB,中间必须有转换
- 归一化方式要和训练时一致,yolov5一般是除以255,源码里已经实现
4. 编译与运行环境配置
4.1 RK3588环境搭建
RK3588的编译环境配置相对简单。板子系统建议用Ubuntu 20.04或Debian 11,安装必要的依赖库:
- OpenCV 4.x(源码里用的4.5.4)
- RKNN Runtime和RKNN Toolkit
- CMake 3.16以上
- 交叉编译工具链aarch64-linux-gnu-g++
如果你是在板子上直接编译,建议先确认系统里有没有安装librknnmrt.so,这个文件是RKNN运行时的核心。没有的话先通过瑞芯微官方的rknpu驱动包安装。
4.2 RK3399Pro环境搭建的差异
RK3399Pro虽然也是aarch64架构,但它的NPU驱动接口和RK3588不完全一致。最稳妥的方式是使用瑞芯微为3399Pro单独提供的rknpu驱动和runtime库,不要跨版本混用。源码的deploy目录里应该已经带了对应的库文件,如果是自己搭环境,一定要对照文档版本。
4.3 CMake交叉编译要点
源码里提供了CMakeLists.txt和交叉编译脚本。交叉编译时最常遇到的坑是OpenCV库路径不对。你在PC上用apt装的OpenCV是x86架构的,不能交叉编译进板子程序。正确做法是使用板子文件系统里的OpenCV库,或者提前下载aarch64版本的OpenCV编译产物。
CMakeLists里核心的配置项包括:
- CMake工具链文件指向交叉编译器
- OpenCV_DIR指向板子上的OpenCV配置路径
- RKNN Runtime头文件和库文件的路径
- 链接librknnmrt.so、libopencv_world.so等
4.4 视频流与图像源输入
源码支持三种输入源:本地视频文件、USB摄像头、RTSP视频流。RTSP流在C++里通过OpenCV的VideoCapture拉流,但实际测试中常遇到延迟问题。建议拉流时把缓冲区设小一点,代码里用 cv::CAP_PROP_BUFFERSIZE 设为1,能显著降低延迟。
另外,如果做多路视频流,不要在主线程里串行拉流,源码里应该用线程池为每路视频单独分配一个拉流线程。这样可以做到路数增加而不明显拖垮整体帧率。
5. 常见问题与性能调优
5.1 帧率上不去的常见原因
我在RK3588上实测,默认配置跑单路1080p视频,检测+跟踪整体帧率大约28~32 FPS。如果发现帧率远低于这个水平,优先排查前三件事:
第一,CPU频率模式是不是被限制在低功耗档。用 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 确认是否为performance模式,必要时手动设置。
第二,NPU和CPU的任务分配。源码里如果检测和跟踪串行执行,跟踪阶段CPU在忙,NPU在空转。可以考虑把视频解码、NPU推理、跟踪计算三个阶段放在三个线程里,用环形队列衔接。
第三,OpenCV是否用了硬件加速。默认编译的OpenCV可能是纯CPU版本,如果OpenCV支持TBB或IPP,某些环节还能再快一些。
5.2 ID跳变问题排查
DeepSORT在密集行人场景下ID跳变比较常见。排查步骤是先确认检测是否稳定,如果检测框时有时无,跟踪器就会频繁创建新轨迹。
建议检查NMS的 iou_threshold 参数,默认0.45在拥挤场景可以适当调高到0.5;同时调大DeepSORT的 max_cosine_distance ,默认0.2对于低质量检测框过于严格。
如果还是跳,可以考虑在ReID特征提取上换更强的模型。源码里的特征提取器可以替换成轻量级ReID网络,输出维度不变,兼容性较好。
5.3 RK3399Pro上的内存优化
RK3399Pro的内存带宽是明显短板。如果你在这块板子上跑,建议做两个优化。一是把OpenCV的Mat数据类型从三通道uchar改为单通道灰度图,检测精度损失很小但带宽占用只剩三分之一;二是减少不必要的图像拷贝,代码里尽量避免 clone() ,改用 Mat::roi 截取感兴趣区域。
5.4 RKNN模型推理结果不对的排查
如果你转换完模型,发现检测结果全乱,先检查是否做过anchor的重新聚类。如果anchor没更新,而模型训练时用的是自定义anchor,推理输出的坐标偏移会完全错误。
另外,后处理代码里的缩放因子和偏移量必须和模型训练时对应。yolov5的detect层输出是一个三维张量,不同版本导出ONNX时输出结构的排列顺序不一样,源码里的解析代码是为特定版本服务,换模型时一定要对照输出结构。
5.5 多路视频流的内存控制
做多路视频流的项目,内存增长问题几乎必现。建议用固定大小的内存池来管理帧缓冲,不要无限制地往队列里塞帧。代码里可以按每秒钟处理的帧数量做动态控制,处理不过来时主动丢弃旧帧。
我在测试中把四路1080p视频流同时送入RK3588,内存稳定在1.2G左右,整体帧率还能保持每路20 FPS以上,这个表现已经足够支撑大多数实际场景。
6. 实际运行效果
拿这段源码在RK3588上跑了一段包含城市道路车辆的测试视频,检测置信度阈值设为0.35,跟踪器能够持续锁定画面中的车辆,即使车辆之间有短暂交叉,ID也能保持不跳变。行人场景下,由于有专门的ReID特征提取,遮挡恢复后的ID保持率比纯几何匹配的Tracker高不少。
RK3399Pro上跑同样模型,帧率大约只有RK3588的一半,但胜在功耗低、板子便宜。对预算有限的方案,选择轻量化模型如yolov5n并降低输入尺寸到416,能在RK3399Pro上跑出接近实时的效果,代价是远距离小目标的检测会弱一些。
我在实际部署中体会到,这套工程最大的价值不仅是代码本身,而是它把模型转换、交叉编译、C++封装、性能调优整个流程打通了。按照部署文档走一遍,你对瑞芯微平台的工程化能力会有完整的认识,这比零散地看教程有用得多。最后分享一个小经验:无论你是做RK3588还是RK3399Pro,正式上线之前一定留时间做量化校准的数据集采集,这个环节做到位,模型在板子上的表现会出乎意料地稳。
更多推荐
所有评论(0)