简介:KCF(核化相关滤波)算法是轻量级目标跟踪领域的经典方案,这份用Python语言复现的代码包,适合计算机视觉初学者、算法研究人员,以及任何需要快速搭建目标跟踪实验的开发者。压缩包共包含13个文件,主体为3个Python脚本,分别对应核心跟踪器、fhog特征提取和主运行程序;另有2个编译缓存文件、若干XML工程配置、Markdown说明文档和开源许可文件等作辅助,整体体积仅20KB,代码量小且目录层级清晰,便于快速定位各功能模块。目前已有592人学习下载。解压后可通过主运行脚本直接观察视频序列中的跟踪效果,也能结合说明文档深入理解KCF如何将方向梯度直方图特征、循环卷积运算和快速傅里叶变换加速有机结合,在保证实时性的同时完成高效目标定位。项目源自GitHub的KCFpy主分支,代码风格简洁,既适合逐行研读算法细节,也可作为基础扩展CSK、MOSSE等同类型相关滤波跟踪方法,对后续学习与工程实践都有直接参考价值。
1. 先看这份 KCF Python 复现包:它替你省掉的是哪几层脏活
做目标跟踪的人基本都绕不开 KCF:2014 年的相关滤波算法,效果能打、公式也不长,但真要用 Python 复现时,能找到的多数是 Matlab 原版注释,或者封装得看不出内部逻辑的第三方库。这份 KCF 的 Python 复现代码包,把训练、检测、特征提取、窗函数和坐标换算整理成了能直接跑的脚本,核心就是 numpy 数组运算,没有黑盒依赖。它能解决的是跟踪里最具体的问题:给我一段视频和首帧目标框,后续帧持续输出目标位置。适合正在学相关滤波的、拿跟踪算法做毕设起点的、以及要在 KCF 上做二开的人。我拆这个包时发现,决定复现成败的往往不是算法本身,而是坐标映射和窗函数这两处细节,后面章节逐个拆开说。
2. 对齐 KCF 的四个核心数组操作:循环样本、高斯标签、核相关与窗函数
KCF 追根溯源就两个公式:训练一个核岭回归分类器,然后用它回归目标在每个位置的响应。真正难的不是公式,而是把公式落成 numpy 数组运算时,每一步的形状、维度和坐标基准都对得上。这一章我把复现包里最关键的四个数组操作拆开讲,你不用背论文,把这几段代码吃透,整份代码包的骨架就清晰了。
2.1 岭回归到频域:为什么一次训练只有五行 numpy
先看训练这一侧。标准岭回归的解是 w = (X^T X + λI)^(-1) X^T y,但 KCF 的训练样本不是一张一张抠出来的,而是用循环移位生成的:以目标中心截取一块 padding 区域作为基础样本,左右上下循环平移,产生大量样本。这些样本组成的矩阵是循环矩阵,傅里叶变换后可以对角化,于是矩阵求逆退化成逐元素除法,整个过程被压到极短。
体现到代码里,训练核心就这么几行:
def _train(self, xf, yf): # xf: 训练样本特征图的 FFT,形状 (H, W, C) # yf: 高斯标签的 FFT,形状 (H, W) # 用当前特征图算核相关,线性核就是通道维度相乘求和 kxx = self._gaussian_kernel(xf, xf) # 核岭回归频域解:逐元素除法,替代矩阵求逆 alphaf = yf / (kxx + self.lambda_) return alphaf, kxx_gaussian_kernel是这一小节的另一个关键:
def _gaussian_kernel(self, xf, yf): n = xf.shape[0] * xf.shape[1] xx = np.sum(xf * np.conj(xf), axis=2) yy = np.sum(yf * np.conj(yf), axis=2) xy = np.sum(xf * np.conj(yf), axis=2) xy = np.real(np.fft.ifft2(xy)) dist = (xx + yy - 2 * xy) / n return np.exp(-dist / self.sigma ** 2)逻辑说明:KCF 用的是高斯核,高斯核的距离项由三部分组成——训练样本能量 xx、当前样本能量 yy、两者的循环互相关 xy。注意 xy 是先把频域特征逐通道相乘再沿通道求和,ifft2 回到空间域,得到的矩阵第 (i, j) 个元素就代表某两个循环移位样本之间的相似度,这就是循环样本在频域被压缩后的形态。参数self.sigma是高斯核带宽,控制相似度衰减速度。
参数说明:self.lambda_默认取 1e-4,正则系数,太小容易过拟合训练样本,太大响应会变平;n = H * W是特征图像素数,因为 numpy 的 ifft2 带了 1/N 归一化,所以距离项也要除以 n 才能和论文公式对齐。早期复现包里最常出现的错误就是忘了这个除法,表现是响应图整体偏平、峰值不集中。
2.2 检测响应图:峰值坐标就是位移
训练完成后,跟踪器手里握的是两份东西:模型特征model_xf和模型的model_alphaf。检测时不需要重新求解任何方程,只需要算一遍当前帧搜索区域和模型特征之间的核相关,再和 alpha 在频域相乘,反向变换就是响应图。
def _detect(self, zf, model_alphaf, model_xf): # zf: 当前帧搜索区域特征图的 FFT kxz = self._gaussian_kernel(zf, model_xf) response = np.real(np.fft.ifft2(kxz * model_alphaf)) # 响应图中最大值的坐标 row, col = np.unravel_index(np.argmax(response), response.shape) dy = row - response.shape[0] // 2 dx = col - response.shape[1] // 2 return response, (dy, dx)逻辑说明:kxz算的是当前帧特征和模型特征的循环相关矩阵,它的每个元素表示目标出现在某个循环移位位置的可能性;乘以model_alphaf相当于用训练好的分类系数给每个位置打分,ifft2 之后最大值所在位置就是目标相对中心点的位移(dy, dx)。这里dy是行方向位移,dx是列方向位移,和图像的 (row, col) 约定一致。如果你把视频画面当作 (x, y, w, h) 的矩形操作,此处非常容易把 dx、dy 写反。
模型更新走的是线性插值,通常写作:
self.model_xf = (1 - interp_factor) * self.model_xf + interp_factor * xf self.model_alphaf = (1 - interp_factor) * self.model_alphaf + interp_factor * alphafinterp_factor默认 0.02,意思是每帧只用 2% 的新信息刷新模型,旧信息占 98%。这个值直接决定跟踪器对目标外观变化的敏感度,后面第 5 章会专门说它带来的坑。
2.3 特征图和三个关键参数:padding、sigma、interp_factor
特征提取之前,复现包会先生成两样东西:余弦窗和高斯标签。余弦窗用来抑制循环移位制造样本时的边界泄漏——图像边缘被循环移位后,左边跑到右边,如果不加窗,这些虚假的边界结构会被当成训练样本的一部分。高斯标签则是训练的目标值:中心位置为 1,越远越小,代表"目标离开中心越远,回归值应该越低"。
def _init_artifacts(self, target_h, target_w): self.crop_h = int(target_h * self.padding) self.crop_w = int(target_w * self.padding) # 余弦窗,尺寸必须和特征图一致 self.cos_window = np.outer(np.hanning(self.crop_h), np.hanning(self.crop_w)) ys, xs = np.mgrid[0:self.crop_h, 0:self.crop_w] cy, cx = self.crop_h // 2, self.crop_w // 2 label = np.exp(-((xs - cx) ** 2 + (ys - cy) ** 2) / (2 * self.sigma ** 2)) self.yf = np.fft.fft2(label)逻辑说明:这里的target_h、target_w是首帧目标框的高和宽,self.padding默认 2.5,意思是搜索区域是目标区域的 2.5 倍。np.hanning生成一维汉宁窗,np.outer得到二维窗矩阵。高斯标签的标准差取self.sigma,在特征像素尺度下一般 0.2 到 0.5 之间,太小会让标签峰值过陡,训练出的分类器对微小位移过于敏感。
参数说明:padding决定搜索范围。目标运动快、相邻帧位移大,就把 padding 调到 3 或 3.5,但代价是背景占比变大,干扰变多;interp_factor前面说过,控制模型更新速度;kernel_sigma控制核函数带宽,这个参数对 HOG 特征尤其敏感,同样一组特征,sigma 从 0.2 拉到 0.5,跟踪稳定性会肉眼可见地下降。复现包里的参数一般集中在文件头部的配置区,改动前建议先跑通默认值。
这一章最后说下代码包的组织方式。这类复现包通常按这个节奏拆模块:tracker.py放 KCFTracker 类,对外只暴露init和update两个接口;feature.py放特征提取;utils.py放窗函数、高斯标签、坐标换算工具;demo.py放可运行的示例。你的二开基本集中在tracker.py和feature.py两个文件里,其他地方很少需要动。
| 模块文件 | 职责 | 核心函数 |
|---|---|---|
| tracker.py | 跟踪器主流程,训练、检测、模型更新 | init / update / _train / _detect |
| feature.py | 灰度或 HOG 特征提取 | extract_feature |
| utils.py | 余弦窗、高斯标签、坐标映射 | cosine_window / gaussian_label |
3. 跑通复现包:依赖安装、首帧选框和完整调用链
代码光读懂是不够的,得先让它跑起来。这一章从零开始走一遍:装依赖、导入包、用 OpenCV 选首帧目标、逐帧输出跟踪框。全程大概三四十行代码,不需要改动任何算法逻辑。
3.1 环境与依赖:从 python 安装到 cv2 导入一次过
先说环境。这个复现包对 numpy 和 OpenCV 的依赖是刚性的,scikit-image 只在启用 HOG 特征时需要。如果你机器上的 python 还没装好,先去 python 官网下 3.8 或 3.11,安装时勾上 Add to PATH,这是最常见的起步坑。包管理用 pip 就行:
python -m pip install --upgrade pip pip install numpy opencv-python scikit-image逻辑说明:opencv-python就是平时用的 cv2,它承担三件事:读视频帧、首帧弹窗选目标、画跟踪框。如果只跑灰度特征不跑 HOG,scikit-image可以不装,但建议装上,因为复现包里的特征函数大概率引了它。装完后验证一下导入是否正常:
import numpy as np import cv2 from skimage import feature print(np.__version__, cv2.__version__)这里有个经验:cv2 的版本不要追最新。某些新版本对selectROI的窗口交互有过调整,和老代码混用会出现选框没反应。我一般固定用 4.x 的稳定版,够用且省事。
3.2 用 selectROI 初始化跟踪器,逐帧更新
跑通一个跟踪 demo 的调用链其实是固定的:读视频 → 第一帧用鼠标选目标 →tracker.init建立模型 → 循环读帧 →tracker.update拿新框。
import cv2 from kcf import KCFTracker cap = cv2.VideoCapture("demo.mp4") ok, frame = cap.read() if not ok: raise IOError("demo.mp4 读取失败,检查视频路径") bbox = cv2.selectROI("first frame", frame, showCrosshair=False) cv2.destroyWindow("first frame") tracker = KCFTracker(padding=2.5, kernel_sigma=0.5, lambda_=1e-4, interp_factor=0.02) tracker.init(frame, bbox) while True: ok, frame = cap.read() if not ok: break bbox = tracker.update(frame) x, y, w, h = bbox cv2.rectangle(frame, (int(x), int(y)), (int(x + w), int(y + h)), (0, 255, 0), 2) cv2.imshow("KCF Tracking", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()逻辑说明:selectROI返回的是(x, y, w, h)的整数元组,x、y 是矩形左上角坐标,w、h 是宽高。tracker.init内部会完成三件事:按 bbox 尺寸算搜索区域大小、生成余弦窗和高斯标签、用第一帧特征建立模型。之后每个循环里update内部先以当前目标中心裁出搜索区域,提取特征加余弦窗,再走第 2 章说的检测流程,返回新的 bbox。注意 KCF 本身不估计尺度,所以返回的 w、h 和首帧一致,变的只有 x、y。
3.3 参数速查表与一次快速冒烟测试
如果你不想动代码里的参数,先按默认值跑一遍,确认能跟住一个稳定移动的目标,再做调参。我习惯的冒烟测试是拿一段目标不遮挡、速度均匀的视频跑 200 帧,观察两个现象:跟踪框是否稳定贴住目标、控制台打印的响应最大值是否平稳。如果响应值突然掉一半,基本是目标出了搜索区域或者参数没对齐。
| 参数 | 默认值 | 作用 | 调节方向 |
|---|---|---|---|
| padding | 2.5 | 搜索区域相对目标框的倍数 | 目标位移大时调大到 3~3.5,场景杂乱时调小 |
| kernel_sigma | 0.5 | 高斯核带宽 | 响应太平就调小,峰值太尖导致抖动就调大 |
| lambda_ | 1e-4 | 岭回归正则系数 | 跟丢且响应振荡时尝试调大到 1e-3 |
| interp_factor | 0.02 | 模型更新率 | 外观快速变化时调到 0.05~0.1,长期稳定场景保持小值 |
这段跑通之后,你已经完成了从下载资源到可视化跟踪的闭环。接下来真正有价值的是把它接进自己的业务场景。
4. 把复现代码改造成自己的跟踪服务:输入源、检测器联动与特征替换
很多人的需求不是跑官方 demo,而是把 KCF 嵌进自己的系统:摄像头画面、图片序列、检测器输出的目标框、换成自己的特征。这一章讲三种最常见的改造,代码量不大,但能直接复用。
4.1 摄像头与图片序列:输入源插拔
跟踪服务的输入源基本就三类:视频文件、摄像头、图片序列。视频文件在上一章已经演示过,摄像头和图片序列的改动很小。
cap = cv2.VideoCapture(0) # 0 代表默认摄像头 # 或者用图片序列 import glob img_paths = sorted(glob.glob("seq/*.jpg")) for i, path in enumerate(img_paths): frame = cv2.imread(path) if frame is None: continue if i == 0: bbox = cv2.selectROI("init", frame, False) tracker.init(frame, bbox) continue bbox = tracker.update(frame) # 后续画框逻辑与视频一致逻辑说明:摄像头场景下第一帧同样需要选目标,选完再进入循环。图片序列场景更直接,glob排序后逐张读图,第一张做初始化,后面逐个update。这里唯一的坑是排序:glob默认按字符串排序,seq/img_10.jpg会排在seq/img_2.jpg前面,序列错乱会让跟踪结果看起来像乱跳。我一般会在路径里补零,或者用sorted(..., key=lambda p: int(re.findall(r'\d+', p)[-1]))按数字排。
4.2 和检测器配合:框图融合与重初始化策略
KCF 单独跑的短板是没有目标重新检测能力,目标出了画面再回来就丢了。实际工程里常见做法是 tracking-by-detection:检测器负责发现目标,KCF 负责帧间平滑。检测器给出高置信度框时,用检测结果修正或重新初始化跟踪器。
def iou(a, b): ax, ay, aw, ah = a bx, by, bw, bh = b inter_w = max(0, min(ax + aw, bx + bw) - max(ax, bx)) inter_h = max(0, min(ay + ah, by + bh) - max(ay, by)) inter = inter_w * inter_h union = aw * ah + bw * bh - inter return inter / union if union > 0 else 0 def merge_detection(frame, det_bbox, det_score, tracker, last_bbox): if det_score < 0.8: return last_bbox if det_bbox is None: return last_bbox if iou(det_bbox, last_bbox) < 0.1: return last_bbox # 检测框和跟踪框重合度较高,用检测结果重初始化 tracker.init(frame, det_bbox) return det_bbox逻辑说明:这个策略的核心是防御性重初始化。检测器置信度不够高时不动作,跟踪框和检测框的 IoU 太低时也不动作——这种情况大概率是跟踪已经丢了而检测框是误报。只有两者都有把握时才用检测框重新初始化。我的习惯是检测器每 5 到 10 帧才输出一次,KCF 在两帧之间顶住,这样既平滑又不会每帧都被检测抖动干扰。
4.3 特征层替换:从灰度到 HOG 再到可学习特征
KCF 论文用的是 31 维 Felzenszwalb HOG,但复现包为了降低依赖,往往先用灰度图跑通。灰度特征在纹理弱的环境下很容易跟丢,换成 HOG 是性价比最高的一步改造。接口通常已经留好了,你只需要替换特征提取函数。
def extract_feature(patch, feature_type="gray"): if feature_type == "gray": patch = cv2.cvtColor(patch, cv2.COLOR_BGR2GRAY) return patch.astype(np.float32) / 255.0 if feature_type == "hog": gray = cv2.cvtColor(patch, cv2.COLOR_BGR2GRAY) from skimage.feature import hog feat = hog(gray, orientations=9, pixels_per_cell=(4, 4), cells_per_block=(3, 3), visualize=False) feat_h = gray.shape[0] // 4 - 2 feat_w = gray.shape[1] // 4 - 2 feat = feat.reshape(feat_h, feat_w, -1) return feat.astype(np.float32) raise ValueError(feature_type)逻辑说明:HOG 提取后是拉平的一维数组,需要 reshape 回二维特征图。feat_h、feat_w的计算依据是:4 像素一个 cell,cells_per_block=(3, 3),滑窗步长一个 cell,所以特征图高宽分别是输入高宽除以 4 再减 2。这里 reshape 的通道数不是固定的,取决于 orientations 和 block 大小,所以用-1让 numpy 自己推断。
注意:skimage 的输出通道数不是 31,而是 293*3 = 162 维。严格复现论文效果需要换成 Felzenszwalb 式 31 维 HOG,很多复现包为此自己实现了 HOG 特征文件。我的建议是:先用上面这段跑通流程,确认整个链路没问题后再考虑换 31 维实现,否则你很难判断跟丢是算法问题还是特征问题。
5. KCF 复现避坑排查:五个常见的翻车点与修复记录
KCF 代码量不大,但翻车点高度集中。我把拆包和实测中反复遇到的五类问题按现象、原因、解决整理如下,基本覆盖了从跑通到调优的大多数报错和异常。
5.1 尺寸与坐标类:索引偏移、特征图缩水
坑 1:跟踪框每帧都往右下角偏几个像素,几十帧后彻底跟丢。
- 现象:目标其实是静止的,但跟踪框在缓慢平移,且偏移方向固定。
- 原因:把响应图
argmax的索引直接当位移用了。numpy 索引从 0 开始,而很多复现代码是从 Matlab 移植的,Matlab 索引从 1 开始,两者在中心点计算上差 1 到 2 个像素。同时响应图中心坐标用的是shape[0] // 2,在奇数尺寸时和真实中心差 0.5 像素,误差逐帧累积。 - 解决:统一用
row - response.shape[0] // 2和col - response.shape[1] // 2计算位移,再把位移乘上特征图到原图的缩放系数后加到旧中心上。改完记得在 init 后单独打印一次中心坐标,人工核对框中心是否和目标中心重合。
坑 2:HOG 特征跑起来就报形状不匹配,或者训练正常但响应图全乱。
- 现象:
ValueError: operands could not be broadcast together,报错位置多半在特征图乘余弦窗那一行。 - 原因:HOG 提取后特征图尺寸比原始 patch 小。比如 100x100 的 patch,4 像素 cell 加 3x3 block,特征图变成 23x23,而余弦窗还是 100x100,直接相乘必然错位。
- 解决:两个方向选一个。要么在特征图尺寸上重新生成余弦窗和标签;要么把 HOG 特征图 padding 回原始 patch 尺寸再乘窗。前者省内存,后者简单直观。无论哪种,改完要确认
cos_window.shape、label.shape、feature_map.shape三者完全一致。
5.2 类型与特征类:uint8 溢出、彩色图直接喂给 HOG
坑 3:运行时报ComplexWarning: Casting complex values to real discards the imaginary part,或者乘法结果变成全黑的图。
- 现象:代码能跑,但跟踪效果明显异常,响应图出现大量噪点。
- 原因:OpenCV 读入的帧是 uint8 类型,直接参与乘法运算会溢出截断;numpy 的 FFT 返回 complex128,随后
np.conj、乘法又把 complex 和 float 混在一起触发警告,有人为了消除警告直接.astype(float),把虚部丢掉了。 - 解决:所有特征数组在进入 FFT 之前统一
astype(np.float32),并归一化到 [0, 1];取实部用np.real,不要用类型转换;复数数组不要做astype(float)。
坑 4:彩色视频直接用 skimage 的 HOG,效果比论文差一大截。
- 现象:目标颜色鲜艳、纹理清楚,但峰值响应就是不明显,跟丢概率很高。
- 原因:KCF 论文的 31 维 HOG 是在灰度图上计算的,梯度方向直方图本身不携带颜色信息。直接把 BGR 三通道一起送进 HOG,等于把颜色差异也当成梯度信息,噪声成倍增加。
- 解决:先
cv2.cvtColor(patch, cv2.COLOR_BGR2GRAY)再提取 HOG。如果确实想用颜色信息,把三个通道分别提取特征再沿通道拼接,也比混合梯度要合理。这条改造对跟踪稳定性提升非常明显,尤其目标是在背景里靠颜色凸出时。
5.3 模型更新类:能量没更新、形变目标跟不上
坑 5:跟踪到 30 到 50 帧后,响应峰值逐渐降低,明明目标还在画面里,跟踪框却抖动甚至丢。
- 现象:响应最大值的数值一路下滑,从 0.3 掉到 0.1 以下,之后跟踪框开始漂。
- 原因:高斯核距离项需要计算模型特征的能量
||x̂||²。如果复现包在检测时用了缓存的旧能量,而model_xf已经通过插值更新过,核距离就会失真,响应图随之退化。线性核没有这个能量项,所以很多复现包默认用线性核来规避,但代价是精度相对低。 - 解决:检查核相关函数里
xx是实时从model_xf算的,还是缓存下来的。实时重算最稳妥,代价只是几十次乘法;如果是缓存,把模型能量也纳入插值更新。
坑 6:目标转头、背包、被短暂遮挡后,再出现就找不回来了。
- 现象:目标外观变化后,跟踪框停在原地或跳到背景纹理相似的位置。
- 原因:
interp_factor=0.02让历史模型占绝对主导,外观突变时新信息被稀释;而循环样本的假设是目标始终在搜索区域内做平稳移动,一旦遮挡或剧烈变形,这个假设就不成立。 - 解决:两个手段配合。一是把快速形变场景下的
interp_factor提高到 0.05 甚至 0.1;二是加一个响应置信度判断,当响应图峰值锐度骤降时,把interp_factor临时置 0,不更新模型。置信度指标在下一章讲,是一个 10 行以内的函数,但能挽救大量翻车现场。
6. 用 OTB 序列验证复现效果,并给跟踪器加一个 APCE 置信度输出
代码跑通后,下一步是验证改的代码真的有效,而不是自我感觉。跟踪领域最省事的验证方式是用 OTB 序列的 groundtruth 算两个指标:中心位置误差 CLE 和精度 Precision。OTB 的 groundtruth 是(x, y, w, h)的矩形格式,算中心误差时记得先转成中心点,很多人在这一步栽过。
import numpy as np def evaluate_otb(tracker, frames, gt_boxes): pred_boxes = [] for i, frame in enumerate(frames): if i == 0: tracker.init(frame, gt_boxes[0]) box = tracker.update(frame) pred_boxes.append(box) err = [] for pred, gt in zip(pred_boxes, gt_boxes): pcx, pcy = pred[0] + pred[2] / 2, pred[1] + pred[3] / 2 gcx, gcy = gt[0] + gt[2] / 2, gt[1] + gt[3] / 2 err.append(np.hypot(pcx - gcx, pcy - gcy)) err = np.array(err) cle = err.mean() precision = (err < 20).mean() return cle, precision, pred_boxes逻辑说明:cle是所有帧中心误差的均值,越低越好;precision是误差小于 20 像素的帧占比,越高越好。用这两个指标对比你改造前和改造后的效果,比肉眼盯着视频看客观得多。如果你的场景里目标尺寸变化大,建议同时记录一下尺度变化率,KCF 不支持尺度估计,这会是后续加 DSST 思路的直接依据。
跟踪器在遮挡、背景杂乱时最容易犯错,但响应图本身的形态会提前暴露问题。APCE(平均峰值相关能量)是一个只用响应图就能算出来的置信度指标,峰值越尖锐 APCE 越大,目标被遮挡时响应图变得平缓,APCE 会骤降。
def apce(response): r_max = np.max(response) r_min = np.min(response) return (r_max - r_min) ** 2 / (np.mean((response - r_min) ** 2) + 1e-10) # 在 update 里拿到 response 后调用 apce_now = apce(response) if apce_now < 0.5 * self.apce_history: interp_factor = 0.0 else: interp_factor = self.interp_factor self.apce_history = 0.7 * self.apce_history + 0.3 * apce_now逻辑说明:apce_history初始在 init 时赋值,之后用指数滑动平均维护。当当前 APCE 低于历史均值的 50% 时,说明响应图已经不可信,把interp_factor置 0 冻结模型更新,避免把遮挡背景学进模型。这里0.5是我试下来比较稳的阈值,如果你的场景遮挡频繁,可以放松到0.4;如果场景干净、目标稳定,收紧到0.6能更早拦下异常帧。
从那以后,我每次拿到一份 KCF 复现代码,都会先走一遍三件事:确认特征图到原图的坐标缩放系数、查余弦窗尺寸是不是跟着特征图走、给 update 加一个 APCE 输出。这三件事跑通了,剩下的就全是参数调优问题。希望帮到你。
本文还有配套的精品资源,点击获取