本系列文章用于记录自己学习MATLAB视频图像处理的记录。学习不息,奋斗不止!
本人是一名大学教师,有一门课《MATLAB应用》,教过两轮,发现只是教一些基础用法学生并不是很感兴趣。所以我也充充电,给学生们带来一些前沿、有趣、有用的知识吧!
使用MATLAB调用电脑的摄像头,代码很简单,过程却很曲折。
先上最终代码:
video_info = imaqhwinfo; % 获取适配器名,一般就是'winvideo'
adaptor = video_info.InstalledAdaptors{1};
device_info = imaqhwinfo(adaptor, 1); % DeviceID等于1的适配器信息;
default_format = device_info.DefaultFormat; % 该设备的默认视频格式
videoObj = videoinput(adaptor, 1, default_format); % 建立连接,创建一个连接对象
set(videoObj , 'ReturnedColorSpace', 'rgb');
% 获取视频的长和宽,以及颜色的层数。
vidRes = get(videoObj, 'VideoResolution');
nBands = get(videoObj , 'NumberOfBands');
axes
% 从数组显示图像
hImage = image(zeros(app.vidRes(2), app.vidRes(1), app.nBands));
% 显示视频-创建一个Video Preview 窗口
preview(videoObj, hImage);
好了,运行代码,MATLAB就会生成一个figure,然后显示摄像头拍到的画面——也就是你自己。
现在,说一下曲折的过程。第一次调用imaqhwinfo这个函数的时候,MATLAB是报错的。
就是需要安装一个工具包Image Acquisition Toolbox Support Package for OS Generic Video Interface。这个工具包2018以后的版本MATLAB都是集成在Add-on模块。正常来说下载就行了。
然而,由于众所周知的原因,(如下:)
在网上冲了一会儿大浪,之后,发现了几个解决方案:
不过那个博主,讲了一堆废话。当然了那个博主的总结还是不错的:
综上所述,其实安装的步骤很简单:
1、用你学校或者机构的官方邮箱注册一个MathWorks账号
2、登录账号,选择代码出错的地方或者去MATLAB主页找获取附加功能页面上找到你要下载的功能包,点击安装,等待下载,安装,环境配置。
3、安装完成,最好重启一下MATLAB,之后正常使用就OK。
原文链接:https://blog.csdn.net/Henryhhc/article/details/126981556
这里贴一下步骤2的地址,真是不一定好找哦!
Image Acquisition Toolbox Support Package for OS Generic Video Interface
以下把本文所有涉及的功能集成于一个GUI中。便于整体演示:
一共三个panel:
主要有
视频控制按钮4个:
好了!在app designer中设计好界面,设置好各个模块的名字和位置以及其他属性之后。一个一个编写Callback吧!
这个就简单修改一下上面的调用摄像头代码即可:
% 调用系统摄像头,并将监控画面显示在图像的axes内
% 建立连接
app.obj = videoinput('winvideo', 1, 'YUY2_1280x720');
set(app.obj, 'ReturnedColorSpace', 'rgb');
% 获取视频的长和宽,以及颜色的层数。
app.vidRes = get(app.obj, 'VideoResolution');
app.nBands = get(app.obj, 'NumberOfBands');
axes(app.UIAxes)
% 从数组显示图像
app.hImage = image(app.UIAxes, zeros(app.vidRes(2), app.vidRes(1), app.nBands));
pause(0.5) % 暂停0.5s,让图窗准备好
% 显示视频-创建一个Video Preview 窗口
preview(app.obj, app.hImage);
% 使按钮失效
set(app.btn_monitor, 'enable', 'off');
app.Monitor_STATE = true;
几点说明:
pause(0.5) % 暂停0.5s,让图窗准备好
这是一个坑啊!!不知道是不是普遍性的原因还是我的电脑太拉胯了
一开始,没有这一行。点击Monitor之后,摄像头也确实亮了,程序也不报错。但是,不显示画面:
可以看出来,图窗是显示了,但是没有画面。然而,这一段代码,我在编辑器里面运行是没有问题的。
后来,我在 preview(app.obj, app.hImage); 这一行加了一个断点。
调试的时候,这里程序中断。再点继续,发现就可以了!!!!
我就想到了,可能是UIAxes还没准备好,就运行了preview。
所以就有了 pause(0.5);这一行。
记录于此,给其他遇到这个问题的小伙伴一个提示。
这里实现点击按钮自动录屏。也不会弹出新的窗口。
回调代码如下:
if app.Monitor_STATE
set(app.btn_collection, 'enable', 'off');
% 直接采集
prompt = {'Please Enter the Filename:', ...
'Length of the Video to be Collected: '};
diltitle = 'Video Info';
dims = [1, 35];
filename = 'CollectedVideo.avi';
dura = 5;
definput = {filename, '5'};
answer = inputdlg(prompt, diltitle, dims, definput);
if ~isempty(answer)
filename = answer{1};
dura = str2double(string(answer{2}));
end
nrate = 24;
nframe = nrate * dura;
writerObj = VideoWriter(filename);
writerObj.FrameRate = nrate;
open(writerObj);
figure(1);
set(1, 'visible', 'off');
for ii = 1:nframe
frame = getsnapshot(app.obj);
% imshow(frame);
f.cdata = frame;
f.colormap = colormap([]);
writeVideo(writerObj, f)
end
close(writerObj);
msgbox('Video Collected Successfully!');
set(app.btn_collection, 'enable', 'on');
else
msgbox('Please TURN ON the monitor first!');
end
首先,一开始是一个判断,采集窗口的状态,得摄像头被调用了才能Collect嘛!
然后,在采集过程中,按钮失效。
% 直接采集
prompt = {'Please Enter the Filename:', ...
'Length of the Video to be Collected: '};
diltitle = 'Video Info';
dims = [1, 35];
filename = 'CollectedVideo.avi';
dura = 5;
definput = {filename, '5'};
answer = inputdlg(prompt, diltitle, dims, definput);
if ~isempty(answer)
filename = answer{1};
dura = str2double(string(answer{2}));
end
这一块是做了一个交互,就是录的视频的长度和文件名。设了一个默认值。用inputdlg()函数问用户要不要改。
这部分可以视需要删减修改。
writerObj = VideoWriter(filename);
writerObj.FrameRate = nrate;
open(writerObj);
figure(1);
set(1, 'visible', 'off');
for ii = 1:nframe
frame = getsnapshot(app.obj);
% imshow(frame);
f.cdata = frame;
f.colormap = colormap([]);
writeVideo(writerObj, f)
end
close(writerObj);
写文件主体代码;
这个比较简单,就是结束监控。
% 停止监控系统
if app.Monitor_STATE
% stop
stoppreview(app.obj);
% 回复monitor按钮
set(app.btn_monitor, 'enable', 'on');
% 设置状态
app.Monitor_STATE = false;
else
msgbox('The Monitor is off!');
end
这个也比较简单,打开一个视频文件,主要是获取到视频的路径和名称。然后读取视频信息,显示在information panel。
主要是视频信息都有哪些。
% 打开一个视频文件
[filename, pathname, ~] = uigetfile({'*.avi', 'VideoFile(*.avi)';...
'*.wmv', 'VideoFile(*.wmv)';'*.*', 'All Files(*.*)'},'MultiSelect','off');
if isequal(filename, 0) || isequal(pathname, 0)
return
end
% 获取完整路径
app.Opened_filepath = fullfile(pathname, filename);
% 将视频文件的信息显示在information panel
VideoInfo = VideoReader(app.Opened_filepath);
app.Video_info = VideoInfo;
% 关闭监控
if app.Monitor_STATE
stoppreview(app.obj);
app.Monitor_STATE = false;
end
set(app.TextArea_action, 'Value', 'Open Video');
set(app.TextArea_frame, 'Value', sprintf('%d', VideoInfo.NumFrames));
app.TextArea_width.Value = sprintf('%d px', VideoInfo.Width);
app.TextArea_height.Value = sprintf('%d px', VideoInfo.Height);
app.TextArea_rate.Value = sprintf('%.1f s', VideoInfo.FrameRate);
app.TextArea_time.Value = sprintf('%.1f s', VideoInfo.Duration);
app.TextArea_format.Value = sprintf('%s', VideoInfo.VideoFormat);
app.TextArea_path.Value = app.Opened_filepath;
% 将视频的第一帧显示在坐标区
temp = read(VideoInfo, 1);
imshow(temp, [], 'Parent', app.UIAxes);
msgbox('Get Video Information Successfully!');
end
% 关闭监控
if app.Monitor_STATE
stoppreview(app.obj);
app.Monitor_STATE = false;
end
% 将视频的第一帧显示在坐标区
temp = read(VideoInfo, 1);
imshow(temp, [], 'Parent', app.UIAxes);
因为视频处理的本质还是对每一帧的图像进行处理,然后再把这些图给拼起来,所以需要将视频转换为图像。
% 将视频分解为图像
% 判断视频是否已经打开
if isequal(app.Opened_filepath, '')
msgbox('Open a video first please!');
return
end
% 读取视频文件信息
% vr = VideoReader(app.Opened_filepath);
nFrames = app.Video_info.NumFrames;
% vidHeight = app.Video_info.Height;
% vidWidth = app.Video_info.Width;
% 视频的文件名,生成保存图像的文件夹
[~, name, ~] = fileparts(app.Opened_filepath);
imagePath = fullfile(pwd, sprintf('%s_images', name));
if ~exist(imagePath, 'dir')
mkdir(imagePath);
end
% 获取当前文件夹下的所有图像,如果已经有nFrames帧图像,则返回
files = dir(fullfile(imagePath, '*.jpg'));
if length(files) == nFrames
return
end
% 创建进度条
wh = waitbar(0, '', 'Name', 'Getting Video ImageList...');
steps = nFrames;
% 输出帧
for step = 1:steps
temp = read(app.Video_info, step);
% 图像名
temp_str = sprintf('%s\\%04d.jpg', imagePath, step);
% 输出
imwrite(temp, temp_str);
pause(0.01);
% 进度条更新
waitbar(step/steps, wh, sprintf('Process %d %%', round(step/steps * 100)));
end
close(wh);
% 将文件夹路径保存
app.Image_filePath = imagePath;
上一个功能的逆,把图拼起来。
% 将图像重新做成视频
% 起始帧
startnum = 1;
% 默认结束帧为文件夹内jpg图像的数目
%
if isempty(app.Image_filePath)
% 选择文件夹
imagepath = uigetdir(pwd, '选择待合成的图片文件夹:');
else
% ls()函数为列出文件夹内容
imagepath = app.Image_filePath;
end
endnum = size(ls(fullfile(imagepath, '*.jpg')), 1);
% 输出的视频的位置
video_filePath = fullfile(pwd, 'ComposedVideo');
if ~exist(video_filePath, "dir")
mkdir(video_filePath);
end
vidWriterObj = VideoWriter(fullfile(video_filePath, 'composedVideo.avi'));
vidWriterObj.FrameRate = 24;
% 开始合成
open(vidWriterObj);
wh = waitbar(0, '', 'name', 'Writing Video File...');
steps = endnum - startnum;
for num = startnum : endnum
% 图片文件名
file = sprintf('%04d.jpg', num);
file = fullfile(imagepath, file);
frame = imread(file); % 读取图片
frame = im2frame(frame); % 转换为帧
writeVideo(vidWriterObj, frame);
pause(0.01);
step = num-startnum;
waitbar(step/steps, wh, sprintf('Process %d %%', round(step/steps * 100)));
end
% 关闭进度条
close(wh);
% 关闭写文件句柄
close(vidWriterObj);
至此,操作面板的功能全部结束。
下面看剩的几个视频操作面板。
当点了Collection按钮之后,窗口只是显示了视频的第一帧,并没有播放。点PLay,开始播放。
% 播放视频
% 判断是否有视频
if isempty(app.Opened_filepath) || isempty(app.Video_info)
msgbox('Please Get the Video First!', 'Info');
return;
end
app.TextArea_action.Value = 'Playing';
% [pathstr, name, ext] = fileparts(app.Opened_filepath);
set(app.btn_stop, 'Enable', 'on');
set(app.btn_go, 'Enable', 'off');
set(app.btn_stop, 'tag', 'Go');
nFrame = app.Video_info.NumFrames;
% 设置进度条
set(app.Slider,'Limits', [0, nFrame] , 'Value', 1);
app.TextArea_slide.Value = sprintf('%d/%d', 0, nFrame);
% 播放
for ii = 1:nFrame
% 监听暂停键
waitfor(app.btn_stop, 'tag', 'Go');
imag = read(app.Video_info, ii);
try
imshow(imag, [], 'Parent', app.UIAxes);
set(app.Slider, 'Value', ii);
set(app.TextArea_slide, 'Value', sprintf('%d/%d', ii, nFrame));
catch
return;
end
% 更新图窗并处理回调
drawnow;
end
% 停用暂停键
set(app.btn_stop, 'Enable', 'off');
set(app.btn_go, 'Enable', 'off');
如上,将stop的标签改一下即可。
% 按下暂停
set(app.btn_stop, 'tag', 'Pause', 'Enable', 'off');
set(app.btn_go, 'Enable', 'on');
就是继续播放,因为点play是重头放,Go是接着放。原理就是把标签改回去。。。
% 继续
set(app.btn_stop, 'tag', 'Go', 'Enable', 'on');
set(app.btn_go, 'Enable', 'off');
最后一个,截图。截取当前帧。
需要先暂停,再截图,再继续。
%先暂停
btn_stopButtonPushed(app, event);
% 截图
snap_filePath = fullfile(pwd, 'snap_images');
if ~exist(snap_filePath, 'dir')
mkdir(snap_filePath);
end
[filename, pathname, ~] = uiputfile({'*.jpg; *.tif; *.png; *.gif;', 'All Image Files';...
'*.*', 'All Files'}, 'Save Snaps', ...
fullfile(snap_filePath, 'snap.jpg'));
if isequal(filename, 0) || isequal(pathname, 0)
return
end
fileStr = fullfile(pathname, filename);
% 获取当前帧
f = getframe(app.UIAxes);
% 转换为图像
f = frame2im(f);
imwrite(f, fileStr);
msgbox('Success!', 'Info');
% 恢复
btn_goButtonPushed(app, event);
最后是退出按钮。
% 退出
delete(app);
完