资讯动态

PPYOLO+DeepSORT:行人检测跟踪项目实战与踩坑总结

发布时间:2026/10/19 16:31:36 来源:尧图企业网站定制
做行人检测跟踪这个项目前前后后折腾了将近两个月。当时选型的时候没有太纠结检测端直接锁定了PaddlePaddle生态里的PPYOLO跟踪端用了经典的DeepSORT。理由是现成的软件杯赛题要求最终能在一个统一框架里跑通训练、推理和部署PaddlePaddle从模型库到推理引擎的链路足够完整PPYOLO在公开数据集上的表现不输同量级的其他检测器预训练权重也方便DeepSORT则是多目标跟踪里被大量项目验证过的成熟方案把它和PPYOLO接在一起思路清楚出问题也好定位。这篇把项目从技术选型、训练调参到系统集成整个过程中的细节和踩过的坑都梳理一遍给后面做“检测跟踪”类任务的同学做个参考。1. 项目思路与技术选型为什么是PPYOLO DeepSORT1.1 检测和跟踪为什么要分成两段行人跟踪这个需求看起来好像一个检测器逐帧画框就够了但真的上手之后会发现检测只能给出“这一帧里有哪些人”解决不了“这个人是不是上一帧那个人”的问题。单独用检测器做跟踪框会跳、ID会乱、一旦目标被柱子挡住几帧再出现直接就当成新人处理了。所以我把任务拆成了两条线检测器负责单帧目标定位跟踪器负责跨帧身份关联。检测器的输出是每一帧的边界框和置信度跟踪器把这些框按时间序列关联起来维护每个人的轨迹和ID。这个拆法最大的好处是调试起来非常清晰检测漏检了问题在PPYOLO那一侧ID频繁切换问题在DeepSORT那一侧。如果做成端到端的联合模型检测和跟踪互相耦合赛程又紧出了问题根本不知道先往哪里查。两段式虽然多了个中间层但每一段都能独立验证项目进度反而更可控。1.2 PPYOLO相比其他检测器的优势当时可选的目标检测模型不算少YOLOv5、FCOS、EfficientDet都有自己的优点。但PPYOLO在比赛场景里有几个不可替代的优势。第一是框架一致性。PaddlePaddle的训练、评估、推理链条是配套的模型从训练到部署不用跨框架转换。我见过很多项目用其他框架训练最后部署又要转成另一种格式光算子对齐就能折腾好几天这在竞赛赛程里非常吃亏。第二是精度和速度的平衡。PPYOLO在COCO级别的评测上mAP和推理速度的平衡做得相当好。尤其是行人这种非刚性目标形变和遮挡很多PPYOLO用的可变形卷积对这类场景比较友好小目标召回率也更好看。第三是上手门槛低。PaddleDetection里面预训练权重和配置文件都是现成的把数据集路径和类别数改一改就能开始训练。对一支主要精力不在检测算法本身的队伍来说这个“省心”属性太重要了。当然PPYOLO也不是没有代价它对PaddlePaddle的算子生态绑定比较深如果非要导出成其他框架格式会折腾。但既然项目本来就用这套框架这个约束等于不存在。1.3 DeepSORT解决的核心问题DeepSORT的前身是SORT核心是卡尔曼滤波加匈牙利匹配。SORT的思路很简单用卡尔曼滤波预测每条轨迹在当前帧的位置然后把预测框和检测框的IOU作为相似度跑一次匈牙利算法做最优匹配。这个方案速度快但一旦目标运动轨迹突变或者被遮挡一段时间关联很容易断裂ID切换率很高。DeepSORT在SORT基础上加了外观特征这个关键组件。对每个检测框用一个行人重识别网络提取特征向量再与轨迹保存的历史特征做余弦距离匹配。最终匹配代价由运动距离和外观距离加权组合而成并且设计了级联匹配机制优先匹配刚更新过的轨迹再处理长时间未更新的旧轨迹。可以这样理解运动模型告诉我们“这个目标大概在哪里”外观模型告诉我们“这个目标是不是之前那个人”。两者配合之后ID切换的问题比纯SORT好很多。这也是我在项目里没有选择其他更冷门跟踪算法的主要原因——DeepSORT的代码实现多、参数资料全踩坑的时候能搜到很多现成的经验。2. 核心原理拆解PPYOLO的检测能力从哪来DeepSORT怎么把框串起来2.1 PPYOLO的结构与关键改进PPYOLO的基本流程还是YOLOv3那套把输入图片划分成网格每个网格预测若干候选框再做分类和边框回归。但在具体实现里PPYOLO做了几处针对性很强的改进。Backbone用的是ResNet50vd。相比普通ResNet它在下采样和stem部分做了调整更适合目标检测这种需要多尺度特征的任务。在backbone里还集成了可变形卷积让卷积核的感受野能自适应目标形变。行人这种姿态多变的非刚性目标用普通卷积很容易笼统地“看”成一个矩形可变形卷积能把感受野贴合到行人轮廓上特征提取质量明显更高。Neck部分用CSPPAN替代了传统的特征金字塔。CSP结构的跨阶段部分连接能在减少计算量的同时保留更多梯度信息让多尺度特征融合更充分。Head部分则引入了IOU Loss做边框回归让模型优化的目标从简单的坐标差变成了预测框和真实框的重叠度收敛更稳定。还有一个容易被忽略的小改动是grid sensitiveness。YOLOv3在预测物体中心坐标时对网格边界的落入位置比较敏感PPYOLO对这个做了缓和处理让回归值在网格边缘附近更平滑。这些改进单独看都不复杂但叠在一起效果很显著。我们在自己的验证集上测过相同分辨率下PPYOLO mAP比普通YOLOv3明显高出一截漏检率也低很多。2.2 DeepSORT的状态估计和数据关联DeepSORT里每条轨迹用一个八维状态向量表示[u, v, s, r, dot_u, dot_v, dot_s]其中u和v是目标中心的横纵坐标s是包围框的面积r是宽高比。前四个是当前观测状态后三个带点是对应的速度信息。卡尔曼滤波假设目标做匀速运动用上一帧的状态预测当前位置再用检测框的测量值去修正预测结果。数据关联是核心中的核心。DeepSORT用了两层相似度第一层是马氏距离用来衡量预测状态和检测状态之间的匹配程度。马氏距离考虑了每个状态变量的方差比单纯的欧式距离更适合表达观测不确定性。第二层是外观特征余弦距离。每个轨迹会保存最近若干帧的ReID特征组成一个特征池新的检测框提取特征后和特征池里所有特征计算余弦相似度取最大值或平均值作为外观距离。最终的总代价是两个距离的加权混合然后通过匈牙利算法求全局最优匹配。级联匹配还有一条额外的规则轨迹越久没有匹配到检测框它在这次匹配中的优先级就越低。这样做的原因是长时间未更新的轨迹预测误差会越来越大如果允许它参与普通竞争很容易抢走其他正常轨迹的目标。理解了这套流程就能解释实际场景里的很多现象。比如一个人被挡住五秒后重新出现ID变了大概率不是因为跟踪算法坏了而是因为外观特征在低分辨率下区分度不够或者级联匹配已经把旧轨迹的优先级降到了很低。2.3 检测结果到跟踪器的衔接细节检测和跟踪不是简单把框丢给DeepSORT就完事项目里最容易翻车的恰恰是这个衔接层。我第一次做联调的时候把所有检测框直接送进跟踪器结果画面里全是乱跳的误检轨迹。后来加了两个前置过滤才稳住局面。第一个是置信度过滤。低于阈值的检测框直接丢弃因为这些框通常来自背景误判、严重遮挡或模糊目标。置信度设多少要看你训练的检测器总体打分分布我一般在0.25到0.4之间调。第二个是NMS的IOU阈值设置。PaddleDetection默认的NMS参数能用但在行人密集的场景里NMS的IOU阈值太高会把相邻的人合并成一个框阈值太低则会产生大量指向同一目标的冗余框反而干扰跟踪器的匹配。这个值需要在验证集上反复试没有固定的“最佳值”。还有一个细节是坐标系的统一。PPYOLO输出的是相对输入图像的坐标而DeepSORT内部习惯使用绝对像素坐标。如果不统一卡尔曼滤波会预测出负数坐标或超出画面范围的框跟踪直接乱掉。我抽了专门时间写了一个坐标转换函数把检测结果统一转成[x1, y1, x2, y2]的整数像素坐标再喂给跟踪器这一步对整个系统的稳定性贡献非常大。3. 实操环境准备、数据集与PPYOLO检测器训练3.1 实验环境清单我的实验环境大概是这样的操作系统Ubuntu 18.04CUDA 10.2或11.2显卡6GB显存起步的中高端游戏卡PaddlePaddle版本2.0以上GPU版依赖库OpenCV、NumPy、scikit-image再加上PaddleDetection自带的工具链。安装PaddlePaddle最烦人的是版本匹配问题。CUDA、cuDNN、PaddlePaddle三者必须对得上否则训练时会出现各种底层报错。我一开始图省事装了CPU版速度慢到完全没法训练后来老老实实重装GPU版才正常。PaddleDetection我强烈建议用源代码方式拉下来而不是只pip安装。因为后面要改配置文件、看训练日志、自定义后处理源码改起来更顺手。装完之后先别急着跑自己的数据先在官方demo上跑通一次确认整个环境没毛病再开始自己的任务。3.2 数据准备、标注与增强行人检测的数据集有两块检测训练集和ReID训练集。检测部分优先用公开的行人检测数据不用从零标注。我们先用公开数据训练出基线模型再在自己的视频里抽帧补充少量难例做微调效果比只用一个公共数据集好很多。PaddleDetection支持COCO和VOC两种标注格式。COCO的标注是JSON每个annotation里有bbox和category_idVOC是XML文件。需要注意类别编号是从0还是从1开始PaddleDetection的配置里“num_classes”写不写背景位一定要和数据集标注对应上不然训练时loss会非常奇怪。数据增强也很关键。PPYOLO配置文件里默认带了一些增强策略比如随机翻转、随机缩放、色域扰动。训练时我额外开了Mosaic增强它能把四张图拼成一张相当于一次看到更多目标对小目标检测帮助很大。代价是显存占用更高batch size要相应缩小。ReID数据同样重要。DeepSORT需要一个能区分不同行人的特征模型通常是在一个包含大量行人ID的数据集上训练分类网络然后取倒数第二层的输出作为特征向量。如果找不到合适的数据集可以用PaddlePaddle里预训练的行人属性模型凑合效果会差一些但能让整个流程先跑起来。3.3 训练PPYOLO的配置与命令PaddleDetection里训练PPYOLO的核心是改yaml配置文件。我以ppyolo_r50vd_dcn_1x_coco.yml为例几个关键修改如下。类别数num_classes改成自己的类别数检测行人时填1数据集路径设置TrainDataset和EvalDataset里的image_dir、anno_path、dataset_dir训练轮数默认是1x即12个epoch数据量不大时可以把学习率调小训练轮数增加到2x甚至3xbatch size与学习率batch size调小时学习率也要等比降低否则梯度更新不稳定。启动命令大概长这样python tools/train.py -c configs/ppyolo/ppyolo_r50vd_dcn_1x_coco.yml \ -o use_gputrue \ --eval加--eval可以让每个epoch在验证集上跑一遍mAP方便观察收敛情况。训练的时候要盯着loss曲线。如果训练开始很久loss都不降多半是数据加载出了问题比如标注坐标越界、图片通道数不对。如果loss缓慢下降说明模型正在正常收敛。我自己踩过的一个坑是6GB显存直接跑完整PPYOLObatch size只能开2训练速度慢到让人崩溃。后来把输入分辨率从608降到416batch size提到4mAP只损失一点但训练时间缩短了一大半。在竞赛场景下这种交换非常划算。3.4 训练效果评估检测器训练完之后不能只盯着mAP。行人跟踪场景里召回率比精确率更值得关注因为漏检会让跟踪轨迹直接断裂。当然召回率也不能拉满否则大量误检框会把跟踪器带偏。我一般同时看mAP和F1分数两个指标都要过线。单帧检测指标和跟踪效果也不是线性关系。有一次我调出来的模型mAP很高但跟踪结果一团糟查了半天发现是小目标召回率不够远处和半遮挡的行人经常检不出来。后来我额外统计了小目标召回率才把问题定位清楚。评估时最有效的办法是把预测结果可视化保存成图片或者视频一帧一帧看。只看数字曲线很难发现NMS阈值和置信度阈值的问题。我在训练后期养成了每个epoch结束都随机抽几个视频片段可视化的习惯模型变化好不好肉眼往往比单点指标更早看出来。4. DeepSORT跟踪器的实现细节4.1 特征提取模型的选择DeepSORT中的ReID特征提取网络要求是速度快、特征区分度强。我一开始试图直接用PPYOLO的backbone提取特征后来发现检测任务训练出的特征对行人ID区分度不够ID切换非常频繁。换成行人重识别任务预训练的轻量网络后情况明显改善。我用的是一个基于MobileNetV3结构的模型输入图像裁剪成128x64输出128维特征向量。这个模型虽然不算大但在跟踪场景里足够用速度也跟得上检测器。PaddlePaddle加载预训练模型很简单import paddle from paddle.vision.models import mobilenet_v3_small model mobilenet_v3_small(pretrainedTrue, num_classes0) model.eval()但要注意输入图像的预处理必须和训练时保持一致尤其是归一化的均值和方差。如果预处理不对特征空间的分布会整个偏移外观匹配基本失效。4.2 跟踪器核心参数与代码骨架DeepSORT的参数看起来不多但每个都会直接影响最终效果。我整理出最核心的几个max_dist最大余弦距离阈值超过则不匹配min_confidence检测框的最低置信度nms_max_overlap检测NMS的IOU阈值max_iou_distance辅助IOU匹配的距离阈值max_age轨迹丢失后的最大保留帧数n_init轨迹被确认前必须连续匹配成功的帧数。我初始配置是max_dist0.2min_confidence0.3max_age30n_init3。这个组合在一般行人场景下表现稳定。核心更新流程用伪代码描述大概是def update(detections): # 1. 预测所有轨迹在当前位置的状态 for track in confirmed_tracks: track.predict() # 2. 计算代价矩阵 cost_matrix compute_cost(tracks, detections) # 3. 级联匹配优先匹配最近更新的轨迹 matches, unmatched_trk, unmatched_det cascade_match(...) # 4. 对未匹配的检测框用IOU匹配补漏 matches, unmatched_trk, unmatched_det iou_match(...) # 5. 更新匹配上的轨迹状态管理未匹配的轨迹 # 6. 用未匹配的检测框创建新轨迹这个骨架看起来不复杂但每一个细节都可能出问题。卡尔曼滤波的误差协方差矩阵初始化不合适预测位置会明显偏移代价矩阵的归一化方式不对匈牙利算法匹配结果也不对。所以我的建议是先跑通一版再一点点调不要一上来就追求完美。4.3 在线关联流程逐步走一遍我用一个实际帧的例子来说明。假设这一帧有三个检测框上一帧有两条已经确认的轨迹。第一步两条轨迹分别通过卡尔曼滤波预测在当前帧的位置得到两个预测框。第二步计算两条轨迹和三个检测框之间的两两距离构成一个2x3的代价矩阵。第三步用匈牙利算法求最优匹配比如轨迹1匹配检测2轨迹2匹配检测3。检测1没有匹配到任何轨迹判定为新出现的目标创建一个新轨迹。第四步匹配成功的轨迹用检测框更新卡尔曼状态并把当前检测框的ReID特征加入到轨迹的特征池里。第五步新轨迹先进入“未确认”状态每连续匹配一帧就累计一次直到连续匹配帧数达到n_init才变成确认轨迹此时ID才稳定输出。如果中途这个人被柱子挡住检测框消失这条轨迹就会进入保留状态max_age逐帧递减。三帧之后重新出现因为外观特征还保留着大概率还能和旧轨迹对上。但如果超过max_age还没出现轨迹被删除人再次出现时就会被分配新的ID。这个生死逻辑就是DeepSORT跟踪的核心。实际项目里的ID切换大部分发生在长时间遮挡后的恢复阶段很少发生在正常行走过程中。所以有一段时间我专门去优化遮挡场景下的轨迹保留策略效果立竿见影。5. 常见问题、调参经验与性能优化5.1 高频问题排查速查表把项目里反复出现的问题整理成一张表后面调起参数来很快。现象可能原因解决思路检测框频繁抖动置信度阈值太低误检框太多调高min_confidence先单独看检测效果ID在遮挡后切换ReID特征区分度不够换成更强的行人重识别模型适当降低max_dist跟踪速度很慢特征提取网络太大或全分辨率推理缩小输入尺寸开启半精度推理轨迹被错误关联NMS阈值过大行人被合并调小NMS的IOU阈值视频开头漏掉目标n_init过高新轨迹确认太慢适当降为2但不能太低检测框超出画面坐标未裁剪卡尔曼预测不受边界约束强制将坐标clip到图像边界这张表是在项目最后几天总结出来的。每次效果不理想先按表里的方向定位而不是拍脑袋乱调参。5.2 参数调整心得想特别说说max_dist。这个参数控制外观相似度的容忍度。设太小了一个人稍微转个身或者分辨率低一点特征余弦距离一超阈值就会被当成新人设太大不同路人之间也可能匹配上。我在密集人群场景里最终取的是0.2到0.35稀疏场景可以放宽到0.5。max_age也不是越大越好。旧轨迹保留时间太长卡尔曼预测框会漂移到离谱的位置反而更容易和其他检测框错误匹配。我自己的经验是max_age设在20到40比较合理配合级联匹配的低优先级才能避免旧轨迹抢占新目标。还有一个容易忽略的组合是n_init和min_confidence的配合。如果min_confidence设太高半遮挡的行人检测不出来新轨迹永远确认不了设太低误检框全被当成目标轨迹全是碎片。我的做法是先单独跑检测器用视频可视化确定一个合理置信度区间再回去调跟踪参数。5.3 性能优化三板斧比赛现场演示最怕卡顿。我们的性能优化思路可以总结成三板斧。第一降低推理分辨率。行人跟踪不需要把1080p的原图全部塞进模型。把输入尺寸缩到720p甚至608x608检测速度能提升将近一倍精度损失完全在可接受范围内。第二使用推理引擎加速。训练完的模型先导出为静态图推理模型再走Paddle Inference或者TensorRT优化比直接调动态图训练模型快很多。如果GPU支持半精度FP16也能带来明显加速但要注意有些算子不一定支持需要逐个benchmark。第三减少重复计算。ReID特征提取可以一次把所有检测框的裁剪图拼成一个batch做一次前向推理而不是每个框都调一次模型。检测框数量多的时候这个优化能省下大量时间。我们最终在演示机器上把输入分辨率控制在608开启半精度后FPS从最初的不到10提升到接近25已经足够流畅。6. 从模型到Demo系统集成与交付细节6.1 整体主循环与模块划分整个系统的集成比想象中费时间。我的主循环框架大致是cap cv2.VideoCapture(input_video) tracker DeepSORT(...) detector PPYOLOInference(...) while cap.isOpened(): ret, frame cap.read() if not ret: break detections detector.predict(frame) tracker.update(detections) results tracker.get_bboxes_and_ids() visualized draw(frame, results) writer.write(visualized)模块划分清楚之后每一步都能单独替换。想换检测器就只改动detector模块想换跟踪策略就只改tracker模块。因为软件杯这种比赛后期需求变动多模块化程度直接决定了修改效率。我在集成时把检测结果转换、跟踪器参数、可视化逻辑都抽成了独立配置项这样换视频、换场景时不用改代码改配置文件就行。这一点强烈建议保留后来评审老师现场提出要看另一个视频我只需要改路径几秒钟就能跑起来。6.2 可视化与输出标准化可视化看着简单实际上有很多细节。框的颜色、ID的位置、轨迹线要不要画都会影响评审观感。我给每个ID分配了一个固定颜色同时画了最近几帧的历史轨迹线。这样即使画面里人很多评委也能一眼看出某个人的运动轨迹。输出标准化同样重要。竞赛通常要求提交特定格式的结果文件比如每一帧的目标坐标和ID列表。我们专门写了一个输出模块把跟踪结果写成了通用的文本格式便于自动评分。这里有一个坑视频帧率要和标注时间轴对齐否则离线评估会错位分数会难看。另外一个容易被忽略的是log日志。集成阶段我加了非常详细的分级日志每一帧检测到几个目标、多少轨迹被更新、多少被新建。排查问题的时候有日志和没日志的效率差距是数量级的。6.3 评审现场的经验评审现场和实验室跑代码是完全两种场景。实验室里可以容忍模型加载慢、参数慢慢调现场不行。我们提前做了一次完整的彩排把模型权重、配置文件、测试视频全部放在一个U盘里确保拔掉外设也能离线跑。还准备了一份精简的启动脚本一键启动Demo不需要打开终端敲命令。这些细节看似琐碎但现场一旦出问题能救命的往往就是这些准备。还有一条经验演示视频不要整段放挑一到两个有代表性的片段最好包含遮挡、人群密集、目标进出的场景。这样既能展示检测效果也能突出DeepSORT的跟踪能力比放一整段平淡无奇的监控录像更有说服力。最后说点个人体会。做这个项目最大的收获不是把PPYOLO和DeepSORT跑通而是真正理解了检测和跟踪之间微妙的配合关系。PPYOLO给过来的检测质量直接决定了DeepSORT的上限但DeepSORT的参数设置又反过来影响最终交付的效果。两段式方案最忌讳的就是把两端割裂开来看。如果你也想做类似的行人跟踪项目建议一开始就盯着漏检率和ID切换这两个综合指标不要过分追求单帧精度。还有一个实用的小技巧所有参数调整都要对着可视化视频来看数字指标再漂亮不如自己的眼睛准。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