光流描述畫面中像素在時序上的位移速度場。Gibson 1950 年提出,核心假設:像素亮度隨時間不變(亮度恆定假設)。
應用:運動偵測、物體追蹤、視覺里程計、動作辨識。
像素 I(x,y,t) 在 Δt 後移至 (x+Δx, y+Δy),亮度不變:

泰勒展開一階近似(忽略高階項):

整理得光流方程:

單一像素兩未知數 (u,v) 一方程,無法唯一解。
假設 3×3 鄰域像素共享相同位移,得 9 條方程,2 未知數,超定系統,用最小二乘求解:

矩陣形式 Av = b,解為 v = (AᵀA)⁻¹Aᵀb。
金字塔層級 (Pyramid) 解決大位移問題:粗略層估大位移,細層細化。
calcOpticalFlowPyrLK
#include <opencv2/video/tracking.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv; using namespace std;
int main() {
VideoCapture cap("G:/.../sample.avi");
TermCriteria termcrit(TermCriteria::COUNT|TermCriteria::EPS, 20, 0.03);
Size subPixWinSize(10,10), winSize(31,31);
const int MAX_COUNT = 500;
bool needToInit = false, nightMode = false;
vector<Point2f> points[2];
Mat gray, prevGray, frame, image;
namedWindow("LK Demo", 1);
setMouseCallback("LK Demo", [](int e, int x, int y, int, void*) {
if (e == EVENT_LBUTTONDOWN) { point = Point2f(x,y); addRemovePt = true; }
}, 0);
for (;;) {
cap >> frame; if (frame.empty()) break;
frame.copyTo(image);
cvtColor(image, gray, COLOR_BGR2GRAY);
if (nightMode) image = Scalar::all(0);
if (needToInit) {
goodFeaturesToTrack(gray, points[1], 100, 0.01, 10, Mat(), 3, 0, 0.04);
cornerSubPix(gray, points[1], subPixWinSize, Size(-1,-1), termcrit);
needToInit = false;
} else if (!points[0].empty()) {
vector<uchar> status; vector<float> err;
if (prevGray.empty()) gray.copyTo(prevGray);
calcOpticalFlowPyrLK(prevGray, gray, points[0], points[1], status, err,
winSize, 3, termcrit, 0, 0.001);
// 過濾失效點、繪製軌跡...
size_t k = 0;
for (size_t i = 0; i < points[1].size(); ++i) {
if (addRemovePt && norm(point - points[1][i]) <= 5) { addRemovePt = false; continue; }
if (!status[i]) continue;
points[1][k++] = points[1][i];
circle(image, points[1][i], 3, Scalar(0,255,0), -1, 8);
}
points[1].resize(k);
}
if (addRemovePt && points[1].size() < MAX_COUNT) {
vector<Point2f> tmp = {point};
cornerSubPix(gray, tmp, winSize, Size(-1,-1), termcrit);
points[1].push_back(tmp[0]); addRemovePt = false;
}
swap(points[1], points[0]); swap(prevGray, gray);
imshow("LK Demo", image);
char c = waitKey(100);
if (c == 27) break;
if (c == 'r') needToInit = true;
else if (c == 'c') { points[0].clear(); points[1].clear(); }
else if (c == 'n') nightMode = !nightMode;
}
return 0;
}
特徵點隨車輛移動自動追蹤:

goodFeaturesToTrack:Shi-Tomasi 角點偵測,取得初始追蹤點。cornerSubPix:亞像素級角點優化。calcOpticalFlowPyrLK:金字塔 Lucas-Kanade 光流,回傳新位置、狀態、誤差。