![]() |
OpenCV 4.12.0
开源计算机视觉
|
光流是由于物体或相机的运动,在两个连续帧之间图像物体表观运动的模式。它是一个 2D 向量场,其中每个向量都是一个位移向量,显示了点从第一帧到第二帧的运动。考虑下面的图像(图片由 维基百科关于光流的文章 提供)。
它显示了一个球在 5 个连续帧中的运动。箭头显示了它的位移向量。光流在许多领域都有应用,例如:
光流基于以下几个假设:
考虑第一帧中的像素 \(I(x,y,t)\) (注意这里添加了一个新的维度,时间。之前我们只处理图像,所以不需要时间)。它在 \(dt\) 时间后的下一帧中移动了 \((dx,dy)\) 的距离。所以由于这些像素是相同的,并且强度没有改变,我们可以说:
\[I(x,y,t) = I(x+dx, y+dy, t+dt)\]
然后对右侧进行泰勒级数近似,删除共同项并除以 \(dt\) 以得到以下等式:
\[f_x u + f_y v + f_t = 0 \;\]
其中
\[f_x = \frac{\partial f}{\partial x} \; ; \; f_y = \frac{\partial f}{\partial y}\]
\[u = \frac{dx}{dt} \; ; \; v = \frac{dy}{dt}\]
上面的等式称为光流方程。其中,我们可以找到 \(f_x\) 和 \(f_y\),它们是图像梯度。类似地,\(f_t\) 是沿时间的梯度。但是 \((u,v)\) 是未知的。我们无法用两个未知变量求解这一个方程。因此,提供了几种解决这个问题的方法,其中一种是 Lucas-Kanade 方法。
我们之前已经看到一个假设,即所有相邻像素将具有相似的运动。 Lucas-Kanade 方法在点周围取一个 3x3 的patch。所以所有 9 个点都有相同的运动。我们可以为这 9 个点找到 \((f_x, f_y, f_t)\)。所以现在我们的问题变成了求解 9 个具有两个未知变量的方程,这是一个超定问题。 使用最小二乘拟合方法可以获得更好的解决方案。下面是最终的解决方案,这是一个二元二方程问题,通过求解得到解决方案。
\[\begin{bmatrix} u \\ v \end{bmatrix} = \begin{bmatrix} \sum_{i}{f_{x_i}}^2 & \sum_{i}{f_{x_i} f_{y_i} } \\ \sum_{i}{f_{x_i} f_{y_i}} & \sum_{i}{f_{y_i}}^2 \end{bmatrix}^{-1} \begin{bmatrix} - \sum_{i}{f_{x_i} f_{t_i}} \\ - \sum_{i}{f_{y_i} f_{t_i}} \end{bmatrix}\]
(检查逆矩阵与 Harris 角点检测器的相似性。它表示角点是更好的跟踪点。)
所以从用户的角度来看,想法很简单,我们给出一些要跟踪的点,我们会收到这些点的光流向量。但是,仍然存在一些问题。到目前为止,我们一直在处理小动作。所以当有大动作时它会失败。所以我们再次使用金字塔。当我们在金字塔中向上移动时,小动作会被移除,大动作会变成小动作。所以在那里应用 Lucas-Kanade 方法,我们得到光流以及尺度。
我们使用以下函数: cv.calcOpticalFlowPyrLK (prevImg, nextImg, prevPts, nextPts, status, err, winSize = new cv.Size(21, 21), maxLevel = 3, criteria = new cv.TermCriteria(cv.TermCriteria_COUNT+ cv.TermCriteria_EPS, 30, 0.01), flags = 0, minEigThreshold = 1e-4)。
| prevImg | 第一个 8 位输入图像或由 buildOpticalFlowPyramid 构建的金字塔。 |
| nextImg | 第二个输入图像或与 prevImg 大小和类型相同的金字塔。 |
| prevPts | 需要找到流的 2D 点向量;点坐标必须是单精度浮点数。 |
| nextPts | 输出 2D 点向量(具有单精度浮点坐标),包含第二个图像中输入特征的计算出的新位置;当传递 cv.OPTFLOW_USE_INITIAL_FLOW 标志时,该向量的大小必须与输入相同。 |
| status | 输出状态向量(无符号字符);如果已找到相应特征的流,则向量的每个元素都设置为 1,否则设置为 0。 |
| err | 输出错误向量;向量的每个元素都设置为相应特征的错误,错误的类型可以在 flags 参数中设置;如果未找到流,则未定义错误(使用 status 参数查找此类情况)。 |
| winSize | 每个金字塔级别的搜索窗口大小。 |
| maxLevel | 基于 0 的最大金字塔级别数;如果设置为 0,则不使用金字塔(单层),如果设置为 1,则使用两层,依此类推;如果金字塔传递给输入,则算法将使用与金字塔一样多的级别,但不超过 maxLevel。 |
| criteria | 参数,指定迭代搜索算法的终止标准(在指定的 criteria.maxCount 最大迭代次数之后,或者当搜索窗口移动小于 criteria.epsilon 时。 |
| flags | 操作标志
|
| minEigThreshold | 该算法计算光流方程的 2x2 正态矩阵的最小特征值,除以窗口中的像素数;如果此值小于 minEigThreshold,则会滤除相应的特征,并且不会处理其流,因此可以消除坏点并提高性能。 |
(此代码不检查下一个关键点是否正确。所以即使任何特征点在图像中消失,光流也有可能找到看起来接近它的下一个点。所以实际上,对于鲁棒的跟踪,应该在特定的时间间隔检测角点。)
Lucas-Kanade 方法计算稀疏特征集的光流(在我们的示例中,是使用 Shi-Tomasi 算法检测到的角点)。OpenCV.js 提供了另一种算法来找到稠密光流。它计算帧中所有点的光流。它基于 Gunnar Farneback 的算法,该算法在 Gunnar Farneback 于 2003 年发表的“基于多项式展开的双帧运动估计”中进行了解释。
我们使用以下函数: cv.calcOpticalFlowFarneback (prev, next, flow, pyrScale, levels, winsize, iterations, polyN, polySigma, flags)
| prev | 第一个 8 位单通道输入图像。 |
| next (下一个) | 与 prev 相同大小和类型的第二个输入图像。 |
| flow | 计算出的流图像,其大小与 prev 相同,类型为 CV_32FC2。 |
| pyrScale | 参数,指定用于构建每个图像的金字塔的图像比例(<1); pyrScale=0.5 表示经典金字塔,其中每个下一层都比前一层小两倍。 |
| levels | 金字塔层数,包括初始图像; levels=1 表示不创建额外的层,只使用原始图像。 |
| winsize | 平均窗口大小;较大的值会增加算法对图像噪声的鲁棒性,并为快速运动检测提供更多机会,但会产生更模糊的运动场。 |
| iterations | 算法在每个金字塔级别执行的迭代次数。 |
| polyN | 用于在每个像素中找到多项式展开的像素邻域的大小;较大的值意味着图像将以更平滑的表面近似,从而产生更鲁棒的算法和更模糊的运动场,通常 polyN =5 或 7。 |
| polySigma | 高斯函数的标准差,用于平滑用作多项式展开基础的导数;对于 polyN=5,可以设置 polySigma=1.1,对于 polyN=7,一个好的值是 polySigma=1.5。 |
| flags | 可以是以下组合的操作标志
|