使用深度学习分类视频
这个例子展示了如何通过结合预先训练的图像分类模型和LSTM网络来创建视频分类网络。
创建视频分类的深度学习网络:
使用预先训练好的卷积神经网络(如GoogLeNet)将视频转换为特征向量序列,从每个帧中提取特征。
在序列上训练LSTM网络来预测视频标签。
组合一个网络,通过组合来自两个网络的层来直接对视频进行分类。
网络结构如下图所示。
要将图像序列输入到网络,请使用序列输入层。
为了使用卷积层提取特征,也就是说,将卷积运算独立应用于视频的每一帧,使用一个序列折叠层,然后是卷积层。
要恢复序列结构并将输出重塑为矢量序列,请使用序列展开层和平坦层。
为了对得到的向量序列进行分类,包括LSTM层和输出层。
加载预训练卷积网络
为了将视频帧转换为特征向量,使用预训练网络的激活。
加载一个预先训练好的GoogLeNet模型googlenet
函数。此功能需要深度学习工具箱™模型为GoogLeNet网络金宝app支持包。如果没有安装此支金宝app持包,则该函数将提供下载链接。
网络cnn =谷歌网络;
加载数据
从。下载HMBD51数据集HMDB:大型人体运动数据库并将RAR文件解压缩到一个名为“hmdb51_org”
.该数据集包含约2 GB的视频数据,涵盖51个类别的7000个剪辑,例如“喝”
,“运行”
,“shake_hands”
.
在提取RAR文件后,使用支持函数金宝apphmdb51Files
来获取视频的文件名和标签。
dataFolder =“hmdb51_org”;[files,labels] = hmdb51Files(dataFolder);
阅读第一个视频使用readVideo
Helper函数,在本例的末尾定义,并查看视频的大小。这个视频是H——- - - - - -W——- - - - - -C——- - - - - -年代数组,H,W,C,年代分别为视频的高度、宽度、通道数和帧数。
Idx = 1;Filename = files(idx);视频= readVideo(文件名);大小(视频)
ans =1×4240 320 3 409
查看对应标签。
标签(idx)
ans =分类brush_hair
要查看视频,请使用implay
函数(需要图像处理工具箱™)。该函数期望[0,1]范围内的数据,因此必须首先将数据除以255。或者,您可以循环遍历各个帧并使用imshow
函数。
numFrames = size(视频,4);数字为i = 1:numFrames frame = video(:,:,:,i);imshow(帧/ 255);drawnow结束
将帧转换为特征向量
使用卷积网络作为特征提取器,在将视频帧输入网络时获得激活。将视频转换为特征向量序列,其中特征向量是函数的输出激活
函数在GoogLeNet网络的最后一个池化层(“pool5-7x7_s1”
).
这张图说明了通过网络的数据流。
要读取视频数据并调整其大小以匹配GoogLeNet网络的输入大小,请使用readVideo
而且centerCrop
Helper函数,在本例的末尾定义。这一步可能需要很长时间才能运行。将视频转换为序列后,将序列保存在tempdir
文件夹中。如果MAT文件已经存在,则从MAT文件加载序列,而不需要重新转换它们。
inputSize = netCNN.Layers(1).InputSize(1:2);layerName =“pool5-7x7_s1”;tempFile = fullfile(tempdir,“hmdb51_org.mat”);如果存在(tempFile“文件”)负载(tempFile“序列”)其他的numFiles = numel(文件);sequences = cell(numFiles,1);为i = 1:numFiles fprintf"读取%d的文件%d…\n", i, numFiles)视频= readVideo(文件(i));视频= centerCrop(视频,inputSize);序列{i,1} =激活(netCNN,视频,layerName,“OutputAs”,“列”);结束保存(tempFile,“序列”,“-v7.3”);结束
查看前几个序列的大小。每个序列都是D——- - - - - -年代数组,D特征的数量(池化层的输出大小)和年代是视频的帧数。
序列(1:10)
ans =10×1单元格数组{1024×409单}{1024×395单}{1024×323单}{1024×246单}{1024×159单}{1024×137单}{1024×359单}{1024×191单}{1024×439单}{1024×528单}
准备培训数据
通过将数据划分为训练和验证分区并删除任何长序列,为训练准备数据。
创建训练和验证分区
划分数据。将90%的数据分配给训练分区,10%分配给验证分区。
numObservations = numel(序列);idx = randperm(numObservations);N =地板(0.9 * numObservations);idxTrain = idx(1:N);sequencesTrain = sequences(idxTrain);labelsTrain = labels(idxTrain);idxValidation = idx(N+1:结束);sequencesValidation = sequences(idxValidation);labelsValidation = labels(idxValidation);
删除长序列
比网络中的典型序列长得多的序列可以在训练过程中引入大量的填充。填充过多会对分类精度产生负面影响。
获得训练数据的序列长度,并将其可视化为训练数据的直方图。
numObservationsTrain = numel(sequencesTrain);sequenceLengths = 0 (1,numObservationsTrain);为i = 1:numObservationsTrain sequence = sequencesTrain{i};sequenceLengths(i) = size(sequence,2);结束数字直方图(sequenceLengths)“序列长度”)包含(“序列长度”) ylabel (“频率”)
只有少数序列有超过400个时间步长。为了提高分类精度,将超过400个时间步长的训练序列与其对应的标签一起去除。
maxLength = 400;idx = sequenceLengths > maxLength;sequencesTrain(idx) = [];labelsTrain(idx) = [];
创建LSTM网络
接下来,创建一个LSTM网络,可以对表示视频的特征向量序列进行分类。
定义LSTM网络体系结构。指定以下网络层。
序列输入层,其输入大小对应于特征向量的特征维
一个带有2000个隐藏单元的BiLSTM层,后面还有一个退出层。属性可为每个序列只输出一个标签
“OutputMode”
选项的BiLSTM层“最后一次”
一个输出大小与类数量相对应的全连接层,一个软最大层和一个分类层。
numFeatures = size(sequencesTrain{1},1);numClasses =编号(类别(labelsTrain));layers = [sequenceInputLayer(numFeatures,“名字”,“序列”) bilstmLayer (2000,“OutputMode”,“最后一次”,“名字”,“bilstm”) dropoutLayer (0.5,“名字”,“下降”) fullyConnectedLayer (numClasses“名字”,“俱乐部”) softmaxLayer (“名字”,“softmax”) classificationLayer (“名字”,“分类”));
指定培训项目
属性指定培训选项trainingOptions
函数。
设置一个小批大小为16,初始学习率为0.0001,梯度阈值为2(以防止梯度爆炸)。
对每个纪元的数据进行洗牌。
每个纪元验证一次网络。
在图中显示训练进度并抑制详细输出。
miniBatchSize = 16;numObservations = numel(sequencesTrain);numIterationsPerEpoch = floor(numObservations / miniBatchSize);选项= trainingOptions(“亚当”,...“MiniBatchSize”miniBatchSize,...“InitialLearnRate”1的军医,...“GradientThreshold”2,...“洗牌”,“every-epoch”,...“ValidationData”{sequencesValidation, labelsValidation},...“ValidationFrequency”numIterationsPerEpoch,...“阴谋”,“训练进步”,...“详细”、假);
训练LSTM网络
训练网络使用trainNetwork
函数。这可能需要很长时间才能运行。
[netLSTM,info] = trainNetwork(sequencesTrain,labelsTrain,layers,options);
计算网络在验证集上的分类精度。使用与培训选项相同的迷你批量大小。
YPred = category (netLSTM,sequencesValidation,“MiniBatchSize”, miniBatchSize);YValidation = labelsValidation;accuracy = mean(YPred == YValidation)
准确度= 0.6647
组建视频分类网络
要创建直接对视频进行分类的网络,请使用创建的两个网络中的层组合一个网络。使用卷积网络中的层将视频转换为向量序列,使用LSTM网络中的层对向量序列进行分类。
网络结构如下图所示。
要将图像序列输入到网络,请使用序列输入层。
为了使用卷积层提取特征,也就是说,将卷积运算独立应用于视频的每一帧,使用一个序列折叠层,然后是卷积层。
要恢复序列结构并将输出重塑为矢量序列,请使用序列展开层和平坦层。
为了对得到的向量序列进行分类,包括LSTM层和输出层。
添加卷积层
首先,创建GoogLeNet网络的层图。
cnnLayers = layerGraph(netCNN);
删除输入层(“数据”
)和池化层之后的层用于激活(“pool5-drop_7x7_s1”
,“loss3-classifier”
,“概率”
,“输出”
).
layerNames = [“数据”“pool5-drop_7x7_s1”“loss3-classifier”“概率”“输出”];cnnLayers = removeLayers(cnnLayers,layerNames);
添加序列输入层
创建一个序列输入层,接受包含与GoogLeNet网络输入大小相同的图像序列。若要使用与GoogLeNet网络相同的平均图像来规范化图像,请设置“归一化”
选项的序列输入层“zerocenter”
和“的意思是”
选项到GoogLeNet输入层的平均图像。
inputSize = netCNN.Layers(1).InputSize(1:2);averageImage = netCNN.Layers(1).Mean;inputLayer = sequenceInputLayer([inputSize 3],...“归一化”,“zerocenter”,...“的意思是”averageImage,...“名字”,“输入”);
将序列输入层添加到图层图中。为了将卷积层独立应用于序列的图像,通过在序列输入层和卷积层之间包含序列折叠层来去除图像序列的序列结构。将序列折叠层的输出连接到第一卷积层的输入(“conv1-7x7_s2”
).
图层= [inputLayer sequenceFoldingLayer(“名字”,“折”));lgraph = addLayers(cnnLayers,layers);lgraph = connectLayers(“折/”,“conv1-7x7_s2”);
添加LSTM层
通过移除LSTM网络的序列输入层,将LSTM层添加到层图中。为了恢复被序列折叠层删除的序列结构,在卷积层之后包括序列展开层。LSTM层期望向量序列。为了将序列展开层的输出重塑为矢量序列,在序列展开层之后包含一个压平层。
从LSTM网络中取层并删除序列输入层。
lstmLayers = netLSTM.Layers;lstmLayers(1) = [];
将序列展开层、压平层和LSTM层添加到图层图中。连接最后一个卷积层(“pool5-7x7_s1”
)到序列展开层的输入(“展开/”)
.
层= [sequenceUnfoldingLayer(“名字”,“展开”) flattenLayer (“名字”,“平”) lstmLayers);lgraph = addLayers(lgraph,layers);lgraph = connectLayers(“pool5-7x7_s1”,“展开/”);
要使展开层恢复序列结构,请连接“miniBatchSize”
序列折叠层的输出到序列展开层的相应输入。
lgraph = connectLayers(“折/ miniBatchSize”,“展开/ miniBatchSize”);
组装网络
方法检查网络是否有效analyzeNetwork
函数。
analyzeNetwork (lgraph)
组合网络,以便可以使用assembleNetwork
函数。
net =汇编网络(lgraph)
net = DAGNetwork with properties: Layers: [148×1 nnet.cnn.layer.Layer] Connections: [175×2 table]
使用新数据进行分类
阅读视频并将其居中剪裁“pushup.mp4”
使用与前面相同的步骤。
文件名=“pushup.mp4”;视频= readVideo(文件名);
要查看视频,请使用implay
函数(需要图像处理工具箱)。该函数期望[0,1]范围内的数据,因此必须首先将数据除以255。或者,您可以循环遍历各个帧并使用imshow
函数。
numFrames = size(视频,4);数字为i = 1:numFrames frame = video(:,:,:,i);imshow(帧/ 255);drawnow结束
利用组合网络对视频进行分类。的分类
函数需要一个包含输入视频的单元格数组,因此必须输入一个包含视频的1乘1单元格数组。
视频= centerCrop(视频,inputSize);YPred = category (net,{video})
YPred =分类俯卧撑
辅助函数
的readVideo
函数将视频读入文件名
并返回H
——- - - - - -W
——- - - - - -C -
由- - - - - -年代
数组,H
,W
,C
,年代
分别为视频的高度、宽度、通道数和帧数。
函数视频= readVideo(文件名)vr = VideoReader(文件名);H = vr.高度;W = vr.宽度;C = 3;预分配视频数组numFrames =地板(vr。Duration * vr.FrameRate); video = zeros(H,W,C,numFrames);读帧百分比I = 0;而hasFrame(vr) i = i + 1;video(:,:,:,i) = readFrame(vr);结束移除未分配的帧如果Size (video,4) > I video(:,:,:, I +1:end) = [];结束结束
的centerCrop
函数裁剪视频的最长边并调整其大小inputSize
.
函数videoresize = centerCrop(视频,inputSize) sz =大小(视频);如果Sz (1) < Sz (2)%视频为横屏Idx = floor((sz(2) - sz(1))/2);视频(:,1:(idx-1),:,:) = [];视频(:,(sz(1)+1):end,:,:) = [];elseifSz (2) < Sz (1)%视频为竖屏Idx = floor((sz(1) - sz(2))/2);视频(1:(idx-1),:,:,:) = [];视频((sz(2)+1):end,::,:) = [];结束videoresize = imresize(视频,inputSize(1:2));结束
另请参阅
trainNetwork
|trainingOptions
|lstmLayer
|sequenceInputLayer
|sequenceFoldingLayer
|sequenceUnfoldingLayer
|flattenLayer