为啥 cv2.calcOpticalFlowFarneback 在简单的合成示例上失败?

Posted

技术标签:

【中文标题】为啥 cv2.calcOpticalFlowFarneback 在简单的合成示例上失败?【英文标题】:Why does cv2.calcOpticalFlowFarneback fail on simple synthetic examples?为什么 cv2.calcOpticalFlowFarneback 在简单的合成示例上失败? 【发布时间】:2017-10-02 08:22:03 【问题描述】:

cv2.calcOpticalFlowFarneback 似乎在自然图像上工作得很好,但如果我在简单的合成示例上尝试它,例如下面的一个,它认为没有流量:

import cv2
import numpy as np

a = np.zeros((10, 10), dtype=np.uint8); a[1:4] = 127; a[2] = 255; a

等于

array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [127, 127, 127, 127, 127, 127, 127, 127, 127, 127],
       [255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
       [127, 127, 127, 127, 127, 127, 127, 127, 127, 127],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0]], dtype=uint8)

b = np.roll(a, 1, 0); b

等于

array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [127, 127, 127, 127, 127, 127, 127, 127, 127, 127],
       [255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
       [127, 127, 127, 127, 127, 127, 127, 127, 127, 127],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0]], dtype=uint8)

流程:

flow = cv2.calcOpticalFlowFarneback(a, b, pyr_scale=0.5, levels=3, winsize=15, iterations=3, poly_n=5, poly_sigma=1.2, flags=0)

本质上是0,因为

np.abs(flow).max()

计算为

1.3305091e-13

我尝试了不同的winsize 值,结果相似。

为什么会这样?还有其他参数在这里会更好吗?

(我的 OpenCV 版本是 2.4.8。版本 3 需要添加 None 作为第三个参数,我相信)

【问题讨论】:

是否适合winsize = 15 大小为(10,10) 的“图像”?此外,当运动纯粹是仿射时,您的算法背后的算法似乎真的不喜欢 - 它使最小二乘算法背后的矩阵不可逆。见第4页底部here @DanielF 不确定哪个 winsize 合适,但 flow 与其他 winsize 值(例如 2 或 5)基本上仍然为 0(例如 2 或 5) 【参考方案1】:

原因是等式 19、20、23 和 25 中的here[1]。

值得注意的是,(抱歉,没有mathJax grumble,很难写出方程式)

I_x[x,y] = (A[x-1, y] - A[x+1, y]) / 2  # Equation 19

这在您的示例中减少为 np.zeros((10,10)),这会导致后续问题:

G = sum([[I_x**2, I_x * I_y],[I_x * I_y, I_y**2]], axis = (2,3))  # Equation 23

由于I_x 为零,这意味着G 采用这种形式

G = [[0, 0], [0, I_y**2]]

无处不在,这是一个奇异矩阵。由于需要反转,所以求解器卡住了。

之后发生的事情很难理解(我无法很好地阅读 c 以深入研究 openCV 核心代码),但根据 minEigThreshold 参数的文档,似乎跳过了奇异矩阵为calcOpticalFlowPyrLK。这可能意味着您的输出是缓冲区垃圾,或者至少是缓冲区垃圾的一些高斯混合。

这也是@JulioDanielReyes 在添加噪声参数时能够得到响应的原因 - 这添加了足够的 I_x 术语以使 G 非单数。

参考:

[1] Lucas Kanade 特征跟踪器的金字塔实现 算法描述,Jean-Yves Bouguet

【讨论】:

第一个 eq 中缺少括号?【参考方案2】:

我不知道这是否会回答你的问题,但是除了winsize太大之外,算法很难知道这样规则的数字在哪个方向(左/右)移动。

尝试添加一些噪音,例如a[4,4] += 1; 在调用滚动之前,您会看到很大的不同。

编辑:添加我的结果

print(np.abs(flow).max())

没有噪音:

winsize 15: 1.33051e-13
winsize 2: 6.00387e-11

噪音 1:

# a[4,4] += 1; 
winsize 15: 0.00332422
winsize 2: 1.82871

噪音 2:

# noise = np.round(np.random.random(a.shape) * 2.0).astype(np.int8)
# a = a + noise;
winsize 15: 0.207728
winsize 2: 324.527

【讨论】:

以上是关于为啥 cv2.calcOpticalFlowFarneback 在简单的合成示例上失败?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?

为啥 CAP 定理中的 RDBMS 分区不能容忍,为啥它可用?