3. 光流约束方程:亮度恒定假设与梯度约束方程的推导

好,咱们进入正题。

光流法这东西,说白了就是让计算机学会「看运动」。你想想看,我们人眼看到物体在动,是因为视网膜上的像素点在变化。计算机也一样——它得知道前一帧的某个点,跑到后一帧的哪个位置去了。

那问题来了:怎么找到这种对应关系?

嗯,这里就要引入一个核心假设——亮度恒定

3.1 亮度恒定假设:光流法的基石

我个人习惯把亮度恒定假设叫做「铁律」。什么意思呢?

假设你在拍一段视频,画面里有个小球在滚动。小球表面上的某个点,在 t 时刻和 t+Δt 时刻,它的亮度应该是一样的。除非灯光变了,或者小球本身变色了——但那些情况我们暂时不考虑。

亮度恒定假设的数学表达:

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

其中 I 表示像素亮度,(x, y) 是空间位置,t 是时间。

我在项目中遇到过一个问题:有次做室外监控的目标跟踪,大白天突然飘来一朵云,整个场景亮度骤变。结果光流算出来全是错的。后来我加了预处理——先做直方图均衡化,再算光流,效果就好多了。

所以你要记住:亮度恒定假设在理想条件下成立,但实际场景中经常被打破

3.2 从假设到方程:泰勒展开的妙用

有了亮度恒定假设,我们怎么把它变成可计算的方程?

这里要用到泰勒展开。别怕,咱们一步步来。

把 I(x + dx, y + dy, t + dt) 在 (x, y, t) 处展开:

I(x+dx, y+dy, t+dt) ≈ I(x,y,t) + ∂I/∂x · dx + ∂I/∂y · dy + ∂I/∂t · dt + 高阶项

根据亮度恒定假设,左边等于右边 I(x,y,t)。两边一减,得到:

∂I/∂x · dx + ∂I/∂y · dy + ∂I/∂t · dt = 0

两边同时除以 dt,令 u = dx/dt, v = dy/dt,就得到了大名鼎鼎的:

光流约束方程:

Ix · u + Iy · v + It = 0

其中 Ix = ∂I/∂x, Iy = ∂I/∂y, It = ∂I/∂t

u 和 v 就是我们要求的光流——也就是像素在 x 方向和 y 方向的速度。

3.3 一个方程,两个未知数:病态问题来了

看到这里你可能会想:这不就解出来了吗?

别急,问题来了。一个方程里有两个未知数 u 和 v。数学上这叫「病态问题」——解不唯一。

我刚开始学光流时,卡在这里好几天。心想:这方程是不是白推了?

后来才明白,正因为有这个问题,才催生了各种光流算法——Lucas-Kanade、Horn-Schunck 等等。它们本质上都是在想办法「补」另一个约束条件。

小技巧:

在实际代码中,Ix, Iy, It 是通过图像梯度计算的。我一般用 Sobel 算子或者 Scharr 算子来算,效果比较稳定。

3.4 梯度计算:从理论到代码

理论讲完了,咱们看看代码怎么写。

计算 Ix, Iy, It 其实很简单:

import cv2
import numpy as np

# 读取两帧图像
frame1 = cv2.imread('frame1.jpg', cv2.IMREAD_GRAYSCALE)
frame2 = cv2.imread('frame2.jpg', cv2.IMREAD_GRAYSCALE)

# 计算空间梯度 Ix, Iy
Ix = cv2.Sobel(frame1, cv2.CV_64F, 1, 0, ksize=3)
Iy = cv2.Sobel(frame1, cv2.CV_64F, 0, 1, ksize=3)

# 计算时间梯度 It
It = frame2.astype(np.float64) - frame1.astype(np.float64)

这里要注意几点:

  • 空间梯度用第一帧算,或者两帧平均,我个人习惯用第一帧
  • 时间梯度就是两帧相减,简单粗暴
  • 数据类型要转成 float,不然梯度计算会出问题

避坑指南:

我曾经在计算梯度时忘了归一化,结果光流值大得离谱。后来养成了习惯——算完梯度后,检查一下数值范围,必要时做归一化处理。

3.5 光流约束方程的几何意义

咱们换个角度理解这个方程。

Ix · u + Iy · v + It = 0 其实是一个直线方程。在 (u, v) 平面上,它代表一条直线。所有满足条件的 (u, v) 都在这条直线上。

这就解释了为什么只有一个方程不够——我们需要从直线上「挑」出唯一的一个点。

不同的光流算法,本质上就是不同的「挑点」策略:

算法 补充约束 核心思想
Lucas-Kanade 局部窗口内运动一致 用多个像素的方程联立求解
Horn-Schunck 全局平滑约束 让光流场尽量平滑
Farneback 多项式展开近似 用二次多项式拟合局部图像

3.6 实际应用中的注意事项

最后,分享几个我在实战中总结的经验:

  1. 亮度恒定假设不成立时怎么办? 试试图像预处理——直方图均衡化、高斯滤波、甚至用深度学习做光照归一化。
  2. 梯度计算要选对核。Sobel 核大小 3x3 够用,但如果你要更高精度,试试 Scharr 核。
  3. 时间梯度要小心。如果两帧之间运动太大,It 会很大,导致光流计算不稳定。这时候可以考虑用金字塔方法——先粗后细。
  4. 别忘了检查数值。我习惯在每一步都打印一下梯度的均值和方差,确保没有异常值。

核心要点回顾:

  • 亮度恒定假设是光流法的根基
  • 光流约束方程:Ixu + Iyv + It = 0
  • 一个方程两个未知数,需要额外约束
  • 梯度计算用 Sobel/Scharr,注意数据类型

好了,这一章的内容就到这儿。下一章咱们会深入 Lucas-Kanade 算法,看看怎么用局部窗口来解这个「一个方程两个未知数」的难题。

记住:理论是基础,代码是工具,实战才是检验真理的唯一标准