classdef CarMotionDetectorApp < matlab.apps.AppBase
% 主应用程序类 - 基于光流场的汽车运动检测系统
properties (Access = public)
% UI组件
UIFigure matlab.ui.Figure
UIAxes matlab.ui.control.UIAxes
LoadVideoButton matlab.ui.control.Button
ProcessButton matlab.ui.control.Button
ThresholdSlider matlab.ui.control.Slider
ThresholdLabel matlab.ui.control.Label
StatusLabel matlab.ui.control.Label
FrameRateEdit matlab.ui.control.NumericEditField
FrameRateLabel matlab.ui.control.Label
SaveResultsButton matlab.ui.control.Button
% 数据处理相关
VideoReader % 视频读取对象
VideoPath char = '' % 视频路径
FlowObj % 光流对象(修复:移除初始类型定义)
FlowThreshold double = 0.2 % 光流幅值阈值
IsProcessing logical = false % 处理状态标志
OutputVideoWriter % 输出视频写入对象
end
methods (Access = private)
% 加载视频按钮回调
function onLoadVideoButtonPushed(app, ~)
[file, path] = uigetfile({'*.mp4;*.avi;*.mov','视频文件 (*.mp4, *.avi, *.mov)'}, '选择视频文件');
if isequal(file,0)
return; % 用户取消选择
end
app.VideoPath = fullfile(path, file);
try
app.VideoReader = VideoReader(app.VideoPath);
% 显示第一帧
if hasFrame(app.VideoReader)
frame = readFrame(app.VideoReader);
imshow(frame, 'Parent', app.UIAxes);
title(app.UIAxes, '视频第一帧');
app.StatusLabel.Text = '视频加载完成,点击"开始处理"';
% 重置视频读取器
app.VideoReader = VideoReader(app.VideoPath);
end
% 初始化光流对象(关键修正)
app.FlowObj = opticalFlowFarneback; % 在方法中实例化
app.FlowObj.PyramidScale = 0.5;
app.FlowObj.NumPyramidLevels = 3;
app.FlowObj.FilterSize = 15;
app.FlowObj.NumIterations = 3;
app.FlowObj.PolynomialDegree = 5;
app.FlowObj.PolySigma = 1.2;
catch ME
uialert(app.UIFigure, sprintf('视频加载失败: %s', ME.message), '错误');
end
end
% 处理按钮回调
function onProcessButtonPushed(app, ~)
if isempty(app.VideoReader) || isempty(app.VideoPath)
uialert(app.UIFigure, '请先加载视频文件', '未选择视频');
return;
end
% 切换处理状态
if ~app.IsProcessing
app.IsProcessing = true;
app.ProcessButton.Text = '停止处理';
app.LoadVideoButton.Enable = 'off';
app.SaveResultsButton.Enable = 'off';
processVideo(app);
else
app.IsProcessing = false;
app.ProcessButton.Text = '开始处理';
app.LoadVideoButton.Enable = 'on';
app.SaveResultsButton.Enable = 'on';
app.StatusLabel.Text = '处理已停止';
end
end
% 保存结果按钮回调
function onSaveResultsButtonPushed(app, ~)
if isempty(app.VideoReader) || isempty(app.VideoPath)
uialert(app.UIFigure, '请先加载并处理视频', '无处理结果');
return;
end
[file, path] = uiputfile({'*.avi','AVI 视频文件 (*.avi)'}, '保存结果视频');
if isequal(file,0)
return; % 用户取消保存
end
outputPath = fullfile(path, file);
try
% 创建输出视频写入器
app.OutputVideoWriter = VideoWriter(outputPath, 'Motion JPEG AVI');
app.OutputVideoWriter.FrameRate = app.VideoReader.FrameRate;
open(app.OutputVideoWriter);
% 重置视频读取器和光流对象
app.VideoReader = VideoReader(app.VideoPath);
reset(app.FlowObj); % 重置光流状态
app.IsProcessing = true;
app.ProcessButton.Enable = 'off';
app.LoadVideoButton.Enable = 'off';
app.SaveResultsButton.Enable = 'off';
app.StatusLabel.Text = '正在保存结果...';
% 处理第一帧初始化
if hasFrame(app.VideoReader)
frame = readFrame(app.VideoReader);
currentGray = app.convertToGray(frame);
estimateFlow(app.FlowObj, currentGray); % 初始化光流
end
frameCount = 0;
% 处理并保存每一帧
while hasFrame(app.VideoReader) && app.IsProcessing
frame = readFrame(app.VideoReader);
frameCount = frameCount + 1;
currentGray = app.convertToGray(frame);
% 计算光流(使用预初始化的对象)
flowVectors = estimateFlow(app.FlowObj, currentGray);
Vx = flowVectors.Vx;
Vy = flowVectors.Vy;
magnitude = sqrt(Vx.^2 + Vy.^2);
motionMask = magnitude > app.FlowThreshold;
% 可视化
imshow(frame, 'Parent', app.UIAxes);
hold(app.UIAxes, 'on');
% 绘制运动区域(红色半透明)
redMask = cat(3, ones(size(motionMask)), zeros(size(motionMask)), zeros(size(motionMask)));
h = imshow(redMask, 'Parent', app.UIAxes);
set(h, 'AlphaData', 0.3 * motionMask);
% 绘制光流向量(采样绘制)
[h, w] = size(magnitude);
[X, Y] = meshgrid(1:10:w, 1:10:h);
U = Vx(1:10:h, 1:10:w);
V = Vy(1:10:h, 1:10:w);
quiver(app.UIAxes, X(:), Y(:), U(:), V(:), 2, 'Color', 'g', 'LineWidth', 1);
hold(app.UIAxes, 'off');
title(app.UIAxes, '汽车运动检测(红色区域为运动区域)');
% 获取当前帧图像并写入视频
frameWithOverlay = getframe(app.UIAxes);
writeVideo(app.OutputVideoWriter, frameWithOverlay.cdata);
% 每10帧释放内存(性能优化)
if mod(frameCount, 10) == 0
drawnow;
clear frameWithOverlay redMask motionMask magnitude Vx Vy;
end
app.StatusLabel.Text = sprintf('保存中... 帧: %d', frameCount);
drawnow limitrate; % 优化刷新性能
end
close(app.OutputVideoWriter);
app.IsProcessing = false;
app.ProcessButton.Enable = 'on';
app.LoadVideoButton.Enable = 'on';
app.SaveResultsButton.Enable = 'on';
app.StatusLabel.Text = sprintf('结果已保存至: %s', outputPath);
catch ME
uialert(app.UIFigure, sprintf('保存失败: %s', ME.message), '错误');
app.IsProcessing = false;
app.ProcessButton.Enable = 'on';
app.LoadVideoButton.Enable = 'on';
app.SaveResultsButton.Enable = 'on';
if ~isempty(app.OutputVideoWriter) && isvalid(app.OutputVideoWriter)
close(app.OutputVideoWriter);
end
end
end
% 视频处理主函数
function processVideo(app)
% 重置视频读取器和光流对象
app.VideoReader = VideoReader(app.VideoPath);
reset(app.FlowObj); % 重置光流状态
% 获取帧率控制值
frameRate = app.FrameRateEdit.Value;
if frameRate <= 0
frameRate = app.VideoReader.FrameRate;
end
pauseTime = 1/frameRate;
% 处理第一帧初始化
if hasFrame(app.VideoReader)
frame = readFrame(app.VideoReader);
currentGray = app.convertToGray(frame);
estimateFlow(app.FlowObj, currentGray); % 初始化光流
imshow(frame, 'Parent', app.UIAxes);
title(app.UIAxes, '视频第一帧(已初始化)');
end
frameCount = 0;
% 循环处理每一帧
while hasFrame(app.VideoReader) && app.IsProcessing
frameCount = frameCount + 1;
% 读取当前帧
frame = readFrame(app.VideoReader);
% 转换为灰度图像(版本兼容)
currentGray = app.convertToGray(frame);
% 计算光流(使用预初始化的对象)
flowVectors = estimateFlow(app.FlowObj, currentGray);
% 获取光流向量
Vx = flowVectors.Vx;
Vy = flowVectors.Vy;
magnitude = sqrt(Vx.^2 + Vy.^2); % 光流幅值
% 检测运动区域(超过阈值的区域)
motionMask = magnitude > app.FlowThreshold;
% 可视化
imshow(frame, 'Parent', app.UIAxes);
hold(app.UIAxes, 'on');
% 绘制运动区域(红色半透明)
redMask = cat(3, ones(size(motionMask)), zeros(size(motionMask)), zeros(size(motionMask)));
h = imshow(redMask, 'Parent', app.UIAxes);
set(h, 'AlphaData', 0.3 * motionMask);
% 绘制光流向量(采样绘制)
[h, w] = size(magnitude);
[X, Y] = meshgrid(1:10:w, 1:10:h);
U = Vx(1:10:h, 1:10:w);
V = Vy(1:10:h, 1:10:w);
quiver(app.UIAxes, X(:), Y(:), U(:), V(:), 2, 'Color', 'g', 'LineWidth', 1);
hold(app.UIAxes, 'off');
title(app.UIAxes, '汽车运动检测(红色区域为运动区域)');
% 更新状态
app.StatusLabel.Text = sprintf('处理中... 当前帧: %d', frameCount);
% 每10帧释放内存(性能优化)
if mod(frameCount, 10) == 0
drawnow;
clear redMask motionMask magnitude Vx Vy;
end
% 控制处理速度
pause(pauseTime);
end
% 处理完成或停止
if app.IsProcessing
app.IsProcessing = false;
app.ProcessButton.Text = '开始处理';
app.LoadVideoButton.Enable = 'on';
app.SaveResultsButton.Enable = 'on';
app.StatusLabel.Text = '处理完成!';
end
end
% 灰度转换(版本兼容)
function gray = convertToGray(~, frame)
% 检查MATLAB版本
if verLessThan('matlab', '9.8') % R2020a之前版本
gray = rgb2gray(frame);
else
gray = im2gray(frame);
end
end
% 阈值滑动条回调
function onThresholdSliderValueChanged(app, ~)
app.FlowThreshold = app.ThresholdSlider.Value;
app.ThresholdLabel.Text = sprintf('运动阈值: %.2f', app.FlowThreshold);
end
% 帧率编辑框回调
function onFrameRateEditValueChanged(app, ~)
frameRate = app.FrameRateEdit.Value;
if frameRate <= 0
app.FrameRateEdit.Value = app.VideoReader.FrameRate;
end
end
end
methods (Access = private)
% 创建UI组件
function createComponents(app)
% 创建主窗口
app.UIFigure = uifigure('Name', '基于光流场的汽车运动检测系统', ...
'Position', [100 100 900 650]);
% 创建坐标轴
app.UIAxes = uiaxes(app.UIFigure);
app.UIAxes.Position = [50, 180, 800, 450];
app.UIAxes.XTick = [];
app.UIAxes.YTick = [];
title(app.UIAxes, '视频显示');
% 创建加载视频按钮
app.LoadVideoButton = uibutton(app.UIFigure, 'push', ...
'Position', [50, 130, 100, 30], ...
'Text', '加载视频', ...
'ButtonPushedFcn', @(src, event) onLoadVideoButtonPushed(app, event));
% 创建处理按钮
app.ProcessButton = uibutton(app.UIFigure, 'push', ...
'Position', [170, 130, 100, 30], ...
'Text', '开始处理', ...
'ButtonPushedFcn', @(src, event) onProcessButtonPushed(app, event));
% 创建保存结果按钮
app.SaveResultsButton = uibutton(app.UIFigure, 'push', ...
'Position', [290, 130, 100, 30], ...
'Text', '保存结果', ...
'Enable', 'off', ...
'ButtonPushedFcn', @(src, event) onSaveResultsButtonPushed(app, event));
% 创建阈值滑动条
app.ThresholdSlider = uislider(app.UIFigure, ...
'Position', [420, 150, 200, 3], ...
'Limits', [0.05, 1], ...
'Value', app.FlowThreshold, ...
'ValueChangedFcn', @(src, event) onThresholdSliderValueChanged(app, event));
% 创建阈值标签
app.ThresholdLabel = uilabel(app.UIFigure, ...
'Position', [420, 120, 200, 22], ...
'Text', sprintf('运动阈值: %.2f', app.FlowThreshold));
% 创建帧率标签
app.FrameRateLabel = uilabel(app.UIFigure, ...
'Position', [650, 120, 80, 22], ...
'Text', '处理帧率:');
% 创建帧率编辑框
app.FrameRateEdit = uieditfield(app.UIFigure, 'numeric', ...
'Position', [730, 120, 60, 22], ...
'Value', 10, ...
'Limits', [1, 60], ...
'ValueChangedFcn', @(src, event) onFrameRateEditValueChanged(app, event));
% 创建状态标签
app.StatusLabel = uilabel(app.UIFigure, ...
'Position', [50, 80, 800, 22], ...
'Text', '请加载视频文件');
end
end
methods (Access = public)
% 构造函数
function app = CarMotionDetectorApp
% 创建UI组件
createComponents(app);
% 初始化光流对象(避免空数组错误)
app.FlowObj = []; % 初始化为空,稍后实例化
end
end
end
分析一下这个代码告诉我运行之后的红色区域和绿色区域的意思是什么