0、前言

在上篇文章中https://blog.csdn.net/Yaoyao2024/article/details/136625461?spm=1001.2014.3001.5501,我们对目标跟踪任务和目标跟踪算法有了大致的了解。今天我们就来详细介绍一下其中的生成式算法的一种:光流法。

在介绍光流法之前,让我们先回顾一下生成式目标跟踪算法:

生成式的方法,强调先对所需跟踪的目标进行外观特征建模(属于特征检测部分的内容)——得到目标的一系列关键点or直方图。然后再利用目标跟踪算法进行对目标在当前帧中位置的确定(目标跟踪)

比如:对上一帧中一系列关键特征点用LK光流法确定其每个关键像素点的位移,从而得到当前帧中的位置。对于Meanshift算法,根据目标建模,在当前帧中寻找局部最相似的位置,向其靠近,从而得到目标在当前帧中位置。

‍♀️总之,对目标区域进行建模,在特征检测中是很常见的步骤。这里的光流法和Meanshift方法主要强调的是,在对目标特征区域进行建模之后,利用相邻帧之间目标位置变换不大等原理(上下文信息的利用),从而利用上一帧和当前帧的信息,判断目标从上一帧移动到当前帧的位置。它的这种确定位置是迭代逼近、有原理的,并不是一般的盲搜。

一、光流法

1.1:什么是光流?

光流(optical flow)是空间运动物体在观察成像平面上的像素运动的 瞬时速度。

所谓光流就是瞬时速率,在时间间隔很小(比如视频的连续前后两帧之间)时,也等同于 目标点的位移

樂所以也就是说,只要我们求出这个“速度”,也就是只要确定当前目标区域的位移,即可确定这个目标在下一帧中的位置(有时候也会说,根据上一帧和当前帧的信息,确定目标在当前帧中的位置)。——这也是生成式方法中逐次逼近搜索的体现,光流法的关键也就在此

光流法是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。(可以从后面1.4小节讲光流法的基本原理来更好的理解)。总而言之就是:光流法就是利用了一系列前提和假设条件,得到了相邻帧之间物体运动的对应关系;比如:我们知道了当前帧中物体的位置(特征检测),通过光流算法,结合相邻两帧图像中的信息,即可确定物体在下一帧中的位置。

1.2:光流的物理意义

一般而言,光流是由于场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。

当人的眼睛观察运动物体时,物体的景象在人眼的视网膜上形成一系列连续变化的图像,这一系列连续变化的信息不断“流过”视网膜(即图像平面),好像一种光的“流”,故称之为光流。光流表达了图像的变化,由于它包含了目标运动的信息,因此可被观察者用来确定目标的运动情况。

下图展示的便是三维空间内物体的运动在二维成像平面上的投影。得到的是一个描述位置变化的二维矢量,但在运动间隔极小的情况下,我们通常将其视为一个描述该点瞬时速度的二维矢量u=(u,v),称为光流矢量。(由相邻帧共同计算得到)

1.3:光流场

在空间中,运动可以用运动场描述,而在一个图像平面上,物体的运动往往是通过图像序列中不同图像灰度分布的不同体现的,从而,空间中的运动场转移到图像上就表示为光流场(optical flow field)。

光流场是一个二维矢量场,它反映了图像上每一点灰度的变化趋势,可看成是带有灰度的像素点在图像平面上运动而产生的瞬时速度场。它包含的信息即是各像点的瞬时运动速度矢量信息(上文说到过,在极短的时间内,这些速度即可等同于位移)。

一个像素的光流失量,是由当前帧和下一帧共同确定:

一个像素的光流矢量是通过考虑当前帧和下一帧之间的像素强度变化计算得出的。这是因为光流的主要假设是随着时间的推移,物体运动导致像素的强度改变。 举个例子,假设你在观看一部电影,并试图计算主角在某个瞬间的运动。你会需要考虑主角在当前帧和下一帧的位置,然后通过他们的变化计算出光流矢量。这个矢量将会告诉你主角运动的方向和速度。 这就是为什么光流矢量通常由两帧图片计算得出,这两帧图片一般是时间上连续的(例如在视频中相邻的两帧)。光流计算是对这两帧图片中的每一个像素进行的。然而,像素光流的准确度依赖于实际处理数据和使用的算法。一些复杂的运动,例如快速旋转和变形,可能会使光流计算变得具有挑战性。

