RK3588/RK3399Pro平台YOLOv5+DeepSORT目标跟踪C++工程实战
简介面向RK3588、RK3399Pro等嵌入式平台的目标检测与跟踪需求这份C完整源码将YOLOv5与DeepSORT算法落地到实际开发板适合具有一定嵌入式Linux和深度学习部署经验的开发者。压缩包共68个文件除头文件、源文件外还提供10个RKNN模型、3个动态库和详细操作说明整体约92MB代码与模型目录划分清晰。资源针对部署中常见问题做了专门优化修复边界框漂移、无检测目标时的异常并处理了代价矩阵出现的NaN同时加入隔帧检测开关与Re-ID多线程功能可根据算力需求灵活调整显著提升推理速度。配套说明文档覆盖环境配置、编译步骤和运行方式配合演示视频和GIF动图可帮助开发者快速在RK3588/RK3399Pro上完成算法迁移。目前已有5591人学习/下载对正在做车辆、行人检测跟踪项目落地的工程师有直接参考价值。 最近在嵌入式板子上跑目标跟踪的需求不少刚好手上有一套在RK3588和RK3399Pro上部署YOLOv5DeepSORT的C完整工程车辆与行人跟踪本文把这套方案的落地思路、模型转换细节、C工程结构和避坑经验完整梳理一遍给正在做边缘端视觉项目的人一个可直接上手的参考。这套代码不是简单的demo而是可以真正接入业务的完整链路模型推理、跟踪匹配、目标生命周期管理、可视化输出都做了编译脚本和部署文档也齐全适合有C基础、熟悉Linux开发的算法工程师或嵌入式开发人员。1. 项目整体设计与硬件平台特点1.1 为什么选YOLOv5DeepSORT这个组合目标跟踪任务在安防、交通流量统计、无人零售这些场景里非常常见核心诉求是把视频帧序列中的同一辆车或同一个人持续标记出来而不是每一帧都重新识别。YOLOv5负责检测当前帧里的目标位置DeepSORT负责把这些检测结果和已有的轨迹做关联从而给出稳定的ID。这个组合在工程界的成熟度极高社区资料多、模型好调、部署方案完善不是最优的算法但绝对是落地最顺的套路。单帧检测任务只需要一个检测器就够了但凡是涉及统计某条路上30分钟内过了多少辆车某个区域里人员徘徊多久就必须上跟踪。DeepSORT的核心逻辑是通过卡尔曼滤波预测目标在下一帧的位置然后用匈牙利算法把当前帧的检测框和已有轨迹做最优匹配。它比简单的IOU匹配强在两点一是加了运动模型不会因为目标短暂被遮挡就丢失ID二是用了级联匹配策略优先处理那些连续被跟踪的可靠轨迹。1.2 RK3588和RK3399Pro的算力差异与适配思路这两个芯片都是瑞芯微的旗舰级SoC但跨度很大。RK3399Pro是老一代产品板载NPU算力约3.0 TOPS放在今天只能说够用RK3588则是新一代平台NPU算力达到6 TOPSINT8CPU也从A72升级到了A76的大核架构整体性能翻倍。这个差异直接决定了部署策略不同项目RK3399ProRK3588NPU算力3.0 TOPS6 TOPSCPU核心双A72四A53四A76四A55内存通常2-4GB最高可达32GB推理建议输入640x640FP16或INT8640x640或更高INT8在RK3399Pro上我建议YOLOv5模型使用s或n版本输入尺寸保持在640x640INT8量化这样才能压着实时性走而RK3588可以比较轻松地跑m版本甚至可以适当提高batch或增加输入分辨率来提升小目标的检测能力。这套源码在工程结构上对两板做了统一的封装核心推理接口用条件编译区分平台底层NPU调用统一走RKNN Toolkit生成的runtime API上层业务代码完全一致。2. 模型训练、转换与量化实操2.1 训练自己的车辆行人数据集这套代码自带了针对车辆和行人类的COCO子集权重但真要落地到具体场景比如某个特定停车场或某个路口的视角我强烈建议用自己的数据微调。训练部分用的是标准的YOLOv5流程建议用yolov5s.pt作为预训练权重因为s模型的体积小、推理速度快在嵌入式端的性价比最高。数据集格式用YOLO格式最省事每张图片对应一个同名txt文件每行是class_id cx cy w h坐标全部归一化。要是没有现成的标注工具可以用LabelImg或者Label Studio转成这个格式。训练命令三件套python train.py --data your_dataset.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 python val.py --data your_dataset.yaml --weights runs/train/exp/weights/best.pt python export.py --weights runs/train/exp/weights/best.pt --include torchscript onnx关键点在于训练前要仔细检查类别数量和名称是否和部署端的标签文件一致。很多人会在这里翻车部署时发现检测出来的框对应的标签全乱了其实是类名顺序没对齐。2.2 RKNN模型转换从ONNX到RKNN的完整链路这一部分是最容易卡住新手的地方。RK3588和RK3399Pro不能直接消化ONNX或者PyTorch的权重必须经过RKNN Toolkit转成rknn格式。工具链选择上要注意RK3588需要rknn-toolkit2而RK3399Pro用的是rknn-toolkit1.x两套工具链的输出格式不通用。转换过程最核心的代码是基于Python的主要逻辑如下from rknn.api import RKNN rknn RKNN() # 1. 配置量化参数target_platform按芯片选择 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588 ) # 2. 加载ONNX模型 ret rknn.load_onnx(modelyolov5s.onnx) if ret ! 0: print(模型加载失败) # 3. 构建RKNN模型do_quantization开启INT8量化 ret rknn.build(do_quantizationTrue, datasetdataset.txt) if ret ! 0: print(模型构建失败) # 4. 导出rknn文件 ret rknn.export_rknn(yolov5s_rk3588.rknn)注意RK3399Pro上target_platform要改成rk3399pro而且rknn-toolkit的版本不要乱动最好用官方文档配好的版本组合。我踩过一次很深坑用新版本的toolkit去转老芯片的模型转换时报错一堆莫名其妙的算子不支持。后来老老实实看了瑞芯微官方wiki把toolkit版本、runtime版本、NPU驱动版本对齐了才顺畅起来。2.3 量化过程中如何保住精度INT8量化就是用一张校准集去统计各层的数值范围然后把FP32的权重和激活值映射到8bit整数。校准集不用太多100-300张能代表真实场景的图就够了但要注意这300张图必须覆盖各种光照、距离、目标规模和遮挡情况。如果只用干净的白天图做校准到了傍晚或者逆光场景检测率会明显下降。代码里做量化时dataset.txt指定的是校准图片路径列表每行一张图片按实际路径修改即可。量化完建议先在PC上用模拟器跑一遍精度评估对比FP32和INT8的mAP差异通常掉点在2%以内算正常超过5%就必须检查校准集或者某个特殊层是否被异常量化。3. C工程结构核心模块解析3.1 源码目录与模块划分这套C源码的工程组织非常清晰大致结构如下project/ ├── CMakeLists.txt ├── src/ │ ├── detect/ │ │ ├── rknn_detector.h # 封装RKNN推理 │ │ └── rknn_detector.cpp │ ├── track/ │ │ ├── tracker.h # DeepSORT核心 │ │ ├── tracker.cpp │ │ ├── kalman_filter.h │ │ ├── hungarian.h # 匈牙利匹配 │ │ └── feature_extractor.cpp │ ├── utils/ │ │ ├── postprocess.h # YOLOv5后处理 │ │ └── config_parser.cpp │ ├── main.cpp # 主流程 │ └── camera/ │ ├── stream_reader.cpp # 支持视频文件/RTSP │ └── stream_reader.h └── models/ ├── yolov5s_vehicle_person.rknn └── labels.txtCMakeLists.txt里需要链接的依赖包括RKNN runtime的librknnmrt.so、OpenCV、用于图像的前处理解码和目标框绘制以及Eigen3卡尔曼滤波的计算。如果是从SDK包里拉出来的rknpu runtime要多注意头文件和库的路径是否指向了板卡对应的架构目录。3.2 RKNN推理接口封装RKNN的C调用链路是读取模型文件、初始化上下文、设置输入、运行推理、获取输出。我在工程里把这些细节全部封装成了RKNNDetector类外部调用只需要传入一帧图像就能拿到vector 格式的检测结果。// 初始化 int rknn_init(const char* model_path); // 推理接口输入cv::Mat图像输出检测列表 std::vectorDetection rknn_detect(const cv::Mat img, float conf_thres, float iou_thres);实现里有一个很重要的细节YOLOv5的输出需要经过完整的后处理才能变成目标框。后处理包括去除低置信度框、按类别阈值过滤、NMS去重。这部分我建议用C手写实现不要直接用Python转换时那套numpy操作否则数据搬运和解析效率会差很多。NMS我用的OpenCV的dnn模块里的NMSBoxes实测比手写快且稳定自适应于不同尺寸的输入。3.3 DeepSORT跟踪器的集成细节在DeepSORT模块里最核心的类就是Tracker和KalmanFilter。每个跟踪目标对应一个KalmanFilter状态向量格式为[x, y, a, h, vx, vy, va, vh]其中a是宽高比h是高度。卡尔曼滤波做的事情本质是用一个恒定速度模型预测下一时刻目标状态然后用新的检测来更新修正。匈牙利算法的输入是一个代价矩阵矩阵的每个元素表示某个Detection和某个Track之间的距离。这里距离融合了两个信号一是运动距离马氏距离通过卡尔曼滤波预测的协方差来衡量检测框和预测框位置的接近程度二是外观距离ReID特征向量的余弦相似度。只有两个距离都小于一定阈值时才认为该检测属于该轨迹。刚开场的几帧会有大量新的跟踪目标出现因此代码里专门为临时轨迹保留了一个unconfirmed队列。轨迹只有在连续命中若干帧后才会转正避免误检测造成ID飘移如果连续丢帧达到上限比如30帧轨迹会被删除释放。4. 部署实测与性能调优4.1 RK3399Pro板卡上的实测数据先说配置RK3399Pro板卡双核A72四核A53板载NPU 3.0 TOPS运行时CPU Governor设为performance模式2路视频流以1080p分辨率输入。测试结果YOLOv5sINT8量化640x640输入单路视频流上的NPU推理耗时约在85ms到110ms之间结合DeepSORT跟踪的整个链路平均帧率大概能跑到9-12 FPS。这个结果实时性不够但对于很多安防巡检场景其实够用了因为并不是每一帧都需要模型跑可以设定隔帧推理策略比如每3帧推理一次中间帧用上一次检测结果补上帧率可以拉到20FPS以上。4.2 RK3588上的性能表现与优化空间RK3588上换上同样的YOLOv5s INT8模型NPU推理耗时能压到25ms左右如果开启多进程或多线程并发处理2-4路视频流同时跑单路还能维持25-30 FPS。换YOLOv5m模型推理耗时大约45ms仍然有实时性。如果还想压性能有几个实测有效的维度输入分辨率从640x640降到544x320精度有轻微损失推理耗时下降非常明显。CPU绑核把NPU相关线程绑定到A76大核把图像解码线程绑到A55小核能有效减少调度噪声。RKNN的zero-copy模式让NPU直接读取摄像头buff里对齐过的内存避免copy到NPU再copy出来这一大段搬运。4.3 大核小核调度的坑RK3588是大小核架构如果所有线程都默认调度系统可能会把计算密集型的推理线程丢到A55小核上导致性能骤降。我建议在初始化时用pthread_setaffinity_np将推理线程绑定到4个A76核心实测性能提升可达20%-30%。void bind_to_big_core(pthread_t thread) { cpu_set_t mask; CPU_ZERO(mask); // RK3588: CPU 4-7 是A76大核 CPU_SET(4, mask); CPU_SET(5, mask); CPU_SET(6, mask); CPU_SET(7, mask); pthread_setaffinity_np(thread, sizeof(mask), mask); }同理OpenCV的图像缩放和颜色转换操作比较吃CPU但也属于可以并行化的操作用OpenMP或者自己开线程池分摊就好。5. 常见问题与排查技巧实录5.1 部署后检测结果错乱这是最让人头疼的问题明明PC上跑ONNX一切正常部署到板卡后检测框位置不对、类别错乱、甚至全黑。我遇到过的原因主要集中在三类一是输入图像的尺寸和通道没有按模型要求预处理比如模型要RGBOpenCV默认读进来是BGR忘了色彩空间转换就会导致检测结果完全不对二是mean_values和std_values设置错误量化后输出层结果的scale崩了三是标签文件顺序和模型训练时的类别顺序不一致检测框位置对但标签名字全错。排查方法先在板端跑一张固定的图片把所有中间数据打印出来和PC端的输出做对比一行一行地排查差异点。一旦上了板卡不要相信任何应该没问题的直觉。5.2 RKNN模型初始化失败初始化失败十有八九是runtime版本不匹配。NPU驱动版本、rknn-toolkit版本和runtime库版本必须严格匹配这个在瑞芯微的官方release note里会明确说明。我用的组合是rknn-toolkit2 1.5.0 runtime 1.5.0 板卡固件对应版本稳定运行。版本一旦不对初始化直接报错或者模型莫名跑出NaN。5.3 多线程下视频流延迟追不上DeepSORT的关联阶段在目标多的时候会瞬间计算量激增比如画面里同时出现50辆车匈牙利算法要处理一个50x50的代价矩阵耗时明显上升。如果严格按照串行逻辑去跑解码-推理-跟踪-绘制帧率会非常不稳。我的处理方式是把这条流水线改成四个线程通过BlockingQueue连接各环节推理线程和跟踪线程并发执行用结果显示最新的帧即可。实测延迟依然可控但吞吐量提升明显。6. 扩展方向与最终心得6.1 从车辆行人跟踪到业务落地的演进整个工程里DeepSORT部分负责稳定输出每个目标的ID和轨迹但业务逻辑比如逆行检测、区域计数、出入口流量统计这些并没有写死。如果我们想统计一个区域内的在岗人数只需要订阅Tracker输出的轨迹对轨迹做区域判断即可。这样设计的好处是检测器和跟踪器是纯算法组件不跟业务耦合将来换场景换模型完全不影响跟踪逻辑。6.2 多路视频流的并发处理建议RK3588的NPU支持多个上下文context并发执行我在工程里提供了一个多路复用示例做法是每一路视频流创建独立的RKNN上下文通过线程池管理实测4路1080p视频流同时推理每路帧率依旧能保持25FPS以上。注意一点多路并发时NPU的内存占用是线性增长的如果板卡内存只有8GB建议单路输入分辨率控制在640x640以下。6.3 一些实际操作后的体会最后说一点个人感受。这类部署项目最耗时的往往不是算法本身而是工具链和开发板的磨合。第一次做RK系列部署的人建议拿到板子的前两周不要急着跑模型先把官方给的rknn_benchmark跑通理解推理耗时怎么评测、内存占用怎么看、模型文件各段的内存分配规律是什么后面再上手业务工程会顺畅很多。对于RK3588和RK3399Pro这两个平台代码层面已经通过宏隔离好了只需在CMake里传入目标平台标识就能同一套源码编译出两份二进制挂到不同板卡上后续维护非常方便。本文还有配套的精品资源点击获取