卡尔曼滤波单目标跟踪:遮挡重叠场景的CPU工程实现

发布时间:2026/11/2 5:15:05

卡尔曼滤波单目标跟踪:遮挡重叠场景的CPU工程实现 简介围绕遮挡与重叠场景下的单目标跟踪这份项目以卡尔曼滤波为核心将状态预测与观测更新结合缓解最大IOU匹配在目标短暂不可见时容易丢失的问题。它面向具备一定计算机视觉与机器学习基础、希望理解传统滤波器如何与检测结果配合的学习者可作为课程设计、算法复现或小型实验的参考。压缩包共338个文件约25.87MB以327个txt标签与帧数据文件为主辅以2个Python实现脚本、4个xml配置、2个pyc缓存及1个mp4测试视频目录中保留算法文件、辅助工具、标签数据与测试视频等模块。已有514人学习下载。读者可从中获得卡尔曼跟踪的预测—更新流程、IOU匹配思路、标签组织方式与测试视频验证路径并借此观察遮挡后重新找回目标和平滑跟踪噪声的实现细节适合作为深入目标跟踪与视觉应用的学习起点。1. 从一段地下车库监控说起遮挡之后跟踪框为什么总断车库里有根承重柱目标车辆每次经过柱子检测器就漏一两帧出来时位置已经偏了小半个车身两辆车并排交汇时检测框还会被合并成一个。这类场景对单目标跟踪最不友好也是卡尔曼滤波最擅长处理的地方它不指望每帧都有准确观测而是先按匀速模型预测再拿检测框作为观测来修正中间漏几帧就靠预测支撑。这套「基于卡尔曼滤波的遮挡与重叠环境下的单目标跟踪器」本质上就是一条工业界常见的跟踪基线流程检测器给候选框卡尔曼滤波器给预测框遮挡打分决定这一帧信谁。适合两类人想在 CPU 上跑一个不掉帧的目标跟踪模块的工程师以及刚接触计算机视觉、想弄明白 SORT 系列为什么能作为多目标跟踪常青基线的人。它属于人工智能里偏工程的那一侧机器学习模型只出现在检测环节跟踪本身是纯数值运算。2. 技术选型与整体架构为什么是卡尔曼滤波而不是相关滤波或深度孪生网络先把边界说清楚这是一个跟踪器不是检测器。它的输入是「每帧的候选框 置信度」输出是「带稳定 ID 的框序列」。检测环节可以换任何模型跟踪环节不动。这种拆分在整个计算机视觉工程里非常常见因为检测模型迭代速度快而跟踪逻辑一旦写死成端到端网络换检测器就得重训。选卡尔曼滤波而不是 KCF、CSRT 这类相关滤波或者 SiamFC 这类孪生网络原因很实际。相关滤波在目标被完全遮挡时会把背景学进模板遮挡结束后基本救不回来孪生网络精度好但没有 GPU 的盒子跑 1080p 只有个位数帧率。卡尔曼滤波则是纯矩阵运算8 维状态一次 predict 加 update 大概几十微秒遮挡期间「什么都不做、只用预测撑住」这个能力是它天然具备的。代价是它不懂外观一旦目标彻底消失再出现或者两个目标重叠后分开必须靠外部信号检测置信度、外观直方图、运动一致性来重新认领。2.1 状态建模为什么用中心点加宽高的 8 维向量框的表示有两种常见写法左上右下(x1, y1, x2, y2)和中心宽高(cx, cy, w, h)。跟踪里我一般用后者因为位置和尺寸在运动上是解耦的——目标平移时中心点变、宽高基本不变目标靠近镜头时宽高变、中心点缓慢移动。用(cx, cy, w, h)建模噪声协方差矩阵的对角线才有明确的物理含义调参时知道自己在调什么。状态向量取 8 维[cx, cy, w, h, vcx, vcy, vw, vh]。后四个是前四个的一阶导数也就是每帧的速度。观测向量只有 4 维[cx, cy, w, h]因为检测器给不出速度——速度是滤波器自己推断出来的隐变量。这个「状态维度 8、观测维度 4」的不对称是整个实现里最容易让人困惑的地方观测矩阵 H 不是单位阵而是一个 4×8 的「抽取位置和尺寸」的矩阵。为什么不用 6 维只给中心点加速度、宽高当常量因为目标在靠近或远离相机时宽高变化是真实存在的强行当常量会让 R 矩阵被撑大位置精度跟着下降。8 维多出来的计算量可以忽略。2.2 观测从哪来检测器、IoU 门控与外观特征的三角关系跟踪器每帧会收到一批检测框。单目标场景下最简单的做法是取置信度最高的那个但这在有干扰物同款车、路人时会被带偏。合格的做法是做一次数据关联把上一帧所有轨迹的预测框和这一帧的检测框算 IoU构成代价矩阵用匈牙利算法求最优匹配。门控gate是这里的关键。IoU 低于阈值常见 0.3的匹配直接判为无效宁可让这条轨迹「这一帧没有观测」也不要把一个远处的检测框硬塞进来。塞错的代价比漏观测大得多——漏观测只是让协方差矩阵 P 变大下一帧预测变模糊塞错则是直接把状态向量拉偏而且这个偏差会通过速度项持续传播。外观特征只在重叠场景下启用。做法是给每条轨迹维护一个 HSV 颜色直方图当两个轨迹的预测框 IoU 很高说明重叠了、而检测框只有一个时用直方图的巴氏距离判断这个检测框更属于谁。这一层是兜底不是主力。2.3 技术栈与运行形态numpy 加 OpenCV纯 CPU 也能跑依赖清单很短numpy做矩阵运算opencv-python读写视频和画框scipy提供匈牙利算法scipy.optimize.linear_sum_assignmentpyyaml读配置。检测器部分留了适配层实际用哪个由使用者决定。这个组合的优点是部署简单。整个跟踪器是一个纯 Python 包没有编译依赖塞进树莓派或者工控机都能跑。如果对延迟敏感把卡尔曼那几十行用 C 重写或者用 numba 加速都行但通常不是瓶颈——瓶颈在检测器。运行形态是「逐帧流式处理」读一帧、检测、跟踪、画框、写一帧。没有全局状态依赖所以天然支持断点续跑和分段处理。3. 核心模块拆解8 维状态向量、遮挡判定与 ID 交接的完整实现这一章把代码摊开。整个包大约六七百行真正决定效果的是三个文件kalman.py、occlusion.py、manager.py。目录结构长这样kf-sot/ ├── configs/ │ ├── base.yaml # 通用阈值 │ └── garage.yaml # 某个场景的覆盖配置 ├── tracker/ │ ├── kalman.py # KalmanBoxTrackerpredict / update │ ├── occlusion.py # 遮挡打分与面积突变检测 │ ├── associate.py # IoU 代价矩阵 匈牙利匹配 │ ├── manager.py # 轨迹生命周期状态机 │ └── appearance.py # HSV 直方图特征重叠时兜底 ├── detectors/ │ ├── base.py # 检测器统一接口 │ └── yolo_wrapper.py # 任意检测器的适配层 ├── io/ │ ├── video_reader.py │ └── overlay.py # 画框、画轨迹尾巴、写结果视频 ├── eval/ │ ├── mot_eval.py # MOTA / IDF1 计算 │ └── slice_occlusion.py # 按遮挡时长切片段单独统计 ├── run_track.py # 主入口 └── requirements.txttracker是纯算法detectors和io是外围eval单独拎出来是因为调参必须靠量化指标靠肉眼看视频永远调不准。3.1 状态预测predict 与 8×8 协方差矩阵的传播先看卡尔曼类的前半部分构造和预测。# tracker/kalman.py import numpy as np class KalmanBoxTracker: 单目标卡尔曼跟踪器。 状态 x [cx, cy, w, h, vcx, vcy, vw, vh]^T 观测 z [cx, cy, w, h]^T def __init__(self, bbox_xyxy, q_scale1e-2, r_scale1e-1): cx, cy, w, h self._to_cxcywh(bbox_xyxy) # 初始速度给 0目标刚出现时我对它的运动方向一无所知 self.x np.array([cx, cy, w, h, 0., 0., 0., 0.], dtypenp.float64).reshape(8, 1) # 状态转移矩阵位置 速度 * dt这里 dt 固定为 1 帧 self.F np.eye(8) self.F[0, 4] self.F[1, 5] self.F[2, 6] self.F[3, 7] 1.0 # 观测矩阵只能看到位置和尺寸看不到速度 self.H np.eye(4, 8) self.P np.eye(8) * 10.0 # 先验协方差初始给大表示不确定 self.Q np.eye(8) * q_scale # 过程噪声运动模型有多不可信 self.R np.eye(4) * r_scale # 观测噪声检测框有多不准 self.age 0 self.time_since_update 0 self.occluded False self.history [] def predict(self): # 尺寸不能为负这一行是长序列不崩的关键 if self.x[2, 0] self.x[6, 0] 1.0: self.x[6, 0] 0.0 if self.x[3, 0] self.x[7, 0] 1.0: self.x[7, 0] 0.0 self.x self.F self.x # 状态外推 self.P self.F self.P self.F.T self.Q # 协方差外推 self.age 1 self.time_since_update 1 self.history.append(self.x[:4].flatten().copy()) return self.to_xyxy()F是一个稀疏的单位阵加四个 1含义就是「位置这一帧等于上一帧位置加一次速度」。P的传播是F P F^T Q其中Q那一项很重要没有它协方差会随着迭代单调收缩滤波器会越来越自信最终完全无视新的观测这就是常见的「滤波器僵死」。Q给多大合适q_scale1e-2是个稳妥起点目标运动越不规律急刹、变向就给越大。_to_cxcywh和to_xyxy是两个静态转换方法实现简单把(x1,y1,x2,y2)转成中心点加宽高再转回来注意宽高至少为 1避免后面除零。time_since_update这个计数器贯穿全局它为 0 表示这一帧有观测大于 0 表示已经连续多少帧只靠预测。轨迹管理器的所有超时判断都基于它。3.2 观测更新update 里的卡尔曼增益到底在纠正什么预测之后如果有匹配上的检测框就调用update。def update(self, bbox_xyxy): z np.array(self._to_cxcywh(bbox_xyxy), dtypenp.float64).reshape(4, 1) # 新息检测框和预测框差了多少 y z - self.H self.x # 新息协方差预测不确定性 观测不确定性 S self.H self.P self.H.T self.R # 卡尔曼增益该信预测多少、该信观测多少 K self.P self.H.T np.linalg.inv(S) self.x self.x K y # Joseph 形式更新协方差保证 P 长期保持对称正定 I_KH np.eye(8) - K self.H self.P I_KH self.P I_KH.T K self.R K.T self.time_since_update 0 self.occluded False staticmethod def _to_cxcywh(b): x1, y1, x2, y2 b return (x1 x2) / 2.0, (y1 y2) / 2.0, max(x2 - x1, 1.0), max(y2 - y1, 1.0) def to_xyxy(self): cx, cy, w, h self.x[:4, 0] w, h max(w, 1.0), max(h, 1.0) return [cx - w / 2, cy - h / 2, cx w / 2, cy h / 2]核心就三行算新息y、算增益K、更新状态。K的物理意义是「预测协方差占总不确定性的比例」。当连续多帧没有观测P被Q一点点撑大K就趋近于 1意味着滤波器几乎完全接受新的观测当观测很密集P被压小K就趋近于 0滤波器更信自己的预测。这个自适应过程不需要任何手工规则。用 Joseph 形式更新协方差而不是直接(I-KH)P是个血泪经验。浮点运算下I-KH很快就不再对称几百帧后P会出现负特征值然后增益变成 NaN跟踪框直接消失。Joseph 形式多算一次矩阵乘法代价可以忽略但长序列稳定性差一个数量级。R的取值决定了跟踪框的「跟随性」。r_scale大跟踪框平滑但滞后目标急转弯时明显跟不上小则跟随紧但抖动大。0.1 是 1080p、25fps 下的经验值。3.3 遮挡与重叠判定三个信号一起投票单独一个信号判断遮挡都不可靠所以这里做加法投票。# tracker/occlusion.py def occlusion_score(track, det_score, iou_with_others, area_ratio): 三个信号投票返回 0~1 的遮挡程度。 det_score : 这一帧匹配到的检测框置信度 iou_with_others: 该轨迹预测框与其他轨迹预测框的最大 IoU area_ratio : 本帧观测面积 / 历史平均面积 s 0.0 if det_score 0.35: # 检测器自己都没把握 s 0.50 if iou_with_others 0.45: # 与别的轨迹严重重叠 s 0.30 if area_ratio 0.6 or area_ratio 1.8: # 面积突变多半是被切了一刀 s 0.20 return min(s, 1.0)三个阈值分别对应三类现场。检测置信度低于 0.35说明目标可能已经被部分遮挡检测器只能看到半截与其他轨迹的 IoU 超过 0.45说明发生了重叠两个框互相咬住了面积比偏离 1 太多说明框被截断遮挡或者被误扩背景被吞进来。加权分别是 0.5、0.3、0.2不是拍脑袋置信度是最直接的一手信息重叠是几何推断面积突变只是间接证据。三项全中就是 1.0此时轨迹管理器会把这个轨迹标记为occluded同时把Q临时放大三倍——因为目标被遮挡时运动模型最不可信让滤波器更快地接受下一帧的观测。提示面积比要用「历史滑动平均」而不是「上一帧」单帧的面积抖动在检测器上很常见拿上一帧当基准会频繁误判。3.4 轨迹生命周期三条状态线与一次 ID 交接manager.py是调度中心负责把预测、关联、更新、回收串起来。# tracker/manager.py from scipy.optimize import linear_sum_assignment import numpy as np class TrackManager: def __init__(self, max_age30, n_init3, gate_iou0.3, occ_thr0.5, q_scale1e-2, r_scale1e-1): self.tracks [] self.max_age max_age self.n_init n_init self.gate_iou gate_iou self.occ_thr occ_thr self.q_scale q_scale self.r_scale r_scale self._next_id 1 def step(self, dets, scores, frameNone): # 1) 全部轨迹先外推一帧 preds [t.predict() for t in self.tracks] # 2) 构造 IoU 代价矩阵并门控 cost np.zeros((len(self.tracks), len(dets))) for i, pb in enumerate(preds): for j, db in enumerate(dets): v iou(pb, db) cost[i, j] v if v self.gate_iou else 1e5 matched, unmatched_dets [], list(range(len(dets))) if len(self.tracks) and len(dets): rows, cols linear_sum_assignment(cost) for r, c in zip(rows, cols): if cost[r, c] 1e5: # 门控通过才算匹配 matched.append((r, c)) unmatched_dets.remove(c) # 3) 命中轨迹走 update未命中只累积 time_since_update for r, c in matched: self.tracks[r].update(dets[c]) # 4) 新检测起轨迹连续 n_init 帧才转正 for c in unmatched_dets: t KalmanBoxTracker(dets[c], self.q_scale, self.r_scale) t.id, t.hits self._next_id, 1 t.state TENTATIVE self._next_id 1 self.tracks.append(t) # 5) 超过 max_age 的轨迹回收释放 ID self.tracks [t for t in self.tracks if t.time_since_update self.max_age] return [t for t in self.tracks if t.state CONFIRMED]三个状态TENTATIVE刚出现、还没确认、CONFIRMED稳定跟踪中、LOST超过 1 帧没观测但还没到max_age。n_init3表示连续 3 帧命中才转正这条规则能挡掉大量由噪声引起的虚假轨迹。max_age30是遮挡容忍度的核心参数。25fps 下 30 帧大约 1.2 秒足够穿过一根柱子。但给太大也有副作用目标真正离开画面后轨迹还会在画面上「幽灵」式地飘一秒钟。过路口这种场景我一般收到 15 到 20。门控用1e5而不是inf是为了让匈牙利算法在无解时仍然给出一个确定解避免linear_sum_assignment在某些版本上报错。判断时用cost[r, c] 1e5反向过滤等价于「低于门限的匹配」。4. 环境搭建与跑通从依赖安装到在自己的视频上出第一帧跟踪框这一节保证你能跑起来。整个流程在纯 CPU 的笔记本上大概十分钟能走完。4.1 依赖清单与目录落位python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install numpy opencv-python scipy pyyaml # 检测器按需装不装也能跑仓库里带了一个基于帧差法的假检测器用于自测 pip install onnxruntime # 走 ONNX 导出的检测模型时用依赖之间没有版本强耦合numpy1.21、opencv-python4.5就行。onnxruntime是可选的只有在接真实检测模型时才需要。仓库里那个帧差法检测器虽然精度很差但足以验证跟踪链路是否通畅——先跑通链路再接模型比一上来就接模型要快得多。4.2 配置文件阈值全部外置别写死在代码里# configs/base.yaml video: samples/garage.mp4 output: out/garage_tracked.mp4 detector: fakedet # fakedet / onnx max_age: 20 # 丢失后最多靠预测撑多少帧 n_init: 3 # 连续命中几帧才确认成一条轨迹 occ_thr: 0.5 # 遮挡打分超过多少判定为遮挡 gate_iou: 0.3 # 关联时 IoU 低于此值直接拒绝 recover_iou: 0.2 # 遮挡恢复后重新认领的最低 IoU q_scale: 0.01 r_scale: 0.10 draw_tail: 30 # 轨迹尾巴长度0 表示不画garage.yaml只需要写和base.yaml不一样的那几项加载时做一次浅合并。项目里所有可调参数都在这里代码里不出现魔法数字——这不是洁癖是因为调参时你会反复改这六七个值散落在代码里就是灾难。detector字段用工厂模式分发detectors/base.py定义了统一接口infer(frame) - (boxes, scores)新增检测器只要实现这个接口再注册进去。4.3 跑通第一段视频与前 100 帧自检python run_track.py --config configs/garage.yaml跑完先别急着看整段视频看前三秒。我的习惯是导出前 100 帧的 CSV手工检查三件事每帧是否都有且只有一条CONFIRMED轨迹框的宽高是否稳定在 ±10% 以内ID 有没有中途变化。这三条过了再看长序列。# eval/quick_check.py import pandas as pd def sanity(csv_path, tol0.10): df pd.read_csv(csv_path) df[w] df[x2] - df[x1] df[h] df[y2] - df[y1] drift (df[w].std() / df[w].mean()) print(f宽高波动比: {drift:.3f}) # 大于 0.15 基本说明有问题 print(fID 变更次数: {df[id].nunique() - 1}) print(f空帧数: {df[x1].isna().sum()})宽高波动比是最灵敏的健康指标。正常情况下同一个目标在几十帧内面积变化很小波动比超过 0.15 意味着滤波器在跟噪声赛跑。ID 变更次数在目标没出画的片段里应该是 0不是 0 就说明关联的门控太松或太紧。4.4 指标评估MOTA、IDF1 与遮挡片段单独切片整体指标会掩盖遮挡段的问题。一个跟踪器在无遮挡段跑 0.9 的 MOTA、在遮挡段跑 0.4平均下来还是 0.8看不出毛病。所以eval/slice_occlusion.py会按真值里的遮挡标注把序列切成「无遮挡 / 短遮挡1 到 10 帧/ 长遮挡大于 10 帧」三段分别算指标。指标含义本项目关注点MOTA漏检、误检、ID 切换的综合错误率整体不低于 0.6IDF1ID 保持一致性遮挡恢复段最关键MT被跟踪时长占比 80% 的轨迹比例衡量长时稳定性Frag轨迹断裂次数长遮挡段最敏感调参的优先级是 IDF1 Frag MOTA。MOTA 里漏检占了很大权重而漏检主要由检测器决定跟踪器改不动IDF1 和 Frag 才是跟踪逻辑的真实成绩单。5. 避坑与排查跟踪框漂移、ID 跳变、预测发散的五个高频现场下面五条都是实际跑视频时踩出来的每条按现场表现、原因和修法写。5.1 跟踪框越跑越大最后吞掉半个画面现象视频跑到一两百帧后框的宽高缓慢增长最终覆盖整个画面但目标还在框里。原因predict里对速度项没有约束vw、vh因为连续几帧的检测框抖动被推成正数之后每帧按w vw累加。卡尔曼滤波本身是线性系统一旦速度项被噪声推偏没有任何机制把它拉回来只会靠观测一点点修而观测的修正量又被过小的R压制了。解决在predict开头加尺寸非负钳制3.1 节那段代码同时给vw、vh设一个硬上限比如不超过当前宽高的 5%。另一个更根本的做法是把状态降到 6 维去掉宽高速度把宽高当慢变量处理牺牲一点尺寸响应换取稳定性。两种做法我都用过场景里目标尺度的变化幅度小于 30% 时6 维更省心。5.2 遮挡恢复后 ID 变了还多出一条幽灵轨迹现象目标穿过柱子出来时框还在但 ID 从 3 变成 7画面上短暂出现两个交织的框。原因max_age设得太小目标在柱子后面的帧数超过了它旧轨迹被回收了回收后新检测框直接起了新轨迹。而旧轨迹被回收的瞬间如果没有立刻清掉画布上的缓存还会残留几帧。解决先把max_age按「最长遮挡帧数 × 1.5」估一个值。更稳的做法是回收时不立刻删转成LOST状态放进一个待匹配池新出现的轨迹先跟这个池子做一次外观特征比对HSV 直方图巴氏距离小于 0.25 就认领原 ID匹配上就复活原轨迹并把速度项清零。这一层加完车库场景的 ID 保持率能明显改善。5.3 跟踪框整个飞出去坐标变成 NaN 或者负数爆炸现象某一帧之后框跑到画面外面打印状态向量全是nan。原因协方差矩阵P在几百帧迭代后失去正定性np.linalg.inv(S)算出病态解甚至除零。直接写P (I - K H) P的实现在浮点误差下几乎必然出现这个问题只是时间早晚。解决换成 Joseph 形式更新3.2 节代码这是根本解法。另外加一道保险update之后检查np.isfinite(self.x).all()为假就重置这个跟踪器。还有一个隐蔽的坑——S接近奇异时inv不报错但结果巨大可以在求逆前判断np.linalg.cond(S) 1e8就跳过本帧更新。注意这类数值问题在短序列上测不出来一定要用至少 2000 帧的视频跑一遍这是最容易漏掉的验证。5.4 两辆车交汇时被合成一个框分开后再也拆不开现象重叠期间只剩一条轨迹检测框是两者的并集分开后跟踪框跟错了车。原因检测器在重叠时给出的是合并框update直接用这个合并框去更新状态把宽高拉大了一倍。等两车分开检测框正常了但历史平均面积已经被污染面积比判据失效。解决在update之前做一道面积突变拦截——当本帧观测面积超过历史滑动平均的 1.8 倍时判定为「合并框」降低这次观测的权重把R临时放大 10 倍甚至直接跳过更新只保留预测。同时启用外观特征在合并帧用直方图判断这个框的主体更接近哪条轨迹。5.5 帧率从 25 掉到 6但 GPU 占用是 0现象跟踪器本身很快但整体处理帧率极低nvidia-smi显示 GPU 空闲。原因瓶颈在视频 IO。cv2.VideoWriter用了默认编码器写 1080p或者读帧之后做了多余的cvtColor拷贝。还有一个常见原因是每帧都重新 new 了一个VideoWriter。解决VideoWriter在循环外创建一次编码用mp4v或者直接写无损的中间文件后期再转读帧用cv2.CAP_PROP_BUFFERSIZE设 1 减少缓冲。性能分析用cProfile跑一遍八成的时间会落在 IO 而不是算法上——算法这边一次 predict 加 update 的耗时就几十微秒。6. 二次开发把检测器换成自己的模型并让跟踪器在重叠场景稳定交接改造的切入点几乎总是检测器因为跟踪逻辑本身已经很薄了。做法是继承detectors/base.py里的基类实现infer一个方法即可# detectors/my_model.py from .base import BaseDetector class MyDetector(BaseDetector): def __init__(self, model_path, conf_thr0.3, devicecpu): self.session load_onnx(model_path, device) self.conf_thr conf_thr def infer(self, frame): # 预处理要跟训练时完全一致这是换模型最常见的翻车点 blob preprocess(frame, size(640, 640), keep_ratioTrue) outs self.session.run(None, {images: blob})[0] boxes, scores postprocess(outs, frame.shape, self.conf_thr) return boxes, scores关键在于infer返回的坐标必须是原图坐标系不能是 letterbox 之后的坐标系。预处理做了等比缩放和填充后处理就得按同样的比例反算回去少做这一步框会整体偏移并且尺寸缩水而现象看起来特别像「卡尔曼滤波发散」很容易查错方向。第二个改造点是重叠场景的交接。默认实现只在LOST池里做外观比对实际用的时候可以再加一条运动一致性约束候选轨迹的预测位置和新检测框中心的距离与它最近 5 帧的平均速度方向应该大致一致夹角超过 60 度就降低匹配权重。这条规则在交叉路口、两人擦肩而过的场景里很管用因为外观特征在低分辨率下本来就不可靠运动信息反而是更稳的信号。验证方法不能只靠看视频。我的做法是固定三段素材一段无遮挡、一段短遮挡、一段长遮挡加重叠每次改完跑这三段输出 IDF1 和 Frag 两个数只有两个数都不退化才合并。改动前后的对比表要留在 commit message 里因为跟踪这东西太容易在修好一个场景的同时弄坏另一个没有回归基线根本不知道自己在往前走还是在原地打转。还有一个常被忽略的技巧把q_scale和r_scale暴露成可以在运行时按遮挡状态动态调整的量而不是固定的配置值。遮挡判定为真时把q_scale放大两三倍、r_scale缩小一点让滤波器在目标被遮的时候更愿意接受观测、更不相信自己的外推。这个改动只有十来行但长遮挡段的 ID 保持率提升往往比换检测器还明显。从那以后我每次接一个新的跟踪场景都强制先把「前 100 帧自检 三段回归素材」跑一遍再动参数而不是直接盯着视频一帧一帧调阈值——肉眼看到的「好像跟丢了」八成只是检测器那一帧没给框。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/11/2 5:15:05

