
简介这份资源是一套基于YOLO11与DeepSORT的目标跟踪算法实战项目适合有一定目标检测基础、希望系统掌握检测与多目标跟踪链路搭建的开发者。项目将YOLO11作为检测前端DeepSORT负责轨迹关联与身份匹配覆盖从模型权重加载、预处理到视频推理输出的完整流程。资源共54个文件以Python脚本29个py、编译生成的pyc文件15个、说明文档3个md、模型权重2个pt、测试视频2个mp4、配置文件yaml与授权文件license为主压缩包大小约27.92MB。配套内容包含可运行的track.py主程序、utils工具模块、weights模型权重及deepsort配置目录便于直接调试与二次开发。目前已有462人学习下载适合课程设计、算法调研或工程实践参考。1. 把检测和跟踪串起来YOLO11DeepSORT这个组合到底能干什么做视频目标分析的人十有八九都卡在同一个问题上检测器只能给单帧画框一旦物体动起来、被挡住、或者离开画面再回来框就丢了。YOLO11DeepSORT这个项目组合解决的就是从“每帧框在哪”到“这个物体从哪来、到哪去、是谁”的跨越。YOLO11负责单帧检测DeepSORT负责跨帧关联给每个目标分配一个稳定ID顺便输出轨迹。这个方案特别适合做车流统计、行人计数、安防监控、甚至体育赛事分析——凡是需要知道“谁在什么时间出现在什么位置”的场景都能直接套用。新手拿到这套代码先跑通再改自己的数据是最快的落地路径熟手则可以把注意力集中在检测与跟踪的接口改造和参数调优上。下面从选型逻辑开始逐步拆到可以照着复现的程度。2. 为什么选 YOLO11 做检测、DeepSORT 做关联先想清楚任务分工2.1 检测与跟踪的分工逐帧画框不算跟踪很多刚接触目标跟踪的人第一反应是“我每帧用YOLO检测一次然后把框连起来不就行了”。这在静止画面、目标少、无遮挡的情况下确实能跑但只要目标一交叉、一遮挡ID立刻全乱。因为检测器没有任何“记忆”它不知道这一帧的框跟上一帧的框是不是同一个物体。跟踪器存在的意义就是给检测结果做时间上的关联用运动信息预测目标下一帧的位置用外观特征确认“这是同一张脸/同一辆车”。YOLO11在单帧上的检测精度再高也替代不了这个时序逻辑。反过来DeepSORT没有检测能力它只能接收检测框。所以两者是天然的上下游关系检测器输出每帧的边界框、类别、置信度跟踪器拿到这些框结合历史轨迹输出带ID的轨迹集合。这个分工决定了整个项目的代码结构也决定了调试时该分别看哪一端的日志。2.2 YOLO11 相比 YOLOv8/YOLOv5 在跟踪场景里的升级点YOLO11 是 ultralytics 在 YOLOv8 之后推出的检测模型网络结构上最大的变化是主干里引入了 C3k2 模块——你可以把它理解为 C3 和 C2f 的融合变体用更少的参数量保留多尺度特征表达能力。颈部继续沿用 PAN-FPN 结构但把上采样和拼接后的卷积做了细化。对跟踪场景而言YOLO11 的价值不在于那 1%~2% 的 mAP 提升而是小目标检测更稳了。视频中远处的行人、车辆往往只占几十个像素YOLO11 的 anchor-free 检测头和新的特征融合策略对这一类目标的召回率有实打实的提升。召回率上去了DeepSORT 的输入框就更连续不会因为目标时有时无导致 ID 频繁切换。另一个实际优势是部署性。YOLO11 有 n/s/m/l/x 五个规格其中 YOLO11n 在 CPU 上也能跑到接近实时的速度对于不打算上 GPU 的监控项目非常友好。而且 ultralytics 官方包同时支持检测、分割、姿态跟 DeepSORT 对接时只需要取results[0].boxes接口统一少踩很多版本坑。2.3 DeepSORT 的关联逻辑级联匹配、马氏距离、外观特征各起什么作用DeepSORT 的核心理念是“先易后难”的关联策略。每次处理一帧时它先对每个已有轨迹用卡尔曼滤波预测下一时刻的位置得到预测框然后计算预测框与当前检测框之间的两个距离一个是 IoU 距离或者叫运动距离另一个是外观特征之间的余弦距离。级联匹配是 DeepSORT 最精华的一步——它按照轨迹的年龄连续未匹配帧数从新到旧排序优先匹配刚出现不久的新轨迹后匹配长期未被确认的旧轨迹。这样做的原因很现实新轨迹的不确定度低预测位置更可信旧轨迹可能已经丢失很久预测框漂移严重如果跟它们先匹配反而会引入噪声。马氏距离负责把“位置和速度的相似度”拉进同一个量纲它会考虑卡尔曼滤波的协方差矩阵所以能自动对不同方向上的不确定性做加权。外观特征则是用一个独立的 ReID 模型提取目标的外观向量通常是 128 维或 512 维用余弦距离判断“长得像不像”。两者用加权公式合并成一个总距离distance lambda * 马氏距离 (1 - lambda) * 余弦距离其中lambda默认取 0意思是几乎完全信任外观特征。这个细节很反直觉但在行人跟踪上确实比纯运动模型更稳。2.4 什么场景该选 YOLO11DeepSORT什么场景该换 BoT-SORT / ByteTrackYOLO11DeepSORT 最舒服的场景是目标类别相对固定行人、车辆、目标之间外观差异明显、遮挡不是特别频繁、帧率能保证在 10 FPS 以上。典型如十字路口车流统计、商场客流分析、园区安防。因为 DeepSORT 的外观特征能扛住短期遮挡只要目标重新出现时露出的外观足够分辨ID 就能找回来。但如果你的视频是拥挤人群、目标密集且互相遮挡严重DeepSORT 的级联匹配就容易翻车。这时候常见做法是换 ByteTrack——它不做外观特征纯靠运动检测置信度做关联反而在密集场景更稳而且极快。或者上 BoT-SORT它在 ByteTrack 基础上融合了相机运动补偿和更鲁棒的关联策略。还有一类场景是无人机的俯视视角目标很小、移动快DeepSORT 的卡尔曼滤波可能跟不上建议先用 YOLO11 把小目标召回率拉起来再换 SORT 系算法。也就是说YOLO11DeepSORT 不是万能组合但它是第一个值得你跑通并理解关联流程的基线方案。跑通之后你再往别的跟踪器迁移会发现接口几乎都能复用因为大家都在谈 detection、track、match 这三件事。3. 环境配置与项目结构从零跑通 YOLO11DeepSORT 的流程教程3.1 创建虚拟环境并安装 YOLO11 依赖拿到项目源码后第一步不是打开 IDE 读代码而是先复现出能跑的环境。YOLO11 的依赖比 DeepSORT 干净得多核心就是一个 ultralytics 包。建议用 Python 3.9 或 3.10避免 3.12 上一些旧版编译包缺失的兼容问题。# 创建独立虚拟环境避免污染系统 Python conda create -n yolo11_track python3.10 -y conda activate yolo11_track # 安装 PyTorch先装 CPU 版跑通流程再换 CUDA 版 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 YOLO11 依赖ultralytics 会自行匹配配套版本 pip install ultralytics pip install opencv-python这里有个经验不要一开始就pip install -r requirements.txt有些项目里锁定的版本过旧或过新容易跟当前 Python 不匹配。先装好 ultralytics再用import ultralytics; ultralytics.checks()验证版本再回头看项目自带 requirements缺什么补什么。CUDA 版本建议跟随你的显卡驱动用nvidia-smi看支持的 CUDA 版本然后去 PyTorch 官网选对应安装命令。3.2 DeepSORT 依赖py-torchreid、scipy、lapDeepSORT 本身不是独立 pip 包项目里通常直接把deep_sort_pytorch源码目录拿过来用。它的依赖中有两个容易踩坑的库scipy用于线性分配linear_sum_assignmentlap用于提速的 Jonker-Volgenant 算法。lap在 Windows 上没有官方 wheel经常编译失败常见做法是换成lapx或者直接用scipy.optimize.linear_sum_assignment兜底。# DeepSORT 核心依赖 pip install scipy1.6 pip install lap # 如果失败执行下面两行兜底 pip install lapx另外外观特征提取需要 ReID 模型项目里常见的做法是使用torchreid包训练好的 ResNet50 特征提取器。这个包比较老安装后先做一次冒烟测试python -c import torchreid; print(torchreid.__version__)如果报错No module named torchreid检查项目是否自带deep_sort_pytorch/deep_sort/deep/下的模型定义很多项目已经把这个子模块内嵌了不需要额外装 torchreid。3.3 下载权重与测试视频跑通第一个推理命令大多数 YOLO11DeepSORT 项目会自带一个yolov11n.pt或要求你从 ultralytics 下载。测试视频建议先用项目自带的示例视频如果没带随便找一个行人过马路的公开视频即可。跑通的最小命令通常是这样的python main.py --source test.mp4 --yolo-weights yolov11n.pt --reid-weights weights/osnet_x1_0.pth --conf 0.4 --save-video具体参数名因项目而异但核心逻辑一致给一个视频源加载检测权重和 ReID 权重输出标注后的视频。如果项目提供的是track.py那输入参数多半是--source和--yolo-model先用python track.py --help看一遍再跑。跑通后你会看到终端每隔几帧打印跟踪结果包括目标 ID、类别、置信度、坐标。这一步的意义是验证整个链路——摄像头读取、检测、特征提取、关联、可视化——都通着。3.4 项目文件结构说明detector、tracker、utils 各司其职YOLO11DeepSORT 的源码包结构大同小异理解文件职责比记住路径重要。典型结构如下目录/文件职责关键模块detector/封装 YOLO11 检测器YOLODetector 类返回 boxes、scores、classesdeep_sort/DeepSORT 跟踪器tracker.py、nn_matching.py、linear_assignment.pydeep_sort/deep/ReID 特征提取feature_extractor.py加载模型输出 512 维向量tracker/或main.py主流程调用检测器转换格式更新 tracker可视化utils/画框、轨迹、统计工具draw_bboxes.py、io_utils.pyweights/存放模型权重yolov11n.pt、osnet_x1_0.pth调试时最常改动的是main.py里的检测结果转 tracking 输入的代码以及deep_sort/deep_sort.py里的 tracker 参数初始化。建议第一次跑通之前不要改任何代码只改路径。跑通后再逐行读主流程你会发现所谓的“接入”其实只有二十几行核心代码。4. 把 YOLO11 接到 DeepSORT 上核心代码改造与 4 个必调参数4.1 将 YOLO11 检测结果转为 DeepSORT 的 detections 输入DeepSORT 的Detection数据结构需要四样东西左上角坐标、宽高、置信度、外观特征。YOLO11 输出的是xyxy左上右下坐标所以要先换算成xywh再构造Detection对象。下面是完整的最小转换代码import numpy as np from deep_sort.deep_sort.detection import Detection from deep_sort.deep_sort.tracker import Tracker from deep_sort.deep_sort import nn_matching from deep_sort.deep_sort.deep.reid_model import ReIDModel # 初始化 ReID 模型 reid_model ReIDModel(weights/osnet_x1_0.pth, devicecuda) # 初始化跟踪器 max_cos_dist 0.4 metric nn_matching.NearestNeighborDistanceMetric(cosine, max_cos_dist) tracker Tracker(metric, max_iou_distance0.7, max_age70, n_init5) def yolo11_to_deepsort_detections(boxes_xyxy, scores, classes): 将 YOLO11 检测结果转为 DeepSORT 的 Detection 列表 detections [] for box, score, cls in zip(boxes_xyxy, scores, classes): x1, y1, x2, y2 box w, h x2 - x1, y2 - y1 # YOLO11 的 xyxy 转 DeepSORT 需要的 xywh bbox_xywh [x1, y1, w, h] # 提取外观特征维度 512 crop frame[int(y1):int(y2), int(x1):int(x2)] # 对太小的裁剪块保护 if crop.size 0 or w 2 or h 2: continue feature reid_model.extract(crop) detections.append(Detection(bbox_xywh, score, feature, cls)) return detections # 每帧调用示例 boxes_xyxy, scores, classes yol11_detector.detect(frame) detections yolo11_to_deepsort_detections(boxes_xyxy, scores, classes) tracker.predict() # 卡尔曼预测 tracker.update(detections) # 关联更新 for track in tracker.tracks: if track.is_confirmed() and track.time_since_update 1: x1, y1, w, h track.to_tlwh() track_id track.track_id这段代码里的ReIDModel和yol11_detector是项目里已有的封装重点理解Detection的四个参数顺序(bbox_xywh, score, feature, cls)。注意cls不是 DeepSORT 原生参数而是很多项目为了后续按类别过滤自己加的如果项目没改过这里要去掉。4.2 外观特征提取ReID 模型的接入与维度对齐DeepSORT 对特征维度没有硬性要求但它内部做余弦距离时需要特征向量长度一致。常见项目用的 ResNet50 输出 512 维OSNet 输出 512 维MobileNet 输出 128 维。如果接入时报维度错误多半是两个模块之间的feature_dim没对齐。# nn_matching 里初始化距离度量时指定维度 nn_matching.NearestNeighborDistanceMetric(cosine, max_cos_dist, max_feature_dim512) # 特征提取后必须 L2 归一化 feature feature / np.linalg.norm(feature)归一化这步很容易忽略。DeepSORT 的余弦距离计算会默认输入已经归一化如果没做距离范围就不是 [0,2]max_cos_dist的阈值就会失真。另外ReID 模型的输入尺寸是 64x128宽高YOLO11 的检测框裁剪出来是任意尺寸所以裁剪后要先resize到 64x128还要做一次 padding 保持比例否则特征质量会崩。4.3 参数调优max_dist、max_iou_distance、max_age、n_init这四个参数直接决定跟踪的“性格”。max_dist即 max_cos_dist是外观匹配阈值越大越容易匹配上但代价是误匹配多max_iou_distance是运动匹配的 IoU 阈值默认 0.7 表示 IoU 小于 0.3 就算不匹配max_age是轨迹失联后保留多少帧太大导致 ID 切换变慢、计算量增加太小导致短暂遮挡后轨迹直接消失n_init是轨迹要连续匹配多少帧才确认默认 5值越大误报越少但确认越慢。我一般在车辆跟踪项目上这样设max_dist0.3因为车辆颜色形状差异大阈值严一点不容易串 ID行人项目设0.5因为行人外观相似度高太严会频繁新建轨迹。max_iou_distance固定 0.7这个是 DeepSORT 作者的默认值经验上不太需要改。max_age根据遮挡时长来定行人最多被挡 3~5 秒按 25 FPS 算就是 75~125取 70~100 比较稳车辆在十字路口被大车遮挡可能长达 10 秒这时要设到 200 以上否则过路口就换 ID。tracker Tracker( metric, max_iou_distance0.7, max_age100, n_init3 )n_init我这里改成 3因为测试视频里目标在画面边缘一闪而过时连续出现 3 帧就应该认成轨迹5 帧太保守了。如果你的场景误检多反而要加大到 8~10避免把误检也当成轨迹确认。4.4 跟踪 ID 跳变与丢失如何用置信度阈值过滤跟踪效果差一半原因不在跟踪器而在检测器输出的置信度阈值。YOLO11 默认conf0.25在低光照、运动模糊视频里会产生大量低置信度框。这些框进 DeepSORT 后会跟真实目标抢匹配名额导致 ID 跳变。常见做法是调高到 0.4~0.5宁可漏检少数目标也不要让噪声进入关联流程。results self.model(frame, conf0.45, iou0.5, classes[0, 2]) # 只留人/车另一个技巧是按类别设置动态阈值行人容易被遮挡导致置信度波动阈值可以低一点车辆在远处很小阈值要更高才能过滤树影误检。你可以在yolo11_to_deepsort_detections里按cls做分级过滤而不是统一阈值。还有一点YOLO11 的iou参数默认 0.7NMS 时框之间重叠越多越容易留下靠前的框这会导致密集人群里的检测框抖动建议调成 0.5让相邻目标各归各框。5. 避坑YOLO11DeepSORT 实战中的 5 个高频翻车现场5.1 现象装了 ultralytics 后 import deep_sort 仍然报 ModuleNotFoundError这是几乎每个人都遇到过的坑。原因是项目源码里的deep_sort_pytorch目录没有加入 Python 路径或者你把代码克隆到中文路径下导致某些老库的导入逻辑异常。还有一个隐蔽原因你 pip 安装了另一个同名deep-sort包跟项目自带的源码目录重名导入时去了 site-packages。解决办法分三步走先确认项目根目录有deep_sort文件夹并在主脚本开头显式把根目录加入sys.pathimport sys, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))再查一下有没有安装多余的同名包pip list | grep deep有就pip uninstall。最后把整个项目移到纯英文路径比如D:/yolo11_deepsort/不要放桌面或带中文的用户目录下。这一步能顺带解决 ReID 模型权重文件读取失败的问题。5.2 现象GPU 显存占用高但 FPS 只有个位数新手最容易犯的错是抱着 YOLO11 检测器每帧做全图推理还开着最大的yolov11x权重再加上 ReID 特征提取又开一遍前向两个模型在 GPU 上来回切换时间全花在调度上了。真正的瓶颈通常在特征提取DeepSORT 默认每个检测框都要过一遍 ReID 网络行人多时一次可能几十个框每个都要前向比检测还慢。解决方法是把特征提取从 GPU 挪到 CPU 上跑或者把批量剪裁框攒起来只做一次前向。实践里我常用的是固定最大特征提取数当检测框数量超过 20 个时只取置信度最高的 20 个来提取特征其余直接丢弃低置信度框。另一个更有效的手段是换更轻的 ReID 模型比如osnet_x0_25或 MobileNetV2精度下降不到 2%FPS 能翻倍。# 用半精度推理显存减半速度提升显著 python main.py --fp16 --yolo-weights yolov11n.pt如果项目原本不支持--fp16可以在检测代码里加model.half()并把输入图像转成frame frame.half()。注意 CPU 上不能开半精度只有 CUDA 支持。5.3 现象跟踪 ID 频繁切换同一个目标框旁边的编号一直在变ID 切换的原因要分两类排查。第一类是检测框本身不稳定目标在移动时 YOLO11 的框会抖导致同一目标的中心点位移超过卡尔曼预测范围匹配失败。这时可以给检测框加时间平滑用指数移动平均来稳定框的位置# 对每个 track 的 bbox 做 EMA 平滑 alpha 0.7 track.smooth_bbox alpha * track.smooth_bbox (1 - alpha) * new_bbox第二类是外观特征太弱。光照变化、目标转身、分辨率低都会让 ReID 特征漂移。常见办法是给每个 track 保存最近 100 帧的特征集合匹配时用最小余弦距离而不是只比最后一帧的特征。DeepSORT 源码里的NearestNeighborDistanceMetric默认就支持这个你只需要调大budget参数metric nn_matching.NearestNeighborDistanceMetric( cosine, max_cos_dist, budget100 )budget是每个轨迹保留的特征数量上限默认是 100。如果你发现 ID 切换问题严重可以改到 150但要注意内存占用。5.4 现象密集场景下目标框乱跳互相抢 IDDeepSORT 在人群密集时的最大软肋是 IoU 关联会在目标紧挨着时把框错配给旁边的人。现象就是两个目标走近后出来时编号互换。这不是参数能根治的而是算法的固有局限。能做的缓解手段有三个第一个是把max_iou_distance从 0.7 调小到 0.5迫使关联更严格第二个是开启 DeepSORT 的“马氏距离门控”让预测位置离谱的候选直接不被匹配第三个是降低 ReID 特征距离阈值只允许“非常像”的目标互相匹配。如果这都不行就该考虑换跟踪器了。上面提过 ByteTrack 在密集场景更强尤其是它对低置信度检测框采用了 second association能保留更多可能的匹配。YOLO11ByteTrack 的代码结构与 DeepSORT 几乎一样只是把匹配部分换成了 BYTETracker。5.5 现象YOLO11 权重无法加载报错提示模型结构不匹配这个坑大多发生在你下了旧版 YOLOv8 的.pt文件然后改文件名为yolov11n.pt试图骗过代码。ultralytics 在加载时会检查模型 YAML 配置版本不一致就直接报错。另外权重文件的路径里如果有特殊字符比如#或中文也会被 torch 的torch.load拒绝。解决方法是直接从 ultralytics 官方模型仓库下载对应版本from ultralytics import YOLO model YOLO(yolov11n.pt) # 会自动下载官方权重 model YOLO(yolov11s.pt)下载完成后用model.predict(sourcebus.jpg)做一次单图验证。能跑完说明权重没问题。如果你的项目是改过的 YOLO11 结构比如加了小目标增强模块那必须使用项目提供配套的权重文件不能用官方原版替代。这属于特殊定制场景调权重时一定要核对项目 README 里写的自定义结构说明。6. 用 MOTA、IDF1 和轨迹可视化来验证跟踪效果我从菜鸟到老手的实操习惯跟踪做完了不能只在输出视频上“看着差不多”。要说服自己或别人这套 YOLO11DeepSORT 方案可靠至少要从三个维度验证身份保持能力、检测召回率、轨迹平滑度。先看身份保持对应的是 MOTA 和 IDF1 两个指标。MOTA 综合了漏检、误检和 ID 切换但它对 ID 切换的惩罚没有单独列出IDF1 则是只看 ID 匹配正确率能更直观反映“目标是不是老换号”。计算这两个指标需要真实标注框和预测跟踪轨迹做匹配公开数据集标准做法是用一个叫py-motmetrics的库import motmetrics as mm acc mm.MOTAccumulator(auto_idTrue) # 每一帧gt_ids 为真实目标IDpred_ids 为跟踪IDious 为两两IoU矩阵 for frame_idx in range(total_frames): acc.update( gt_ids[frame_idx], pred_ids[frame_idx], ious[frame_idx] ) mh mm.metrics.create() summary mh.compute(acc, metrics[num_switches, idf1, mota, motp])你的真实标注如果没有就先人工标几十帧。我一般在项目里留一个validation/video1_gt.txt格式是frame_id, track_id, x, y, w, h。这是做跟踪绕不开的活别嫌烦。其次是轨迹可视化。OpenCV 画轨迹是个简单但极有用的调试工具给每个 ID 维护一个 deque 存最近 30 帧中心点然后cv2.polylines连接这样能直接看到轨迹是否平滑、是否在遮挡时跳飞。我习惯把轨迹的透明度和颜色与 ID 绑定同一个 ID 颜色不变轨迹线逐渐变淡看起来更直观。import cv2, collections trajectory collections.defaultdict(lambda: collections.deque(maxlen30)) for track in tracker.tracks: if not track.is_confirmed(): continue tid track.track_id x1, y1, w, h track.to_tlwh() cx, cy int(x1 w/2), int(y1 h/2) trajectory[tid].append((cx, cy)) # 画最近轨迹线 for i in range(1, len(trajectory[tid])): alpha i / len(trajectory[tid]) color (int(255*alpha), 0, 255-int(255*alpha)) cv2.line(frame, trajectory[tid][i-1], trajectory[tid][i], color, 2)最后说一个我踩过的大坑验证时用了训练视频的同一段。跟踪模型很容易在“见过的画面”上表现虚高一旦换新场景ID 切换率立刻翻倍。正确的习惯是留出独立的一周采集数据作为验证集跑完指标后把max_dist、max_age再微调一轮直到MOTA和IDF1都稳定。我自己做行人跟踪项目时第一版 IDF1 只有 42%调完budget150和conf0.45后涨到 63%。这种提升不是靠算法创新而是靠把上面这几个参数和验证流程老老实实走一遍。希望这套流程对你也有同样的帮助。本文还有配套的精品资源点击获取