简而言之,光流场就是由图像中每个像素的光流失量组成的一个集合;光流场在理想情况下,光流场对应于运动场(还是上文所说的,速度等同于位移)。

三维空间的矢量场及其在二维平面内的投影:

现实场景的可视化光流场:

总结一下:

所谓光流场就是很多光流的集合当我们计算出了一幅图片中每个像素的光流,就能形成光流场构建光流场是试图重现现实世界中的运动场,用以运动分析

1.4:基本原理

1.4.1.基本假设条件(前提!)

(1)亮度恒定不变(Brightness Constancy Assumption)⭐。即相邻帧中的同一目标关键像素在灰度值上保持不变(位置当然可变)。根据这个假设,我们可以假设两个相邻帧之间的像素强度变化可以通过运动向量来表示。这是基本光流法的假定(所有光流法变种都必须满足),用于得到光流法基本方程;

(2)时间连续或运动是“小运动”(Temporal Coherence Assumption)⭐。即时间的变化不会引起目标位置的剧烈变化,相邻帧之间位移要比较小。同样也是光流法不可或缺的假定。

(3)空间一致性(Spatial Coherence Assumption):该假设认为相邻像素点在图像中的运动是相似的。也就是说,如果两个像素在连续帧中位于相似的位置,它们的运动向量也应该相似。这个假设在光流法中用于增强运动向量的连续性和平滑性。

1.4.2 基本约束方程

考虑一个像素I(x,y,t)在第一帧的光强度(其中t代表其所在的时间维度)。它移动了 (dx,dy) 的距离到下一帧,用了 dt 时间。因为是同一个像素点,依据上文提到的第一个假设我们认为该像素在运动前后的光强度是不变的,即式(1):

I

(

x

,

y

,

t

)

=

I

(

x

+

d

x

,

y

+

d

y

,

t

+

d

t

)

I(x,y,t) = I(x+dx,y+dy,t+dt)

I(x,y,t)=I(x+dx,y+dy,t+dt)

对于函数 I(x+dx, y+dy, t+dt),我们可以在点 (x, y, t) 处进行泰勒展开,得到式(2):

I

(

x

,

y

,

t

)

=

I

(

x

,

y

,

t

)

+

I

x

d

x

+

I

y

d

y

+

I

t

d

t

+

ξ

I(x,y,t) = I(x,y,t) +\frac{\partial I}{\partial x}dx +\frac{\partial I}{\partial y}dy+\frac{\partial I}{\partial t}dt +\xi

I(x,y,t)=I(x,y,t)+∂x∂I​dx+∂y∂I​dy+∂t∂I​dt+ξ

将(2)式代入(1)式右边,无穷小项忽略,且两端同除dt可得式(3)

I

x

d

x

d

t

+

I

y

d

y

d

t

+

I

t

d

t

d

t

=

0

\frac{\partial I}{\partial x}\frac{\text{d}x}{\text{d}t}+\frac{\partial I}{\partial y}\frac{\text{d}y}{\text{d}t}+\frac{\partial I}{\partial t}\frac{\text{d}t}{\text{d}t}=0

∂x∂I​dtdx​+∂y∂I​dtdy​+∂t∂I​dtdt​=0

设u,v分别为光流分别沿x轴和y轴的速度矢量,得:

u

=

d

x

d

t

v

=

d

y

d

t

u = \frac{\text{d}x}{\text{d}t},v=\frac{\text{d}y}{\text{d}t}

u=dtdx​,v=dtdy​

令,Ix.Iy,It分别表示图像中像素点的灰度沿X,Y,T方向的偏导数:

I

x

=

I

x

,

I

y

=

I

y

,

I

t

=

I

t

I_{x} = \frac{\partial I}{\partial x},I_{y} = \frac{\partial I}{\partial y},I_{t} = \frac{\partial I}{\partial t}

Ix​=∂x∂I​,Iy​=∂y∂I​,It​=∂t∂I​

综上,式(3)可写为如下式(4):

I

x

u

+

I

y

v

+

I

t

=

0

I_{x}u+I_{y}v+I_{t}=0

Ix​u+Iy​v+It​=0

其中:Ix,Iy,It均可由图像数据求得,而 (u,v)为所求光流矢量

