经典的光流
经典的光流估计的基本准则是在满足一定前提条件下,最小化能量值。一般情况下,大位移的光流依赖于刚性匹配或者小区域的匹配算法,所用的特征点一般是基于Hog或者sift描述子,采用最近邻方法匹配。
每个像素的运动被限制在几个像素以内。大位移光流覆盖了运动不受限制且比物体尺寸更大的更真实情况。为了改善类似问题,LK光流又增加了图像金字塔来针对处理大位移的光流的情况。
缺点是
1,基于块区域内是光流的一致性,如果在这个块区域内有着不同方向的位移像素,此时这种方法是无法解决的。
2,在纹理不明显的地方这种方法是不可靠的。
稠密光流----Farneback
在2003年提出来的,它是基于前后两帧所有像素点的移动估算算法,其效果要比稀疏光流算法更好。
稀疏的关键点是计算角点,稠密的关键点是找图像上所有移动的点。
论文“Two-Frame Motion Estimation Based on Polynomial Expansion”
1、将图像视为二维信号的函数(输出图像是灰度图像),因变量是二维坐标位置 [公式] ,并且利用二次多项式对于图像进行近似建模的话,会得到:
其中,
A 是一个2×2的对称矩阵(是通过像素的邻域信息的最小二乘加权拟合得到的,权重系数与邻域的像素大小和位置有关)
b是一个2×1的矩阵向量;
c为标量;
因此系数化之后,以上公式等号右侧可以写为 :
2、如果将原有(笛卡尔坐标系)图像的二维信号空间,转换到以
(
1
,
x
,
y
,
x
2
,
y
2
,
x
y
)
(1,x,y,x^2 , y^2,xy)
(1,x,y,x2,y2,xy)
作为基函数的空间,则表示图像需要一个六维向量作为系数,代入不同像素点的位置 x,y求出不同像素点的灰度值。
源码解读可参考:https://blog.csdn.net/ironyoung/article/details/60884929
#!/usr/bin/env python
'''
# author:lowkeyway
# This file creat for test calcOpticalFlowFarneback API
'''
import sys
import cv2 as cv
import numpy as np
fb_params = dict(pyr_scale = 0.5,
levels = 3,
winsize = 15,
iterations = 3,
poly_n = 5,
poly_sigma = 1.2,
flags = 0)
class app:
def __init__(self, src):
self.cap = cv.VideoCapture(src)
def run(self):
ret, preFrame = self.cap.read()
if ret is not True:
return
preFrameGary = cv.cvtColor(preFrame, cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(preFrame)
hsv[..., 1] = 255
while True:
ret, curFrame = self.cap.read()
if ret is not True:
break
curFrameGary = cv.cvtColor(curFrame, cv.COLOR_BGR2GRAY)
flow = cv.calcOpticalFlowFarneback(preFrameGary,curFrameGary, None, **fb_params)
mag, ang = cv.cartToPolar(flow[..., 0], flow[..., 1])
hsv[..., 0] = ang * 180 / np.pi / 2
hsv[..., 2] = cv.normalize(mag, None, 0, 255, cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv, cv.COLOR_HSV2BGR)
cv.imshow("Frame", bgr)
ch = cv.waitKey(30) & 0xff
if ch == 27:
break
preFrameGary = curFrameGary
self.cap.release()
def main_func(argv):
try:
videoSrc = sys.argv[1]
except:
videoSrc = "vtest.avi"
app(videoSrc).run()
if __name__ == '__main__':
print(__doc__)
main_func(sys.argv)
- https://zhuanlan.zhihu.com/p/89615274
DeepMatching
DeepMatching是jerome revaud在2013年开发的一种匹配算法。其目的是计算两幅图像之间的密集对应关系。深度匹配依赖于为匹配图像而设计的深层、多层、卷积结构。它可以处理非刚性变形和重复纹理,因此可以在图像之间存在显著变化的情况下有效地确定稠密对应关系。
在这个匹配算法的基础上又改进了很多其他方面的计算,相关论文主要有:
《DeepMatching: Hierarchical Deformable Dense Matching》
《DeepFlow:Large displacement optical flow with deep matching》
《EpicFlow: Edge-Preserving Interpolation of Correspondences for Optical Flow》
《Fast Optical Flow using Dense Inverse Search》
稠密光流 DeepFlow
稠密光流算法。该算法已经集成到OpenCV中,算法介绍网址:
- http://lear.inrialpes.fr/src/deepmatching/
- https://cloud.tencent.com/developer/article/1512072
DeepFlow可以说是DeepMatching算法的改进算法,将匹配算法与变分方法相结合,应用于光流的计算,是一种适应光流问题的描述子匹配算法,可以提高光流法在快速运动的表现。
算法的具体步骤
1,给定两幅图像,我们首先从第一幅图像中提取小的4x4pixels块。
2,使用第一幅图中的patch与第二幅图patch进行卷积
3,此时我们得到了这个patch的响应Map
4,对所有的图像中的patch重复此过程。得到论文中称之为响应图像。
模块二
在模块一得到的响应图上再继续一下操作
1,应用一个max pooling操作符。允许该结果的响应图与patch移动1像素时的响应图相同。
2,由于响应图现在在空间上变化缓慢,这里增加了一个下采样步骤。该步骤可以减少后面步骤的复杂性。
3,然后利用4个响应图的稀疏卷积,即4个位移响应图之和,计算较大patch的响应图。
4, 最后加入一个非线性滤波,以避免过快的收敛到1。结果,得到了虚拟8x8块的响应图,
其中每个子块的位置在3x3像素附近优化。
5,然后,从这个第二级响应映射的计算,获取虚拟16x16响应映射。
6,重复以上过程
最终迭代过程的结果是一个多尺寸的响应金字塔。在其中扩展了尺度的局部最大值。
图源:https://thoth.inrialpes.fr/src/deepflow/
from deepmatching import deepmatching
from deepflow2 import deepflow2
import numpy
from PIL import Image
im1 = numpy.array(Image.open('sintel1.png'))
im2 = numpy.array(Image.open('sintel2.png'))
matches = deepmatching(im1, im2)
flow = deepflow2(im1, im2, matches, '-sintel')
DeepFlow: http://lear.inrialpes.fr/src/deepflow/
DeepMatching: http://lear.inrialpes.fr/src/deepmatching/
ColorFlow: http://vision.middlebury.edu/flow/data/
DIS稠密逆搜索实现快速光流
DIS 光流算法是Dense Inverse Search-basedmethod的简称,于2016年由Till Kroeger等人提出的一种在光流质量和计算时间中取得平衡的算法。
DIS算法最大的优点在于运算速度的提升,在单核cpu上,1024*436分辨率可以达到300Hz-600Hz运行速率;包含图像预处理,例如平滑,缩放,梯度计算等运行速度能达到42/46 Hz,完全满足实时的需求。
论文中《Fast Optical Flow using Dense Inverse Search》详细的介绍了如何更加高效的计算光流的值,是一种更为高效的优化方法。
论文中计算光流的步骤:
(1)初始化:对两幅图像构建图像金字塔,以及一些计算量进行初始化
(2)for循环:这一步与LK光流一致,都是从最顶层到最低层的迭代求解的过程
1 根据当前层的梯度图像计算图像的块状积分图
2 逆向搜索求解稀疏的图像光流场,(这一步是这篇论文的核心和关键,提出了完全不同于传统光流不一样的求解方法)
3,根据稀疏光流场计算稠密光流。
(3)最底层的光流resize到跟原始图像一样大小并乘以相应的放大比例,得到最后的光流。
以前的FB光流基于多项式方式,是基于像素级别的光流估算、非常耗时、这里作者提出基于块级别的相关性反向搜索,实现一次计算梯度,多次反向搜索使用,不用每次重新初始化计算梯度,节省了大量计算,从而提高了性能。基于网格化实现反向搜索、稠化光流与优化。
CV
创建DIS光流实例与调用计算光流
C++版本
Ptr<DenseOpticalFlow> algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_FAST);
algorithm->calc(preGray, gray, flow);
Python版本
dis = cv.DISOpticalFlow_create(1)
flow = dis.calc(prvs,next, None,)
这里创建DIS支持三种计算方式如下:
PRESET_ULTRAFAST = 0,
PRESET_FAST = 1,
PRESET_MEDIUM = 2
- https://blog.csdn.net/weixin_43922139/article/details/84861569
Dis光流结果
以下demo是集成在OpenCV4.0以上版本的DIS光流算法.
说明:把所有的位置转换成了极坐标,然后通过极坐标中的半径和角度对应到HSV上,将光流场转换到HSV空间中显示,通过HSV再转换成RGB显示出来。这么做的好处是非常直观,而且炫彩光流的数值用不同颜色来区分,色斑颜色深浅代表运动速率,白色代表物体无运动。
import cv2 as cv
import numpy as np
cap = cv.VideoCapture("demo1.mp4")
# https://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1,cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
dis = cv.DISOpticalFlow_create(1)
while(1):
ret, frame2 = cap.read()
next = cv.cvtColor(frame2,cv.COLOR_BGR2GRAY)
flow = dis.calc(prvs,next, None,)
# flow = cv.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang*180/np.pi/2
hsv[...,2] = cv.normalize(mag,None,0,255,cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
cv.imshow('result',bgr)
cv.imshow('input', frame2)
k = cv.waitKey(30) & 0xff
if k == 27:
break
elif k == ord('s'):
cv.imwrite('opticalfb.png',frame2)
cv.imwrite('opticalhsv.png',bgr)
prvs = next
cap.release()
cv.destroyAllWindows()
- 在纹理丰富场景,OpenCV 4.0中DIS光流算法对运动物体检测精准流畅,行人色斑一致顺畅,复杂场景(运动与固定物体重叠)无明显错误,达到了实用标准,比Farneback 光流算法表现更好。
- DIS光流算法对天空无文理、地面弱文理等场景支持不足,出现了大量混乱光斑,该场景下效果不及Farneback 光流算法。建议在使用OpenCV 4.0应对弱纹理场景时,使用Farneback 光流算法。
备注
除了改进光流计算效率的算法Fast Optical Flow using DIS。还有基于DeepMatching的改进算法还有EpicMatching
光流场的可视化
HSV是一种将RGB色彩模型中的点在圆柱坐标系中的表示法,这种表示法试图做到比基于笛卡尔坐标系的几何结构RGB更加直观。
色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。 饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。 明度(V),亮度(L),取0-100%,如下图所示。
为了更直观地表示光流场,我们可以将h * w * 2的光流场转化成h * w * 3的HSV图像。H通道表示该像素点移动的方向,S或V通道表示该像素点移动的快慢。
def show_flow_hsv(flow, show_style=1):
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])#将直角坐标系光流场转成极坐标系
hsv = np.zeros((flow.shape[0], flow.shape[1], 3), np.uint8)
#光流可视化的颜色模式
if show_style == 1:
hsv[..., 0] = ang * 180 / np.pi / 2 #angle弧度转角度
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)#magnitude归到0~255之间
elif show_type == 2:
hsv[..., 0] = ang * 180 / np.pi / 2
hsv[..., 1] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
hsv[..., 2] = 255
#hsv转bgr
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
return bgr
- https://www.jianshu.com/p/03f358002291