光流(Optical Flow)是计算机视觉中的核心技术之一,主要用于估计图像序列中像素的运动,广泛用于运动检测、目标跟踪、视觉里程计、SLAM 等任务。
一、光流的基本假设(Brightness Constancy)
设一段时间内,图像亮度在像素点处保持不变,即:
I ( x , y , t ) = I ( x + Δ x , y + Δ y , t + Δ t ) I(x, y, t) = I(x + \Delta x, y + \Delta y, t + \Delta t) I(x,y,t)=I(x+Δx,y+Δy,t+Δt)
对上式进行泰勒展开,取一阶忽略高阶项,得:
I x ⋅ u + I y ⋅ v + I t = 0 I_x \cdot u + I_y \cdot v + I_t = 0 Ix⋅u+Iy⋅v+It=0
其中:
- I x = ∂ I ∂ x I_x = \frac{\partial I}{\partial x} Ix=∂x∂I:x 方向梯度
- I y = ∂ I ∂ y I_y = \frac{\partial I}{\partial y} Iy=∂y∂I:y 方向梯度
- I t = ∂ I ∂ t I_t = \frac{\partial I}{\partial t} It=∂t∂I:时间导数
- u = d x d t , v = d y d t u = \frac{dx}{dt}, v = \frac{dy}{dt} u=dtdx,v=dtdy:光流(像素速度)
这就是著名的光流约束方程(Optical Flow Constraint Equation):
I x u + I y v = − I t I_x u + I_y v = -I_t Ixu+Iyv=−It
二、常见光流求解方法
1. Lucas-Kanade 光流(局部窗口法)
假设:邻域内所有像素点的光流 ( u , v ) (u,v) (u,v) 相同,在 N × N N \times N N×N 区域内建立约束系统:
写成正规方程:
A T A ⋅ [ u v ] = − A T b ⇒ [ u v ] = − ( A T A ) − 1 A T b A^T A \cdot \begin{bmatrix} u \\ v \end{bmatrix} = -A^T b \Rightarrow \begin{bmatrix} u \\ v \end{bmatrix} = -(A^T A)^{-1} A^T b ATA⋅[uv]=−ATb⇒[uv]=−(ATA)−1ATb
要求 A T A A^T A ATA 可逆,即图像局部结构足够丰富(角点处求解稳定)。
2. Horn-Schunck 光流(全局光滑约束)
引入平滑项,最小化以下能量函数:
E ( u , v ) = ∬ [ ( I x u + I y v + I t ) 2 + α 2 ( ∣ ∇ u ∣ 2 + ∣ ∇ v ∣ 2 ) ] d x d y E(u, v) = \iint \left[ (I_x u + I_y v + I_t)^2 + \alpha^2 \left( |\nabla u|^2 + |\nabla v|^2 \right) \right] dxdy E(u,v)=∬[(Ixu+Iyv+It)2+α2(∣∇u∣2+∣∇v∣2)]dxdy
采用梯度下降或迭代法求解(偏耗时)。
三、OpenCV中的光流使用C++代码示例以及结果可视化
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void drawOptFlowMap(const Mat& flow, Mat& cflowmap, int step, const Scalar& color) {
for (int y = 0; y < cflowmap.rows; y += step) {
for (int x = 0; x < cflowmap.cols; x += step) {
const Point2f& fxy = flow.at<Point2f>(y, x);
line(cflowmap, Point(x, y), Point(cvRound(x + fxy.x), cvRound(y + fxy.y)), color);
circle(cflowmap, Point(x, y), 1, color, -1);
}
}
}
int main() {
Mat prev = imread("frame1.png", IMREAD_GRAYSCALE);
Mat next = imread("frame2.png", IMREAD_GRAYSCALE);
Mat flow;
calcOpticalFlowFarneback(prev, next, flow,
0.5, // pyr_scale
3, // levels
15, // winsize
3, // iterations
5, // poly_n
1.2, // poly_sigma
0); // flags
Mat flow_vis;
cvtColor(prev, flow_vis, COLOR_GRAY2BGR);
drawOptFlowMap(flow, flow_vis, 16, Scalar(0, 255, 0));
imshow("Farneback Flow", flow_vis);
waitKey(0);
return 0;
}
四 、cv::calcOpticalFlowFarneback和cv::calcOpticalFlowPyrLK参数说明
calcOpticalFlowPyrLK函数原型
void cv::calcOpticalFlowPyrLK(
InputArray prevImg, // 前一帧灰度图
InputArray nextImg, // 当前帧灰度图
InputArray prevPts, // 前一帧点坐标(vector<Point2f>)
InputOutputArray nextPts, // 当前帧点坐标(输出)
OutputArray status, // 跟踪成功标志(uchar,1: 成功,0: 失败)
OutputArray err, // 每个点的误差(如残差)
Size winSize = Size(21, 21), // 每层金字塔窗口大小
int maxLevel = 3, // 最大金字塔层数
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
int flags = 0, // 额外标志(可选)
double minEigThreshold = 1e-4 // 最小特征值阈值
);
参数解释
参数 | 含义 |
---|---|
prevImg | 前一帧图像(必须为单通道 CV_8U 或 CV_32F) |
nextImg | 当前帧图像 |
prevPts | 需要跟踪的点(例如通过角点检测提取) |
nextPts | 跟踪得到的新位置(如果 flags = OPTFLOW_USE_INITIAL_FLOW ,将作为初始估计) |
status | 输出每个点是否成功跟踪(uchar) |
err | 每个点的残差或误差 |
winSize | 每层金字塔的光流搜索窗口大小 |
maxLevel | 金字塔层数(0 表示不使用金字塔) |
criteria | 收敛条件(迭代次数与残差变化) |
flags | 标志位,如 OPTFLOW_USE_INITIAL_FLOW 、OPTFLOW_LK_GET_MIN_EIGENVALS |
minEigThreshold | 特征值小于此阈值的点会被认为跟踪失败 |
calcOpticalFlowFarneback函数原型
void cv::calcOpticalFlowFarneback(
InputArray prev, // 前一帧图像(灰度)
InputArray next, // 当前帧图像(灰度)
OutputArray flow, // 输出光流(CV_32FC2,每个像素的(x, y)光流向量)
double pyr_scale, // 金字塔缩放因子(如 0.5)
int levels, // 金字塔层数
int winsize, // 每层窗口大小(如 15)
int iterations, // 每层迭代次数
int poly_n, // 多项式展开邻域大小(如 5)
double poly_sigma, // 高斯平滑标准差(如 1.1)
int flags // 额外标志(如 OPTFLOW_USE_INITIAL_FLOW)
);
参数说明
参数 | 说明 |
---|---|
prev / next | 灰度图像,CV_8U 或 CV_32F |
flow | 输出光流矩阵(类型为 CV_32FC2 ,flow(y, x)[0] 为水平位移,[1] 为垂直位移) |
pyr_scale | 每层金字塔图像的缩放比例,通常为 0.5 |
levels | 金字塔层数(例如 3~5) |
winsize | 每层图像窗口大小,影响精度与鲁棒性(常用 15~25) |
iterations | 每层迭代次数(3~5) |
poly_n | 多项式邻域的大小(5 或 7) |
poly_sigma | 高斯平滑标准差(通常 1.1) |
flags | 可用 OPTFLOW_USE_INITIAL_FLOW (重用上一次光流),或 OPTFLOW_FARNEBACK_GAUSSIAN (使用高斯而非框形窗口) |
五、光流法比较总结
方法 | 特点 | 优点 | 缺点 |
---|---|---|---|
Lucas-Kanade | 局部窗口法,线性方程组 | 快速,适用于稀疏光流 | 靠角点稀疏性,易受遮挡干扰 |
Horn-Schunck | 全局最小化,带平滑项 | 稠密估计效果好 | 迭代复杂,慢 |
Farneback | 多项式逼近+金字塔 | 密集光流,性能稳定 | 较慢 |
DeepFlow/PWCNet | 深度学习方法 | 精度高,适应复杂变形 | 需 GPU,模型大 |
推荐
- 推荐读物:《Multiple View Geometry》、《Computer Vision: Algorithms and Applications》