公式4,便是咱们的核心公式了。其中u,v代表两个方向(x方向和y方向)的移动速度,Ix、Iy、It代表了亮度在三个轴上的偏导(也就是梯度)。把u、v计算出来,咱们的光流也就算出来了,该像素在下一帧中的位置也就求得了。

约束方程只有一个,而方程的未知量有两个,这种情况下无法求得u和v的确切值。此时需要引入另外的约束条件,从不同的角度引入约束条件,导致了不同光流场计算方法。

下图是求解公式(4)的一种利用差分的算法:LK算法

从上面也可以看到,我们求解光流涉及到的不是单独一张图片,而是上一帧和当前帧两个,利用上述原理公式,共同求解光流,即求解当前帧中的关键点在下一帧中位置。

约束方程只有一个,而方程的未知量有两个,这种情况下无法求得u和v的确切值。此时需要引入另外的约束条件,从不同的角度引入约束条件,导致了不同光流场计算方法。按照理论基础与数学方法的区别把它们分成四种:基于梯度(微分)的方法、基于匹配的方法、基于能量(频率)的方法、基于相位的方法和神经动力学方法。

1.5.几种光流估计算法的简介

1) 基于梯度的方法

基于梯度的方法又称为微分法,它是利用时变图像灰度(或其滤波形式)的时空微分(即时空梯度函数)来计算像素的速度矢量。

由于计算简单和较好的结果,该方法得到了广泛应用和研究。典型的代表是Horn-Schunck算法与 Lucas-Kanade(LK) 算法。

Horn-Schunck算法在光流基本约束方程的基础上附加了全局平滑假设,假设在整个图像上光流的变化是光滑的,即物体运动矢量是平滑的或只是缓慢变化的。

基于此思想,大量的改进算法不断提出。Nagel采用有条件的平滑约束,即通过加权矩阵的控制对梯度进行不同平滑处理;Black和Anandan针对多运动的估计问题,提出了分段平滑的方法。

2)基于匹配的方法

基于匹配的光流计算方法包括基于特征和区域的两种。

基于特征的方法不断地对目标主要特征进行定位和跟踪,对目标大的运动和亮度变化具有鲁棒性。存在的问题是光流通常很稀疏,而且特征提取和精确匹配也十分困难。

基于区域的方法先对类似的区域进行定位,然后通过相似区域的位移计算光流。这种方法在视频编码中得到了广泛的应用。然而,它计算的光流仍不稠密。另外,这两种方法估计亚像素精度的光流也有困难,计算量很大。

3)基于能量的方法

基于能量的方法又称为基于频率的方法,在使用该类方法的过程中,要获得均匀流场的准确的速度估计,就必须对输入的图像进行时空滤波处理,即对时间和空间的整合,但是这样会降低光流的时间和空间分辨率。基于频率的方法往往会涉及大量的计算,另外,要进行可靠性评价也比较困难。

4)基于相位的方法

基于相位的方法是由Fleet和Jepson提出的,Fleet和Jepson最先提出将相位信息用于光流计算的思想。当我们计算光流的时候,相比亮度信息,图像的相位信息更加可靠,所以利用相位信息获得的光流场具有更好的鲁棒性。基于相位的光流算法的优点是:对图像序列的适用范围较宽,而且速度估计比较精确,但也存在着一些问题:第一,基于相位的模型有一定的合理性,但是有较高的时间复杂性;第二,基于相位的方法通过两帧图像就可以计算出光流,但如果要提高估计精度,就需要花费一定的时间;第三,基于相位的光流计算法对图像序列的时间混叠是比较敏感的。

5)神经动力学方法

神经动力学方法是利用神经网络建立的视觉运动感知的神经动力学模型,它是对生物视觉系统功能与结构比较直接的模拟。

尽管光流计算的神经动力学方法还很不成熟,然而对它的研究却具有极其深远的意义。随着生物视觉研究的不断深入,神经方法无疑会不断完善,也许光流计算乃至计算机视觉的根本出路就在于神经机制的引入。神经网络方法是光流技术的一个发展方向。

1.6.稠密光流与稀疏光流

除了根据原理的不同来区分光流法外,还可以根据所形成的光流场中二维矢量的疏密程度将光流法分为稠密光流与稀疏光流两种。

