Классификация видео с использованием глубокого обучения

В этом примере показано, как создать сеть для классификации видео путем объединения предварительно обученной модели классификации изображений и сети LSTM.

Чтобы создать нейронную сеть для глубокого обучения для классификации видео:

  1. Преобразуйте видео в последовательности векторов функций с помощью предварительно обученной сверточной нейронной сети, такой как GoogLeNet, чтобы извлечь функции из каждой системы координат.

  2. Обучите сеть LSTM на последовательностях, чтобы предсказать метки видео.

  3. Соберите сеть, которая классифицирует видео непосредственно путем объединения слоев из обеих сетей.

Следующая схема иллюстрирует сетевую архитектуру.

  • Чтобы ввести последовательности изображений в сеть, используйте входной слой последовательности.

  • Чтобы использовать сверточные слои для извлечения функций, то есть для применения сверточных операций к каждой системе координат видео независимо, используйте слой сгибания последовательности, за которым следуют сверточные слои.

  • Чтобы восстановить структуру последовательности и изменить форму выхода на векторные последовательности, используйте развернутый слой последовательности и уплощенный слой.

  • Чтобы классифицировать получившиеся векторные последовательности, включите слои LSTM, за которыми следуют выходные слои.

Загрузка предварительно обученной сверточной сети

Чтобы преобразовать системы координат видео в векторы признаков, используйте активации предварительно обученной сети.

Загрузите предварительно обученную модель GoogLeNet с помощью googlenet функция. Этой функции требуется Модель Deep Learning Toolbox™ для пакета поддержки GoogLeNet Network. Если этот пакет поддержки не установлен, то функция предоставляет ссылку на загрузку.

netCNN = googlenet;

Загрузка данных

Загрузите набор HMBD51 данных из HMDB: большой базы данных движения человека и извлечите файл RAR в папку с именем "hmdb51_org". Набор данных содержит около 2 ГБ видеоданных для 7000 клипов в 51 классе, таких как "drink", "run", и "shake_hands".

После извлечения файлов RAR используйте вспомогательную функцию hmdb51Files для получения имен файлов и меток видео.

dataFolder = "hmdb51_org";
[files,labels] = hmdb51Files(dataFolder);

Прочтите первое видео с помощью readVideo helper, заданная в конце этого примера, и просматривать размер видео. Видео является массивом H-на-W-на-C-на-S, где H, W, C и S являются высотой, шириной, количеством каналов и количеством систем координат видео, соответственно.

idx = 1;
filename = files(idx);
video = readVideo(filename);
size(video)
ans = 1×4

   240   320     3   409

Просмотрите соответствующую метку.

labels(idx)
ans = categorical
     brush_hair 

Чтобы просмотреть видео, используйте implay функция (требует Image Processing Toolbox™). Эта функция ожидает данные в области значений [0,1], поэтому сначала необходимо разделить данные на 255. Также можно закольцовывать отдельные системы координат и использовать imshow функция.

numFrames = size(video,4);
figure
for i = 1:numFrames
    frame = video(:,:,:,i);
    imshow(frame/255);
    drawnow
end

Преобразуйте системы координат в векторы объектов

Используйте сверточную сеть как экстрактор функций путем получения активаций при вводе видеокадров в сеть. Преобразуйте видео в последовательности векторов функций, где векторы функций являются выходными данными activations функция на последнем слое объединения сети GoogLeNet ("pool5-7x7_s1").

Эта схема иллюстрирует поток данных через сеть.

Чтобы прочитать видео данных и изменить его размер, чтобы соответствовать входу размеру сети GoogLeNet, используйте readVideo и centerCrop вспомогательные функции, заданные в конце этого примера. Этот шаг может занять много времени. После преобразования видео в последовательности сохраните последовательности в MAT-файле в tempdir папка. Если файл MAT уже существует, загружайте последовательности из MAT-файла, не переопределяя их.

inputSize = netCNN.Layers(1).InputSize(1:2);
layerName = "pool5-7x7_s1";

