简介:这份MATLAB实现面向计算机视觉与目标跟踪方向的学生和工程师,提供KCF(核化相关滤波)算法的可直接运行代码,适用于实时视频对象跟踪场景。压缩包共19个文件,体积仅48KB,以.m源文件为主(15个),辅以说明文档及预编译的mex动态库,便于在不同平台调用。已有875人学习下载。代码按训练、预测、更新等模块拆分,包含特征提取、高斯核相关计算、滤波器训练与模型更新等核心函数,并预留OTB基准测试接口,可用于精度与成功率评估。通过阅读和调试这套实现,可深入理解循环卷积与高斯核在目标跟踪中的具体用法,也为后续对比MIL、TLD等算法提供了基础。
1. 为什么KCF目标跟踪这套MATLAB代码值得拆:从循环矩阵到OTB基线
KCF(Kernelized Correlation Filter)目标跟踪的MATLAB实现,圈内叫tracker_release2,是很多人在OTB基准上复现的第一个基线。它反直觉的地方在于:代码里几乎没有显式的负样本提取循环,全靠循环矩阵在频域对角化,把训练和检测压成几次FFT和点乘。你只需要在第一帧框出目标,后续帧里它就能持续学习外观、预测新位置,速度比当年的TLD、MIL快一个量级。对做目标跟踪研究、本科毕设,或者给工业视觉项目搭快速跟踪原型的人来说,这套KCF MATLAB代码值得拆开逐行看。它把HOG特征、高斯核相关滤波、模型在线更新这几件事装进不到300行代码里,配合precision_plot.m可以直接评估。
2. 代码库结构与训练-检测闭环:拆开tracker.m看四行关键调用
2.1 文件清单与功能接线
拿到压缩包后,先在readme.txt和external.txt上花两分钟。external.txt声明了依赖外部特征库,实际上fhog.m和gradientMex.mexw64已经打包在目录里,所以解压后不需要额外装包就能跑。gradientMex.mexa64是Linux版本,macOS用户需要重新编译。
| 文件 | 角色 | 说明 |
|---|---|---|
run_tracker.m | 入口脚本 | 负责加载视频序列、调用tracker、保存结果 |
tracker.m | 核心算法 | 训练/检测闭环,约200行 |
get_features.m | 特征提取 | 默认fhog,可选gray |
fhog.m | HOG特征计算 | 依赖gradientMex计算梯度直方图 |
gaussian_shaped_labels.m | 回归标签 | 生成高斯形状软标签 |
get_subwindow.m | 样本截取 | 带余弦窗的裁剪与填充 |
linear_correlation.m/gaussian_correlation.m/polynomial_correlation.m | 核相关计算 | 三种核函数对应三个版本 |
load_video_info.m | 数据读取 | 解析OTB的groundtruth_rect |
precision_plot.m | 评估画图 | 画中心位置误差和精度曲线 |
videofig.m/show_video.m | 可视化 | 窗口展示跟踪框 |
文件与代码的接线关系很直接:run_tracker.m调choose_video.m或直接接收路径,内部用load_video_info.m读取初始框,然后进入tracker.m主循环。tracker.m每一步调用get_subwindow.m截取图像区域,交给get_features.m提特征,再用gaussian_correlation.m算核相关矩阵。最后结果交给precision_plot.m出图。
2.2 训练-检测闭环:tracker.m的核心环路怎么走
下面这段是tracker.m主循环的简化还原,保留了和原版一致的调用顺序和变量名,方便对照文件看:
for frame = 1:numel(img_files) if frame > 1 % 用上一帧学习到的模型预测当前位置 z = get_subwindow(im, pos, window_sz); zf = fft2(get_features(z, features, cell_size, cos_window)); kz = gaussian_correlation(zf, model_xf, kernel.sigma); response = real(ifft2(model_alphaf .* kz)); [vert_delta, horiz_delta] = find(response == max(response(:))); pos = pos - floor(window_sz / 2) + [vert_delta, horiz_delta]; end % 在当前帧提取样本,更新滤波器 x = get_subwindow(im, pos, window_sz); xf = fft2(get_features(x, features, cell_size, cos_window)); kf = gaussian_correlation(xf, xf, kernel.sigma); alphaf = yf ./ (kf + params.lambda); if frame == 1 model_alphaf = alphaf; model_xf = xf; else % 线性插值更新模型,interp_factor控制学习速率 model_alphaf = (1 - interp_factor) * model_alphaf + interp_factor * alphaf; model_xf = (1 - interp_factor) * model_xf + interp_factor * xf; end end第一段是预测:get_subwindow在上一帧位置周围截取搜索窗口,提特征后转频域,和模型做核相关,ifft2回到空域找响应峰值,峰值位置就是目标新位置。第二段是训练:同样截取当前帧样本,计算自相关核矩阵kf,然后除以kf + lambda得到滤波器系数alphaf。第一帧直接赋值,后续帧用interp_factor做平滑更新。
这里有个关键点:代码里没有显式采样一堆正负样本,因为KCF假设所有循环移位样本构成训练集。yf是gaussian_shaped_labels生成的高斯回归目标,中心为1,向四周衰减,FFT后存在yf里。循环矩阵在频域能被fft2对角化,所以训练和检测都只是矩阵点乘,这就是这套代码快的本质原因。
2.3 循环移位、余弦窗与特征通道的配合
gaussian_shaped_labels.m计算目标尺寸对应的输出响应,输出sigma由output_sigma_factor乘以目标尺寸得到,做成一个二维高斯软标签。直接用硬标签(0/1)会导致频域能量泄漏,跟踪容易跳框,换成软标签后滤波器学习到的模型更平滑。
get_subwindow.m里有两行容易被忽略:先按window_sz裁剪,再乘以余弦窗。余弦窗的作用是消减图像边缘的突变,避免FFT时把边缘当作高频信号。原版实现对每个特征通道都乘同一个余弦窗,这一步不能省——省了以后跟踪框会在目标周围抖动。
get_features.m里默认走fhog分支,得到31维梯度直方图特征,加上cell_size控制空间分辨率。tracker_release2默认cell_size设为1,意思是每个像素一个特征单元;老版本cell_size=4虽然快,但在小目标序列上精度损失明显,后续跑实验时会专门讲到这个参数的取舍。
3. 在OTB上跑通run_tracker:传给这个函数的三个路径参数
3.1 从交互选视频到直接指定序列路径
choose_video.m是交互入口,运行后弹窗让你选序列名,适合第一次跑通。实际做批量实验时,更多是直接调run_tracker并显式传路径:
% 方式一:交互选择(适合第一次验证) choose_video; % 方式二:直接指定OTB序列(适合批量实验) run_tracker('Basketball', 'D:/OTB100/Basketball/img', 1, false);run_tracker第一个参数是序列名,会作为保存结果的文件名前缀;第二个参数是图像序列所在目录,注意要精确到img这一层;第三个参数1表示使用灰度特征,传3则使用RGB三通道;第四个参数控制是否实时显示跟踪画面,批量实验建议传false,不然画图窗口会拖慢速度。运行结束后,run_tracker.m会在当前目录生成一个.mat文件,里面有positions和resize_factor两个变量,precision_plot就是读这两个变量出图的。
load_video_info.m在内部根据传入路径定位groundtruth_rect.txt,读取第一帧目标矩形。OTB序列的groundtruth格式是x, y, width, height,其中y是行坐标、x是列坐标,MATLAB的矩阵索引是反的,所以代码里做了换算。这个换算平时不觉得重要,一旦你手动替换成自己标注的数据,坐标反了会直接导致跟踪框跑到几帧后偏离目标。
3.2 准备自定义视频序列的目录约定
OTB的目录结构是序列名/img/0001.jpg加groundtruth_rect.txt。想用自己的视频测试,最省事的办法是按这个约定组织数据:
% 读取并检查groundtruth.txt的基本结构 fid = fopen('D:/MySeq/groundtruth_rect.txt', 'r'); raw = textscan(fid, '%f,%f,%f,%f'); fclose(fid); gt = cell2mat(raw); disp(size(gt)); % 期望是 N x 4注意这里用textscan按逗号分隔读浮点数,OTB里有些序列首行是注释说明,读进来会是非数字,需要先做过滤。常见做法是先逐行读字符串,判断第一列是否全为数字,再把数字行转成矩阵。
load_video_info.m对大部分OTB序列够用,但碰到个别序列的groundtruth_rect.txt首行带说明或末尾有空行时,会读到多余的NaN。我自己写的过滤逻辑很简单:读进来后调用any(isnan(gt(:)))检查一遍,有NaN就把该行删掉。这一段检查值得保留,因为后面批量跑50个序列时,任何一个序列读坐标出错,整个批量任务都会中途停掉。
3.3 可视化与结果的保存位置
show_video.m和videofig.m负责画跟踪框。show_video接收tracker.m输出的positions和每帧图像路径,画框后按帧率刷新。videofig是一个封装好的视频播放器,支持缩放和拖动进度条,调试单帧时很好用。
结果保存的位置由run_tracker.m末尾决定,默认放在当前工作目录,命名是results_序列名_时间戳.mat。批量跑完所有序列后,把每个results_*.mat收集到一个目录,再写个循环批量调precision_plot出图即可。这样跑一次OTB100的50个序列,大约十几分钟到半小时,取决于图像分辨率和机器性能。
4. 参数调整与核函数选型:cell_size和interp_factor决定了稳不稳
4.1 核心参数表与手工调参实例
KCF这套包能调的核心参数其实不多,但它们直接影响结果能不能复现原论文精度。我整理了一份常用参数表,按tracker.m和run_tracker.m里能直接改的位置列出来:
| 参数 | 默认值 | 作用 | 调参方向 |
|---|---|---|---|
padding | 1.5 | 搜索窗口相对目标尺寸的放缩 | 目标快速运动时调大,但峰值会变钝 |
cell_size | 1(release2)/ 4(旧版) | 特征图下采样倍数 | 小目标序列调小,大目标可调大提速 |
output_sigma_factor | 0.125 | 高斯标签带宽系数 | 目标形变严重时调小 |
lambda | 1e-4 | 岭回归正则化 | 特征噪声大时调大 |
interp_factor | 0.012 | 模型学习率 | 外观变化快调大,背景杂调小 |
kernel.sigma | 0.5 | 高斯核带宽 | 目标纹理稀疏时调小 |
kernel.type | 'gaussian' | 核函数类型 | 线性场景用'linear'更快 |
以interp_factor为例,它控制旧模型保留多少、新样本学多少。调大后模型对目标外观变化更敏感,但背景一旦干扰就容易被污染;调小则模型更稳,但目标快速变形时跟不上。原论文里0.012是个保守值,工程上我会在目标外观稳定的场景把它调到0.02,在长期遮挡场景降到0.008。
4.2 三种核函数的选择逻辑
linear_correlation.m、gaussian_correlation.m和polynomial_correlation.m对应三种核相关计算。线性核只做点乘,等价于岭回归的频域版本,速度最快,适合背景简单、目标与背景区分明显的场景。高斯核把特征映射到无限维再生核希尔伯特空间,对非线性外观变化更鲁棒,是默认选项。多项式核介于两者之间,但需要调阶数和系数,实际用得少。
% 切换核函数:把tracker.m里的调用换掉即可 kf = linear_correlation(xf, xf); % kf = gaussian_correlation(xf, xf, kernel.sigma); % 默认 % kf = polynomial_correlation(xf, xf, poly_a, poly_b);切换核函数后不需要改其他代码。注意gaussian_correlation第三个参数是kernel.sigma,这个值设得过大会让响应峰变成矮胖型,目标定位变模糊;设得过小则核矩阵退化成稀疏矩阵,容易把峰值锁在错误位置。经验值0.5在多数OTB序列上表现稳定,但在低纹理序列上我会降到0.3。
4.3 cell_size和特征通道的取舍
tracker_release2默认cell_size=1,也就是一个像素一个特征单元,空间分辨率最高。旧版本用cell_size=4时速度快了近4倍,但在OTB-100上平均精度大约下降3到5个百分点。对于小目标序列,cell_size调大以后特征图只有十几个像素宽,核相关矩阵的分辨率不够,峰值很容易偏一格。
get_features.m里支持两种特征:fhog和灰度。fhog是31维梯度直方图,加上gradientMex算梯度,每个通道独立做FFT,最后在频域取平方和。灰度特征只有1个通道,速度快很多,但对光照变化和边缘模糊敏感。工程上如果目标本身是刚体且纹理简单,灰度就够用;如果目标有旋转、形变,老老实实用fhog。
我一般会把cell_size和kernel.sigma放在一起调:cell_size决定特征图分辨率,sigma决定核在特征空间的作用半径。cell_size=1时sigma=0.5合适;cell_size=4时sigma也应当相应放大到1.5左右,否则核矩阵在低分辨率特征图上覆盖范围太小。
5. 避坑与常见问题:MEX文件、中文路径与groundtruth的读取陷阱
5.1 现象:加载gradientMex时报“Invalid MEX file”
MATLAB在R2017b之后更新了MEX文件的ABI,老版本编译的.mexw64在新版MATLAB里经常直接报错。现象是运行run_tracker到get_features调用fhog时报“无法加载模块”,或者提示编译器版本不匹配。
原因:gradientMex.mexw64是旧版MATLAB编译的,新版MATLAB的MEX接口不兼容;Linux端的gradientMex.mexa64同理。解决:在MATLAB里重新编译源码。包里没有给gradientMex.c源码的话,把fhog.m里梯度计算部分替换成标准梯度算子,或者保留下gradientMex.c从原版仓库编译。我编译时用mex -O gradientMex.c,几分钟就好。
5.2 现象:视频路径里有中文,图像读不出来
读者复现时最容易踩的坑是路径全中文或者带有空格。现象是imread返回空矩阵,tracker里size(im)变成一个很大的随机行列,跟踪框直接飞出画面。
原因:load_video_info和imread在中文路径下对某些系统编码处理不好,误把中文路径当成非法字符。解决:把整个数据集放到纯英文路径下,比如D:/OTB100/Basketball/img,并且运行前用cd到工作目录,保证video_path是绝对路径。如果用了choose_video,它内部拼接路径的方式也会受当前目录影响,建议直接手动传路径。
5.3 现象:跟踪框整体偏移半个目标宽度
用OTB40测试时,第一帧框的位置正确,从第二帧开始框整体向右下偏移,且偏移量固定。
原因:groundtruth_rect.txt里的坐标是x,y,width,height,而MATLAB矩阵操作要求的是row, col,即y,x。有些序列的坐标是0-based,需要加1才能对应MATLAB索引。load_video_info.m默认处理了这个问题,但如果你把坐标从别的工具拷过来,常常忘记这个约定。解决:读取后做一次坐标转换gt = [x+1, y+1, w, h],再检查初始框中心是否落在目标中心上。
5.4 现象:precision_plot画不出曲线或曲线全是直线
跑完run_tracker后执行precision_plot,窗口弹出但曲线是空的,或者是一条45度直线。
原因:results里保存的positions没有乘resize_factor还原到原图坐标。run_tracker为了加速把输入图像缩小了,特征图上的坐标是小图坐标,保存时要还原才能和groundtruth对比。解决:在precision_plot之前先检查results.resize_factor,用positions * resize_factor得到原图坐标,再传给precision_plot。如果所有帧的误差都相等,打印positions看是不是第一帧之后没有更新。
5.5 现象:OTB数据集下载不下来
download_videos.m会在外网受限的机器上失败,或者下载到一半断掉。现象是curl报连接超时,或者zip包解压到一半报文件不完整。
原因:OTB官方源在国外,网络受限时无法稳定拉取。解决:手动从OTB官网或镜像下载压缩包,本地解压后按序列名/img结构放置,然后直接用run_tracker传路径,完全跳过download_videos.m。如果个别序列解压后有缺失帧,先检查img目录里0001.jpg到最后一帧是否连续,不连续的话把img_files列表手动过滤一下。
6. 用precision_plot做验证:从positions反推目标移动速度
precision_plot默认画的是中心位置误差和精度曲线,这能告诉你跟踪准不准,但不能告诉你目标运动得多快。实际项目里,目标运动速度信息通常更有用。利用results.mat里保存的positions,可以反推出目标每帧的像素位移:
load('results_Basketball.mat', 'positions', 'resize_factor'); pix_pos = positions * resize_factor; displacement = sqrt(sum(diff(pix_pos).^2, 2)); frame_rate = 30; % 根据实际视频设置 velocity_pix = displacement * frame_rate; % 像素/秒diff(pix_pos)求相邻帧中心点位移,sqrt得到每帧移动的像素数,乘以帧率就是像素速度。这个速度和KCF响应峰值的含义正好互补:如果velocity_pix在某几帧突然跳高,同时response最大值明显下降,说明目标处于快速运动或遮挡状态。这个组合判据可以用来判断是否该触发重新检测,比单纯看误差曲线更早发现问题。
进一步的技巧是用最近N帧的中值速度平滑,避免单帧抖动误判。我通常取N=5,因为追丢通常发生在连续几帧位移超过窗口大小的时候,单帧跳变不一定是真的丢失:
smooth_vel = medfilt1(velocity_pix, 5); fast_motion = smooth_vel > size(positions, 1) * 0.3;在OTB的Basketball这类快速运动序列上,这个方法能提前几帧预警目标即将跑出搜索窗口,让你在padding参数上做针对性调整。配合precision_plot出图后,我会把速度和中心位置误差画在同一张图上对比——误差小的帧速度一定不会突跳,如果出现误差小但速度突跳的帧,检查是不是前景里另一个相似目标被锁上了。
从那以后我每次跑OTB批量实验前,都会先花一分钟检查三件事:路径是否纯英文、MEX文件能否正常加载、resize_factor有没有参与坐标还原。这三样不出错,结果才有可比性,希望帮到你。
本文还有配套的精品资源,点击获取