稠密光流——类比于搜索策略的中的密集搜索 稠密光流是一种针对图像或指定的某一片区域进行逐点匹配的图像配准方法,它计算图像上所有的点的偏移量,从而形成一个稠密的光流场。通过这个稠密的光流场,可以进行像素级别的图像配准。 稀疏光流——类比于搜索策略中的图像金字塔 与稠密光流相反,稀疏光流并不对图像的每个像素点进行逐点计算。它通常需要指定一组关键点进行跟踪,这组点最好具有某种明显的特性,例如Harris角点等,那么跟踪就会相对稳定和可靠。稀疏跟踪的计算开销比稠密跟踪小得多。

上文提到的基于特征的匹配方法是典型的属于稀疏光流的算法。

1.7.具体算法:KLT算法 (基于梯度的方法

LK光流法于1981年提出,最初是用于求稠密光流的,由于算法易于应用在输入图像的一组点上,而成为求稀疏光流的一种重要方法。

LK光流法在原先的光流法两个基本假设的基础上,增加了一个**“空间一致”的假设,即所有的相邻像素有相似的行动**。也即在目标像素周围m×m的区域内,每个像素均拥有相同的光流矢量。以此假设解决式

I

x

u

+

I

y

v

+

I

t

=

0

I_{x}u+I_{y}v+I_{t} = 0

Ix​u+Iy​v+It​=0 无法求解的问题。

1.4.7.1: LK光流法约束方程

在一个小邻域内,LK光流法通过该邻域的所有关键像素采用下式的加权平方和最小化来估计光流矢量

x

,

y

Ω

W

2

(

x

)

(

I

x

u

+

I

y

v

+

I

t

)

2

\sum_{x,y\inΩ}^{}W^{2}(x)(I_{x}u+I_{y}v+I_{t})^{2}

x,y∈Ω∑​W2(x)(Ix​u+Iy​v+It​)2

⭐原理:在目标像素周围m×m的区域内,每个像素均拥有相同的光流矢量

上式中

W

2

(

x

)

W^{2}(x)

W2(x)是一个窗口权重函数,该函数使得领域中心的加权比周围大,对于窗口范围Ω内的n个点X1…Xn,有

V

=

(

u

,

v

)

T

,

I

(

x

)

=

(

I

x

I

y

)

T

V=(u,v)^{T},▽I(x)=(I_{x},I_{y})^{T}

V=(u,v)T,▽I(x)=(Ix​,Iy​)T

最后可得

V

=

(

A

T

W

2

A

a

)

1

A

T

W

2

b

V=(A^{T}W^{2}Aa)^{-1}A^{T}W^{2}b

V=(ATW2Aa)−1ATW2b

通过结合几个邻近像素点的信息,LK光流法通常能够消除光流方程里的多义性。而且与逐点计算的方法相比,LK方法对图像噪声不敏感

1.4.7.2:改进:金字塔LK光流法

LK算法的约束条件即:小速度,亮度不变以及区域一致性都是较强的假设,并不很容易得到满足。如当 物体运动速度较快时,假设不成立,那么后续的假设就会有较大的偏差,使得最终求出的光流值有较大的误差。图像金字塔可以解决这个问题。

考虑物体的运动速度较大时,算法会出现较大的误差。那么就希望能减少图像中物体的运动速度。一个直观的方法就是,缩小图像的尺寸。假设当图像为400×400时,物体速度为[16 16],那么图像缩小为200×200时,速度变为[8,8]。缩小为100*100时,速度减少到[4,4]。所以在源图像缩放了很多以后,原算法又变得适用了。所以光流可以通过生成 原图像的金字塔图像,逐层求解,不断精确来求得。简单来说上层金字塔(低分辨率)中的一个像素可以代表下层的四个

下图黑色方块代表两个连续的帧内同一目标的不同位置。为了观察方便我才将其放在一张图里

面对“大运动”时光流法的局限是什么?显然是运动距离大,原算法不适用。(hhh)那么其实只要通过图片尺寸的不断缩小,而且目标选框大小保持不变,让运动前后两个物体的位置看上去“不断靠近”,直到变成小运动就可以使用光流法了。读者可能会存在很多疑问:

1. 为什么图片整体尺寸缩减时假定的目标其“尺寸”可以保持不变? 不论是在什么尺寸(尺度,缩小图片其实是模拟观察者的远近)的情况下,我们都坚持LK光流法的假设,即“空间一致”,不管我们看到图像是1000x1000还是10x10,我们都认为一个像素和它周围的一片区域内的若干个像素具有相同的速度。这是你使用LK光流法就必须承认的一个假设。这一片区域大小不一定是50x50,但始终是存在且不随客观情况改变只由你自己的判断而确定。 2.只在分辨率很小的尺度内计算光流,有什么用?能替代真实的光流矢量的值吗?

在低尺度下只计算一次当然不准,为什么?你把图片弄得很模糊又很小,丢失了很多信息,还想检测准确?但是在低尺度下的光流矢量的测量能给我们一个指示,就像是低尺度下它综观全局,告诉你“运动大概是5点钟方向,大小大概是【32,57】”之类的信息。这条信息够粗糙,但是大体上是有用的。它使得你在进入金字塔的下一层时有一个大概的头绪,你由此指示可以在下一层金字塔再一次顺利“靠近”真实目标,强行符合“小运动”。这里可能说得很抽象,大家细细看下面的具体算法再回来看这一段或许会有所启示。

具体算法:https://blog.csdn.net/sgfmby1994/article/details/68489944

如下图:低尺度下找到光流矢量d0,将其扔到下一层去指引我们前行。扔下去后首先要放大两倍,此时你开始抱怨:“上一层找到的d0根本不靠谱,差距好大”(图2中蓝方块与右下角黑色方块的差距)但是你也要庆幸,正是“不靠谱”的d0让你到达了蓝色方块的位置,让你离真实区域前进了很多,否则你还在左上角苦于“小运动”寸步难行呢。在蓝色位置你就满足“小运动”了,继续计算光流,得到d1,然后把(2d0+d1)扔到下一层指导我们继续前行,如此往复。

1.8.基于光流的目标追踪

使用特征点跟踪的方式进行目标跟踪,计算量小,运行速度快(并不是对每个像素进行光流计算)

1. 光流跟踪的思路与流程图

2.特征点检测 (和我们之前了解和学习的一样)——可以在这里用上限制区域搜索算法(下面的发明专利里就用到了) 包括 2D和3D角点,SIFT特征点,时空兴趣点

3.特征点的删除 运动大小

刚体:轨迹的子空间约束

局部刚体:结合1和2

4.目标位置如何估计——光流法进行特征点跟踪

CN104200494B发明专利https://patents.google.com/patent/CN104200494B/zh中具体步骤:

步骤一,获取跟踪目标第一帧图像的初始位置; 跟踪目标的初始位置已知,给定一个矩形框,表示目标所在位置,在目标追踪的过程中,每帧图像中目标的位置都由一个矩形框表示。 步骤二,获取特征点序列 (特征点检测) 在图像It中目标所在位置的矩形框内,筛选富含纹理信息的特征点,方法如下: 设p=[px,py]为矩形框内任意一点,px、py表示p点在像素坐标系下的坐标,设以p为中心,半径为w的范围为搜索区域,搜索区域大小为(2w+1)×(2w+1),设矩阵M为: 其中:Ix与Iy为搜索区域中所有点在像素坐标系下x、y方向上的微分; 对于矩形框中每一个点,计算出其矩阵M的两个特征值λ1、λ2,设其中最小的特征值为λmin,设定特征值的阈值λ{thres hold},丢弃λmin<λ{thres hold}的点,剩余的点按λmin从大到小的顺序排列,设为预选点集合,选定预选点集合中λmin最大的点,设定最小距离,丢弃与λmin最大点距离小于最小距离的点,将λmin最大的点添加到特征点序列中,剩下的点形成新的预选点集合,在新的预选点集合中,选定λmin最大的点,丢弃与λmin最大点距离小于最小距离的点,将λmin最大的点添加到特征点序列中,剩下的点再次形成新的预选点集合,依次循环,直到预选点集合为空,最终,获取得到特征点序列; 步骤三:获取跟踪目标的新时刻的图像It+1 步骤四:计算光流,进行特征点跟踪⭐ 根据图像It中的特征点序列,采用Lucas-Kanade方法,得到图像It中的特征点序列在图像It+1中的对应点,相对应的点形成N个点对。 本发明中使用了Lucas-Kanade方法对特征点进行匹配,从而求得两张图像之间特征点的运动,也即对特征点进行了跟踪 (p路‍♀️s:我感觉于其说它是一种跟踪算法,倒不如说它是一种匹配or预测算法,但从在跟踪任务里它起到的作用来看,只是一个特征匹配的作用,只是这种匹配在某种意义上来说,也是一种跟踪,而且是精确到点的) 步骤五:对点对进行筛选 步骤六:消除背景特征点的影响; 步骤七:系统状态估计

1.9:基于光流的运动目标检测(前景检测)算法

基于光流运动目标检测是在对摄像机采集到的图像序列进行重采样和去噪预处理后,利用光流法计算出各点的光流值,得出各点的光流场。然后对光流场进行阈值分割,区分出前景与背景,得到清运动目标区域。一般还会再采用形态学滤波中的开、闭运算滤除孤立噪声点,最后经过区域连通便可识别出目标区域并统计其特征信息。流程图如下:

如果图像中没有运动目标,则光流矢量在整个图像区域是连续变化的。当图像中有运动物体时,目标和背景存在着相对运动。运动物体所形成的速度矢量必然和背景的速度矢量有所不同,如此便可以计算出运动物体的位置。

通过观察上图我们可以看到,发生运动的物体的光流矢量与背景光流矢量之间存在差异。使用阀值分割可以将整幅图片的光流矢量分成两个部分,即区分出背景与前景。阀值的选取可以使用最大类间方差法(大津算法)来确定。它是按图像的灰度特性,将图像分成背景和目标两部分。背景和目标之间的类间方差越大,说明构成图像的2部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。

光流场经过阈值分割后,有一些独立的点或者有凹区域,影响了运动目标的提取。可先利用开运算,去除那些光流值与结构元素不相吻合的凹区域,同时保留那些相吻合的凹区域。然后,利用形态学滤波的闭运算,填充凹区域。

通过前面的处理,一帧图像中可能的目标区域已经成为一个可以连成一体的区域,采用合理的区域连通合并和分割技术来找出最终的目标区域。

1.10.总结

光流法的优缺点

优点 光流法的优点在于它无须了解场景的信息,就可以准确地检测识别运动日标位置,且在摄像机处于运动的情况下仍然适用。 而且光流不仅携带了运动物体的运动信息,而且还携带了有关景物三维结构的丰富信息,它能够在 不知道场景的任何信息的情况下,检测出运动对象。 且支持目标的更新(下一帧目标的检测总是基于上一帧检测处的目标进行检测) 缺点 光流法的适用条件,即两个基本假设,在现实情况下均不容易满足。 假设一:亮度恒定不变。 但是实际情况是光流场并不一定反映了目标的实际运动情况,如图,所示。图中,光源不动,而物体表面均一,且产生了自传运动,却并没有产生光流图中,物体并没有运动,但是光源与物体发生相对运动,却有光流产生。因此可以说光流法法对光线敏感, 光线变化极易影响识别效果。 假设二:小运动。 前文也有提到,现实情况下较大距离的运动也是普遍存在的。因此当需要检测的目标运动速度过快是,传统光流法也不适用。

孔径问题

观察上图(a)我们可以看到目标是在向右移动,但是由于**“观察窗口”过小我们无法观测到边缘也在下降**。LK算法中选区的小邻域就如同上图的观察窗口,邻域大小的选取会影响到最终的效果。当然,这是针对于一部分稀疏光流算法而言,属于稠密光流范畴的算法一般不存在这个问题。

但是稠密光流法的显著缺点主要体现在,计算量大,耗时长,在对实时性要求苛刻的情况下并不适用。

‍♀️几种光流算法的比较:

补充:【【入门向】光流法(optical flow)基本原理+深度学习中的应用【FlowNet】【RAFT】 - CSDN App】http://t.csdnimg.cn/bi61R

二、代码实例

2.1:

carFlow.py 删除静止点的光流分析:

实现流程:

加载视频。 调用cv2.GoodFeaturesToTrack 函数寻找兴趣点(关键点)。 调用cv2.CalcOpticalFlowPyrLK 函数计算出两帧图像中兴趣点的移动情况。 删除未移动的兴趣点。 在两次移动的点之间绘制一条线段

import numpy as np

import cv2 as cv

cap = cv.VideoCapture('car_flow.mp4')

# 角点检测参数

feature_params = dict(maxCorners=100, qualityLevel=0.1, minDistance=7, blockSize=7)

# KLT光流参数

lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.02))