tempFile = fullfile(tempdir,"hmdb51_org.mat");

if exist(tempFile,'file')
    load(tempFile,"sequences")
else
    numFiles = numel(files);
    sequences = cell(numFiles,1);
    
    for i = 1:numFiles
        fprintf("Reading file %d of %d...\n", i, numFiles)
        
        video = readVideo(files(i));
        video = centerCrop(video,inputSize);
        
        sequences{i,1} = activations(netCNN,video,layerName,'OutputAs','columns');
    end
    
    save(tempFile,"sequences","-v7.3");
end

Просмотрите размеры первых нескольких последовательностей. Каждая последовательность является массивом D-на-S, где D - количество функций (выход размер слоя объединения), а S - количество систем координат видео.

sequences(1:10)
ans = 10×1 cell array
    {1024×409 single}
    {1024×395 single}
    {1024×323 single}
    {1024×246 single}
    {1024×159 single}
    {1024×137 single}
    {1024×359 single}
    {1024×191 single}
    {1024×439 single}
    {1024×528 single}

Подготовка обучающих данных

Подготовьте данные к обучению путем разбиения данных на разделы для обучения и валидации и удаления любых длинных последовательностей.

Создайте разделы для обучения и валидации

Разделите данные на разделы. Присвойте 90% данных обучающему разделу и 10% - разделу валидации.

numObservations = numel(sequences);
idx = randperm(numObservations);
N = floor(0.9 * numObservations);

idxTrain = idx(1:N);
sequencesTrain = sequences(idxTrain);
labelsTrain = labels(idxTrain);

idxValidation = idx(N+1:end);
sequencesValidation = sequences(idxValidation);
labelsValidation = labels(idxValidation);

Удаление длинных последовательностей

Последовательности, которые намного длиннее, чем типичные последовательности в сетях, могут вводить много заполнения в процесс обучения. Наличие слишком большого количества заполнения может негативно повлиять на точность классификации.

Получите длины последовательности обучающих данных и визуализируйте их в гистограмме обучающих данных.

numObservationsTrain = numel(sequencesTrain);
sequenceLengths = zeros(1,numObservationsTrain);

for i = 1:numObservationsTrain
    sequence = sequencesTrain{i};
    sequenceLengths(i) = size(sequence,2);
end

figure
histogram(sequenceLengths)
title("Sequence Lengths")
xlabel("Sequence Length")
ylabel("Frequency")

Только несколько последовательностей имеют более 400 временных шагов. Чтобы улучшить точность классификации, удалите обучающие последовательности, которые имеют более 400 временных шагов вместе с соответствующими метками.

maxLength = 400;
idx = sequenceLengths > maxLength;
sequencesTrain(idx) = [];
labelsTrain(idx) = [];

Создание сети LSTM

Затем создайте сеть LSTM, которая может классифицировать последовательности векторов функций, представляющих видео.

Определите сетевую архитектуру LSTM. Задайте следующие слои сети.

  • Входной слой последовательности с размером входа, соответствующей размерностью признаков векторов функции

  • Слой BiLSTM с 2000 скрытыми модулями с выпадающим слоем после. Чтобы вывести только одну метку для каждой последовательности путем установки 'OutputMode' опция слоя BiLSTM для 'last'

  • Полносвязный слой с размером выхода, соответствующим количеству классов, слоем softmax и слоем классификации.

numFeatures = size(sequencesTrain{1},1);
numClasses = numel(categories(labelsTrain));

layers = [
    sequenceInputLayer(numFeatures,'Name','sequence')
    bilstmLayer(2000,'OutputMode','last','Name','bilstm')
    dropoutLayer(0.5,'Name','drop')
    fullyConnectedLayer(numClasses,'Name','fc')
    softmaxLayer('Name','softmax')
    classificationLayer('Name','classification')];

Настройка опций обучения

Задайте опции обучения, используя trainingOptions функция.

  • Установите размер мини-пакета 16, начальную скорость обучения 0,0001 и порог градиента 2 (чтобы предотвратить взрыв градиентов).

  • Перетасовывайте данные каждую эпоху.

  • Проверяйте сеть один раз в эпоху.

  • Отображение процесса обучения на графике и подавление подробного выхода.

