MATLAB 代码,使用 Lucas-Kanade 光流法 计算视频序列中运动物体的光流场,并输出每个像素点的运动状态(速度矢量、方向角、运动幅度)。
一、算法原理概述
光流法通过连续帧间的像素强度变化估计运动场。Lucas-Kanade 法假设局部窗口内运动一致,通过最小二乘法求解光流方程:
Ixu+Iyv+It=0I_x u + I_y v + I_t = 0Ixu+Iyv+It=0
其中 Ix,IyI_x, I_yIx,Iy 为空间梯度,ItI_tIt为时间梯度,u,vu, vu,v为光流矢量。
二、 MATLAB 代码
2.1 主函数:optical_flow_demo.m
matlab
%% 光流法检测运动物体并输出每个像素的运动状态
clear; clc; close all;
%% 1. 读取视频或图像序列
% 方式一:读取视频文件
vidObj = VideoReader('motion.avi'); % 替换为你的视频文件
% 方式二:生成模拟运动图像(用于演示)
% 这里使用内置的 shuttle 视频
if ~exist('vidObj','var')
vidObj = VideoReader('shuttle.avi'); % MATLAB自带示例
end
% 读取前两帧
frame1 = readFrame(vidObj);
frame2 = readFrame(vidObj);
% 转为灰度图
if size(frame1,3)==3
gray1 = rgb2gray(frame1);
gray2 = rgb2gray(frame2);
else
gray1 = frame1;
gray2 = frame2;
end
%% 2. 计算光流(Lucas-Kanade 法)
% 参数设置
windowSize = 15; % 局部窗口大小(奇数)
sigma = 2; % 高斯平滑标准差
threshold = 0.01; % 特征值阈值(过滤低纹理区域)
% 计算光流
[u, v] = lucas_kanade_flow(double(gray1), double(gray2), windowSize, sigma, threshold);
%% 3. 计算每个像素的运动状态
% 运动幅度(速度大小)
magnitude = sqrt(u.^2 + v.^2);
% 运动方向(弧度,范围 -π ~ π)
direction = atan2(v, u);
% 转换为角度(度)
direction_deg = rad2deg(direction);
%% 4. 可视化光流场
figure('Position',[100 100 1400 650]);
% 原图
subplot(2,3,1);
imshow(gray1); title('当前帧');
% 光流矢量图(稠密采样)
subplot(2,3,2);
imshow(gray1); hold on;
step = 8; % 每隔 step 像素显示一个箭头
[X, Y] = meshgrid(1:step:size(gray1,2), 1:step:size(gray1,1));
quiver(X, Y, u(1:step:end,1:step:end), v(1:step:end,1:step:end), ...
2, 'r', 'LineWidth', 1);
title('光流矢量场');
% 运动幅度图
subplot(2,3,3);
imagesc(magnitude); axis image; colorbar;
colormap(jet); title('运动幅度');
% 运动方向图
subplot(2,3,4);
imagesc(direction_deg); axis image; colorbar;
colormap(hsv); title('运动方向(°)');
% 运动物体分割(幅度阈值)
subplot(2,3,5);
threshold_mag = 0.5; % 根据实际情况调整
moving_mask = magnitude > threshold_mag;
imshow(moving_mask); title('运动区域检测');
% 叠加显示
subplot(2,3,6);
imshow(gray1); hold on;
overlay = imoverlay(gray1, moving_mask, [1 0 0]);
imshow(overlay); title('运动物体标记(红色)');
sgtitle('Lucas-Kanade 光流分析','FontSize',14,'FontWeight','bold');
%% 5. 输出每个像素的运动状态(示例:前10×10区域)
fprintf('前10×10像素的运动状态(部分):\n');
fprintf(' (x,y) | u (pix/frame) | v (pix/frame) | 速度幅值 | 方向角(°)\n');
for i = 1:min(10,size(u,1))
for j = 1:min(10,size(u,2))
fprintf('(%3d,%3d) | %12.4f | %12.4f | %10.4f | %8.2f\n', ...
j, i, u(i,j), v(i,j), magnitude(i,j), direction_deg(i,j));
end
end
2.2 Lucas-Kanade 光流函数:lucas_kanade_flow.m
matlab
function [u, v] = lucas_kanade_flow(im1, im2, windowSize, sigma, threshold)
% Lucas-Kanade 稠密光流计算
% 输入:
% im1, im2 - 连续两帧灰度图像(double类型)
% windowSize - 局部窗口大小(奇数,默认15)
% sigma - 高斯平滑标准差(默认2)
% threshold - 特征值阈值(默认0.01)
% 输出:
% u, v - 水平/垂直光流分量
if nargin < 3, windowSize = 15; end
if nargin < 4, sigma = 2; end
if nargin < 5, threshold = 0.01; end
% 高斯平滑预处理
h = fspecial('gaussian', max(3,ceil(2*sigma)), sigma);
im1 = imfilter(im1, h, 'replicate');
im2 = imfilter(im2, h, 'replicate');
% 计算空间梯度 Ix, Iy 和时间梯度 It
fx = [-1 1; -1 1]; % Sobel-like
fy = [-1 -1; 1 1];
ft = [1 1; 1 1];
Ix = imfilter(im1, fx, 'replicate') + imfilter(im2, fx, 'replicate');
Iy = imfilter(im1, fy, 'replicate') + imfilter(im2, fy, 'replicate');
It = imfilter(im1, ft, 'replicate') - imfilter(im2, ft, 'replicate');
% 初始化光流
u = zeros(size(im1));
v = zeros(size(im1));
half = floor(windowSize/2);
% 遍历每个像素
for i = 1+half : size(im1,1)-half
for j = 1+half : size(im1,2)-half
% 提取窗口内的梯度
winIx = Ix(i-half:i+half, j-half:j+half);
winIy = Iy(i-half:i+half, j-half:j+half);
winIt = It(i-half:i+half, j-half:j+half);
% 构建矩阵 A 和向量 b
A = [winIx(:), winIy(:)];
b = -winIt(:);
% 求解最小二乘:A'*A * [u;v] = A'*b
AtA = A' * A;
Atb = A' * b;
% 检查特征值(避免病态)
eigenvalues = eig(AtA);
if min(eigenvalues) > threshold
flow = AtA \ Atb;
u(i,j) = flow(1);
v(i,j) = flow(2);
else
u(i,j) = 0;
v(i,j) = 0;
end
end
end
end
2.3 辅助函数:imoverlay.m(用于叠加显示)
matlab
function overlay = imoverlay(baseImg, mask, color)
% 将二值掩膜以指定颜色叠加到灰度图上
if nargin < 3, color = [1 0 0]; end % 默认红色
if size(baseImg,3)==1
baseImg = repmat(baseImg, [1 1 3]);
end
overlay = baseImg;
for c = 1:3
channel = overlay(:,:,c);
channel(mask) = color(c) * 255;
overlay(:,:,c) = channel;
end
overlay = uint8(overlay);
end
三、运行结果说明
3.1 输出内容
- 光流矢量图:红色箭头表示运动方向和速度大小
- 运动幅度图:颜色越亮表示运动越快
- 运动方向图:HSV色环表示方向(0°红,90°黄,180°青,270°紫)
- 运动区域检测:二值掩膜,白色为运动区域
- 运动物体标记:红色覆盖在原图上
- 命令行输出:前100个像素的详细运动状态(u, v, 幅值, 方向角)
3.2 参数调优建议
| 参数 | 作用 | 建议值 |
|---|---|---|
windowSize |
局部窗口大小 | 11~21(运动剧烈用小窗口,平滑运动用大窗口) |
sigma |
高斯平滑程度 | 1~3(噪声大时增大) |
threshold |
特征值阈值 | 0.001~0.1(纹理丰富区域可设小,平坦区域设大) |
threshold_mag |
运动分割阈值 | 根据运动幅度动态调整(可用Otsu自动阈值) |
四、扩展功能
4.1 使用 Horn-Schunck 全局光流法(备选)
若需要全局平滑约束,可替换为 Horn-Schunck 算法:
matlab
function [u, v] = horn_schunck_flow(im1, im2, alpha, iterations)
% alpha: 平滑权重(越大越平滑)
% iterations: 迭代次数
% ...(实现略,可参考标准算法)
4.2 运动状态统计分析
matlab
% 统计运动区域的平均速度和方向
moving = magnitude > threshold_mag;
mean_speed = mean(magnitude(moving));
mean_dir = mean(direction_deg(moving));
fprintf('运动区域平均速度: %.2f pix/frame\n', mean_speed);
fprintf('运动区域平均方向: %.2f°\n', mean_dir);
4.3 光流轨迹追踪
matlab
% 使用累积光流追踪特征点轨迹
% 可结合 KLT 特征点追踪
参考代码 基于光流算法,检测运动物体的光流场 www.youwenfan.com/contentcsv/80977.html
五、注意事项
- 图像预处理:建议先进行直方图均衡化或光照归一化,减少光照变化影响。
- 多尺度处理 :对大运动(>1像素/帧),应采用金字塔 Lucas-Kanade 法(
cv2.calcOpticalFlowPyrLK类似)。 - 边缘处理:窗口靠近图像边界时,会自动缩小窗口(上述代码已处理边界)。
- 性能优化 :对于大尺寸图像,可先用
imresize降采样再计算光流。