# 随机颜色

color = np.random.randint(0, 255, (100, 3))

# 读取第一帧

ret, old_frame = cap.read()

old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)

p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params, useHarrisDetector=False, k=0.04)

good_ini = p0.copy()

def caldist(a, b, c, d):

return abs(a - c) + abs(b - d)

mask = np.zeros_like(old_frame)

cv2.namedWindow("frame", cv2.WINDOW_NORMAL)

# 光流跟踪

while True:

ret, frame = cap.read()

if not ret:

break

frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)

# 计算光流

p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)

# 根据状态选择

good_new = p1[st == 1]

good_old = p0[st == 1]

# 删除静止点

k = 0

for i, (new0, old0) in enumerate(zip(good_new, good_old)):

a0, b0 = new0.ravel()

c0, d0 = old0.ravel()

dist = caldist(a0, b0, c0, d0)

if dist > 2:

good_new[k] = good_new[i]

good_old[k] = good_old[i]

good_ini[k] = good_ini[i]

k = k + 1

# 提取动态点

good_ini = good_ini[:k]

good_new = good_new[:k]

good_old = good_old[:k]

# 绘制跟踪线

for i, (new, old) in enumerate(zip(good_new, good_old)):

a, b = new.ravel()

c, d = old.ravel()

a, b, c, d = np.int32(a), np.int32(b), np.int32(c), np.int32(d) # 将坐标值转换为整数类型

