前言
在计算机视觉的众多技术中,光流法(Optical Flow)可以说是最古老也最具生命力的算法之一。从 1950 年代心理学家 Gibson 首次提出视觉运动感知理论,到 1981 年 Lucas 和 Kanade 发表那篇经典论文,再到今天深度学习时代的 RAFT、GMFlow 等现代光流网络,这项技术已经走过了半个多世纪的历程。
我第一次接触光流法是在大学的计算机视觉课程上。当时教授在黑板上写下那个著名的光流方程 Iₓu + Iᵧv + Iₜ = 0,然后告诉我们:“这个简单的方程,蕴含了理解运动的全部秘密。” 那时候我还不太理解这句话的含义,直到后来在实际项目中用它实现了一个简单的视频目标跟踪系统,才真正体会到光流法的强大之处。
在今天的边缘计算和嵌入式 AI 场景中,光流法依然占据着不可替代的地位。相比于深度学习的目标跟踪算法,传统光流法具有以下优势:
- 计算量小:不需要复杂的神经网络,可以在资源受限的嵌入式设备上实时运行
- 无需训练:不需要标注数据,开箱即用
- 实时性好:很多优化后的实现可以轻松达到 30 FPS 以上
- 适用范围广:从无人机的视觉导航,到视频防抖,再到动作识别,光流法无处不在
本文将带您从零开始深入理解光流法的原理,从最基本的亮度恒定假设,到经典的 Lucas-Kanade 算法,再到 OpenCV 中的各种光流法实现。我们会通过大量代码示例,让您不仅理解理论,更能在实际项目中应用这项技术。
一、什么是光流法?
1.1 光流的定义
简单来说,光流就是空间中运动物体在成像平面上像素运动的瞬时速度。当你盯着窗外行驶的汽车时,视网膜上汽车图像的移动速度就是光流。
更正式的定义是:给定图像序列 I(x, y, t),光流法的目标是为每个像素点 (x, y) 找到一个速度向量 (u, v),使得:
I(x, y, t) = I(x + u·dt, y + v·dt, t + dt)
这个等式表达的就是:经过微小的时间间隔 dt 后,像素点 (x, y) 移动到了 (x + u·dt, y + v·dt),而亮度保持不变。
1.2 光流法的三大假设
所有传统光流算法都建立在三个基本假设之上:
假设一:亮度恒定(Brightness Constancy)
同一物体像素点的亮度在连续帧之间不会发生变化。这是最核心的假设,也是光流约束方程的基础。
I(x, y, t) = I(x + dx, y + dy, t + dt)
这个假设在现实中并不总是成立——光照变化、阴影移动、物体旋转都会导致亮度变化。但在大多数场景下,尤其是帧间时间间隔很短时,这个假设是近似成立的。
假设二:时间连续或小运动(Temporal Persistence)
物体的运动是连续平滑的,不会发生突变。换句话说,相邻两帧之间的位移很小。
这就是为什么我们经常需要对图像进行降采样(构建金字塔)——因为当运动较大时,这个假设就不成立了。
假设三:空间一致性(Spatial Coherence)
相邻像素具有相似的运动。如果一个像素属于某个物体,那么它周围的像素也应该属于同一个物体,因此具有相似的运动速度。
这个假设是 Lucas-Kanade 算法能够求解的关键——因为光流约束方程只有一个,但有两个未知数 u 和 v,是病态问题。利用空间一致性,我们可以在一个小邻域内建立多个方程,从而求解出唯一解。
1.3 光流法的分类
光流算法可以大致分为两类:
稀疏光流(Sparse Optical Flow)
只计算图像中部分特征点的光流(比如角点、边缘点)。计算速度快,但只能得到稀疏的运动向量。
代表算法:Lucas-Kanade
稠密光流(Dense Optical Flow)
计算图像中每一个像素的光流。计算量大,但能得到完整的运动场。
代表算法:Farneback, Horn-Schunck, TV-L1
在实际应用中,稀疏光流通常用于目标跟踪、视觉里程计等任务,而稠密光流则用于视频插帧、动作识别、视频分割等需要完整运动信息的场景。
二、光流约束方程的数学推导
理解光流约束方程的推导过程,是深入理解光流法的关键。让我们从亮度恒定假设开始。
2.1 泰勒展开
我们从亮度恒定假设出发:
I(x, y, t) = I(x + dx, y + dy, t + dt)
对右边进行一阶泰勒展开:
I(x + dx, y + dy, t + dt) = I(x, y, t) + (∂I/∂x)dx + (∂I/∂y)dy + (∂I/∂t)dt + ε
其中 ε 是高阶无穷小,可以忽略。
2.2 光流约束方程
将两式相减,两边除以 dt,得到:
(∂I/∂x)(dx/dt) + (∂I/∂y)(dy/dt) + (∂I/∂t) = 0
令:
Iₓ = ∂I/∂x,图像在 x 方向的空间梯度Iᵧ = ∂I/∂y,图像在 y 方向的空间梯度Iₜ = ∂I/∂t,图像的时间梯度u = dx/dt,x 方向的光流速度v = dy/dt,y 方向的光流速度
就得到了著名的光流约束方程:
Iₓu + Iᵧv + Iₜ = 0
2.3 孔径问题
现在问题来了:一个方程,两个未知数 u 和 v,怎么解?
这就是光流法中的孔径问题(Aperture Problem)。
想象一下,你透过一个小孔看一个运动的物体——你只能看到边缘的运动,却无法确定完整的运动方向。比如一条倾斜的直线向右运动,透过小孔你看到的可能只是直线沿着垂直方向移动。
数学上说,光流约束方程只能约束沿着梯度方向的运动分量,但垂直于梯度方向的运动分量是无法确定的:
∇I · (u, v) = -Iₜ
这说明光流向量 (u, v) 在梯度方向 ∇I 上的投影等于 -Iₜ / |∇I|,但垂直于梯度方向的分量可以是任意值。
如何解决这个问题?这就是 Lucas-Kanade 算法的精髓所在——利用空间一致性假设,在一个小窗口内建立多个方程,从而求解出唯一的光流向量。
三、Lucas-Kanade 算法详解
Lucas-Kanade 算法由 Bruce D. Lucas 和 Takeo Kanade 于 1981 年提出,是最经典的稀疏光流算法,也是今天 OpenCV 中 calcOpticalFlowPyrLK 函数的理论基础。
3.1 核心思想
Lucas-Kanade 的核心思想就是利用空间一致性假设:在一个小的邻域窗口内(比如 3×3 或 5×5),所有像素都具有相同的光流向量 (u, v)。
如果窗口内有 n 个像素,我们就可以建立 n 个方程:
Iₓ₁u + Iᵧ₁v = -Iₜ₁
Iₓ₂u + Iᵧ₂v = -Iₜ₂
...
Iₓₙu + Iᵧₙv = -Iₜₙ
写成矩阵形式就是:
[Iₓ₁ Iᵧ₁] [u] [-Iₜ₁]
[Iₓ₂ Iᵧ₂] [v] [-Iₜ₂]
... = ...
[Iₓₙ Iᵧₙ] [-Iₜₙ]
简记为:
A · v = b
其中:
[Iₓ₁ Iᵧ₁]
A = [Iₓ₂ Iᵧ₂]
[... ...]
[Iₓₙ Iᵧₙ]
[u]
v = [v]
[-Iₜ₁]
b = [-Iₜ₂]
[... ]
[-Iₜₙ]
3.2 最小二乘解
这是一个超定方程组,我们用最小二乘法求解。两边乘以 Aᵀ:
AᵀA · v = Aᵀb
得到:
[ΣIₓ² ΣIₓIᵧ] [u] [-ΣIₓIₜ]
[ΣIₓIᵧ ΣIᵧ²] [v] = [-ΣIᵧIₜ]
记 M = AᵀA,则:
v = M⁻¹ · Aᵀb
只要矩阵 M 可逆,我们就能求出唯一解。什么时候 M 可逆呢?当 M 的两个特征值都比较大的时候——换句话说,当这个窗口内包含至少两个不同方向的边缘时,这通常就是角点的特征。
这就是为什么 Lucas-Kanade 光流通常只对角点进行计算——只有角点区域的 M 矩阵才是良态的,才能得到稳定的光流解。
3.3 算法步骤
Lucas-Kanade 算法的完整步骤如下:
- 特征点检测:在第一帧图像中检测角点(通常用 Shi-Tomasi 算法)
- 图像梯度计算:计算当前帧和下一帧图像的空间梯度
Iₓ, Iᵧ和时间梯度Iₜ - 窗口内方程构建:对每个特征点,在其邻域窗口内构建超定方程组
- 最小二乘求解:计算
M = AᵀA,求解光流向量v = M⁻¹Aᵀb - 迭代优化:使用牛顿法或高斯-牛顿法进行迭代求精
(第一部分完,约2200字)
四、金字塔光流:解决大位移问题
标准的 Lucas-Kanade 算法有一个严重的局限:它只能处理小运动。还记得小运动假设吗?如果物体在两帧之间移动了超过几个像素,泰勒展开的一阶近似就不再成立,光流计算就会失效。
金字塔光流(Pyramidal Lucas-Kanade)就是为了解决这个问题而提出的。
4.1 图像金字塔
图像金字塔就是对原始图像进行多次降采样得到的一系列图像。最顶层是分辨率最低的图像,最底层是原始图像。
比如一个 4 层金字塔:
- 第 0 层(原始):640×480
- 第 1 层:320×240
- 第 2 层:160×120
- 第 3 层:80×60
这样做的好处是:在低分辨率图像上,原来 10 个像素的位移可能就变成了 1-2 个像素,满足小运动假设。
4.2 由粗到精(Coarse-to-Fine)策略
金字塔光流采用"由粗到精"的计算策略:
- 顶层计算:在最顶层(分辨率最低)计算光流,此时位移很小,计算结果准确
- 结果传播:将顶层的光流结果作为下一层的初始估计
- 逐层求精:在下一层图像上,以之前的估计为起点进行迭代求精
- 直到底层:重复上述过程直到达到原始图像层
这样即使原始图像中有较大的位移,在顶层金字塔中也会被压缩成小位移,从而可以被准确计算。
4.3 数学原理
在金字塔的每一层 L,我们计算光流增量 (dLx, dLy),然后将累计的光流传递到下一层:
gL-1 = 2 × (gL + (dLx, dLy))
其中 gL 是第 L 层的累计光流估计。
这个过程一直进行到第 0 层(原始图像),最终得到完整的光流向量。
在 OpenCV 中,calcOpticalFlowPyrLK 函数默认使用 3-4 层金字塔,这也是实践中的最佳配置。
五、OpenCV 稀疏光流实战
现在让我们进入实战环节,用 OpenCV 实现 Lucas-Kanade 光流跟踪。
5.1 环境准备
首先确保安装了正确版本的 OpenCV:
pip install opencv-python numpy matplotlib
5.2 基本代码框架
这是一个最基本的 Lucas-Kanade 光流跟踪示例:
import cv2
import numpy as np
# 视频捕获
cap = cv2.VideoCapture(0) # 0 表示默认摄像头
# Shi-Tomasi 角点检测参数
feature_params = dict(
maxCorners=100, # 最多检测多少个角点
qualityLevel=0.3, # 角点质量阈值
minDistance=7, # 角点之间的最小距离
blockSize=7 # 计算协方差矩阵的窗口大小
)
# Lucas-Kanade 光流参数
lk_params = dict(
winSize=(15, 15), # 搜索窗口大小
maxLevel=4, # 金字塔层数
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
)
# 随机颜色(用于绘制轨迹)
color = np.random.randint(0, 255, (100, 3))
# 读取第一帧并检测角点
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 创建一个 mask 用于绘制轨迹
mask = np.zeros_like(old_frame)
while True:
ret, frame = cap.read()
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 计算光流
p1, st, err = cv2.calcOpticalFlowPyrLK(
old_gray, frame_gray, p0, None, **lk_params
)
# 筛选出跟踪成功的点
good_new = p1[st == 1]
good_old = p0[st == 1]
# 绘制轨迹
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)
frame = cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)
img = cv2.add(frame, mask)
cv2.imshow('Optical Flow', img)
if cv2.waitKey(30) & 0xFF == 27: # ESC 退出
break
# 更新上一帧和特征点
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
cap.release()
cv2.destroyAllWindows()
5.3 参数详解
让我们详细解释一下关键参数:
Shi-Tomasi 角点参数:
maxCorners:检测的最大角点数。数量越多跟踪越稳定,但计算越慢。通常 50-200 是合理范围。qualityLevel:角点质量阈值,0-1 之间。值越高,检测到的角点越少但质量越好。0.3 是经验值。minDistance:角点之间的最小像素距离。避免在同一区域检测到大量重叠的角点。blockSize:计算角点响应时的窗口大小。
Lucas-Kanade 光流参数:
winSize:搜索窗口大小。窗口越大,对噪声的鲁棒性越好,但计算量也越大。(15, 15) 或 (21, 21) 是常用值。maxLevel:金字塔层数。层数越多能处理的运动越大,但计算时间也越长。通常 3-4 层就足够了。criteria:迭代停止条件。这里设置最多迭代 10 次,或者当光流变化小于 0.03 时停止。
5.4 返回值解析
calcOpticalFlowPyrLK 返回三个值:
- p1:下一帧中对应特征点的位置
- st:状态向量,1 表示该点跟踪成功,0 表示跟踪失败
- err:每个点的跟踪误差
st 向量非常重要——我们可以通过它筛选出跟踪成功的点,避免绘制错误的轨迹。在实际应用中,每隔一段时间就应该重新检测一次特征点,因为随着时间推移,很多点会逐渐丢失。
六、稠密光流:Farneback 算法
稀疏光流只跟踪少数特征点,但很多应用场景需要知道每一个像素的运动——这就是稠密光流。
6.1 Farneback 算法简介
Farneback 算法由 Gunnar Farneback 于 2003 年提出,是基于多项式展开的稠密光流算法。它的核心思想是:
- 对每个像素周围的邻域进行二次多项式拟合
- 在邻域变换下,观察多项式系数如何变化
- 通过这些变化来估计光流
相比于其他稠密光流算法,Farneback 的优势在于:
- 计算速度相对较快
- 可以得到稠密的光流场
- 对光照变化有一定的鲁棒性
6.2 Farneback 基本原理
Farneback 算法假设图像在局部可以近似为二次多项式:
f(x) = xᵀAx + bᵀx + c
其中 A 是 2×2 对称矩阵,b 是 2 维向量,c 是标量。
当发生全局位移 d 时,多项式变换为:
f₂(x) = f₁(x - d) = (x - d)ᵀA(x - d) + bᵀ(x - d) + c
通过比较变换前后的多项式系数,我们可以解出位移 d。
在实际实现中,Farneback 算法同样使用金字塔策略,由粗到精地计算光流。
(第二部分完,约2100字)
6.3 Farneback 光流代码实现
下面是使用 Farneback 算法计算稠密光流并可视化的完整代码:
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
# 创建 HSV 图像用于可视化光流
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255 # 饱和度设为最大值
while True:
ret, frame2 = cap.read()
if not ret:
break
next = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
# 计算 Farneback 稠密光流
flow = cv2.calcOpticalFlowFarneback(
prvs, next, None,
pyr_scale=0.5, # 金字塔缩放比例
levels=3, # 金字塔层数
winsize=15, # 平均窗口大小
iterations=3, # 每层迭代次数
poly_n=5, # 多项式邻域大小
poly_sigma=1.2, # 多项式标准差
flags=0
)
# 将光流转换为极坐标 (幅度, 角度)
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
# 角度对应 Hue 通道,幅度对应 Value 通道
hsv[..., 0] = ang * 180 / np.pi / 2
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
# HSV 转 BGR 显示
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('Dense Optical Flow', bgr)
if cv2.waitKey(30) & 0xFF == 27:
break
prvs = next
cap.release()
cv2.destroyAllWindows()
6.4 Farneback 参数调优
Farneback 算法的参数对结果影响很大:
- pyr_scale=0.5:经典值,每层金字塔分辨率减半。设为 0.8 可以保留更多细节但计算量增加。
- levels=3:金字塔层数,越多能处理越大的运动。
- winsize=15:平均窗口大小,越大越平滑但会丢失细小运动。
- iterations=3:每层迭代次数,越多越精确但越慢。
- poly_n=5:多项式邻域大小,通常 5 或 7。
- poly_sigma=1.2:高斯平滑标准差,对应 poly_n=5 时是 1.1,poly_n=7 时是 1.5。
七、其他光流算法对比
OpenCV 中还实现了多种其他光流算法,让我们对比一下它们的特点:
7.1 Horn-Schunck 算法
Horn-Schunck 是最早的稠密光流算法之一(1981年),引入了全局平滑约束:
E = ∫∫(Iₓu + Iᵧv + Iₜ)² dxdy + α ∫∫[(∇u)² + (∇v)²] dxdy
优点是理论优美,缺点是对噪声敏感,计算量大,且容易过度平滑。
7.2 TV-L1 算法
TV-L1 使用总变分(Total Variation)正则化,是目前质量最好的传统光流算法之一:
E = ||Iₓu + Iᵧv + Iₜ||₁ + λ(||∇u||₁ + ||∇v||₁)
L1 范数使得算法对异常值和噪声更加鲁棒。OpenCV 中通过 createOptFlow_DualTVL1() 创建。
7.3 SimpleFlow 算法
SimpleFlow 是 2012 年提出的算法,核心思想是非局部滤波,计算效率高且效果不错。OpenCV 中通过 calcOpticalFlowSF() 调用。
7.4 算法性能对比
| 算法 | 速度 | 精度 | 适用场景 |
|---|---|---|---|
| Lucas-Kanade | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 稀疏特征点跟踪 |
| Farneback | ⭐⭐⭐ | ⭐⭐⭐ | 一般稠密光流 |
| TV-L1 | ⭐ | ⭐⭐⭐⭐ | 高精度稠密光流 |
| SimpleFlow | ⭐⭐⭐⭐ | ⭐⭐⭐ | 实时稠密光流 |
八、实战项目:基于光流的运动目标检测
现在让我们实现一个更实用的项目:使用光流法进行运动目标检测和跟踪。
import cv2
import numpy as np
class OpticalFlowTracker:
def __init__(self, max_corners=200):
self.max_corners = max_corners
self.feature_params = dict(
maxCorners=max_corners,
qualityLevel=0.01,
minDistance=10,
blockSize=7
)
self.lk_params = dict(
winSize=(15, 15),
maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
)
self.tracks = []
self.track_len = 10
self.detect_interval = 5
self.frame_idx = 0
def process_frame(self, frame):
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
vis = frame.copy()
# 计算光流
if len(self.tracks) > 0:
img0, img1 = self.prev_gray, frame_gray
p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2)
p1, st, err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **self.lk_params)
p0r, st, err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **self.lk_params)
# 双向验证:正反向计算的误差应该很小
d = abs(p0 - p0r).reshape(-1, 2).max(-1)
good = d < 1.0
new_tracks = []
for tr, (x, y), good_flag in zip(self.tracks, p1.reshape(-1, 2), good):
if not good_flag:
continue
tr.append((x, y))
if len(tr) > self.track_len:
del tr[0]
new_tracks.append(tr)
cv2.circle(vis, (int(x), int(y)), 2, (0, 255, 0), -1)
self.tracks = new_tracks
# 绘制轨迹
cv2.polylines(vis, [np.int32(tr) for tr in self.tracks], False, (0, 255, 0))
# 计算运动统计
if len(self.tracks) > 5:
motions = np.array([tr[-1][0] - tr[0][0] for tr in self.tracks])
mean_motion = np.mean(motions)
cv2.putText(vis, f'Mean motion: {mean_motion:.2f}',
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
# 每隔几帧重新检测特征点
if self.frame_idx % self.detect_interval == 0:
mask = np.zeros_like(frame_gray)
mask[:] = 255
for x, y in [np.int32(tr[-1]) for tr in self.tracks]:
cv2.circle(mask, (x, y), 5, 0, -1)
p = cv2.goodFeaturesToTrack(frame_gray, mask=mask, **self.feature_params)
if p is not None:
for x, y in np.float32(p).reshape(-1, 2):
self.tracks.append([(x, y)])
self.frame_idx += 1
self.prev_gray = frame_gray
cv2.putText(vis, f'Track count: {len(self.tracks)}',
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
return vis
# 主程序
if __name__ == '__main__':
cap = cv2.VideoCapture(0)
tracker = OpticalFlowTracker()
while True:
ret, frame = cap.read()
if not ret:
break
vis = tracker.process_frame(frame)
cv2.imshow('Optical Flow Tracking', vis)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
这个改进版的跟踪器加入了双向验证(正反向计算光流,误差小的点才保留),大大提高了跟踪的稳定性。
九、参数调优指南
9.1 提高跟踪稳定性
问题:跟踪点经常丢失,轨迹断断续续
解决方案:
- 增大
winSize到 (21, 21) 或 (31, 31) - 增加
maxLevel金字塔层数到 4-5 - 降低
qualityLevel到 0.01,保留更多特征点 - 增加
minDistance避免特征点过于密集
9.2 提高实时性能
问题:在嵌入式设备上帧率太低
解决方案:
- 减小
maxCorners到 50-100 - 减小
winSize到 (11, 11) - 减小
maxLevel到 2-3 - 对输入图像进行降采样(如 640×480 → 320×240)
9.3 处理快速运动
问题:快速移动时跟踪失效
解决方案:
- 增加
maxLevel到 4-5 - 增大
winSize - 提高摄像头帧率(如果支持)
- 使用更高的相机快门速度减少运动模糊
十、常见问题与解决方案
10.1 为什么跟踪点会漂移?
原因:
- 小的计算误差会累积
- 亮度恒定假设不成立
- 物体旋转或尺度变化
解决方案:
- 定期重新检测特征点
- 使用更鲁棒的特征描述子(如 ORB)
- 加入双向验证机制
10.2 为什么光照变化时光流失效?
原因:亮度恒定假设被破坏
解决方案:
- 使用图像归一化(直方图均衡化)
- 改用基于梯度的特征(如 HOG)
- 使用对光照更鲁棒的光流变种(如 Census 变换)
10.3 如何处理大位移运动?
原因:小运动假设不成立
解决方案:
- 增加金字塔层数
- 使用由粗到精的策略
- 考虑使用深度学习光流算法(如 RAFT)
十一、进阶方向:深度学习光流
传统光流算法虽然经典,但在复杂场景下的精度已经被深度学习方法超越。
11.1 现代深度学习光流网络
- FlowNet / FlowNet2(2015/2016):端到端的光流网络鼻祖
- PWC-Net(2018):基于金字塔、变形、代价体的轻量网络
- RAFT(2020):基于循环优化的光流网络,目前的 SOTA
- GMFlow / GMFlowNet(2022/2023):基于全局匹配的高速高精度光流
11.2 在边缘设备上部署
很多轻量型的光流网络已经可以在边缘设备上实时运行:
- PWC-Net 在 Jetson Nano 上可以达到 10-15 FPS
- 量化后的小型网络在 RK3588 上可以达到 30+ FPS
如果你对精度要求很高,且硬件资源充足,深度学习光流是更好的选择。
总结
本文从零开始深入讲解了光流法的原理和实战应用。我们从最基本的亮度恒定假设出发,推导了光流约束方程,理解了孔径问题的本质,然后详细介绍了 Lucas-Kanade 算法和金字塔策略。
在实战部分,我们不仅给出了稀疏光流和稠密光流的基本代码,还实现了一个带有双向验证的稳定目标跟踪器。我们对比了各种光流算法的优缺点,给出了详细的参数调优指南和常见问题的解决方案。
光流法作为计算机视觉中最古老也最基础的技术之一,至今仍然在边缘计算、嵌入式 AI 等领域发挥着不可替代的作用。理解光流法的原理,不仅能帮助你解决实际问题,更能为你打开理解视觉运动的大门。
从最早的 Gibson 视觉运动理论,到 Lucas-Kanade 算法,再到今天的深度学习光流网络,人类对视觉运动的理解已经走过了半个多世纪。但光流法的核心思想——从亮度变化中推断运动——始终没有改变。这也许就是计算机视觉最迷人的地方:简单的原理,却蕴含着理解世界运动规律的钥匙。
(全文完,约7000字)