miniBatchSize = 16;
numObservations = numel(sequencesTrain);
numIterationsPerEpoch = floor(numObservations / miniBatchSize);

options = trainingOptions('adam', ...
    'MiniBatchSize',miniBatchSize, ...
    'InitialLearnRate',1e-4, ...
    'GradientThreshold',2, ...
    'Shuffle','every-epoch', ...
    'ValidationData',{sequencesValidation,labelsValidation}, ...
    'ValidationFrequency',numIterationsPerEpoch, ...
    'Plots','training-progress', ...
    'Verbose',false);

Обучите сеть LSTM

Обучите сеть с помощью trainNetwork функция. Это может занять много времени, чтобы бежать.

[netLSTM,info] = trainNetwork(sequencesTrain,labelsTrain,layers,options);

Вычислите точность классификации сети на наборе валидации. Используйте тот же размер мини-пакета, что и для опций обучения.

YPred = classify(netLSTM,sequencesValidation,'MiniBatchSize',miniBatchSize);
YValidation = labelsValidation;
accuracy = mean(YPred == YValidation)
accuracy = 0.6647

Сборка сети классификации видео

Чтобы создать сеть, которая классифицирует видео непосредственно, соберите сеть с помощью слоев из обеих созданных сетей. Используйте слои из сверточной сети, чтобы преобразовать видео в векторные последовательности, и слои из сети LSTM, чтобы классифицировать векторные последовательности.

Следующая схема иллюстрирует сетевую архитектуру.

  • Чтобы ввести последовательности изображений в сеть, используйте входной слой последовательности.

  • Чтобы использовать сверточные слои для извлечения функций, то есть для применения сверточных операций к каждой системе координат видео независимо, используйте слой сгибания последовательности, за которым следуют сверточные слои.

  • Чтобы восстановить структуру последовательности и изменить форму выхода на векторные последовательности, используйте развернутый слой последовательности и уплощенный слой.

  • Чтобы классифицировать получившиеся векторные последовательности, включите слои LSTM, за которыми следуют выходные слои.

Добавление сверточных слоев

Сначала создайте график слоев сети GoogLeNet.

cnnLayers = layerGraph(netCNN);

Удалите слой входа ("data") и слои после слоя объединения, используемые для активаций ("pool5-drop_7x7_s1", "loss3-classifier", "prob", и "output").

layerNames = ["data" "pool5-drop_7x7_s1" "loss3-classifier" "prob" "output"];
cnnLayers = removeLayers(cnnLayers,layerNames);

Добавление входного слоя последовательности

Создайте слой входа последовательности, который принимает последовательности изображений, содержащие изображения того же размера входа, что и сеть GoogLeNet. Чтобы нормализовать изображения, используя то же среднее изображение, что и сеть GoogLeNet, установите 'Normalization' опция входного слоя последовательности для 'zerocenter' и 'Mean' опция для среднего изображения входного слоя GoogLeNet.

inputSize = netCNN.Layers(1).InputSize(1:2);
averageImage = netCNN.Layers(1).Mean;

inputLayer = sequenceInputLayer([inputSize 3], ...
    'Normalization','zerocenter', ...
    'Mean',averageImage, ...
    'Name','input');

Добавьте входной слой последовательности к графику слоев. Чтобы применить сверточные слои к изображениям последовательностей независимо, удалите структуру последовательности последовательностей изображений путем включения слоя складывания последовательности между входным слоем последовательности и сверточными слоями. Соедините выход слоя складывания последовательности с входом первого сверточного слоя ("conv1-7x7_s2").

layers = [
    inputLayer
    sequenceFoldingLayer('Name','fold')];

lgraph = addLayers(cnnLayers,layers);
lgraph = connectLayers(lgraph,"fold/out","conv1-7x7_s2");

Добавление слоев LSTM