mask = cv.line(mask, (a, b), (c, d), color[i].tolist(), 2)

frame = cv.circle(frame, (a, b), 5, color[i].tolist(), -1)

cv.imshow('frame', cv.add(frame, mask))

k = cv.waitKey(30) & 0xff

if k == 27:

cv.imwrite("flow.jpg", cv.add(frame, mask))

break

# 更新

old_gray = frame_gray.copy()

p0 = good_new.reshape(-1, 1, 2)

if good_ini.shape[0] < 40:

p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

good_ini = p0.copy()

cv.destroyAllWindows()

cap.release()

2.2:

slectToTrack.py 运行用户在第一帧图片中框选目标得到目标在第一帧图像中的初始位置

计算特征点

然后进行光流法得到下一帧中目标特征点位置

并且用矩形框绘制将下一帧中图像框出。

import cv2

import numpy as np

# 光流法参数

lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# 初始化目标位置和特征点

bbox = None

prev_points = None

prev_gray = None

# 创建跟踪器

tracker = cv2.TrackerCSRT_create()

# 读取视频文件

cap = cv2.VideoCapture("D:/CollegeStudy/AI/windElectProject/VS2019_cpp/data/video_good/test_video.mp4")

# 读取第一帧图像

ret, frame = cap.read()

if not ret:

