ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

YOLOv5+DeepSORT+FastReID重构:视频行人多目标跟踪与ReID落地实践

YOLOv5+DeepSORT+FastReID重构:视频行人多目标跟踪与ReID落地实践 简介本资源是一套面向计算机相关专业学生与初学者的行人多目标跟踪MOT与重识别ReID一体化实践方案基于YOLOv5检测、DeepSORT追踪与FastReID特征提取三大主流框架深度重构解决视频中行人检测、轨迹关联与跨镜像身份匹配的核心任务适用于课程设计、毕业设计、项目立项演示及AI方向入门进阶学习。压缩包共37个文件含31个Python源码覆盖数据预处理、模型推理、特征提取、轨迹管理等模块、2个YAML配置文件定义模型结构与运行参数、2个说明类TXT文档及README.md等辅助文件整体仅38KB轻量易读、结构清晰、接口封装规范。已有84人下载学习所有代码均通过实测验证附带详细技术文档与高分项目全流程说明包含导师认可的答辩材料、模块调用示例及常见问题排错提示可直接部署运行或二次开发拓展功能。1. 项目概述与整体价值搞过视频行人多目标跟踪MOT和行人重识别ReID的人应该都经历过这种尴尬Github上star过万的Yolov5-Deepsort工程一抓一大把但真正要用到自己项目里要么接口混乱难以扩展要么ReID部分就是个花架子提取出来的特征做跨镜头匹配时效果惨不忍睹。这次的重构项目就是围绕这个问题展开的。项目拿到了Yolov5-Deepsort-Fastreid的完整源码包这本身就是一个比较成熟的组合方案Yolov5负责检测Deepsort负责跟踪FastReID负责行人特征提取。但源码归源码直接跑demo没问题想集成到实际业务里就是另一回事了。所以这次重构的核心目标很明确把视频行人MOT和行人ReID特征提取这两块代码重新组织抽离出干净、可复用的接口同时补全详细文档和全部配套资料让这个项目能真正落地。这个项目对于什么人最有价值我认为有三类人第一类是刚接触MOT和ReID的研究生需要一个完整的、能跑通的参考实现来理解整个pipeline第二类是工程人员需要在业务系统里集成行人跟踪和检索功能但不想从零造轮子第三类是想深入源码细节的学习者FastReID的代码结构比一般ReID工程复杂不少如果有人帮忙把核心路径和关键模块梳理清楚学习曲线会平缓很多。2. 环境准备与基础架构2.1 环境依赖与版本选型这一节先说环境因为MOT和ReID项目对版本极其敏感经常出现“昨天还能跑今天pip install了别的东西就崩了”的情况。我实际测试下来Python 3.8配合PyTorch 1.9.0或1.10.0是比较稳的组合CUDA 11.1到11.3之间都可以。这里不建议一上来就追求最新版本PyTorch 2.x虽然快但很多老代码里用到的API已经变了改起来浪费太多时间。依赖安装我建议用conda建独立环境避免污染。conda create -n mot_reid python3.8 conda activate mot_reid pip install torch1.9.0cu111 torchvision0.10.0cu111 -f https://download.pytorch.org/whl/torch_stable.html pip install opencv-python4.5.5.64 pip install numpy scipy scikit-learn pandas matplotlib pip install pycocotools cython pip install faiss-gpu1.7.1 # ReID特征检索会用到CPU版本也行有几点需要提醒一下。scipy的版本不要直接上最新版1.7.x比较稳新版scipy对deepsort里用到的linear_assignment接口支持有问题——当然如果你用scipy 1.9以上版本需要用scipy.optimize.linear_sum_assignment替换老的API这块我在后面问题排查里会详细说。还有opencv如果只是做demo装opencv-python-headless版本就够了但如果要用到cv2.imshow做实时可视化那就必须装完整的opencv-python否则会报qt相关的错误。2.2 源码目录结构与模块职责拿到源码包之后第一件事不是急着运行而是把目录结构摸清楚。重构前的常见问题就是代码文件散落各处有的是脚本有的是模块职责混在一起。这个项目重构之后的结构是这样的project_root/ ├── configs/ # 配置文件统一放在这里 │ ├── yolov5s.yaml # 检测模型配置 │ ├── deepsort.yaml # 跟踪器配置 │ └── reid_config.yml # ReID配置包括模型路径、输入尺寸、阈值 ├── detector/ # Yolov5检测模块 │ ├── yolo_detector.py # 检测器封装类 │ └── models/ # 存放yolov5权重 ├── tracker/ # DeepSort跟踪模块 │ ├── deepsort_tracker.py │ └── utils/ # 卡尔曼滤波、匈牙利匹配等 ├── reid/ # FastReID特征提取模块 │ ├── reid_feature.py # 特征提取封装类 │ └── models/ # 存放ReID权重如ResNet50、Swin等 ├── pipeline/ # 一站式pipeline │ └── mot_reid_pipeline.py ├── tools/ # 各种工具脚本 │ ├── train_reid.py # 训练ReID模型 │ ├── evaluate_mot.py # 评估MOT指标 │ └── extract_gallery.py # 提取底库特征 └── docs/ # 详细文档这个结构的核心思路就是模块化检测、跟踪、ReID三者解耦每个模块都可以单独替换。比如说你不想用Yolov5想换成YoloX或者YoloV8只需要在detector目录下新增一个封装类接口保持一致即可。后面如果要部署到边缘设备比如RK3568这种也能很容易地把某个模块替换成对应的推理后端。3. 核心模块拆解与原理分析3.1 检测模块的设计与实现Yolov5检测器是整个pipeline的第一步。原始Yolov5仓库里已经提供了detect.py但那个脚本是面向单张图片或者视频demo的不适合作为模块嵌入到MOT系统中。所以在重构时我把Yolov5封装成了一个独立的类核心接口就三个方法load_model()、inference()、post_process()。inference阶段需要特别注意几个细节。Yolov5在推理时有一个默认的预处理流程letterbox保持长宽比的resize padding这个必须保留因为模型训练时用的就是这种输入方式。另外原始detect.py里有个agnostic_nms参数跨类别NMS在行人检测场景下建议开启。原因很简单我们只关注person这一类不同类别之间其实不存在什么竞争关系但开启agnostic NMS可以避免同一个目标被多个类别框同时框住的情况尤其是行人贴着骑自行车的人这种场景如果不做跨类别NMS会出现两个高度重叠的框分属person和bicycle两个类别传到deepsort里会产生重复轨迹。后处理部分NMS的IOU阈值建议设置在0.45到0.5之间。置信度阈值这里要分开看训练阶段追求召回率阈值可以低到0.1但推理阶段如果阈值太低deepsort会收到大量误检框导致大量短命轨迹出现。我实测在密集场景下置信度阈值0.3到0.4是个比较平衡的选择。3.2 跟踪模块的核心原理Deepsort的核心算法流程一句话概括就是检测框输入 - 卡尔曼滤波预测 - 匈牙利算法匹配 - 轨迹更新。重构时理解的难点在于它有两个匹配阶段。第一阶段是级联匹配Cascade Matching。这个阶段解决的是“谁先被优先匹配”的问题。Deepsort给每条轨迹维护了一个time_since_update变量表示这条轨迹有多久没被匹配上了。优先匹配time_since_update小的轨迹也就是最近一直在正常跟踪的目标。为什么要这样设计因为卡尔曼滤波的预测会随时间积累误差轨迹越久没更新预测框的位置越不可信如果让老轨迹和新检测框公平竞争老轨迹很容易抢走本该属于新轨迹的匹配。所以在代码里可以看到级联匹配是循环进行的先匹配最近更新过的轨迹再逐步放宽到更老的轨迹。第二阶段是IOU匹配。级联匹配之后剩下的检测框会和那些很久没更新、但还没被删除的轨迹做纯IOU匹配。这个阶段不需要外观特征纯粹利用位置重叠程度。它的作用是处理目标的短暂遮挡比如行人被路边电线杆挡住一两帧跟踪框还在附近徘徊等行人重新出现时靠IOU就能重新接上。整个deepsort实现里还有一个容易被忽视的细节轨迹确认机制。新检测框产生后会先进入“不确定态”tentative连续匹配上3帧才会转为“确认态”confirmed只有confirmed轨迹才会输出。这个机制本质上是防抖单帧误检不会产生一条假轨迹有效滤除了静态背景噪声和偶尔出现的车灯误检。重构时这个参数我保留了默认值实际使用下来3帧是一个不错的折中。如果是密集人群场景可以适当降低到2帧加快轨迹确认速度。3.3 ReID特征提取模块的设计FastReID是京东开源的一个ReID工具箱特点是模型丰富、训练pipeline完善。但在MOT场景里我们需要裁剪掉大部分功能只保留推理时的特征提取部分。核心的ReID推理流程是这样的输入一张行人图像通过检测框从原图裁剪得到- 预处理resize normalize- 特征提取网络前向 - 输出一个固定维度的特征向量。经过L2归一化后可以用余弦相似度来衡量两个行人是否是同一个人。这里有一个非常关键的技术细节ReID模型输入的尺寸。FastReID预训练模型如基于Market1501训练的输入尺寸通常是256x128长宽比2:1。这个尺寸不是拍脑袋定的而是统计了数据集里行人框的长宽比分布后选定的。所以从检测模块拿到的检测框不能直接喂给ReID网络必须先做alignment。裁剪时一般会做一些扩展比如上下左右各放宽5%到10%把行人的头部和一截脚部都包含进来这样ReID模型能提取到更完整的特征。特征向量维度方面通用的ResNet50版ReID模型输出是2048维经过一个BN层后得到512维的最终特征。维度并不是越高越好512维和2048维在各种检索任务上的差距并不大但512维可以显著降低存储和检索压力。项目里默认是用512维特征如果没有特别场景要求这个维度已经足够用了。3.4 为什么要把检测、跟踪、ReID三者解耦原始代码把三者耦合在一起最典型的表现是Deepsort内部直接调用了ReID模型提取特征而ReID特征的提取又紧跟着检测框的后处理。这看起来没什么问题但它实际上破坏了两个工程上的灵活性。第一性能瓶颈无法单独优化。如果整个链路是串行的——检测、跟踪、ReID依次执行——GPU利用率严重不足。检测模型和ReID模型可以放到两个显存分段并发推理但耦合代码做不到这点。第二ReID特征无法复用。在实际业务场景里同一个行人往往在视频里出现几十帧甚至上千帧耦合结构下每一帧都会重新提取一次ReID特征。但其实这个人的外观在短时间内变化很小完全可以缓存特征每隔N帧再更新一次。我在重构时加入了特征缓存机制对同一个track_id如果距离上次特征提取的时间小于阈值直接复用缓存的特征实测在长视频上可以节省40%以上的ReID推理时间。解耦之后检测模块输出的是“每帧检测框列表”跟踪模块消费检测框并输出“带ID的轨迹框”ReID模块消费“某条轨迹在某一帧的裁剪图”并输出“特征向量”。三个模块之间只通过简单数据结构通信每个模块都可以独立测试、独立替换。4. 关键代码重构与接口设计4.1 批量推理与接口统一原始deepsort的接口设计得不太合适每输入一个检测框就调用一次特征提取然后马上做匹配。在低帧率视频上勉强能跑但一旦视频里行人数量增多比如地铁站场景单帧十几个人ReID特征提取就会成为性能瓶颈。重构后我采用的方案是先让Yolov5完成整帧推理拿到所有检测框然后对检测框做一次“是否需要更新特征”的判断把需要提取特征的行人裁剪图批量打包一次性送入ReID网络。这样做的原因在于GPU推理的吞吐量在小batch和大batch之间差距不大但单独调用的开销kernel launch、数据搬运却会线性增长。实测从逐框提取改为批量提取后ReID模块的FPS提升了一倍不止。批量处理的代码结构大致是这样的def extract_features_batch(self, imgs): imgs: list of numpy array, shape (H, W, 3) in BGR or RGB if len(imgs) 0: return None batch self.transform(imgs) with torch.no_grad(): features self.model(batch) features F.normalize(features, p2, dim1) return features.cpu().numpy()4.2 特征提取结果缓存与复用在长视频中同一个ID每帧都在更新检测框但行人的外观不会在几帧内突然改变因此持续提取ReID特征纯粹是浪费算力。我在实践中参考了通用ReID系统里的做法给每个跟踪轨迹增加一个特征缓存字段包含特征向量和时间戳。当Deepsort对某个轨迹完成一次成功匹配后判断当前帧号与上次特征提取帧号的差值如果大于预设的更新周期我在项目中默认配置为15帧才重新提取特征并更新缓存否则直接复用。这里有一个问题特征复用周期太长可能导致同一轨迹的特征长期不更新当行人转身、换背包等外观发生较大变化时匹配质量会受影响。我试过30帧的周期在视角固定的摄像头下问题不大在大角度变化的场景比如行人在镜头前转弯30帧会导致有些轨迹匹配失败。最终我取了15帧作为默认值兼顾性能与稳定性。4.3 多线程/多进程加速设计上面的优化完成后单卡GPU上的MOT推理流程其实还可以再进一步把数据预处理和模型推理解耦。代码里我用threading.Thread和queue.Queue组合实现了一个简化版的生产者-消费者模式。主线程负责视频解码和Yolov5检测子线程负责Deepsort跟踪和ReID推理通过两个队列传递数据。具体来说检测线程把检测框放到det_queue中跟踪线程从队列里取数据进行卡尔曼预测和匹配同时把需要提取ReID特征的裁剪图放到reid_queue中让ReID线程异步处理。这样做的好处是检测和ReID两个GPU任务可以并行执行不会互相阻塞。特别是在GPU资源相对充裕显存大于12GB的情况下两个模型可以同时驻留显存达到更高的硬件利用率。当然多线程比单线程复杂不少有一个细节必须处理好ReID特征提取是异步的也就是说帧N的行人在帧N5提取出特征这会导致跟踪匹配时拿到的特征比较“旧”。为了解决这个问题我在请求特征更新时会同时记录当前帧号特征算出来后直接把“特征 帧号”一并返回给跟踪线程。从代码层面看就是一个带有帧号字段的结构体这比用全局变量存特征要干净得多。实测这个延迟对跟踪结果的影响几乎可以忽略因为ReID特征提取耗时通常只有几毫秒到十几毫秒而特征缓存周期是15帧延迟远小于缓存周期。5. ReID模型训练与特征质量优化5.1 数据准备与预处理项目自带的ReID预训练模型是在Market1501这类公开数据集上训练出来的直接用在监控视频上效果是会打一些折扣的。原因在于公开数据集里的行人图像通常较为清晰、光照条件相对固定而真实监控场景往往存在光照突变、低分辨率、遮挡等问题。所以在实际项目中我建议用你自己场景的数据对ReID模型做一轮微调fine-tune哪怕只是几百张标注好的行人图像也能带来显著提升。FastReID训练时要求图片以特定目录结构组织每个行人一个文件夹目录结构大致如下dataset_root/ ├── train/ │ ├── id_001/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ │ └── ... │ ├── id_002/ │ └── ... └── query/ # 查询集可选训练时需要注意每个ID的图片数量不能太少建议至少10张如果某个ID只有1到2张图片容易导致模型过拟合这个样本最好直接过滤掉。5.2 训练超参数设置与Loss设计FastReID默认使用的损失函数是CrossEntropy Loss和Triplet Loss的组合。CrossEntropy Loss是分类损失把每个行人ID当作一个类别Triplet Loss是度量学习损失它要求同一行人的特征距离远小于不同行人的特征距离。两者搭配使用效果会更好。训练超参数方面我常用的配置是batch size 64每个ID采样4张图初始学习率0.00035使用Warmup策略前500步从0线性升到初始学习率之后按余弦退火衰减。训练轮数在Market1501这类数据集上一般需要60到80个epoch自建小数据集可能20到30个epoch就会收敛注意观察验证集指标避免过拟合。5.3 单通道灰度图输入的适配热搜词里出现了“yolov5训练单通道”这个场景在ReID里也常见——有些老旧的监控摄像头输出的是灰度视频只有Y通道没有彩色信息。Yolov5和FastReID模型原始输入是3通道RGB所以需要做通道变换。处理方式很简单把灰度图复制三份堆叠成三通道。这个操作可以用numpy一行搞定gray cv2.imread(image.jpg, cv2.IMREAD_GRAYSCALE) rgb np.stack([gray] * 3, axis-1)但要注意模型训练时见过的是彩色图的分布如果推理时输入的三通道图其实是灰度复制的模型的特征提取能力会打折扣。要真正解决这个问题最好是准备一份灰度化的训练数据对ReID模型做微调。还有一个技巧是数据增强阶段引入灰度化在FastReID的transform里随机对部分样本做灰度化处理让模型对颜色信息降低依赖。这样在遇到真正的灰度摄像头时模型仍然能提取到较好的特征。5.4 特征归一化与相似度度量FastReID在训练完成之后通常会对特征进行L2归一化也就是每个特征向量的模长变成1。这样做的原因是训练阶段Triplet Loss内部计算的是欧氏距离但推理阶段我们更多使用余弦相似度。L2归一化之后欧氏距离和余弦相似度在数学上是单调等价的模型的使用就变得统一了。实际使用时比较两个行人特征一般用余弦相似度。我会设置一个判定阈值相似度大于0.7的认为是同一个人小于0.5的基本判定为不同人0.5到0.7之间是模糊地带需要人工确认或提高阈值。这个阈值不是固定的跟你的ReID模型在训练集上的分布强相关。建议在项目落地前在当前场景收集一批正负样本对画出相似度分布直方图找到分界点。6. 训练与推理过程中的坑与排查6.1 SciPy版本导致Deepsort红线问题Deepsort的核心匹配代码在老的实现里调用的是scipy.optimize.linear_assignment但这个函数在SciPy 1.8.0之后被移除了换成了linear_sum_assignment。两者的输入输出基本一致但函数名和路径不一样。如果你直接pip install最新版scipy运行时大概率会报AttributeError: module scipy.optimize has no attribute linear_assignment。解决方案有两种一是把scipy降到1.7.x二是修改源码把deepsort的匹配函数改成from scipy.optimize import linear_sum_assignment as linear_assignment # 注意返回值是一个tuple需要转置 row_ind, col_ind linear_assignment(cost_matrix)我用的是第二种方案因为锁定旧版本scipy会限制其他库的兼容性。改完后记得测试一下匹配结果是否正常——主要是确认索引顺序没有搞反否则会出现张冠李戴式的匹配错误。6.2 特征比对耗时过高在行人数量较多的场景下Deepsort需要对每个轨迹和每个检测框计算特征距离矩阵。如果轨迹数量M和检测框数量N都比较大ReID特征比对MxN次余弦相似度计算可能成为新的瓶颈。这种情况下有两个优化方向。第一是使用向量化计算而非循环。假如特征矩阵为features_trackMx512和features_detNx512直接用矩阵乘法计算余弦相似度similarity np.matmul(features_track, features_det.T) # shape: M x N这比逐对调用cosine_similarity快几个数量级。第二是使用FAISS建立索引。如果底库特征非常多比如做跨摄像头检索时底库有上万条特征线性扫描的代价就太大了。FAISS支持GPU加速和近似检索可以把延迟从毫秒级降到微秒级。项目里已经集成了FAISS具体用法是import faiss index faiss.IndexFlatIP(512) # IP表示内积等价于余弦相似度特征已L2归一化 index.add(gallery_features) scores, indices index.search(query_features, k10)6.3 目标遮挡严重导致ID Switch问题所谓ID Switch就是同一个行人在视频中被赋予了两个不同的ID或者两个不同行人共享了一个ID。这在MOT评估中是比较影响精度的指标。ID Switch最常出现在遮挡场景行人在镜头前被其他人或物体完全遮挡数秒后重新出现。Deepsort的处理方式是轨迹在max_age帧内没有被匹配上就继续保留在候选集里等待重新匹配超过max_age还没出现就删除这条轨迹。如果遮挡时间超过了max_age轨迹被删除行人再次出现时会被当作新目标产生ID Switch。解决这个问题有几个思路。可以把max_age从默认的70调大到150或200但这会带来一个副作用删除掉的轨迹不会马上释放如果一直有误检和它做IOU匹配可能产生鬼影轨迹。更稳妥的做法是加强ReID特征的质量如果ReID特征足够好即使遮挡后重新出现也能靠外观特征重新匹配回原轨迹。所以这个问题的根因往往不在deepsort参数调优上而在于ReID模型的特征判别力不够。6.4 低帧率视频中的跟踪恢复MOT任务里有个常见假设视频是连续帧帧间位移较小。但现实中的很多摄像头是低帧率比如5到10FPS或者抽帧处理的这时卡尔曼滤波的预测误差会明显增大因为连续两帧之间目标可能已经移动了很长一段距离而卡尔曼滤波基于恒速模型的预测根本追不上。一个有效的缓解手段是放宽级联匹配和IOU匹配的阈值。比如把max_iou_distance从默认的0.7调整到0.8或0.85允许更大重叠程度的匹配。同时ReID特征匹配阈值的权重需要提升低帧率下位置信息不可靠就要更多依赖外观特征。在Deepsort中级联匹配阶段的计算方式通常是0.5 * (1 - 余弦相似度) (1 - 位置IoU)可以适当调低位置项的权重让外观特征在匹配中占更大的决定权。7. 跨摄像头ReID应用与底库检索7.1 从底库特征提取到检索匹配跨摄像头ReID是MOT的自然延伸。MOT解决的是单镜头内的身份保持问题ReID解决的是跨镜头身份匹配问题。两者结合后的典型应用是在A摄像头对某个目标建模到B摄像头去检索这个目标是否出现。项目中的extract_gallery.py就负责这个流程。它会遍历指定视频或图片集对每个检测到的行人裁剪出图像提取512维特征连同时间戳、摄像头ID、坐标信息一起存入数据库这里用的本地npy或faiss index。之后给定一张查询图或某条MOT轨迹的特征均值就可以在底库中检索最相似的Top-K个结果。实际项目中对同一条MOT轨迹的多帧特征做均值化处理feature averaging通常能获得比单帧特征更稳定的检索效果。但均值化有个前提先把每帧特征做L2归一化再取平均最后再归一化一次。否则可能出现特征模长被非线性放大的问题。7.2 特征库更新与时效性问题在长时运行的场景中比如商场摄像头连续运行一整天底库特征会不断膨胀。如果不做清理检索速度和内存占用都会出问题。更麻烦的是底库中可能存在大量同一行人的重复特征——这个人一天内可能进出监控区域十几次。我通常的做法是给底库特征加上有效期比如120秒内的特征视为“活跃”超过后进入“冷数据”区。生成检索结果时优先展示活跃特征冷数据区则定期做去重合并把相似度超过0.9的特征聚类成一个代表特征。这样底库规模可以控制在相对稳定的水平。7.3 实际效果评估指标MOT和ReID的评估指标完全是两套体系。MOT主要看MOTA多目标跟踪准确率、IDF1ID F1分数和ID Switch次数ReID看Rank-1、mAP和mINP。项目里提供了evaluate_mot.py脚本可以用MOTChallenge官方评测工具算MOTA和IDF1前提是推理结果要按指定格式输出成txt文件。做评估时有个容易踩的坑只跑一段连续的干净视频指标看着很好但换到真实长视频后指标断崖式下跌。原因是长视频里有大量遮挡、目标出镜入镜、光照变化等复杂情况。所以评估时建议多选几个不同场景、不同时长的视频片段至少覆盖白天、黄昏、夜晚三类光照条件。单独一个场景的指标没有任何说服力。8. 性能优化与部署实践8.1 推理速度瓶颈定位拿到重构代码后怎么优化性能第一步不是盲目改代码而是先定位瓶颈在哪里。我通常用cProfile或简单的time日志统计各个模块的耗时占比。经验数据是Yolov5检测大约占30%到50%的总耗时取决于分辨率ReID特征提取占20%到40%Deepsort跟踪的纯计算部分其实很低通常不到10%剩下的就是视频解码、图像预处理和可视化。最典型的性能瓶颈是视频解码。如果直接用OpenCV的VideoCapture读取视频在高分辨率1080p以上场景下解码耗时可能超过所有模型推理耗时之和。这时候建议用PyAV或者NVIDIA的PyNvVideoCodec做硬件解码显卡支持NVDEC的话可以显著降低解码开销。每个模块都单独计时一次比你瞎猜哪块慢要高效得多。8.2 TensorRT加速与边缘设备适配如果是部署到边缘设备比如RK3568、Jetson建议把Yolov5和ReID模型都转换为ONNX再转成TensorRT引擎。这一步很关键因为边缘设备的算力有限FP32的模型跑起来帧率往往惨不忍睹。转换流程一般是pytorch模型 - ONNX - TensorRT engine具体操作# 先导出ONNX torch.onnx.export(model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[output])注意ONNX导出时Yolov5的模型结构里有几个动态分支比如nms操作目前很多版本的Yolov5导出ONNX时默认不包含NMS层需要在后处理里自己实现。FastReID的模型导出相对简单全程是线性结构几乎不会有兼容问题。TensorRT推理时建议使用FP16精度模型体积缩小一半速度提升一倍左右精度损失很小。如果设备支持INT8量化RK3568可以还能进一步优化但需要一小批校准数据否则量化误差可能会比较大。8.3 边缘设备上的资源管理在RK3568这类设备上部署还有一个常被人忽略的点内存带宽和CPU负载。模型推理被放到NPU上之后CPU还是承担着视频解码、图像预处理、后处理等工作。我在RK3568上跑过一次发现瓶颈转移到了CPU的letterbox和NMS操作上。一个直观的优化是把letterbox的resize用硬件加速库比如RKNN的缩放接口或OpenCV的CUDA/OpenCL版本完成而不是纯CPU写循环。另外一个容易被忽略的细节在边缘设备上多线程的线程数设置很讲究。Python的GIL导致多线程无法真正并行CPU密集型操作所以实际上是多进程更有效。但如果每个进程都加载一个模型内存又不够了。折中方案是保持单进程把视频解码放入子线程模型推理走NPU纯CPU操作尽量用OpenCV内部多线程OpenCV默认开启了TBB并行会自己利用多核。8.4 针对RK3568部署的模型配置技巧这里展开说一下RK3568上部署Yolov5和ReID模型的几个注意点。RK3568的NPU算力大约是1TOPS级别跑Yolov5s大概能到10-15FPSReID模型用ResNet50大概能到30FPS以上所以整个MOT pipeline的瓶颈是检测部分。在模型选择上我建议把Yolov5s换成Yolov5n或者Yolov5s精简通道版检测精度会下降一点但FPS能翻倍。ReID模型也可以换成轻量的MobileNetV3或ShuffleNetV2主干。有一点务必注意RKNN工具链对某些PyTorch算子的支持还不完整比如nn.AdaptiveAvgPool2d在某些版本下转换会报错建议在导出ONNX之前把模型的全局池化层手动改成固定尺寸的AvgPool2d可以省去很多调试时间。9. 常见问题速查与避坑指南问题现象根本原因解决方案推理时报scipy linear_assignment找不到scipy版本过新降版本到1.7.x或用linear_sum_assignment替换ReID特征全是NaN输入图像存在全黑/全白区域ReID推理前检查图像的平均像素值排除异常图跟踪FPS极低ReID特征逐帧逐个提取改为批量提取加入特征缓存机制视频画面卡顿但CPU占用高视频解码瓶颈使用PyAV/PyNvVideoCodec硬件解码检测框频繁闪烁NMS置信度阈值过低提高阈值到0.3以上开启agnostic NMSID Switch多次发生ReID特征区分度不够使用场景数据微调ReID模型增大特征更新频率行人出画面马上被删除max_age太小增大max_age适当放宽匹配阈值还有一个很重要但经常被忽略的问题检测框的坐标精度。Deepsort代码里默认检测框格式是[x1, y1, w, h]左上角坐标宽高不管是Yolov5输出的[x1, y1, x2, y2]左上角右下角还是其他检测器输出的中心点格式都必须在送入跟踪器之前统一转换。我就见过有同学debug到深夜最后发现是坐标格式不统一导致卡尔曼滤波器输入的观测值完全错乱。另外min_height这个参数值得关注。Deepsort默认会忽略高度小于min_height默认是0也就是不忽略的检测框。在行人检测场景一个小目标比如100米外的人只有20像素高提取ReID特征基本是纯噪声而且会形成大量短轨迹消耗计算资源。我建议把min_height设置为30或40像素可以明显减少计算浪费。10. 项目扩展方向与实操心得前面讲了很多具体的实现细节最后聊几句扩展方向。这个项目重构完之后我发现它其实可以很自然地扩展到几个新任务上而不只是做行人MOT和ReID。第一个方向是车辆跟踪与检索。只需要把Yolov5的检测类别改为vehicle相关类别再把ReID模型换成VehicleID训练的模型整个pipeline完全不需要改动。这就是模块化解耦带来的直接收益。第二个方向是结合大模型做自然语言行人检索。用一个CLIP类模型替换掉传统ReID模型可以把“行人图片”映射到文本-图像跨模态空间这样查询条件就变成了“穿红色上衣的男子”而不是必须提供一张参考图。这个玩法在安防场景很实用。第三个方向是动态调整ReID的更新策略。当前的特征缓存是基于固定帧数但实际场景中行人如果一直在正常行走外观变化不大完全可以降低更新频率而如果行人的运动速度突变比如从走路变为奔跑姿态变化大说明外观可能变化应该提高更新频率。这里可以用简单的运动状态预判来实现自适应策略。我个人在实际项目里的一个体会是MOT和ReID这个问题真正的难点不在某个模型的精度而在于整个pipeline的工程化能力——怎么让检测、跟踪、特征提取稳定协同工作怎么在真实场景的噪声下保持鲁棒性怎么在算力有限时仍然跑出可用的帧率。这套重构代码更大的价值在于给你提供了一个可以按需修改的架子你可以把它当作玩具直接跑demo也可以把它当作起点逐步炼成适合自己业务的分工体系。最后再分享一个调试小技巧当你觉得跟踪效果不好先把ReID特征可视化出来看看。用TensorBoard或者matplotlib把特征向量降维PCA或者t-SNE之后显示你会发现数据分布是否清晰——同类特征是否聚在一起、不同类是否分散。如果特征空间本来就是一团浆糊那调任何匹配参数都是白搭问题出在模型和数据侧而不是跟踪侧。本文还有配套的精品资源点击获取
返回列表