
简介面向监控视频行人检索场景的机器学习应用代码包。借助一张目标图像自动在大量视频中搜索包含该行人的片段并完成轨迹标记适用于安防巡查、人流分析等场景适合具备一定编程与深度学习基础的开发者二次开发。压缩包内共365个文件整体约30.72MB以脚本源码、编译模块、配置文档和预训练模型为主包括Python脚本、JSON配置、模型权重与网络结构文件以及说明文档便于环境搭建与运行参考。目前已有145人学习下载说明该方案具备一定参考价值。通过完整工程可掌握从目标检测、特征提取到轨迹搜索的落地流程并基于自带模型与配置快速复现实验是学习行人重识别与视频检索的实用素材。1. 机器学习在图像行人轨迹搜索里到底解决了什么你把一个“基于 机器学习的图像行人轨迹搜索.zip”拿到手第一反应是想知道里面的模型能不能跑、跑出来的轨迹靠不靠谱。这个标题描述的是一个标准的多目标跟踪任务输入一段连续图像序列比如监控视频按帧导出的图像输出每个行人的唯一ID和随时间变化的位置最终连成一条可搜索的轨迹。它解决的是“这个人在哪出现过、从哪里来到哪里去”的检索问题常见于安防回溯、门店客流统计、自动驾驶行人意图判断。适合的是有Python基础、做过目标检测但没碰过跟踪的工程师以及想从纯图像算法往时序关联方向进阶的开发者。别把它想成一个黑匣子它本质上是“检测关联”两条腿走路。2. 拆解“机器学习图像行人轨迹搜索”的技术栈与数据准备2.1 为什么是“检测跟踪ReID”级联结构而不是一个端到端模型很多人第一次拿到这类项目误以为会有一个“黑匣子”模型丢进去一段视频直接吐出轨迹。真实工程里端到端的检测跟踪一体化模型比如JDE、FairMOT确实存在但训练它们需要稀缺的轨迹级标注数据且对类别的变化非常敏感。你换了一批摄像头就可能要重新标数据、调损失权重。而级联式结构是更接近工业方案的做法一个图像目标检测模型负责找出每帧里所有行人一个跟踪器负责把相邻帧中同一个人的框关联起来必要时再用行人重识别ReID模型提取外观特征解决“同一个人被遮挡后重新出现”的匹配问题。这个结构里的“机器学习”主要指两个地方检测器用的是基于深度学习的目标检测算法ReID特征提取器用的也是深度神经网络。跟踪器本身往往是卡尔曼滤波、匈牙利匹配这类经典算法不重但同样决定轨迹的连续性和稳定性。所以你在压缩包里大概率会看到三类文件检测模型权重、ReID特征网络权重、跟踪器的Python实现。选型逻辑也很直白检测模型要快YOLO系是大多数人的第一选择ReID模型要稳常见做法是用在行人重识别数据集上预训练过的ResNet系列输出一个256维或512维的特征向量跟踪器则优先看你的遮挡场景多不多常见做法是ByteTrack或DeepSORT。如果项目里还有“轨迹搜索”的交互逻辑通常还会有一个字典或数据库按时间戳和ID存储历史位置供后续检索回放。2.2 数据准备从视频抽帧到MOT格式标注做轨迹搜索手头必须有两类数据一类是训练检测器用的图像检测数据一类是评估跟踪器用的轨迹标注数据。很多开源压缩包会自带一个MOT格式的标注集你打开可能看到一堆txt文件。MOT格式是行人多目标跟踪领域最常见的交换格式每一行代表一个目标在这一帧的状态字段顺序是帧序号目标ID目标框左上角x坐标目标框左上角y坐标目标框宽度目标框高度置信度以及三维坐标通常为0。如果你要自己造一份建议直接用视频抽帧脚本再用标注工具比如labelme或LabelImg框人最后写个转换脚本把它转成MOT格式。下面是一个最简单的抽帧脚本用OpenCV把视频按帧保存成jpgimport cv2 import os video_path monitor.mp4 output_dir frames os.makedirs(output_dir, exist_okTrue) cap cv2.VideoCapture(video_path) frame_id 0 while True: ret, frame cap.read() if not ret: break # 按监控场景一般每秒取2到5帧就够避免附近路线上重复太多 if frame_id % 3 0: cv2.imwrite(os.path.join(output_dir, f{frame_id:06d}.jpg), frame) frame_id 1 cap.release()这里的关键参数是抽帧间隔。做轨迹搜索时不需要每一帧都处理监控视频通常25fps按3帧取1帧可以降低计算量同时还能保证相邻两帧的行人位移在可匹配范围内。如果你用来做测试的脚本要求连续帧那就不要跳帧。这个脚本只是帮你建立数据集真正落到搜索任务里帧率与匹配窗口是强相关的。标注完成后需要生成一个类似下面这样的track.txt文件0, 1, 378, 181, 56, 154, 1, -1, -1, -1 0, 2, 512, 204, 48, 148, 1, -1, -1, -1 1, 1, 380, 183, 55, 153, 1, -1, -1, -1第一列是帧号第二列是行人ID。注意同一个ID在不同帧里可以位移但ID不能随意变。这文件就是轨迹搜索的“答案标准”项目里的评估脚本通常会把你的跟踪结果跟它对齐算出准确率。2.3 公开数据集与自采数据怎么取舍如果你不想从零标注公开数据集是首选。多目标跟踪领域有MOT17、MOT20行人重识别领域有Market1501、DukeMTMC。不过DukeMTMC因为涉及个人隐私问题已经不再维护你要么换用其他ReID数据集要么只在内部测试时用。换到真实监控场景时这些数据集训练的模型大概率会“水土不服”原因很简单公开集多来自校园或街拍镜头高度、俯仰角、光照与你实际场景差异很大。我的经验是检测模型用公开集预训练权重起步然后用自采的几百张图微调ReID模型直接用公开权重如果场景里人的外观差异特别大比如都是制服可以再收敛一轮。轨迹评估则一定要用自采视频抽帧标注。因为轨迹搜索的坑不在单帧识别准不准而在跨帧关联稳不稳定这是公开数据集无法覆盖的。3. 复现一个最小可跑的轨迹搜索脚本YOLOByteTrack步骤与参数3.1 环境准备与检测模型推理拿到压缩包后先建一个干净的Python环境安装PyTorch、OpenCV、ultralytics这些基础库。检测部分我用过的最省事方案是ultralytics的YOLOv8当然你也可以换成YOLOX或RT-DETR逻辑差不多。把这代写进一个detect.pyimport cv2 from ultralytics import YOLO # 加载检测模型n是轻量版换成s/m看显存和精度需要 model YOLO(yolov8n.pt) for img_file in sorted(frame_names): frame cv2.imread(img_file) # conf是置信度阈值iou是NMS的IoU阈值 results model(frame, conf0.35, iou0.6, classes[0]) for box in results[0].boxes: x1, y1, x2, y2 box.xyxy.tolist()[0] score float(box.conf) print(img_file, [round(v) for v in (x1, y1, x2, y2)], round(score, 3))这里classes[0]是因为COCO数据集里类别0是person。如果场景中出现的是其他移动目标比如车辆你需要把classes改掉或不做限制。conf0.35表示置信度得分超过0.35的框才留下这个值偏小适用于有遮挡和远距离小目标的监控画面如果误检多可以提到0.5。iou0.6是NMS的去重阈值表示两个框的IoU超过0.6就认为是同一个目标值越大保留的框越多重叠严重的行人容易出现一个框压两个框。3.2 把检测结果喂给ByteTrack做帧间关联轨迹搜索最核心的是帧间关联。常见做法是使用ByteTrack它的思路是先给高置信度检测框分配轨迹再利用低置信度框找回被遮挡的目标对手掌握得比DeepSORT好。搭建跟踪器from bytetrack.byte_tracker import BYTETracker import numpy as np # 参数含义见下方说明 tracker BYTETracker( track_thresh0.35, match_threshold0.8, track_buffer30, frame_rate30 ) track_results [] for frame_idx, img_file in enumerate(sorted(frame_names)): frame cv2.imread(img_file) # 这里复用上一步的YOLO结果 results model(frame, conf0.35, iou0.6, classes[0]) detections [] for box in results[0].boxes: x1, y1, x2, y2 box.xyxy.tolist()[0] score float(box.conf) detections.append(np.array([x1, y1, x2, y2, score], dtypenp.float32)) if len(detections) 0: online_targets tracker.update(np.array(detections)) for t in online_targets: track_id t.track_id x1, y1, x2, y2 t.tlwh[:4] # bytetrack返回的是左上角坐标宽高 track_results.append((frame_idx, track_id, x1, y1, x2, y2))三个参数需要重点说明。track_thresh是检测框进入关联的置信度下限。低于这个值的框不是直接丢弃而是进入第二帧用来跟未匹配上的轨迹做低置信度关联。调到0.4左右时高置信度框占主导轨迹更干净调到0.3能找回更多遮挡后的目标但噪声也会进来。match_threshold是轨迹匹配的IoU下限。连续两帧同一个人的框如果重叠太少说明相机移动快或人运动剧烈你需要把值调低比如0.7静止监控下0.8到0.9都可以。track_buffer是轨迹能“失忆”多少帧。一个人被挡住5秒150帧buffer只有30就永远找不回这个人了会新开一个ID。这个参数决定ID稳定性也影响轨迹碎片化程度。视频帧率30时我一般设60到90。需要注意frame_rate影响卡尔曼滤波器的运动估计时间步长。如果抽帧后实际处理帧率低于视频帧率这里要设成实际送入tracker的帧率否则运动预测会偏快或偏慢。3.3 轨迹录取与“搜一个人”的简单逻辑跟踪器输出的是带ID的框序列但要支持“搜索”你得把这些框按ID聚合成轨迹并且能按ID和时间范围查询。我这里用一个字典来存from collections import defaultdict trajectories defaultdict(list) # id - [(frame_idx, cx, xy)] def add_track(track_result): frame_idx, track_id, x1, y1, w, h track_result cx, cy (x1 w/2), (y1 h/2) trajectories[track_id].append((frame_idx, cx, cy)) def search_trajectory(target_id, start0, end10**9): return [pt for pt in trajectories.get(target_id, []) if start pt[0] end]add_track里的cx, cy是行人框中心点通常用中心点代表位置。如果你要做精细的路径分析也可以用脚底的坐标——框的底边中点更接近人在地面的真实位置因为摄像机的透视会让头顶位置漂移。搜索函数里加start和end是为了支持“某个时间段内他经过哪些地方”的回溯查询实际项目往往还会把结果写入SQLite或PostgreSQL免得视频长了内存爆掉。你可能还会遇到按“图像特征”搜索轨迹的需求比如给目标一个裁好的头像去找他在整个视频里出现的所有片段。这是一个人ReID查询问题先保存每个track中质量最好的一帧特征然后计算目标特征与所有track特征的余弦相似度返回Top-K。这个不在这份最小脚本里但理解了上面按ID查询的逻辑扩展出“按特征查询”只差一个特征库。3.4 小目标行人多尺度推理的一个土办法监控画面里离镜头远的人高度可能只有30像素。YOLO对这类小目标经常漏检导致轨迹断成好几段。一个不做模型改造的土办法是图像金字塔把整帧放大1.5倍再检测一次然后把小框映射回原坐标。虽然推理时间增加但召回率提升明显。scale_boxes [] for scale in [1.0, 1.5]: if scale ! 1.0: resized cv2.resize(frame, None, fxscale, fyscale) results model(resized, conf0.35, iou0.6, classes[0]) for box, cls_conf in zip(results[0].boxes.xyxy, results[0].boxes.conf): x1, y1, x2, y2 box.tolist() x1/scale; y1/scale; x2/scale; y2/scale scale_boxes.append( np.array([x1, y1, x2, y2, float(cls_conf)]) ) else: # 与原尺寸检测结果合并再做一次NMS pass两个scale的检测结果会有大量重复最简单的去重方式是按置信度排序然后把IoU超过0.5的框保留高分那个。这个方案笨一点但能稳定解决中远景行人的漏检问题。更好的做法是加一个专门的小目标检测头或者换用更高分辨率的输入训练但不适合“拿到zip立刻跑通”的阶段。4. 让轨迹搜索更稳四个必调参数与验证指标4.1 检测置信度阈值漏检与误检的跷跷板检测置信度阈值是轨迹搜索的“总闸门”。调得过高远处行人漏检轨迹链断开调得过低墙面纹理、柱子反光都成了人跟踪器被迫为每个误检传播ID。我通常在实景上按三种阈值做快速对比0.3、0.4、0.5然后看一眼轨迹碎片率。碎片率的计算很简单轨迹数目除以视频长度。如果1分钟视频里出现200条轨迹但实际只有50个人那说明大量ID切换多半是阈值太低或外观特征太弱。在光照良好的固定监控下0.4到0.45是平衡点。夜间场景要想CTR敢调到0.7因为误检相对更多低阈值会害死跟踪器。如果一定要低阈值后面要加一个“轨迹长度过滤”轨迹少于3个点就丢弃。这可以避免误检产生的单帧伪轨迹。4.2 匹配阈值ID Switch和Fragmentation的纠缠匹配阈值对应ByteTrack的match_threshold也就是两帧框之间的IoU门槛。它决定一个轨迹愿意“接受”多大的位置变化。门槛太高人稍微加速就跟丢了轨迹分裂门槛太低把旁边另一个人甚至墙上的误检框并进来ID发生“换人”。在固定摄像头下0.8是个稳妥起点。如果视频本身有轻微抖动你可以先把所有框做一次平滑再进跟踪器这样能缓解抖动导致的框偏移不必把match_threshold压到0.7——那可能引入误关联。4.3 轨迹缓冲长度遮挡后还能不能找回同一人行人互相遮挡是轨迹搜索最频繁的翻车点。两个人交错而过其中一人在画面里消失1秒tracker如果提前判了“死亡”回来后就变成新ID。track_buffer这个参数就是允许轨迹在多少帧内没有匹配而不被删除。这里的帧数指的是送入tracker的帧数不是视频总帧率。如果你抽帧跳过一半track_buffer要适当加长。经验公式track_buffer 最大遮挡时长(秒) * 视频帧率 / 抽帧间隔。正常人群密集场景60到90之间表现不错再多轨迹会跟死人“诈尸”的误检产生纠缠。4.4 用MOTA、IDF1、HOTA评估而不是只看检测mAP很多人在验证轨迹搜索效果时还是盯着检测器的mAP看这是不对的。轨迹搜索的评价标准是关联性能常用的三个指标指标关注点对轨迹搜索的意义MOTA漏检、误检、ID切换的综合错误反应整体跟踪准确度IDF1同一ID在不同帧里维持一致性的能力越大说明ID越稳定HOTA检测和关联的平衡得分比MOTA更重视关联质量MOTA更像是“检测关联”的复合分数如果检测框质量差MOTA很难高IDF1只关心ID能不能持续只要一个行人从头到尾是一个ID哪怕框偏一点也能得分HOTA在两个维度上做调和平均。做轨迹搜索项目至少要用MOTA或HOTA来发布结果。如果你调完参数发现MOTA很高但IDF1很低说明检测不错但ID切换太频繁重点查track_buffer和外观特征反过来IDF1很高但MOTA低说明跟踪器在“死守”一个ID漏检其实很多重点查track_thresh和检测模型。5. 避坑行人轨迹搜索项目的5个典型翻车现场5.1 现象两个人并排走其中一人的ID突然跳到另一个人身上原因两个人外观高度相似检测框重叠时跟踪器依赖交叠度做匹配ReID特征在低分辨率下区分度不够。解决提高输入分辨率把行人框裁出来缩放到128x256以上再提特征同时在跟踪器中额外启用外观特征距离只有运动模型和外观模型同时达标才允许匹配不要只看IoU。5.2 现象每个人的轨迹都呈锯齿状路径画出来像毛毛虫原因检测框本身就带抖动中心点随身体摆动左右偏。解决后处理加卡尔曼平滑或简单移动平均。更直接的是用行人脚底点替代中心点作为轨迹点因为脚底在 suelo上的投影比头部稳定。如果你已经有tracker的输出可以每次取框的底部中点(x1w/2, y1h)作为轨迹坐标这样画出来的路径更接近真实走线。5.3 现象两个穿同色衣服的人交叉走过轨迹也交叉互换原因外观特征无法区分“穿白衣黑裤”的两个人运动模型又允许他们短暂重合。解决在交叉时刻降低match_threshold我不建议因为会漏掉更多合法匹配。更靠谱的是启用“轨迹抑制”当两个轨迹靠得极近且交叉后分离出现概率很高的ID switch可以在分离瞬间用ReID特征对两条轨迹做一次重新校验如果特征距离过大但轨迹匹配过于接近标记为可疑回退为两个新ID。5.4 现象离镜头远的人时有时无轨迹断成好几截原因远距离行人像素少检测器直接漏检跟踪器无法连接。解决第一个是前面提到的多尺度推理第二个是检测阈值不要设置太高远距离目标置信度天然低第三个如果场景固定可以在远处区域裁剪出ROI单独放大该区域送入检测器。这些办法叠加后远处轨迹的断裂会明显减少。5.5 现象换了个摄像头准确率断崖下跌原因镜头高度、角度、光照变了检测器和ReID特征都遇到过拟合。解决检测模型最好用大规模数据集预训练过的不要只在自己的数据上训练容易忘掉通用特征ReID模型换用“直方图深度学习特征”融合的方案颜色直方图在跨场景时反而更稳定。实际项目里换摄像头后重新采集30分钟视频做参数验证比硬调模型效果来得快。6. 进阶把轨迹搜索变成可复用的分析工具基础跑通后真正的价值在于把轨迹输出成结构化数据并且能回放、能统计。我习惯把轨迹存成JSON再加上一个简单的可视化脚本这样别人拿到你的算法结果时不用重新跑模型就能看。轨迹导出代码可以很简洁import json output [] for tid, points in trajectories.items(): # 过滤掉太短的轨迹通常少于5帧的点没有分析价值 if len(points) 5: continue output.append({ id: tid, start_frame: min(p[0] for p in points), end_frame: max(p[0] for p in points), path: [{frame: f, x: x, y: y} for f, x, y in points] }) with open(trajectories.json, w) as fp: json.dump(output, fp, indent2)再配合OpenCV把每个ID的轨迹线画到原图上用不同颜色区分就能快速检查调参效果。另一个常用功能是“按时间切窗口画热力图”把画面划分成网格统计每个网格里轨迹点的出现次数用色阶表示密集区域。这个热力图层叠到视频上可以直接看出人流走线、常驻区对门店选品或安检布点很有用。轨迹搜索项目做到这一步剩下就是工程化问题了换用数据库存储轨迹、加入多摄像头时间同步、把检测和跟踪拆成独立进程分布式跑。最后按老规矩给你的轨迹加上置信度字段这样任何一条轨迹都能被下游任务追溯。我的习惯是在所有追踪结果里保留检测的置信度和特征向量宁可多写几行也不删。这些看似冗余的字段在以后做轨迹复现和数据清洗时是真的后悔药。这次调完我最大的教训是跟踪器不是越复杂越好监控场景里先把检测置信度和track_buffer调稳再考虑上ReID和深度学习特征融合。顺序搞反了你会被ID Switch折磨一整周。希望这篇笔记能帮你把这个“图像行人轨迹搜索”的压缩包真正跑成一套能交差的系统。本文还有配套的精品资源点击获取