本文主要介绍相关滤波算法开篇——mosse具体原理及其python代码实现流程
相关滤波(Correlation Filter )介绍
相关滤波(CF)源于信号处理领域,有这么一句话“两个信号越相似,其相关值越高。在跟踪,就是找到与跟踪目标响应最大的项” 贯穿了整个相关滤波算法的根本。
2010年CVPR,David S.Bolme在文章《visual object tracking using adaptive correlation filters》中首次将相关滤波用在了跟踪领域,在其文章的基础之上,很多改进的算法相继出现,跟踪的效果也越来越好。
两个信号之间的相关为,
(
f
⊗
g
)
(
τ
)
=
∫
∞
−
∞
f
∗
(
t
)
g
(
t
+
τ
)
d
t
(f\otimes g)(\tau)=\int_\infty^{-\infty}f^*(t)g(t+\tau)dt
(f⊗g)(τ)=∫∞−∞f∗(t)g(t+τ)dt
(
f
⊗
g
)
(
n
)
=
∑
−
∞
∞
f
∗
[
m
]
g
(
m
+
n
)
(f\otimes g)(n)=\sum_{-\infty}^\infty f^*[m]g(m+n)
(f⊗g)(n)=−∞∑∞f∗[m]g(m+n) 其中f∗表示f的复共轭。correlation的直观解释就是衡量两个函数在某个时刻相似程度。
在图像中相关滤波意思就是输入图像(框选的目标)跟滤波器之间做一个相关操作,相关操作类似卷积操作(卷积需要反转,相关不用),相关操作如下图所示。
这里要注意输入图像f,滤波器h,及相应输出g的大小是完全一样的。
相关滤波意思就是现在在第一帧图像中框选了一个目标,然后对这个目标训练一个滤波器(大小相同)使得其输出响应g(大小相同)在中间值最大。其中输入图像给定,响应图也是可以直接生成的。一般都是用高斯函数,中间值最大,旁边逐渐降低。
然后滤波器的值推导过程类似于机器学习的线性回归
m
i
n
H
∗
=
∑
i
=
1
m
∣
H
∗
F
i
−
G
i
∣
2
min_{H^*}=\sum _{i=1}^m|H^*F_i-G_i|^2
minH∗=i=1∑m∣H∗Fi−Gi∣2
就是要找一个滤波器H*使得其上式的结果最小,其实也就是找到一个滤波器,使得其响应在中间的值最大,这就是利用了相关滤波器的原理。
然后根据数学求导,各种计算规则(有兴趣的可以看下原来论文,步骤很详细,这里就不展示了),最终可以得到
H
w
v
=
∑
i
F
i
w
v
G
i
w
v
∗
∑
i
F
i
w
v
F
i
w
v
∗
H_{wv}={\sum _i F_{iwv}G^*_{iwv}\over \sum _i F_{iwv}F^*_{iwv} }
Hwv=∑iFiwvFiwv∗∑iFiwvGiwv∗上面得到是H中每个元素的值,最后得到H为:
H
=
∑
i
F
i
⋅
G
i
∗
∑
i
F
i
⋅
F
i
∗
H={\sum _iF_i\cdot G^*_i \over \sum _iF_i\cdot F^*_i }
H=∑iFi⋅Fi∗∑iFi⋅Gi∗上式就是滤波器的模型公式。
其中的G为高斯函数矩阵的傅里叶变换,F为输入目标图矩阵的傅里叶变换,都是用的快速傅里叶变换(FFT),这块不懂的可以查看快速傅里叶变换
然后为了具有更好的鲁棒性,滤波器采用迭代的方法
H
t
=
A
t
B
t
H_t={A_t\over B_t}
Ht=BtAt
A
t
=
η
F
t
⋅
G
t
∗
+
(
1
−
η
)
A
t
−
1
A_t=\eta F_t \cdot G^*_t +(1-\eta)A_{t-1}
At=ηFt⋅Gt∗+(1−η)At−1
B
t
=
η
F
t
⋅
F
t
∗
+
(
1
−
η
)
B
t
−
1
B_t=\eta F_t \cdot F^*_t +(1-\eta)B_{t-1}
Bt=ηFt⋅Ft∗+(1−η)Bt−1更新参数为η,每一帧的滤波器都与上一帧的取值有关。
至此,第一帧图像目标框选之后就得到了一个设计好的滤波器,然后对第二帧图像提取目标区域对其与刚才的滤波器先进行傅里叶变换进行卷积之后逆向傅里叶变换找最大响应值,这个响应值的位置就是第二帧图像的待追踪目标的中心。
每一步目标图像在傅里叶变换前还要进行一些预处理
(1)FFT卷积算法需要将图像和滤波器映射到拓扑结构上,边界采用循环图像的方式填充,即将图像的左边缘连接到右侧边缘,将顶部连接到底部。
(2)采用点乘余弦窗处理,使图像边缘慢慢变成零。
在代码中都有所体现
接下来讲代码,附上GitHub链接
是python代码(因为对python比较熟悉)
项目根目录下主要有
datasets文件夹(用于存放跟踪图片集)
examples文件夹(用于存放gif动图,可不用)
demo.py (主程序,用于初始化参数,及运行各个函数)
mosse.py(跟踪算法的实现)
utils.py(一些图像处理操作)
代码解读
接下来分别介绍下各个具体模块的代码首先是主程序demo.py
from mosse import mosse
import argparse
parse = argparse.ArgumentParser()
parse.add_argument('--lr', type=float, default=0.125, help='the learning rate')
parse.add_argument('--sigma', type=float, default=100, help='the sigma')
parse.add_argument('--num_pretrain', type=int, default=128, help='the number of pretrain')
parse.add_argument('--rotate', action='store_true', help='if rotate image during pre-training.')
parse.add_argument('--record', action='store_true', help='record the frames')
if __name__ == '__main__':
args = parse.parse_args()
img_path = 'datasets/surfer/'
tracker = mosse(args, img_path)
tracker.start_tracking()
其中下半部分为主程序,进行参数的赋值,图像路径的赋值,跟踪器的设计,开始跟踪
上半部分为参数的赋值,为python自带模块argparse 的使用。
其次是
mosse.py
这里定义了一个mosse类 用于mosse算法的实现
主要就是一个方法
def start_tracking(self):
# 得到图像的第一帧
init_img = cv2.imread(self.frame_lists[0])
init_frame = cv2.cvtColor(init_img, cv2.COLOR_BGR2GRAY)
init_frame = init_frame.astype(np.float32)
# 在第一帧中框选中需要的目标区域
init_gt = cv2.selectROI('demo', init_img, False, False)
init_gt = np.array(init_gt).astype(np.int64)
# 得到当前的高斯响应
response_map = self._get_gauss_response(init_frame, init_gt)
# 得到目标图像大小的高斯响应图
g = response_map[init_gt[1]:init_gt[1]+init_gt[3], init_gt[0]:init_gt[0]+init_gt[2]]
#原始图像
fi = init_frame[init_gt[1]:init_gt[1]+init_gt[3], init_gt[0]:init_gt[0]+init_gt[2]]
#进行傅里叶变换
G = np.fft.fft2(g)
# 预处理
Ai, Bi = self._pre_training(fi, G)
# 开始跟踪
for idx in range(len(self.frame_lists)):
current_frame = cv2.imread(self.frame_lists[idx])
frame_gray = cv2.cvtColor(current_frame, cv2.COLOR_BGR2GRAY)
frame_gray = frame_gray.astype(np.float32)
if idx == 0:
Ai = self.args.lr * Ai
Bi = self.args.lr * Bi
pos = init_gt.copy()
clip_pos = np.array([pos[0], pos[1], pos[0]+pos[2], pos[1]+pos[3]]).astype(np.int64)
else:
Hi = Ai / Bi
fi = frame_gray[clip_pos[1]:clip_pos[3], clip_pos[0]:clip_pos[2]]
fi = pre_process(cv2.resize(fi, (init_gt[2], init_gt[3])))
Gi = Hi * np.fft.fft2(fi)
gi = linear_mapping(np.fft.ifft2(Gi))
# 找到最大的响应点,这就是第二帧的目标位置
max_value = np.max(gi)
max_pos = np.where(gi == max_value)
dy = int(np.mean(max_pos[0]) - gi.shape[0] / 2)
dx = int(np.mean(max_pos[1]) - gi.shape[1] / 2)
# 更新位置信息
pos[0] = pos[0] + dx
pos[1] = pos[1] + dy
# trying to get the clipped position [xmin, ymin, xmax, ymax]
clip_pos[0] = np.clip(pos[0], 0, current_frame.shape[1])
clip_pos[1] = np.clip(pos[1], 0, current_frame.shape[0])
clip_pos[2] = np.clip(pos[0]+pos[2], 0, current_frame.shape[1])
clip_pos[3] = np.clip(pos[1]+pos[3], 0, current_frame.shape[0])
clip_pos = clip_pos.astype(np.int64)
# get the current fi..
fi = frame_gray[clip_pos[1]:clip_pos[3], clip_pos[0]:clip_pos[2]]
fi = pre_process(cv2.resize(fi, (init_gt[2], init_gt[3])))
# online update...
Ai = self.args.lr * (G * np.conjugate(np.fft.fft2(fi))) + (1 - self.args.lr) * Ai
Bi = self.args.lr * (np.fft.fft2(fi) * np.conjugate(np.fft.fft2(fi))) + (1 - self.args.lr) * Bi
#可视化跟踪序列图像
cv2.rectangle(current_frame, (pos[0], pos[1]), (pos[0]+pos[2], pos[1]+pos[3]), (255, 0, 0), 2)
cv2.imshow('demo', current_frame)
cv2.waitKey(100)
# if record... save the frames..
if self.args.record:
frame_path = 'record_frames/' + self.img_path.split('/')[1] + '/'
if not os.path.exists(frame_path):
os.mkdir(frame_path)
cv2.imwrite(frame_path + str(idx).zfill(5) + '.png', current_frame)
整个流程基本与mosse算法原理一致,对照着原理,把这个推导一遍,会更加好理解这个算法基本原理
utils.py里面主要就是对图像进行一些预处理比如图像线性映射,进行点乘余弦窗处理,使图像边缘慢慢变成零,图像反转操作等。
程序框图
以上就是整个mosse算法的原理及代码流程,最后附上一张流程框图,结合去看,会理解的更加深入。
先写到这里,第一次写博客,很多不是很完善,之后慢慢改。。