应用场景
- 相机静止,目标运动—背景提取(减除)
- 相机运动,目标静止—光流估计(全局运动)
- 相机和目标均运动—光流估计
光流估计基本思想
光流的概念是Gibson在1950年首先提出来的。它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。一般而言,光流是由于场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。其计算方法可以分为三类:
(1)基于区域或者基于特征的匹配方法;(L-K的方法)
(2)基于频域的方法;
(3)基于梯度的方法;
简单来说,光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”。光流的研究是利用图像序列中的像素强度数据的时域变化和相关性来确定各自像素位置的“运动”。研究光流场的目的就是为了从图片序列中近似得到不能直接得到的运动场。
光流法的前提假设:
(1)相邻帧之间的亮度恒定;
(2)相邻视频帧的取帧时间连续,或者,相邻帧之间物体的运动比较“微小”;
(3)保持空间一致性;即,同一子图像的像素点具有相同的运动。
光流估计基本模型
在每一像素
(
x
,
y
)
(x,y)
(x,y)处,
I
I
I表示像素的强度(值的大小),
t
t
t表示时间,有:
I
(
x
+
Δ
x
,
y
+
Δ
y
,
t
+
1
)
=
I
(
x
,
y
,
t
)
+
∂
I
∂
x
Δ
x
+
∂
I
∂
y
Δ
y
+
∂
I
∂
t
I(x+\Delta x, y+\Delta y, t+1) =I(x,y,t)+\frac{\partial I}{\partial x}\Delta x+\frac{\partial I}{\partial y}\Delta y+\frac{\partial I}{\partial t}
I(x+Δx,y+Δy,t+1)=I(x,y,t)+∂x∂IΔx+∂y∂IΔy+∂t∂I
因此有:
I
(
x
,
y
,
t
)
+
∂
I
∂
x
Δ
x
+
∂
I
∂
y
Δ
y
+
∂
I
∂
t
=
I
(
x
,
y
,
t
)
I(x,y,t)+\frac{\partial I}{\partial x}\Delta x+\frac{\partial I}{\partial y}\Delta y+\frac{\partial I}{\partial t}=I(x,y,t)
I(x,y,t)+∂x∂IΔx+∂y∂IΔy+∂t∂I=I(x,y,t)
⇒
\Rightarrow
⇒
∂
I
∂
x
Δ
x
+
∂
I
∂
y
Δ
y
+
∂
I
∂
t
=
0
\frac{\partial I}{\partial x}\Delta x+\frac{\partial I}{\partial y}\Delta y+\frac{\partial I}{\partial t}=0
∂x∂IΔx+∂y∂IΔy+∂t∂I=0
⇒
\Rightarrow
⇒
I
x
Δ
x
+
I
y
Δ
y
=
−
I
t
I_x\Delta x+I_y\Delta y=-I_t
IxΔx+IyΔy=−It
- 任务:求 ( u , v ) = ( Δ x , Δ y ) (u,v)=(\Delta x,\Delta y) (u,v)=(Δx,Δy)
- 困难:1个方程两个未知数
Lucas-Kanade(L-K)方法
根据上面的光流估计基本模型得出方程
I
x
Δ
x
+
I
y
Δ
y
=
−
I
t
I_x\Delta x+I_y\Delta y=-I_t
IxΔx+IyΔy=−It,任务:求
(
u
,
v
)
=
(
Δ
x
,
Δ
y
)
(u,v)=(\Delta x, \Delta y)
(u,v)=(Δx,Δy),困难:一个方程包含两个未知数。
Lucas-Kanade方法采用基于领域的计算方法,假设在一个小方格里的所有像素位移相同。则会得到一组光流估计方程。用矩阵表示如下:
[
I
x
1
I
y
1
I
x
2
I
y
2
⋮
⋮
]
[
u
v
]
=
−
[
I
t
1
I
t
2
⋮
]
\begin{bmatrix}I_{x1}&I_{y1}\\I_{x2}&I_{y2}\\\vdots&\vdots\end{bmatrix}\begin{bmatrix}u\\v\end{bmatrix} = -\begin{bmatrix}I_{t1}\\I_{t2}\\\vdots\end{bmatrix}
⎣⎢⎡Ix1Ix2⋮Iy1Iy2⋮⎦⎥⎤[uv]=−⎣⎢⎡It1It2⋮⎦⎥⎤
即
A
u
=
b
Au = b
Au=b
A
=
[
I
x
1
I
y
1
I
x
2
I
y
2
⋮
⋮
]
;
A = \begin{bmatrix}I_{x1}&I_{y1}\\I_{x2}&I_{y2}\\\vdots&\vdots\end{bmatrix};
A=⎣⎢⎡Ix1Ix2⋮Iy1Iy2⋮⎦⎥⎤;
u
=
[
u
v
]
;
b
=
−
[
I
t
1
I
t
2
⋮
]
u = \begin{bmatrix}u\\v\end{bmatrix}; b = -\begin{bmatrix}I_{t1}\\I_{t2}\\\vdots\end{bmatrix}
u=[uv];b=−⎣⎢⎡It1It2⋮⎦⎥⎤
-
将上述问题转化为最优化问题(超定方程求解)
m i n ∥ A u − b ∥ min\left\|Au - b\right\| min∥Au−b∥
-
最小二乘解:
u = ( A T A ) ( − 1 ) A T b u = (A^TA)^(-1)A^Tb u=(ATA)(−1)ATb
-
区域像素只有2个时,就是2元1次方程组求解!
多个像素,比如3*3时,则是求上述最小二乘解
进一步,L-K方法假定在一个小的图像邻域内速度近似一致
约束:
E ( Δ x , Δ y ) = ∑ i w i 2 ( I x i Δ x + I y i Δ y + I t i ) 2 m i n E ( Δ x , Δ y ) E(\Delta x, \Delta y) = \sum_iw_i^2(I_{xi}\Delta x + I_{yi}\Delta y + I_{ti})^2\\ min E(\Delta x, \Delta y) E(Δx,Δy)=i∑wi2(IxiΔx+IyiΔy+Iti)2minE(Δx,Δy)
对应
[ w 1 0 0 0 ⋱ 0 0 0 w N ] [ I x 1 I y 1 I x 2 I y 2 ⋮ ⋮ ] u = [ w 1 0 0 0 ⋱ 0 0 0 w N ] b \begin{bmatrix}w_1&0&0\\0&\ddots&0\\0&0&w_N\end{bmatrix}\begin{bmatrix}I_{x1}&I_{y1}\\I_{x2}&I_{y2}\\\vdots&\vdots\end{bmatrix}u = \begin{bmatrix}w_1&0&0\\0&\ddots&0\\0&0&w_N\end{bmatrix}b ⎣⎡w1000⋱000wN⎦⎤⎣⎢⎡Ix1Ix2⋮Iy1Iy2⋮⎦⎥⎤u=⎣⎡w1000⋱000wN⎦⎤b
类似前述求解,可得 u = ( A T W 2 A ) − 1 A T W 2 b u = (A^TW^2A)^{-1}A^TW^2b u=(ATW2A)−1ATW2b -
可信度判断:矩阵求逆是否能实现?
A T A = [ ∑ I x I x ∑ I x I y ∑ I x I y ∑ I y I y ] = ∑ [ I x I y ] [ I x I y ] = ∑ ∇ I ( ∇ I ) T A^TA = \begin{bmatrix}\sum I_xI_x&\sum I_xI_y\\\sum I_xI_y&\sum I_yI_y\end{bmatrix} = \sum\begin{bmatrix}I_x\\I_y\end{bmatrix}\begin{bmatrix}I_x&I_y\end{bmatrix} = \sum\nabla I(\nabla I)^T ATA=[∑IxIx∑IxIy∑IxIy∑IyIy]=∑[IxIy][IxIy]=∑∇I(∇I)T
我们在计算光流的时候,我们要求图像对应位置灰度变化充分(具有充分特征),假如位置灰度变化平缓,那么沿x和y方向的偏倒就可能为0,那么上述式子不可求逆,也就无法计算光流。
- 通过特征值判断是否计算可信
在数学上我们要判断矩阵是否可逆,我们可以通过计算特征值来判断。如果两个特征值远大于0,那么矩阵求逆是可靠的。假如有一个特征值接近于0的话,那么矩阵求逆是不可靠的。
下面我们举例说明:
如上图所示的邻域,沿x轴方向灰度变化平缓,沿y轴方向灰度变化剧烈,这样矩阵求逆是不可靠的。
上图的位置邻域两个方向灰度变化都很平缓,矩阵求逆也是不可靠的。
综合上面的分析,在一帧图像中,真正能有效计算L-K光流的特征很少,一般是两个方向灰度都变化剧烈的角点特征。我们也把这种计算光流的方法称为稀疏光流计算方法。
金字塔L-K方法
上面讲的L-K方法是针对于x和y方向位移都很小的情况,因为使用了泰勒展开,泰勒展开一定要在某一点的邻域进行展开。但是有时候相邻帧目标的位移很大,比如飞驰的汽车、飞机。这时候我们需要对L-K方法进行扩展。扩展的基本思路:采用图像金字塔方法。
首先有两帧原始图像
I
t
,
I
(
t
+
1
)
I_t, I_(t+1)
It,I(t+1),先对最顶层图像运行L-K方法,得到一个初始的位移估计值,然后进行对准和上采样,将分辨率提高1倍,此时对得到的较大的图像再运行L-K方法。重复上述步骤直至到达原始的分辨率图像。
下图是公式推导:
下面我们通过金字塔光流位移传播示意图来形象解释上述方法。
首先在第l层图像进行L-K运动估计,得到
u
l
,
v
l
u^l, v^l
ul,vl,由于l-1层图像的分辨率是l层的2倍,因此l层经过对准、上采样后,位移变成了
2
u
,
2
v
2u, 2v
2u,2v,然后在l-1层图中红点位置再运行L-K方法,得到
Δ
u
l
−
1
,
Δ
v
l
−
1
\Delta u^{l-1}, \Delta v^{l-1}
Δul−1,Δvl−1,循环上面操作,直至到原图像分辨率,通过这种方法就解决了原始L-K方法对于大位移无法估计的缺陷。
基于opencv示例代码
要求
使用光流法跟踪给定视频或摄像头中的运动特征点
解决思路
- 视频采集(取到视频中当前帧图像)
- 图像预处理
- 提取特征点
- 使用光流法估计特征点运动
- 相邻帧及特征点交换
找到好的特征点
void goodFeaturesToTrack(InputArray _image, OutputArray _corners, int maxCorners, double qualityLevel, double minDistance, InputArray_mask, int blockSize, bool useHarrisDetector, double harrisK)
_image, 8位或32位浮点型输入图像,单通道。
_corners, 保存检测的角点。
maxCorners, 角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点。
qualityLevel, 角点的品质因子,觉得角点可信度。
minDistance, 此邻域范围内如果存在更强的角点,则删除此角点。
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
const char *fn = "768x576.avi";
VideoCapture cap;
Mat source, result, gray, lastGray; // gray, lastGray对应本帧和上一帧灰度图
vector<Point2f> points[2]; // 对应上一帧和本帧的特征点,上一帧是给定的,本帧是预测结果,points[0]存储上一帧特侦点,points[1]存储本帧特征点
vector<uchar> status; // 每一特征点检测状态
vector<float> err; // 每一特征点计算误差
cap.open(fn);
if(!cap.isOpened())
{
cout << "无法打开视频。请检查文件或摄像头" << endl;
return -1;
}
for(;;)
{
cap >> source;
if(source.empty())
break;
cvtColor(source, gray, COLOR_BGR2GRAY);
// 以下是处理
if(points[0].size() < 10) // 点数太少,重新检测特征点
{
goodFeaturesToTrack(gray, points[0], 500, 0.01, 20);
}
if(lastGray.empty())
gray.copyTo(lastGray);
// 使用金字塔L-K方法计算光流
calcOpticalFlowPyrLK(lastGray, gray, points[0], points[1], status, err);
// 下面是删除掉误判点
int counter = 0;
for(int i = 0; i < points[1].size(); i++)
{
double dist = norm(points[1][i] - points[0][i]);
if(status[i] && dist >= 2.0 && dist <= 20.0) // 合理的特征追踪点
{
points[0][counter] = points[0][i];
points[1][counter++] = points[1][i];
}
}
// 由于删除了误判点,因此要改变一下数组大小
points[0].resize(counter);
points[1].resize(counter);
// 显示特征点和运动轨迹
source.copyTo(result);
for(int i = 0; i < points[1].size(); i++)
{
line(result, points[0][i], points[1][i], Scalar(0, 0, 0xff));
circle(result, points[1][i], 3, Scalar(0, 0xff, 0));
}
// 为了下一帧的估计,交换当前帧和上一帧
swap(points[0], points[1]);
swap(lastGray, gray);
imshow("源图像", source);
imshow("检测结果", result);
// 以下检测是否终止(按下esc终止,对应ASCII 27)
char key = waitKey(100);
if(key == 27)
break;
}
waitKey(0);
return 0;
}
结果分析
从检测结果看出一帧图像中真正能有效计算L-K光流的特征很少,一般是两个方向灰度都变化剧烈的角点特征。从估计结果来看,L-K光流估计是有效的,效果还不错。