Добавьте слои LSTM к графику слоев путем удаления входного слоя последовательности сети LSTM. Чтобы восстановить структуру последовательности, удаленную слоем складывания последовательности, включите слой развертывания последовательности после слоев свертки. Слои LSTM ожидают последовательности векторов. Чтобы изменить форму выхода развертывания последовательности на векторные последовательности, включите уплощенный слой после слоя развертывания последовательности.

Возьмите слои из сети LSTM и удалите входной слой последовательности.

lstmLayers = netLSTM.Layers;
lstmLayers(1) = [];

Добавьте слой складывания последовательности, слой уплощения и слои LSTM к графику слоев. Соедините последний сверточный слой ("pool5-7x7_s1") на вход развернутого слоя последовательности ("unfold/in").

layers = [
    sequenceUnfoldingLayer('Name','unfold')
    flattenLayer('Name','flatten')
    lstmLayers];

lgraph = addLayers(lgraph,layers);
lgraph = connectLayers(lgraph,"pool5-7x7_s1","unfold/in");

Чтобы включить развертывающий слой для восстановления структуры последовательности, соедините "miniBatchSize" выход слоя складывания последовательности на соответствующий вход слоя развертывания последовательности.

lgraph = connectLayers(lgraph,"fold/miniBatchSize","unfold/miniBatchSize");

Сборка сети

Проверьте, что сеть действительна, используя analyzeNetwork функция.

analyzeNetwork(lgraph)

Соберите сеть так, чтобы она была готова к предсказанию с помощью assembleNetwork функция.

net = assembleNetwork(lgraph)
net = 
  DAGNetwork with properties:

         Layers: [148×1 nnet.cnn.layer.Layer]
    Connections: [175×2 table]

Классификация с использованием новых данных

Чтение и центрирование видео "pushup.mp4" использование тех же шагов, что и прежде.

filename = "pushup.mp4";
video = readVideo(filename);

Чтобы просмотреть видео, используйте implay функция (требует Image Processing Toolbox). Эта функция ожидает данные в области значений [0,1], поэтому сначала необходимо разделить данные на 255. Также можно закольцовывать отдельные системы координат и использовать imshow функция.

numFrames = size(video,4);
figure
for i = 1:numFrames
    frame = video(:,:,:,i);
    imshow(frame/255);
    drawnow
end

Классификация видео с помощью собранной сети. The classify функция ожидает массив ячеек, содержащий входные видео, поэтому необходимо ввести массив ячеек 1 на 1, содержащий видео.

video = centerCrop(video,inputSize);
YPred = classify(net,{video})
YPred = categorical
     pushup 

Вспомогательные функции

The readVideo функция считывает видео в filename и возвращает H-by- W-by- C-by- S массив, где H, W, C, и S - высота, ширина, количество каналов и количество систем координат видео, соответственно.

function video = readVideo(filename)

vr = VideoReader(filename);
H = vr.Height;
W = vr.Width;
C = 3;

% Preallocate video array
numFrames = floor(vr.Duration * vr.FrameRate);
video = zeros(H,W,C,numFrames);

% Read frames
i = 0;
while hasFrame(vr)
    i = i + 1;
    video(:,:,:,i) = readFrame(vr);
end

% Remove unallocated frames
if size(video,4) > i
    video(:,:,:,i+1:end) = [];
end

end

The centerCrop функция обрабатывает самые длинные ребра видео и изменяет его размер inputSize.

function videoResized = centerCrop(video,inputSize)

sz = size(video);

if sz(1) < sz(2)
    % Video is landscape
    idx = floor((sz(2) - sz(1))/2);
    video(:,1:(idx-1),:,:) = [];
    video(:,(sz(1)+1):end,:,:) = [];
    
elseif sz(2) < sz(1)
    % Video is portrait
    idx = floor((sz(1) - sz(2))/2);
    video(1:(idx-1),:,:,:) = [];
    video((sz(2)+1):end,:,:,:) = [];
end

videoResized = imresize(video,inputSize(1:2));

end

См. также

| | | | | |

Похожие темы