使用 MATLAB 实现帧间差分法运动目标检测的完整代码,包含详细注释和可视化界面。
matlab
function motion_detection_fd()
% 基于帧间差分法的运动目标检测 MATLAB 实现
% 日期: 2023-10-15
% 创建主界面
fig = figure('Name', '帧间差分法运动检测', 'NumberTitle', 'off', ...
'Position', [100, 100, 1200, 700], 'MenuBar', 'none');
% 创建控制面板
controlPanel = uipanel('Title', '控制面板', 'Position', [0.02, 0.02, 0.3, 0.96], ...
'BackgroundColor', [0.9, 0.9, 0.9]);
% 创建参数控件
uicontrol(controlPanel, 'Style', 'text', 'String', '阈值:', ...
'Position', [20, 550, 80, 20], 'HorizontalAlignment', 'left');
thresholdSlider = uicontrol(controlPanel, 'Style', 'slider', ...
'Min', 1, 'Max', 100, 'Value', 30, ...
'Position', [100, 550, 150, 20]);
thresholdText = uicontrol(controlPanel, 'Style', 'text', ...
'String', '30', 'Position', [260, 550, 40, 20]);
uicontrol(controlPanel, 'Style', 'text', 'String', '最小区域:', ...
'Position', [20, 500, 80, 20], 'HorizontalAlignment', 'left');
minAreaSlider = uicontrol(controlPanel, 'Style', 'slider', ...
'Min', 10, 'Max', 2000, 'Value', 500, ...
'Position', [100, 500, 150, 20]);
minAreaText = uicontrol(controlPanel, 'Style', 'text', ...
'String', '500', 'Position', [260, 500, 40, 20]);
uicontrol(controlPanel, 'Style', 'text', 'String', '形态学操作:', ...
'Position', [20, 450, 80, 20], 'HorizontalAlignment', 'left');
morphOps = {'无', '开运算', '闭运算', '开闭运算'};
morphPopup = uicontrol(controlPanel, 'Style', 'popupmenu', ...
'String', morphOps, 'Value', 4, ...
'Position', [100, 450, 150, 20]);
uicontrol(controlPanel, 'Style', 'pushbutton', 'String', '加载视频', ...
'Position', [20, 400, 100, 30], 'Callback', @loadVideo);
uicontrol(controlPanel, 'Style', 'pushbutton', 'String', '开始检测', ...
'Position', [130, 400, 100, 30], 'Callback', @startDetection);
uicontrol(controlPanel, 'Style', 'pushbutton', 'String', '暂停', ...
'Position', [20, 350, 100, 30], 'Callback', @pauseDetection);
uicontrol(controlPanel, 'Style', 'pushbutton', 'String', '停止', ...
'Position', [130, 350, 100, 30], 'Callback', @stopDetection);
uicontrol(controlPanel, 'Style', 'pushbutton', 'String', '保存结果', ...
'Position', [20, 300, 210, 30], 'Callback', @saveResults);
% 创建结果显示区域
resultPanel = uipanel('Title', '检测结果', 'Position', [0.34, 0.52, 0.64, 0.46], ...
'BackgroundColor', 'white');
ax1 = subplot(2, 3, 1, 'Parent', resultPanel);
title(ax1, '原始帧');
ax2 = subplot(2, 3, 2, 'Parent', resultPanel);
title(ax2, '灰度图像');
ax3 = subplot(2, 3, 3, 'Parent', resultPanel);
title(ax3, '帧间差分');
ax4 = subplot(2, 3, 4, 'Parent', resultPanel);
title(ax4, '二值图像');
ax5 = subplot(2, 3, 5, 'Parent', resultPanel);
title(ax5, '形态学处理');
ax6 = subplot(2, 3, 6, 'Parent', resultPanel);
title(ax6, '检测结果');
% 创建状态栏
statusBar = uicontrol('Style', 'text', 'String', '就绪', ...
'Position', [0.34, 0.02, 0.64, 0.04], ...
'HorizontalAlignment', 'left', ...
'BackgroundColor', [0.8, 0.8, 0.8]);
% 初始化变量
videoObj = [];
currentFrame = [];
prevFrame = [];
isPlaying = false;
timerObj = [];
results = struct('original', {}, 'gray', {}, 'diff', {}, ...
'thresh', {}, 'morph', {}, 'result', {});
frameIndex = 0;
% 加载视频回调函数
function loadVideo(~, ~)
[filename, pathname] = uigetfile({'*.avi;*.mp4;*.mov', '视频文件 (*.avi, *.mp4, *.mov)'}, ...
'选择视频文件');
if isequal(filename, 0)
return;
end
videoPath = fullfile(pathname, filename);
try
videoObj = VideoReader(videoPath);
set(statusBar, 'String', ['已加载视频: ' filename]);
% 读取第一帧
prevFrame = readFrame(videoObj);
frameIndex = 1;
% 显示第一帧
axes(ax1);
imshow(prevFrame);
title(ax1, '原始帧');
axes(ax2);
grayImg = rgb2gray(prevFrame);
imshow(grayImg, []);
title(ax2, '灰度图像');
catch ME
errordlg(['无法读取视频文件: ' ME.message], '错误');
end
end
% 开始检测回调函数
function startDetection(~, ~)
if isempty(videoObj)
errordlg('请先加载视频文件', '错误');
return;
end
if isPlaying
return;
end
isPlaying = true;
set(statusBar, 'String', '检测中...');
% 获取参数值
threshold = get(thresholdSlider, 'Value');
minArea = get(minAreaSlider, 'Value');
morphOp = get(morphPopup, 'Value');
% 创建定时器处理视频帧
timerObj = timer('ExecutionMode', 'fixedRate', 'Period', 0.05, ...
'TimerFcn', @processFrame);
start(timerObj);
end
% 暂停检测回调函数
function pauseDetection(~, ~)
if isPlaying
isPlaying = false;
if ~isempty(timerObj) && isvalid(timerObj)
stop(timerObj);
end
set(statusBar, 'String', '已暂停');
end
end
% 停止检测回调函数
function stopDetection(~, ~)
isPlaying = false;
if ~isempty(timerObj) && isvalid(timerObj)
stop(timerObj);
delete(timerObj);
timerObj = [];
end
frameIndex = 0;
results = struct('original', {}, 'gray', {}, 'diff', {}, ...
'thresh', {}, 'morph', {}, 'result', {});
set(statusBar, 'String', '已停止');
end
% 保存结果回调函数
function saveResults(~, ~)
if isempty(results)
errordlg('没有可保存的结果', '错误');
return;
end
[filename, pathname] = uiputfile('motion_detection_results.avi', '保存结果视频');
if isequal(filename, 0)
return;
end
outputPath = fullfile(pathname, filename);
try
writerObj = VideoWriter(outputPath, 'Motion JPEG AVI');
writerObj.FrameRate = videoObj.FrameRate;
open(writerObj);
for i = 1:length(results)
writeVideo(writerObj, results(i).result);
end
close(writerObj);
set(statusBar, 'String', ['结果已保存到: ' outputPath]);
catch ME
errordlg(['保存失败: ' ME.message], '错误');
end
end
% 处理视频帧
function processFrame(~, ~)
if ~hasFrame(videoObj)
stopDetection();
return;
end
% 读取当前帧
currentFrame = readFrame(videoObj);
frameIndex = frameIndex + 1;
% 获取参数值
threshold = get(thresholdSlider, 'Value');
minArea = get(minAreaSlider, 'Value');
morphOp = get(morphPopup, 'Value');
% 转换为灰度图像
grayPrev = rgb2gray(prevFrame);
grayCurr = rgb2gray(currentFrame);
% 计算帧间差分
diffImg = imabsdiff(grayCurr, grayPrev);
% 二值化处理
threshImg = imbinarize(diffImg, threshold/255);
% 形态学处理
se = strel('rectangle', [5, 5]);
switch morphOp
case 1 % 无
morphImg = threshImg;
case 2 % 开运算
morphImg = imopen(threshImg, se);
case 3 % 闭运算
morphImg = imclose(threshImg, se);
case 4 % 开闭运算
temp = imopen(threshImg, se);
morphImg = imclose(temp, se);
end
% 标记运动目标
labeledImg = bwlabel(morphImg);
stats = regionprops(labeledImg, 'BoundingBox', 'Area');
% 在原图上绘制检测结果
resultImg = currentFrame;
for i = 1:length(stats)
if stats(i).Area > minArea
bbox = stats(i).BoundingBox;
resultImg = insertShape(resultImg, 'Rectangle', bbox, ...
'LineWidth', 2, 'Color', 'green');
resultImg = insertText(resultImg, [bbox(1), bbox(2)-20], ...
'运动目标', 'FontSize', 12, ...
'BoxColor', 'yellow', 'TextColor', 'black');
end
end
% 更新结果显示
axes(ax1);
imshow(currentFrame);
title(ax1, sprintf('原始帧 (%d/%d)', frameIndex, videoObj.NumFrames));
axes(ax2);
imshow(grayCurr, []);
title(ax2, '灰度图像');
axes(ax3);
imshow(diffImg, []);
title(ax3, '帧间差分');
axes(ax4);
imshow(threshImg);
title(ax4, '二值图像');
axes(ax5);
imshow(morphImg);
title(ax5, '形态学处理');
axes(ax6);
imshow(resultImg);
title(ax6, '检测结果');
% 保存结果
results(end+1) = struct('original', currentFrame, ...
'gray', grayCurr, ...
'diff', diffImg, ...
'thresh', threshImg, ...
'morph', morphImg, ...
'result', resultImg);
% 更新前一帧
prevFrame = currentFrame;
% 更新状态栏
set(statusBar, 'String', sprintf('处理中: 帧 %d/%d, 检测到 %d 个目标', ...
frameIndex, videoObj.NumFrames, length(stats)));
end
% 滑块回调函数
function thresholdSliderCallback(src, ~)
value = round(get(src, 'Value'));
set(thresholdText, 'String', num2str(value));
end
set(thresholdSlider, 'Callback', @thresholdSliderCallback);
function minAreaSliderCallback(src, ~)
value = round(get(src, 'Value'));
set(minAreaText, 'String', num2str(value));
end
set(minAreaSlider, 'Callback', @minAreaSliderCallback);
end
使用说明
功能特点
- 完整的GUI界面:提供直观的用户界面,包含所有必要的控制选项
- 参数可调: 阈值:控制运动检测的灵敏度(1-100) 最小区域:过滤小面积噪声(10-2000像素) 形态学操作:选择不同的后处理方法(无、开运算、闭运算、开闭运算)
- 实时显示:同时显示原始帧、灰度图像、帧间差分、二值图像、形态学处理结果和最终检测结果
- 视频处理:支持加载和处理AVI、MP4、MOV等常见视频格式
- 结果保存:可将检测结果保存为视频文件
使用步骤
- 运行程序:
motion_detection_fd() - 点击"加载视频"按钮,选择要处理的视频文件
- 调整参数: 阈值:根据场景光照和运动速度调整 最小区域:根据目标大小调整 形态学操作:通常选择"开闭运算"效果最佳
- 点击"开始检测"按钮开始处理视频
- 处理过程中可随时暂停或停止
- 处理完成后,点击"保存结果"将检测结果保存为视频文件
算法原理
-
帧间差分:计算相邻两帧图像的绝对差值
D(x,y)=∣Ik+1(x,y)−Ik(x,y)∣D(x,y)=∣I_{k+1}(x,y)−I_k(x,y)∣D(x,y)=∣Ik+1(x,y)−Ik(x,y)∣
-
二值化:设定阈值T,将差分图像转换为二值图像
Bt(x,y)={255,if ∣It(x,y)−It−1(x,y)∣>T0,otherwiseB_t(x, y) = \begin{cases} 255, & \text{if } |I_t(x, y) - I_{t-1}(x, y)| > T \\ 0, & \text{otherwise} \end{cases}Bt(x,y)={255,0,if ∣It(x,y)−It−1(x,y)∣>Totherwise
-
形态学处理:使用开运算和闭运算去除噪声和填充空洞
-
目标标记:标记连通区域,过滤小面积区域,绘制边界框
参数调整建议
- 阈值: 低光照场景:降低阈值(10-20) 高对比度场景:提高阈值(30-50) 快速运动目标:提高阈值减少噪声
- 最小区域: 小型目标(行人):100-300像素 中型目标(车辆):500-1000像素 大型目标(卡车):1000-2000像素
- 形态学操作: 噪声较多:使用开运算 目标有空洞:使用闭运算 一般情况:使用开闭运算
参考代码 基于帧间差分法的运动目标检测 www.3dddown.com/csa/83299.html
扩展功能
如需进一步增强检测效果,可考虑:
- 添加背景建模(如高斯混合模型)
- 实现三帧差分法
- 结合光流法进行运动估计
- 添加目标跟踪功能
- 集成深度学习目标检测器