多目标跟踪常用概念总结
目前,目标跟踪作为计算机视觉中的一个重要的研究课题,在民用和军事等很多方向有着广泛的应用前景,主要包括自动驾驶、精准制导、视频监控等,而在这些领域寻求一定的精度和速度以及鲁棒性指标具有重要的工程意义。现在主要研究的目标跟踪算法一般是在给定一个视频序列的第一帧中目标位置的基础上,对后续帧中的原始目标进行跟踪。由于目标抖动、局部遮挡、背景光照变化、形状改变、快速运动等原因,在该领域仍然具有很大的挑战。
跟踪方法分类
SOTA模型是指在当前领域中表现最好的模型。SOTA(State-Of-The-Art)模型是指在某个研究任务中,相对于其他模型具有最佳性能的模型。
现有的目标跟踪算法主要有两大类:分别是
- 生成式方法
生成式方法运用生成模型描述目标的外观特征,之后通过对候选区域进行搜索寻找与目标最接近的候选区域作为跟踪的结果。比较常用的方法有卡尔曼滤波、粒子滤波和均值滤波等等。但这种方法,只使用了目标本身的一些特征,没有利用背景信息,所以在目标自身发生剧烈变化和遮挡时,跟踪结果会产生漂移。
- 判别式方法
判别式方法使用图像特征+机器学习的套路,在当前帧目标附近采集正负样本(目标为正样本,背景为负样本),使用这些正负样本训练一个分类器,下一帧用训练好的分类器,找出最后的候选区域作为目标的位置。现在很多跟踪方法都采用判别式方法
,大部分深度学习目标跟踪也是采用这个套路的。由于判别式方法充分利用目标和背景信息,所以在跟踪过程中的表现更具鲁棒性。
按照任务计算类型又可以分为以下2类
- 在线跟踪:在线跟踪需要实时处理任务,通过过去和现在帧来跟踪未来帧中物体的位置。
- 离线跟踪 :离线跟踪是离线处理任务,可以通过过去、现在和未来的帧来推断物体的位置,因此准确率会在线跟踪高。
总结: 生成式目标跟踪方法与相关滤波的判别式目标跟踪方法有几个关键的区别:
模型概念
:
生成式方法:生成式跟踪方法的核心思想是建模目标的外观,并尝试在后续帧中重新找到这个外观。它通常通过建立一个目标的外观模型,然后在新的帧中寻找与该模型最相似的区域。
判别式方法(如相关滤波):判别式方法是基于区分目标和背景的思想。它学习一个分类器或者滤波器来区分目标和周围的背景。相关滤波跟踪算法通过训练相关滤波器来最大化目标和非目标之间的响应差异。
目标定位
:
生成式方法:在新的帧中,生成式方法通常通过寻找与模型最相似的区域来定位目标,通常使用搜索窗口在整个图像中搜索。
判别式方法:相关滤波算法使用滤波器在目标周围的区域进行卷积,产生一个响应图,并在这个响应图中找到最大响应值对应的位置作为目标的新位置。
处理遮挡和变化
:
生成式方法:生成式方法依赖于目标的外观模型,如果目标发生显著变化或遭受遮挡,这种方法可能会丢失目标。
判别式方法:相关滤波算法能够在线更新滤波器来适应目标的外观变化,这使得它们在处理遮挡和外观变化时通常更为鲁棒。
实时性:
生成式方法:尽管一些生成式方法可以实现快速跟踪,但是它们在搜索过程中可能会受到计算效率的限制。
判别式方法:相关滤波算法特别适合于实时跟踪,因为它们在频域中进行计算,可以利用快速傅里叶变换来加速响应图的生成。
背景信息
:
生成式方法:这种方法主要关注于目标的表示,没有显式地利用背景信息。
判别式方法:相关滤波以及其他判别式方法通过考虑背景,学习一个可以区分目标和背景的滤波器。
光流法
光流法和背景减除法是计算机视觉的基础概率,之前对图像以及计算机视觉的相关内容了解较少,作为铺垫对光流法进行学习。
定义:
光流法利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性,根据上一帧与当前帧之间的对应关系,计算得到相邻帧之间物体的运动信息。
缺点
:
大多数的光流计算方法计算量巨大,结构复杂,且易受光照、物体遮挡或图像噪声的影响,鲁棒性差,故一般不被对精度和实时性要求比较高的监控系统所用
光流法的假设原理
光流法基于一下的两种假设
- 在连续的两帧图像之间(目标对象的)像素的灰度值不改变。(
光强不变假设
)
I ( x , y , t ) = I ( x + δ x , y + δ y , t + δ t ) I(x, y, t)=I(x+\delta x, y+\delta y, t+\delta t) I(x,y,t)=I(x+δx,y+δy,t+δt)
进行一阶的多元泰勒展开得到下面的式子:
I ( x + δ x , y + δ y , t + δ t ) = I ( x , y , t ) + ∂ I ∂ x δ x + ∂ I ∂ y δ y + ∂ I ∂ t δ t + … I(x+\delta x, y+\delta y, t+\delta t)=I(x, y, t)+\frac{\partial I}{\partial x} \delta x+\frac{\partial I}{\partial y} \delta y+\frac{\partial I}{\partial t} \delta t+\ldots I(x+δx,y+δy,t+δt)=I(x,y,t)+∂x∂Iδx+∂y∂Iδy+∂t∂Iδt+…
∂ I ∂ x δ x + ∂ I ∂ y δ y + ∂ I ∂ t δ t = 0 \frac{\partial I}{\partial x} \delta x+\frac{\partial I}{\partial y} \delta y+\frac{\partial I}{\partial t} \delta t=0 ∂x∂Iδx+∂y∂Iδy+∂t∂Iδt=0
最后两边同时除以diet可以得到:
∂ I ∂ x u + ∂ I ∂ y v + ∂ I ∂ t = 0 \frac{\partial I}{\partial x} u+\frac{\partial I}{\partial y} v+\frac{\partial I}{\partial t}=0 ∂x∂Iu+∂y∂Iv+∂t∂I=0
- 相邻的像素具有相同的运动.
分为两种:
-
稠密光流算法:涉及到所有的像素计算量巨大。
-
稀疏光流算法:只对角点或者是边界点的一些像素进行计算和跟踪。
Opencv实现光流估计
光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。
-
亮度恒定:同一点随着时间的变化,其亮度不会发生改变。
-
小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
-
空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
用光流估计来完成对行人目标的跟踪示意图如下所示。(从小的方面来看就是1号点到5号点的位置关系。)
那么我们该如何的求光流的一个方向和实际的一个大小呢? 由此引入了第一个算法。
Lucas-Kanade 算法 (卢卡斯—卡纳德算法)
约束方程:
I ( x , y , t ) = I ( x + d x , y + d y , t + d t ) = I ( x , y , t ) + ∂ I ∂ x d x + ∂ I ∂ y d y + ∂ I ∂ t d t I x d x + I y d y + I t d t = 0 \begin{array}{l} I(x, y, t)=I(x+d x, y+d y, t+d t) \\ =I(x, y, t)+\frac{\partial I}{\partial x} d x+\frac{\partial I}{\partial y} d y+\frac{\partial I}{\partial t} d t \\ I_{x} d x+I_{y} d y+I_{t} d t=0 \end{array} I(x,y,t)=I(x+dx,y+dy,t+dt)=I(x,y,t)+∂x∂Idx+∂y∂Idy+∂t∂IdtIxdx+Iydy+Itdt=0
I x u + I y v = − I t ⇒ [ I x I y ] ⋅ [ u v ] = − I t I_{x} u+I_{y} v=-I_{t} \Rightarrow\left[I_{x} I_{y}\right] \cdot\left[\begin{array}{l} u \\ v \end{array}\right]=-I_{t} Ixu+Iyv=−It⇒[IxIy]⋅[uv]=−It
我们的目标是要得到方向向量u和速度v用来做光流估计,此时我们对于一帧的数据进行操作,只得到了一个方程,但是求解两个未知数,因此我们还要在建立另外的一个方程。
在图像处理中,角点通常被认为是两个边缘的交汇点,其中边缘是图像亮度的突然变化。角点被认为是图像中的重要特征,因为它们在平移、旋转和光照变化时相对稳定。
推导求解
但是仔细一想就会发现,对于同一个物体(整体来说)它包含很多的点,这些点每一帧移动的过程中u和v都是相同的,我们就可以根据这些点来建立很多的方程。
→ [ I x 1 I y 1 I x 2 I y 2 ⋮ ] [ u v ] = − [ I t 1 I t 2 ⋮ ] \rightarrow\left[\begin{array}{c} I_{x 1} I_{y 1} \\ I_{x 2} I_{y 2} \\ \vdots \end{array}\right]\left[\begin{array}{l} u \\ v \end{array}\right]=-\left[\begin{array}{c} I_{t 1} \\ I_{t 2} \\ \vdots \end{array}\right] → Ix1Iy1Ix2Iy2⋮ [uv]=− It1It2⋮
其中的x1 y1指的就是其中的第一个点,x2 y2指的就是其中的第二个点的x y依次类推。
将上面的公式写为矩阵的形式(我们假设有25个点) A是 25x2的矩阵 u是2x1的矩阵
A u → = b A \overrightarrow{\mathrm{u}}=b Au=b
A T A ⏟ 2 × 2 u → ⏟ 2 × 1 = A T b ⏟ 2 × 1 \underbrace{A^{T} A}_{2 \times 2} \underbrace{\overrightarrow{\mathrm{u}}}_{2 \times 1}=\underbrace{A^{T} b}_{2 \times 1} 2×2 ATA2×1 u=2×1 ATb
结合最小二乘法的方法进行推导可以得到下面的形式,同时我们还要确保矩阵是可逆的
u → = ( A T A ) − 1 A T b \overrightarrow{\mathrm{u}}=\left(A^{T} A\right)^{-1} A^{T} b u=(ATA)−1ATb
A T A = [ ∑ I x 2 ∑ I x I y ∑ I x I y ∑ I y 2 ] A^{T} A=\left[\begin{array}{cc} \sum I_{x}^{2} & \sum I_{x} I_{y} \\ \sum I_{x} I_{y} & \sum I_{y}^{2} \end{array}\right] ATA=[∑Ix2∑IxIy∑IxIy∑Iy2]
根据对称矩阵和特征值的相关的理论就可以发现,当这两个点是角点的时候就一定满足可逆的条件。
所以做光流估计的时候我们使用的点基本上都是选用的角点来进行实现的
- 我们做的时候首先要进行一个角点检测。
- 将我们的角点传进去。
函数调用
cv2.calcOpticalFlowPyrLK():
参数:
prevImage 前一帧图像
nextImage 当前帧图像
prevPts 待跟踪的特征点向量
winSize 搜索窗口的大小
maxLevel 最大的金字塔层数
返回:
nextPts 输出跟踪特征点向量
status 特征点是否找到,找到的状态为1,未找到的状态为0
光流估计代码实战
经过修改之后的代码如下所示,就可以查看使用光流估计的实际效果了
import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')
# 角点检测所需参数
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7)
# lucas kanade参数
lk_params = dict( winSize = (15,15),
maxLevel = 2)
# 随机颜色条
color = np.random.randint(0,255,(100,3))
# 拿到第一帧图像
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 返回所有检测特征点,需要输入图像,角点最大数量(效率),品质因子(特征值越大的越好,来筛选)
# 距离相当于这区间有比这个角点强的,就不要这个弱的了
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# 创建一个mask
mask = np.zeros_like(old_frame)
while(True):
ret,frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 需要传入前一帧和当前图像以及前一帧检测到的角点
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# st=1表示
good_new = p1[st==1]
good_old = p0[st==1]
# 绘制轨迹
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
# 确保坐标是整数
a, b, c, d = map(int, [a, b, c, d])
mask = cv2.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv2.add(frame,mask)
cv2.imshow('frame',img)
k = cv2.waitKey(150) & 0xff
if k == 27:
break
# 更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cv2.destroyAllWindows()
cap.release()
我们使用光流估计的算法来进行目标的跟踪就会有一个明显的缺陷:即对于新出现的人或者是物体无法的进行跟踪,因为我们在代码中只是传入了第一帧的角点,只会对第一帧之后的图像来进行跟踪操作。对于我们新出现的图像并不执行跟踪。
一旦出现遮挡的现象就会发生跟踪丢失的现象,之后的光流点(角点)不会在进行跟踪了。
总结:推荐阅读的参考博客