MATLAB视频处理学习记录

  • List item

MATLAB视频处理学习记录No.1

  • 这是一个大标题
    • 调用摄像头
    • 一个简单的GUI
      • Monitor
      • Collection
      • StopMonitor
      • Open Video File
      • Video2Imag List
      • Image List 2 Video
      • Play
      • Stop
      • Go
      • Snap
      • Exit

这是一个大标题

本系列文章用于记录自己学习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模块。正常来说下载就行了。

然而,由于众所周知的原因,(如下:)
MATLAB视频处理学习记录_第1张图片
在网上冲了一会儿大浪,之后,发现了几个解决方案:

  1. 应该是激活的不彻底,重新激活试一下————失败!
  2. 重新安装!!!————失败!
  3. 安装低版本的MATLAB比如2014————太低级了!不想试。
  4. 手动下载安装包,手动安装。————亲测可行。
    主要参考博客:
    手动安装的参考文章

不过那个博主,讲了一堆废话。当然了那个博主的总结还是不错的:

综上所述,其实安装的步骤很简单:
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

MATLAB视频处理学习记录_第2张图片
下载,然后安装即可。OK!下一步!

一个简单的GUI

以下把本文所有涉及的功能集成于一个GUI中。便于整体演示:
MATLAB视频处理学习记录_第3张图片
一共三个panel:

  1. Display Panel:用于放映视频或者放摄像头画面。
  2. Information Panerl:这个简单,就是一些TextArea用于显示当前的视频信息。
  3. Control Panel:主要功能区,每一个按钮都是一个学习的片段。

主要有

  • Monitor:调用监控,并显示。
  • Collection:截取一段监控中的画面,保存为视频。
  • Stop Monitor:结束监控。
  • Open Video File:打开一个视频文件——在画面显示。
  • Video2Imag List:将打开的视频转换为一个一个的图,放在一个文件夹。
  • Imag2Video:将一个文件夹里面的所有图,合并成一个视频。
  • Exit:退出系统。

视频控制按钮4个:

  • Play:播放
  • Go:继续
  • Stop:暂停
  • Snap:截图。

好了!在app designer中设计好界面,设置好各个模块的名字和位置以及其他属性之后。一个一个编写Callback吧!

Monitor

这个就简单修改一下上面的调用摄像头代码即可:

            % 调用系统摄像头,并将监控画面显示在图像的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;

几点说明:

  • 这里调用videoinput函数的时候,我直接写了参数。因为是固定的,没必要搞那么复杂了。当然更一般性的代码还是应该如上。
  • 在GUI中显示视频界面需要设置image()函数的第一个参数为app.UIAxes。
  • 主要显示函数preview(obj, hImage);用于在hImage对象预览obj的视频画面。
  • 重点来了:大家可能注意到了这行代码:
            pause(0.5) % 暂停0.5s,让图窗准备好

这是一个坑啊!!不知道是不是普遍性的原因还是我的电脑太拉胯了

一开始,没有这一行。点击Monitor之后,摄像头也确实亮了,程序也不报错。但是,不显示画面:
MATLAB视频处理学习记录_第4张图片
可以看出来,图窗是显示了,但是没有画面。然而,这一段代码,我在编辑器里面运行是没有问题的。
后来,我在 preview(app.obj, app.hImage); 这一行加了一个断点。
调试的时候,这里程序中断。再点继续,发现就可以了!!!!

我就想到了,可能是UIAxes还没准备好,就运行了preview。
所以就有了 pause(0.5);这一行。

记录于此,给其他遇到这个问题的小伙伴一个提示。

Collection

这里实现点击按钮自动录屏。也不会弹出新的窗口。
回调代码如下:

            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);

写文件主体代码;

  1. VideoWriter(filename)——生成一个写Video的对象,或者句柄;
  2. open()打开对象;
  3. figure(1);——貌似一定要有一个新的图窗,不然写不了;但是可以让它不显示,就是下一句set()
  4. 进循环,一帧一帧的存。
  5. getsnapshot()函数,用于截取当前摄像头的图像。
  6. 讲这个东西存到f中
  7. 最后调用writeVideo()把这一帧写入Video对象。

StopMonitor

这个比较简单,就是结束监控。

            % 停止监控系统
            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

Open Video File

这个也比较简单,打开一个视频文件,主要是获取到视频的路径和名称。然后读取视频信息,显示在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

  • uigetfile()获取文件,没啥好说的。
  • fullfile()接路径。
  • VideoReader函数创建一个Video对象。这个就是读取的视频了。包含了视频的信息,但是不是视频本身。
  • 关闭监控这部分涉及到了停止视频画面的函数stoppreview(),也很好理解。如果想再打开,就再preview 一次就行了。
            % 关闭监控
            if app.Monitor_STATE
                stoppreview(app.obj);
                app.Monitor_STATE = false;
            end

  • 最后是显示信息。这部分用到了GUI里面设置属性的两种方法,一种是set(),一种是直接赋值。
  • 读取视频图像,需要使用read函数,read(Video, n),表示读取Video的第n帧图像。然后imshow()出来,注意,这里app.UIAxes是放在‘Parent’这个键值对里面的,不能放在第一个参数。跟别的画图不一样。
            % 将视频的第一帧显示在坐标区
            temp = read(VideoInfo, 1);
            imshow(temp, [], 'Parent', app.UIAxes);

Video2Imag List

因为视频处理的本质还是对每一帧的图像进行处理,然后再把这些图给拼起来,所以需要将视频转换为图像。

            % 将视频分解为图像

            % 判断视频是否已经打开
            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;


  • fileparts()函数将路径分解,获取文件名,作为放图像的文件夹名。
  • 输出为图像的过程其实也很简单,就是一帧一帧的读取视频,然后每一帧保存为一个图像即可。一个循环搞定。
  • 添加了一个进度条,用于显示。

Image List 2 Video

上一个功能的逆,把图拼起来。

            % 将图像重新做成视频

            % 起始帧
            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);

  • 需要确定一共多少帧
  • 然后读取的图片对象通过im2frame()变换为帧,才能放到writerVideo函数里面。

至此,操作面板的功能全部结束。

下面看剩的几个视频操作面板。

Play

当点了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');

  • 因为需要对暂停键有响应,所以需要在循环里面设置一个监听-waitfor():
    – 这里先把stop buttion 的标签设置为‘Go’;
    – 然后,监听waitfor(app.btn_stop, ‘tag’, 'Go); 也就是如果这个btn的标签为’Go’就继续循环,否则就等到它为‘Go’
    – 那么思路就清楚了,stop_btn的回调只需要把tag设置为‘pause’或者其他任何东西就可以了。
  • 播放的控制也很简单,就是依次读取视频的下一帧,放在窗口就行了。然后用第i帧控制slide。
  • drawnow;

Stop

如上,将stop的标签改一下即可。

            % 按下暂停
            set(app.btn_stop, 'tag', 'Pause', 'Enable', 'off');
            set(app.btn_go, 'Enable', 'on');


Go

就是继续播放,因为点play是重头放,Go是接着放。原理就是把标签改回去。。。

            % 继续
            set(app.btn_stop, 'tag', 'Go', 'Enable', 'on');
            set(app.btn_go, 'Enable', 'off');

Snap

最后一个,截图。截取当前帧。
需要先暂停,再截图,再继续。

            %先暂停
            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);

Exit

最后是退出按钮。

            % 退出
            delete(app);

你可能感兴趣的:(MATLAB应用讲义,matlab,音视频,学习)