大概分为: preprocess -> instant flow compute -> filter correct 三个步骤
计算连续两帧的光流
| 算法 | 改进 | 公式 | 效果 | 存储占用 |
|---|---|---|---|---|
| LK Lucas-Kanade |
对图像$I$ 的每个像素, 有 $\frac{\partial I}{\partial x}dx+\frac{\partial I}{\partial y}dy=\frac{dI}{dt}$ 即, $\begin{bmatrix}dx \\ dy\end{bmatrix}=\begin{bmatrix}\frac{\partial I}{\partial x}\frac{\partial I}{\partial x} & \frac{\partial I}{\partial x}\frac{\partial I}{\partial y} \\ \frac{\partial I}{\partial x}\frac{\partial I}{\partial y} & \frac{\partial I}{\partial y}\frac{\partial I}{\partial y} \end{bmatrix}^{-1}\begin{bmatrix}\frac{\partial I}{\partial x}\frac{dI}{dt} \\ \frac{\partial I}{\partial y}\frac{dI}{dt}\end{bmatrix}=H^{-1}\begin{bmatrix}\frac{\partial I}{\partial x}\frac{dI}{dt} \\ \frac{\partial I}{\partial y}\frac{dI}{dt}\end{bmatrix}$ 其中$\frac{\partial I}{\partial x}\approx I(x+1,y)-I(x,y)$ |
只在光流在0-1附近有效(即subpixel的尺度) 和$\frac{\partial I}{\partial x}$ 的计算方式有关 |
||
| LK_MEAN_NORM |
把$\frac{dI}{dt}$ 改为 $\frac{dI}{dt}-mean(\frac{dI}{dt})$ ($mean(\frac{dI}{dt})$ 表示整体亮度的变化, 和光流无关) |
解决亮度变化的情况 | ||
| @pre_shift | 先对齐到上一次计算的光流位置 | |||
| @compute_pyd | 把图像分为多层下采样计算 | |||
| LK-DIS dense inverse search 结合两种方法 |
分块计算 - 每块都迭代计算光流 - 先按整像素移动到ssd最小的位置 - 再用LK_MEAN_NORM的方法不断微调计算光流 - 按照光流计算的方向对齐, 计算ssd - ssd不再变小就跳出循环 用所有分块的平均光流作为最终结果 |
比LK更稳定 | 需要所有patch的Hessian, dx, dy矩阵 对齐patch时移动patch的中间结果 |
|
| 像素neighbor patch比对 |
$diff=dist(F(I_1(x,y))-F(I_2(x+dx,y+dy)))$ $argmin_{(dx,dy)}\sum_{x,y}dist(F(I_1(x,y))-F(I_2(x+dx,y+dy)))$ 其中, $F$ 是特征提取器, dist是距离函数 |
|||
| : 特征 | SAD_SIMPLE sum of average differences |
$sad=L_1(diff)$ 或者$sad=L_1(diff-mean(diff))$ |
第二种更好 | |
| SAD_SUBPIXEL 用多项式拟合sad平面 (opencv Farneback 也是用多项式拟合, 不过拟合的是原图像) |
$P(x+\Delta x)\approx P(x)+P^\prime(x)\Delta x+\frac{P^{\prime\prime}(x)}{2}\Delta x^2$ 即, $\Delta x=-\frac{P^\prime(x)}{P^{\prime\prime}(x)}$ 其中 $P^\prime(x)\approx \frac{dist(x+1,y)-dist(x-1,y)}{2}$ , $P^{\prime\prime}$ 类似 - dx, dy分开计算 - 直接用多项式代入也是等价的 |
没用 可能是因为subpixel的部分不符合多项式 |
||
| SAD_BLUR | 先对图像做2x2的blur 😮💨因为觉得不准的地方是不是因为刚好对齐的地方在像素的中间 |
垃圾 |
||
| : 距离 |
SSD_SIMPLE sum of squared differences |
$ssd=Var(diff)$ | 比SAD稳定 | |
| SAD_BINARY | $hamming=popcount(ref\ \hat\ current)$ 其中图像是binary(image-mean(image)) |
效果明显变差 binary有没有必要呢? |
||
| BINARY_FEATURE | 用gradient descent训练一个特征 | |||
| BINARY_FEATURE_BOOST | 用adaboost训练一个特征 | |||
| : neighbor | SAD_SIMPLE_CROSS neighbor变成cross形状, 节省一些存储 |
节省存储&计算 |
||
| SAD_SPIRAL | - 从上一次的光流位置向外螺旋状计算 - 遇到更小的distance提前结束循环 |
节省存储&计算 和LK效果类似 应该是都用了preshift的原因 |
||
| phase_correlation | $dx,dy=argmax_{x,y} F^{-1}(\frac{F(I_1)\cdot \bar{F(I_2)}}{|F(I_1)\cdot \bar{F(I_2)}|})$ 相当于提取图像的phase部分 然后用cross correlation的dist |
在位移太大时效果不好 光流在0-6时效果可以 另外如果不做phase correlation, 直接做cross correlation 效果并不好 |
需要ref和current的fft频域 |
测试
- 仿真数据: 距离仿真数据的位置
- 真实数据:
- 稳定性: 电机带动匀速转动
- 响应速度: 电机急停急转
一些结果$\downarrow$

- 对于测试场景来说, 所有subpixel的方法似乎都没有必要
- phase correlation > 训练得到的几种特征平移匹配 $\approx$ sad(-mean(diff)) $\approx$ ssd > sad binary » sad spiral $\approx$ LK
- 对图像做detail的提取(如image-blur(image,(6x6))) 之后, sad的结果得到提高, 和训练得到的特征类似
- 几个特征平移方法出现错误的地方可能因为超出了搜索范围导致的
- 平移后diff的例子:

- 响应的测试: 除了lk会反应慢一些, 其他都在可以接受范围内
- 这真是个特别枯燥的工作😑, 很多方法觉得, 啊, 应该不会有效果的, 但还是想着, 坚持着写出来测一下究竟差在哪里吧
几种opencv支持的方法测试
- 代码: flow_opencv.py
- 复现修改opencv的算法, 有时需要opencv代码的中间结果
- 因为我没有下载opencv完整的源码, 而是用pip装opencv的库
- 用
pkg-config --cflags --libs opencv4查看opencv的lib,include path - 单独把要复现的函数复制一个.cpp, 就可以随便打印中间结果了
有用的链接
[1] https://dsp.stackexchange.com/questions/16995/image-reconstructionphase-vs-magnitude 关于图像phase部分的提问
[2] https://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/OWENS/LECT7/node2.html 解释图像边缘部分的Phase congruency 更强
用Phase Congruency提取图像边缘的结果