C语言实现串口转网口:RS485/COM到TCP双向转发与队列背压

简介:《串口和网口数据相互转发》是一份面向嵌入式与工业控制方向学习者的底层通信实践代码,围绕串口与以太网之间的双向数据交换,解决上下位机、PLC/SCADA远程采集等场景中的协议衔接问题。压缩包共5个文件,包含2个C源文件、1个头…

2026/11/2 5:15:05

卡尔曼滤波+模板匹配:轻量单目标跟踪的遮挡处理实践

简介:面向计算机视觉初学者与目标跟踪方向的学习者,这份项目围绕遮挡与重叠场景下的单目标跟踪展开,将卡尔曼滤波的预测—更新机制与最大IOU匹配结合,用于缓解目标被部分遮挡或相互重叠时跟丢的问题,属于机器学习在视觉…

2026/11/2 5:15:05

Windows内网邮件服务器部署:SMTP/POP3/IMAP配置与排障

简介:Foxmail Server For Windows v2.0 公测版面向需要在 Windows 环境下搭建邮件服务的个人与小型团队,提供 SMTP、POP3、LDAP 与 MIME 支持,可通过 Foxmail、Outlook 等客户端或全中文 Web 界面收发和处理邮件,管理员亦能借助 W…

2026/11/2 6:15:09

3 条命令装好 BBDown 并下载 B站 视频

3 条命令装好 BBDown 并下载 B站 视频 【免费下载链接】BBDown Bilibili Downloader. 一个命令行式哔哩哔哩下载器. 项目地址: https://gitcode.com/gh_mirrors/bb/BBDown 想把 B 站视频留存到本地离线看时,网页播放器不提供下载入口,而在线解析站…

2026/11/2 6:10:08

C++ 实现QT信号槽

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/1 1:01:33

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业级SPI隔离设计:硬件抗扰与信号完整性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

PLFM_RADAR:主动探测数据通道,构建低误报的数据感知系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业SPI隔离设计:MAX14431与STM32F746硬件抗干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/1 1:01:33

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