print("无法读取视频帧")

exit()

# 创建一个可以调整大小的窗口

cv2.namedWindow("选择目标", cv2.WINDOW_NORMAL)

# 在第一帧图像中选择目标区域

bbox = cv2.selectROI("选择目标", frame, fromCenter=False, showCrosshair=True)

cv2.destroyAllWindows()

# 初始化跟踪器

tracker.init(frame, bbox)

cv2.namedWindow("Frame", cv2.WINDOW_NORMAL)

while True:

# 读取当前帧图像

ret, frame = cap.read()

if not ret:

break

# 使用光流法跟踪特征点

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

if prev_points is None:

# 在第一帧中计算特征点

mask = np.zeros_like(gray)

mask[int(bbox[1]):int(bbox[1] + bbox[3]), int(bbox[0]):int(bbox[0] + bbox[2])] = 255

prev_points = cv2.goodFeaturesToTrack(gray, mask=mask, maxCorners=100, qualityLevel=0.3, minDistance=7,

blockSize=7)

if prev_points is not None:

if prev_gray is not None:

# 计算光流

next_points, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, gray, prev_points, None, **lk_params)

# 选取符合条件的特征点和目标位置

good_points = next_points[status == 1]

x, y, w, h = cv2.boundingRect(good_points)

# 更新目标位置和特征点

bbox = (x, y, w, h)

prev_points = good_points.reshape(-1, 1, 2)

# 绘制矩形框和特征点

cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

for point in good_points:

x, y = point.ravel()

cv2.circle(frame, (int(x), int(y)), 3, (0, 0, 255), -1)

# 显示图像

cv2.imshow("Frame", frame)

# 按下Esc键退出

if cv2.waitKey(1) == 27:

break

# 更新前一帧图像和特征点

prev_gray = gray.copy()

# 释放资源

cap.release()

cv2.destroyAllWindows()

参考链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: