资讯详情

资讯详情

图像行人轨迹搜索:从目标检测到卡尔曼滤波的完整技术解析

简介这是一份面向计算机视觉与监控视频分析场景的机器学习工程包输入一张目标图像即可从大量视频中筛选出包含该对象的片段并自动标记实现行人轨迹搜索适合有一定深度学习基础、希望直接运行或二次开发的读者。资源基于 Python、TensorFlow-GPU 与 Keras 构建整个压缩包共 365 个文件大小约 30.72MB。文件组成以 JSON 配置、Python 源码及 pyc 编译文件为主大量 JSON 用于保存模型元数据与运行参数py 脚本为可直接修改的源码pyc 为预编译模块同时提供模型权重、图片示例、说明文档与 sqlite 数据持久化文件目录划分清晰便于按用途取用。已有 145 人学习下载。压缩包内含已训练好的检测模型配置与权重、完整可调用脚本及运行说明可帮助使用者快速跑通从目标检测到轨迹关联的全流程并在此基础上调整模型参数、替换自有数据适合作为行人重识别与视频检索项目的参考。1. 基于机器学习的图像行人轨迹搜索把“找相似图”变成“还原路径”接到一个标题叫“基于机器学习的图像行人轨迹搜索”的项目压缩包时很容易被名字误导以为核心工作是图像检索框住一个人去库里找相似画面。实际上监控视频抽帧出来的连续图像里单纯做相似度匹配根本得不出轨迹。真正要做的是三段式任务先靠检测模型在每一帧里定位行人再靠跟踪关联把帧间的同一个行人连成路径最后把路径组织成按时间、区域、轨迹形状可查询的索引。轨迹一旦还原出来才能回答“这个人在什么时间段走过哪条路线”。适合做这件事的人通常是做安防监控画面分析、商场人流动线统计或者拿连续图像批量做行人运动数据集的研究者。这个方向值得投入因为它把几个机器学习子任务串成了一个能落地的闭环。2. 轨迹搜索到底在搜什么和行人重识别的区别与机器学习应用流程2.1 先分清两件事搜索运动记录和搜索身份轨迹搜索这个名词最容易和行人重识别Person ReID混在一起。行人重识别做的是“跨画面身份匹配”给出一张目标图在另外一路摄像机里找出同一个人的照片而行人轨迹搜索做的是“路线还原”输入连续图像序列输出同一个行人在时间轴上的坐标序列。两者并不冲突反而是上下游关系重识别特征可以作为轨迹关联的辅助信号轨迹连成之后跨镜头的身份匹配才有更完整的上下文。我从实际需求出发一般会先问对方一个问题你拿到的输入是连续抽帧还是几张不连续照片只有不连续照片时能做的是“找同一个人”做不到“搜轨迹”只有连续图像才能支撑轨迹关联。这个项目标题里同时出现“图像”和“行人轨迹搜索”侧重点显然是后者输入是图像序列输出是轨迹结果。如果一开始就把目标定成图像检索后面做出来的系统只会在“找相似图”的层面打转根本没法回答“这个人在某个时间段去了哪里”这样的业务问题。2.2 机器学习应用流程里常见的三种实现架构在这个方向的项目里最常见实现方案有三条可以先按自己手里的算力和场景选一条开始第一种是检测器加运动关联。用YOLO这类轻量检测器输出候选框再靠框与框的重叠关系IOU和卡尔曼滤波做帧间关联。优点速度快、依赖少单镜头人少时效果够好。缺点密集或遮挡场景里ID容易互相错配。第二种是检测加重识别再加关联。检测器照常输出框另加一个重识别模型给每个框提取外观特征向量匹配时同时看两个信号运动预测与检测框的距离、外观特征之间的余弦距离。这就是DeepSORT系跟踪器的思路也是我在复现这类项目时最常采用的基线。原因很实际它把“视觉相似”和“运动连续”拆开出问题时能明确知道是哪一个环节出错。第三种是端到端统一输出用可学习的跟踪模块把检测和关联放在同一个网络里输出。这种方案精度理论上最高但训练数据要求高、部署成本大在中小型项目里性价比不高。我通常的做法是先按第二种搭一个能跑通闭环的版本再决定要不要向下换成轻量方案。不管选哪种机器学习应用流程本身都固定是三段式特征提取检测、重识别→ 帧间关联运动推断与匹配→ 结果输出轨迹存储、搜索。把这三段打通之后调参才有意义。这里有一个选型判断表可以参考画面里同时出现的人数少于20人且机位固定用第一种画面密度高、遮挡频繁直接上第二种有离线训练资源和大量同一场景数据才考虑第三种。2.3 最小命令把压缩包里的测试画面跑出一条轨迹假设已经拿到了一个包含模型权重、依赖文件和测试抽帧的压缩包常见做法是按这套顺序快速跑通unzip trajectory_search.zip -d trajectory_search_proj cd trajectory_search_proj python -m venv venv source venv/bin/activate pip install -r requirements.txt python run_search.py --input_dir data/sample_frames --output_dir output/tracks --conf 0.5run_search.py一般由四段组成读帧、检测、关联、写轨迹输出目录里会生成一个按track_id组织的 CSV 或 JSON。--input_dir只接受图像帧目录如果拿到的数据还是原始视频就先要有一个抽帧脚本把视频切成按序号命名的帧--conf是检测置信度只在关联前过滤低分框不能指望把它当作轨迹的开关--output_dir指向输出目录程序会按track_id分文件保存。第一次跑不通时九成问题出在路径而不是模型权重路径写死、数据目录里放的是视频而非图片、pip 依赖版本冲突。一个习惯是先让主程序把每个输入输出路径打印出来确认“它到底在读哪一帧”。先把一条轨迹画出来哪怕它是断的、错的也好过盯着黑匣子猜。这个“端到端先出图再回头调参”的次序是我在机器学习应用落地里比较笃定的习惯。3. 检测器选型与卡尔曼滤波算法参数轨迹能否连起来关键看这一环3.1 实时图像场景里检测器应该选 YOLO 系还是两阶段网络图像行人轨迹搜索对检测部分的实时性要求比普通图像分类高得多。监控抽帧往往要求帧率不低于 10 FPS不然轨迹很容易断。两阶段检测器在小目标、密集人群场景下精度更好但单帧耗时普遍偏高YOLO 系检测器在视频流的实时图像场景里部署更方便对行人这类中大型目标效果足够。选型判断我一般按三个条件卡第一检测帧率要明显高于抽帧帧率留出至少 30% 到 50% 的余量防止排队积压。第二行人类别在验证集上的 AP 要高于 0.8如果模型库里的“person”类 AP 很差说明训练数据里的行人形态与你的画面差异过大换模型不如先做图像预处理。第三部署机的显存要提前量好。一个 YOLOv8n 大约占 1GB 左右显存但加上 ReID 分支后显存会明显上涨一定要在接完整链路之前测一次峰值占用。3.2 检测框质量置信度、NMS 和低对比度画面的预处理检测输出并不直接等于跟踪器的输入。连续两帧之间同一个人的框中心应该只移动很小距离如果框来回跳动跟踪器会被迫频繁开关轨迹。因此我会在检测后做一个轻量处理流程先滤掉低于conf的框再对重叠的高分框做 NMS最后用上一帧的卡尔曼预测框对这个检测框做一点平滑。这里的平滑系数如果偏向当前检测框更贴合但不稳定偏向历史轨迹则更平滑但快速走动的人会拖出一条“尾巴”。监控画面里常见的低对比度问题是另一大坑。我在处理偏暗画面时第一件事不是换检测权重而是先做一次图像增强比如 CLAHE。这个操作很短import cv2 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) frame_gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) frame_enhanced clahe.apply(frame_gray) frame cv2.cvtColor(frame_enhanced, cv2.COLOR_GRAY2BGR)clipLimit控制对比度拉伸强度设得太大容易出现噪声块tileGridSize决定局部块大小8×8 适合 1280×720 的监控帧画面更大时可以换到 16×16。把增强后的帧再送检测器往往比换一个更深的模型权重来得划算。如果画面来源是多路监控并且想做统一检测图像拼接也是一个路子但拼接缝附近的行人框容易被切断先做全景拼接再检测时要在拼接矩阵上保留原始坐标映射不然输出轨迹坐标无法映射回原镜头。3.3 卡尔曼滤波在图像行人轨迹搜索里的状态设计与调参方向帧间关联的“运动预测”部分最常见做法是卡尔曼滤波。按 DeepSORT 的表达习惯状态向量常写成(x, y, w, h, vx, vy)x、y是框中心点坐标w、h是宽高vx、vy是中心点速度。每一帧做三步用上一帧的状态预测当前位置把预测位置和此刻检测框做匹配匹配成功就更新状态。没有检测框命中时预测值可以当作“外推”的临时轨迹点但外推次数不能太多否则轨迹会越飘越远。调参上直接改配置里两个值过程噪声 Q 和测量噪声 R。Q 越大表示模型越不相信匀速运动假设会更快地倒向实时测量结果R 越大表示越不相信检测框精度轨迹更平滑。实际调参时Q 和 R 的比值比绝对值重要。经验做法是先按默认值跑一遍观察轨迹是贴合还是滞后如果贴合得太紧导致框抖动明显就把 Q 调小或把 R 调大如果检测框移动时轨迹迟迟不跟就反向调整。这套调参在噪声数据比较严重的监控画面上尤其玄学同一个参数组换一段光线不同的视频结果可能完全不一样所以每次只动一个变量。一个简化的状态更新代码可以帮助理解这个直觉class KalmanBox: def __init__(self, x, y, w, h): self.x, self.y, self.w, self.h x, y, w, h self.vx self.vy 0.0 self.gain 0.6 def predict(self, dt): self.x self.vx * dt self.y self.vy * dt def update(self, zx, zy, zw, zh): self.x self.gain * (zx - self.x) self.y self.gain * (zy - self.y) self.w self.gain * (zw - self.w) self.h self.gain * (zh - self.h) self.vx (zx - self.x) * 0.5 self.vy (zy - self.y) * 0.5这个类只是教学级简化版本完整实现还要维护协方差矩阵。gain表示更新步长小则平滑、大则跟手。predict里的dt如果恒等 1就等于默认等间隔抽帧如果帧率波动要把真实时间戳差异传进来。在完整工程里手感相同的配置项就是前面说的 Q 和 R调整路径也是先看轨迹漂移量再决定方向。另外卡尔曼滤波不是唯一解。如果检测器稳定性足够好纯 IOU 匹配也可以而且能避免一部分错配。像 ByteTrack 这类跟踪器会把低置信度框也保留下来参与匹配减少漏检导致的断裂在轨迹搜索项目里值得试一下不要一上来就把卡尔曼当成必选组件。4. 把帧间候选框变成轨迹关联、存储与轨迹检索的实现4.1 运动距离与外观特征加权匹配时到底在看什么在图像行人轨迹搜索中单独用 IOU 匹配在密集场景里几乎一定会出错。IOU 只看两个框的重叠面积两个相邻行人的框重合度高的时候会被错误连成同一轨迹。更常见的做法是使用两种互补的度量运动模型距离比如马氏距离和外观特征距离ReID 向量的余弦距离。马氏距离会把“预测位置与观测位置的偏差”按协方差归一化避免大框和小框用欧式距离时天然不公平余弦距离则适合比较特征向量方向。最终匹配得分是两者的加权和。我通常先让运动距离权重占大头跑完看结果再在出现错跟的场景里提高外观权重。怎么判断错跟看可视化轨迹是否在交叉点处交换颜色这是最直观的信号。这里要留意级联匹配的排序逻辑。连续匹配越久、已经稳定存在的轨迹优先匹配刚出现的轨迹放到后面避免“老轨迹被新检测抢走”。这个顺序改不得一旦反过来短暂遮挡后重新出现的老人会被当成新人轨迹一片一片地碎。4.2 轨迹的存储结构、时间戳和区域检索轨迹在运行期以track_id为主键每一条轨迹是一个有序列表元素是(frame_id, x, y, w, h, ts)。其中frame_id用抽帧顺序ts用真实时间戳。如果后续要按时间段和区域搜索我一般会把轨迹落成一张 SQLite 表字段包含track_id、ts、x、y、w、h、region_id。这样能用数据库索引完成查询找出上午十点到十一点之间经过某个区域的人。这比在内存里人工过滤可靠得多也方便前端按track_id回放整段轨迹。一个按时间和区域检索的示例如下SELECT track_id, COUNT(*) AS point_cnt, MIN(ts) AS first_ts, MAX(ts) AS last_ts FROM track_points WHERE ts BETWEEN ? AND ? AND region_id ? GROUP BY track_id HAVING point_cnt 5 ORDER BY first_ts;GROUP BY track_id把每段轨迹的行数、首末时间统计出来HAVING point_cnt 5是为了滤掉点太少、无法构成路线的碎片轨迹。region_id可以预先用检测框中心点落到哪个区域来打标。数据量再大一些时SQLite 的 RTree 索引可以用来做空间范围查询但前期用字段过滤足够支撑原型验证。注意时间戳统一存成毫秒整数避免字符串比较时出现排序错乱。4.3 关联主循环里先调好的五个跟踪参数主循环里的关联参数直接影响轨迹质量我一般把它们放在一个配置块里集中管理。下面的起点值以 1080p 监控、约每 500ms 抽一帧为参照参数含义起点值调节方向conf_thres检测置信度下限0.5 - 0.6漏检多就调低误检多就调高max_dist预测框与检测框匹配的最大像素距离60 - 100画面宽或人移动快就调大track_max_age轨迹在丢失后最多保持的帧数30人流量大时调小防止 ID 堆积n_init新轨迹需要连续命中几帧才确认3 - 5想更快出轨迹就调小但碎片会变多max_cosine_dist外观特征余弦距离阈值0.3 - 0.5多镜头或外观多变场景适当放宽这些参数没有绝对正确值。以画面宽度为参照1920 宽的帧max_dist取 60 到 100 比较稳1280 宽的帧取 40 到 60。人走得快距离阈值就要放大否则下一帧检测框已经在匹配半径之外轨迹必然断。排查顺序比参数本身更重要。轨迹一断就去调track_max_age是很多人都踩过的坑。真正原因往往是conf_thres太高导致跟踪器连续好几帧没有观测可用。建议先看检测日志里每帧的行人框数量如果框数量本身剧烈波动先修检测再动跟踪。4.4 轨迹相似度搜索当输入是“一段路径”时怎么搜有些场景不按时间区域搜而是按轨迹形状搜比如找“从 A 区绕圈走到 B 区的人”。此时不能直接把两条不同长度的轨迹逐点相减常见做法是用动态时间规划DTW计算距离。两条轨迹长度可以不同慢走和快走只要路线相近距离就会小。坐标归一化也重要将x、y分别除以画面宽高避免不同分辨率下距离数值失真。一个最简实现如下def dtw_dist(a, b): import numpy as np n, m len(a), len(b) d np.zeros((n 1, m 1)) d[:, 0] np.inf d[0, :] np.inf d[0, 0] 0 for i in range(1, n 1): for j in range(1, m 1): cost np.linalg.norm(np.array(a[i - 1]) - np.array(b[j - 1])) d[i, j] cost min(d[i - 1, j], d[i, j - 1], d[i - 1, j - 1]) return d[n, m]a和b是两条轨迹的坐标点列表进入函数前已经各自归一化。这个算法的时间复杂度是 O(n*m)对离线搜索足够实时在线搜轨迹时要先降采样或改用近似距离。轨迹搜索项目里这个函数是处理“按形状找路径”需求时最实用的一招。5. 真实监控画面里容易翻车的四个坑现象、原因、处理我在复现类似项目时发现真正让结果崩掉的往往不是模型本身而是几个具体的工程细节。下面四条都来自监控画面的实际场景按“现象、原因、处理”的次序写出来可以当排障清单用。5.1 多镜头或云台切换后同一个人被重开了一条轨迹现象画面切到另一路摄像机或云台转动后刚才的track_id不见了镜头转回来后同一个人又多了一个新 ID。原因相邻两帧的画面内容发生突变ReID 特征距离瞬间超过阈值。固定机位里人的特征变化是渐进的镜头切换会把它变成跳变跟踪器没有“世界坐标”概念自然认为这是一个新目标。处理在做抽帧序列时记录镜头切换帧。检测到两帧图像差异过大时主动把现存轨迹标记为“待匹配”状态切换后尝试用外观特征重新接上旧 ID接不上的再开新 ID。像海康威视监控录像导出后的序列建议先按录像分段不要跨段匹配能显著减少 ID 跳变。5.2 两个人穿同色衣服并行行走时轨迹互串现象两个人并排从画面中走过来中途交叉之后他们的轨迹发生 ID 互换可视化图上颜色在交叉点交换。原因外观特征区分度不够。身高、衣服颜色接近时余弦距离很容易低于内部阈值匹配算法在交叉处“看走眼”。这类错误属于比较典型的噪声数据问题ReID 训练集里很难覆盖所有穿同色衣服的成对样本。处理交叉瞬间历史速度方向比外观颜色更可信把运动距离权重调高即可。再加一个保护逻辑两条轨迹在连续若干帧内距离过近、相对速度又接近时禁止交叉后的 ID 跳变。实现上用各自当前速度外推判断交叉后哪个框更可能属于原轨迹再执行唯一匹配。5.3 抽帧帧率波动导致卡尔曼滤波外推偏差现象服务器负载高时抽帧延迟变大某两帧间隔接近两秒预测框位置和实际框差出大半个画面匹配失败轨迹中断。原因大多数跟踪实现把状态转移里的dt当成常量 1抽帧节奏变了以后状态转移矩阵没有按真实时间变化等于用“上一秒的速度”去预测“两秒后的位置”。处理把抽帧的原始时间戳传入predict。当连续两帧间隔超过阈值比如抽帧间隔 500ms 时阈值设为 800ms先不让这条轨迹参与新帧匹配而是等待避免把另一个行人误配进来。这条在输入是图像序列而非原始视频时尤其重要因为图像序列的“帧间时间”完全取决于抽帧脚本。5.4 低对比度、低亮度画面漏检轨迹断裂现象光线不足的监控画面上行人轮廓几乎与背景融为一体检测器连续几帧没有输出框跟踪器只能保持一段预测轨迹最终因为超过track_max_age而作废。原因检测器训练数据大多来自光照较好的公开数据集弱光环境下直接喂原始帧相当于做了一个分布偏移。处理送检测器之前先做 CLAHE 或低光增强效果不明显再叠加去雾、直方图拉伸。检测阈值可以适当降到 0.4 附近并结合“断帧插值”某track_id缺失一到两帧时用前两帧位置线性补点等检测回来再对齐。这类预处理应该做成插入式模块不要让轨迹搜索主流程变复杂。6. 用可视化验证轨迹搜索并把 MOTA 当成最终接受标准6.1 先画轨迹图再谈参数跑完主流程以后我第一件事不是看指标而是把所有轨迹画在同一张图上按track_id着色。这一步能直接暴露断头和错跟位置import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(output/tracks.csv) for tid, g in df.groupby(track_id): plt.plot(g[x], g[y], lw2, labelfID-{tid}) plt.gca().invert_yaxis() plt.legend(fontsize7) plt.savefig(tracks_preview.png)这段代码假设 CSV 里有track_id、x、y三列。invert_yaxis是因为图像坐标的 y 轴向下不反转看起来会上下颠倒。看见交叉点上两条曲线互换颜色就说明发生了 ID 切换看见轨迹在某个区域密集终止去查那几帧的检测输出通常是漏检而不是跟踪器问题。6.2 MOTA 指标的计算口径可视化说清了“长什么样”数值指标则是最终接受标准。MOTA 把三件事折算成一个分数漏检FN、误检FP、ID 切换IDSW公式是MOTA 1 - (FN FP IDSW) / GT其中 GT 是人工标注的真值框总数。MOTA 越高整体轨迹越靠谱。不要只看检测 AP轨迹搜索项目里一次 ID 切换带来的业务损失往往比单帧框偏几个像素大得多。改参数前记录一次 MOTA 基线改完后再跑同一批抽帧数据比较才能区分“检测器改进”和“跟踪器改进”各自贡献了多少。以前我总把时间花在提升检测精度上后来发现在监控画面里一次 ID 切换造成的轨迹污染远比单帧误检要贵。真正值得投入的是跟踪参数和预处理这个顺序希望在轨迹搜索项目里能帮你少走些弯路希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →