硬货 | 手把手带你构建视频分类模型(附Python演练))
译者 | VK
来源 | Analytics Vidhya
概述
-
了解如何使用计算机视觉和深度学习技术处理视频数据
-
我们将在Python中构建自己的视频分类模型
-
这是一个非常实用的视频分类教程,所以准备好Jupyter Notebook
介绍
我们可以使用计算机视觉和深度学习做很多事情,例如检测图像中的对象,对这些对象进行分类,从电影海报中生成标签。
这一次,我决定将注意力转向计算机视觉中不太引人注目的方面-视频!我们正以前所未有的速度消费视频内容。我觉得对数据科学家来说这个计算机视觉的领域具有很大的潜力。
我很好奇将相同的计算机视觉算法应用于视频数据。我用于构建图像分类模型的方法是否可以推广?
对于机器来说,视频可能很棘手。它们的动态特性与图像的静态特性相反,这可能使数据科学家构建这些模型变得复杂。
但不要担心,它与处理图像数据没有什么不同。在本文中,我们将使用Python构建我们自己的视频分类模型。这是一个非常实用的教程,所以准备好Jupyter Notebook,这将是一个非常有趣的过程。
我们将在本视频分类教程中介绍的内容
-
视频分类概述
-
构建视频分类模型的步骤
-
探索视频分类数据集
-
训练视频分类模型
-
评估视频分类模型
视频分类概述
你会如何定义视频?
我们可以说视频是按特定顺序排列的一组图像的集合。这些图像也称为帧。
这就是为什么视频分类问题与图像分类问题没有什么不同。对于图像分类任务,我们采用图像,使用特征提取器(如卷积神经网络或CNN)从图像中提取特征,然后基于这些提取的特征对该图像进行分类。视频分类仅涉及一个额外步骤。
我们首先从给定视频中提取帧。然后,我们可以按照与图像分类任务相同的步骤进行操作。这是处理视频数据的最简单方法。
实际上有多种其他方式来处理视频,甚至还有视频分析领域。我们将使用CNN从视频帧中提取特征。
构建视频分类模型的步骤
建立一个能够将视频分类到各自类别的模型很兴奋吧!我们将研究UCF101 – Action Recognition Data Set(动作识别数据集),它包含13,320种不同的视频片段,属于101个不同的类别。
让我总结一下我们将构建视频分类模型的步骤:
-
浏览数据集并创建训练和验证集。我们将使用训练集来训练模型和验证集来评估模型
-
从训练集以及验证集中的所有视频提取帧
-
预处理这些帧,然后使用训练集中的帧来训练模型。使用验证集中的帧来评估模型
-
一旦我们对验证集上的性能感到满意,就可以使用训练好的模型对新视频进行分类
我们现在开始探索数据吧!
探索视频分类数据集
你可以从官方UCF101站点(https://www.crcv.ucf.edu/data/UCF101.php)下载数据集。数据集采用.rar格式,因此我们首先必须从中提取视频。创建一个新文件夹,假设为"视频"(你也可以选择任何其他名称),然后使用以下命令提取所有下载的视频:
unrar e UCF101.rar Videos/
UCF101的官方文件指出:
"在训练和测试中,将属于同一组的视频分开是非常重要的。由于组内的视频都是来自一个较长的视频,所以在训练集和测试集上共享来自同一组的视频可以获得较高的性能。"
因此,我们将按照官方文档中的建议将数据集拆分为训练和测试集。你可以从这里下载训练/测试集(https://www.crcv.ucf.edu/data/UCF101/UCF101TrainTestSplits-RecognitionTask.zip)。请记住,由于我们处理的是大型数据集,因此你可能需要较高的计算能力。
我们现在将视频放在一个文件夹中,将训练/测试拆分文件放在另一个文件夹中。接下来,我们将创建数据集。打开你的Jupyter Notebook,然后按照下面的代码块。我们将首先导入所需的库:
import cv2 #捕获视频库
import math #数学操作库
import matplotlib.pyplot as plt #画图的库
%matplotlib inline
import pandas as pd
from keras.preprocessing import image # 预处理图像库
import numpy as np #数学操作库
from keras.utils import np_utils
from skimage.transform import resize #改变图像尺寸
from sklearn.model_selection import train_test_split
from glob import glob
from tqdm import tqdm
我们现在将视频的名称存储在dataframe中:
# 导入训练集txt文件,里面有视频名字列表
f = open("trainlist01.txt", "r")
temp = f.read()
videos = temp.split('\n')
# 创建含有视频名字列表的dataframe
train = pd.DataFrame()
train['video_name'] = videos
train = train[:-1]
train.head()
这就是.txt文件中视频名称的方式。它没有正确对齐,我们需要预处理它。在此之前,让我们为测试视频创建一个类似的dataframe:
# 导入测试集txt文件,里面有视频名字列表
f = open("testlist01.txt", "r")
temp = f.read()
videos = temp.split('\n')
# 创建含有视频名字列表的dataframe
test = pd.DataFrame()
test['video_name'] = videos
test = test[:-1]
test.head()
接下来,我们将添加每个视频的标签(用于训练和测试集)。你是否注意到视频名称中"/"之前的整个部分代表了视频的标签?因此,我们将整个字符串拆分为"/"并选择所有视频的标签:
# 为训练数据集创建标签
train_video_tag = []
for i in range(train.shape[0]):
train_video_tag.append(train['video_name'][i].split('/')[0])
train['tag'] = train_video_tag
# 为测试数据集创建标签
test_video_tag = []
for i in range(test.shape[0]):
test_video_tag.append(test['video_name'][i].split('/')[0])
test['tag'] = test_video_tag
下一步是什么?现在,我们将从训练视频中提取帧,这些视频将用于训练模型。我将所有帧存储在名为train_1的文件夹中。
因此,首先,创建一个新文件夹并将其重命名为"train_1",然后按照下面给出的代码提取帧:
# 存储训练集视频的帧
for i in tqdm(range(train.shape[0])):
count = 0
videoFile = train['video_name'][i]
cap = cv2.VideoCapture('UCF/'+videoFile.split(' ')[0].split('/')[1]) # 从给定路径获取视频
frameRate = cap.get(5) #帧率
x=1
while(cap.isOpened()):
frameId = cap.get(1) #当前帧编号
ret, frame = cap.read()
if (ret != True):
break
if (frameId % math.floor(frameRate) == 0):
# 存储在train_1文件夹
filename ='train_1/' + videoFile.split('/')[1].split(' ')[0] +"_frame%d.jpg" % count;count+=1
cv2.imwrite(filename, frame)
cap.release()
这需要一些时间,因为训练集中有超过9,500个视频。提取帧后,我们将在.csv文件中保存这些帧的名称及其对应的标签。创建此文件将有助于我们读取下一节中将要看到的帧。
# 获取所有图像的名字
images = glob("train_1/*.jpg")
train_image = []
train_class = []
for i in tqdm(range(len(images))):
# 创建图像名
train_image.append(images[i].split('/')[1])
# 创建图像类标
train_class.append(images[i].split('/')[1].split('_')[1])
# 存储在dataframe里
train_data = pd.DataFrame()
train_data['image'] = train_image
train_data['class'] = train_class
# 转换dataframe为csv文件
train_data.to_csv('UCF/train_new.csv',header=True, index=False)
到目前为止,我们已经从所有训练视频中提取了帧,并将它们与相应的标签一起保存在.csv文件中。现在是时候训练我们的模型,我们将用它来预测测试集中视频的标签。
训练视频分类模型
现在是时候训练我们的视频分类模型了!我确信这是本教程中最受期待的部分。为了便于理解,我已将此步骤划分为子步骤:
-
读取我们之前为训练提取的所有帧
-
创建一个验证集,它将帮助我们检查模型在看不见的数据上的表现
-
定义模型的结构
-
最后,训练模型并保存其权重
读取所有视频帧
那么,让我们开始第一步,我们将提取帧。我们将首先导入库:
import keras
from keras.models import Sequential
from keras.applications.vgg16 import VGG16
from keras.layers import Dense, InputLayer, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D, GlobalMaxPooling2D
from keras.preprocessing import image
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tqdm import tqdm
from sklearn.model_selection import train_test_split
我们之前创建了一个.csv文件,其中包含每个框架的名称及其相应的标签,我们在这里也进行读取:
train = pd.read_csv('UCF/train_new.csv')
train.head()
这是前五行的样子。我们为每个帧都有相应的标签。现在,使用此.csv文件,我们将读取先前提取的帧,然后将这些帧存储为NumPy数组:
# 创建空列表
train_image = []
# 循环读取和保存帧
for i in tqdm(range(train.shape[0])):
# 载入图片
img = image.load_img('train_1/'+train['image'][i], target_size=(224,224,3))
# 转换为array
img = image.img_to_array(img)
# 标准化像素值
img = img/255
# 保存到train_image列表
train_image.append(img)
# 转换为numpy数组
X = np.array(train_image)
# 输出形状
X.shape
输出:(73844,224,224,3)
我们有73,844张形状为(224,224,3)的图片。接下来,我们将创建验证集。
创建验证集
要创建验证集,我们需要确保每个类的分布在训练集和验证集中都相似。我们可以使用stratify参数来做到这一点:
# 分离数据集
y = train['class']
# 创建训练与测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, test_size=0.2, stratify = y)
这里,stratify = y(每个帧的标签)在训练和验证集中保持一个类似分布。
视频可以被分为101类。因此,我们必须在目标中创建101个不同的列,每个列对应一个类别。我们将使用get_dummies()函数:
y_train = pd.get_dummies(y_train)
y_test = pd.get_dummies(y_test)
下一步,定义视频分类模型的结构。
定义视频分类模型的结构
由于我们没有非常大的数据集,因此从头开始创建模型可能效果不佳。因此,我们将使用预先训练的模型并利用其学习来解决我们的问题。
对于这个特定的数据集,我们将使用VGG-16预训练模型。让我们创建预训练模型的基本模型:
# 创建预训练的VGG16基本模型
base_model = VGG16(weights='imagenet', include_top=False)
该模型在具有1,000个类的数据集上进行训练。我们将根据我们的要求对此模型进行微调。include_top = False将删除此模型的最后一层,以便我们可以根据需要对其进行调整。
现在,我们将从这个预先训练的模型中提取我们的训练和验证图像的功能:
# 从训练集的帧中提取特征
X_train = base_model.predict(X_train)
X_train.shape
输出:(59075,7,7,512)
我们在训练集中有59,075个图像,并且由于我们已经通过VGG16架构传递了这些图像,因此形状已更改为(7,7,512)。同样,我们将提取验证集的特征:
# 从验证集的帧中提取特征
X_test = base_model.predict(X_test)
X_test.shape
输出:(14769,7,7,512)
验证集中有14,769个图像,这些图像的形状也变为(7,7,512)。我们现在将使用完全连接的网络来微调模型。这个完全连接的网络以单一维度输入。因此,我们将图像重塑为一个维度:
X_train = X_train.reshape(59075, 7*7*512)
X_test = X_test.reshape(14769, 7*7*512)
始终建议对像素值进行归一化,即将像素值保持在0和1之间。这有助于模型更快地收敛。
# 标准化像素值
max = X_train.max()
X_train = X_train/max
X_test = X_test/max
接下来,我们将创建模型的体系结构。我们必须为此定义输入形状。那么,让我们检查一下图像的形状:
# 图像形状
X_train.shape
输出:(59075,25088)
输入形状为25,088。我们现在定义结构:
#定义结构
model = Sequential()
model.add(Dense(1024, activation='relu', input_shape=(25088,)))
model.add(Dropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(101, activation='softmax'))
我们有多个完全连接的全连接层。我也添加了Dropout层控制模型不会过拟合。最后一层中的神经元数量等于我们拥有的类别数量,因此这里的神经元数量为101。
训练视频分类模型
我们现在将使用训练框架训练我们的模型,并使用验证框架验证模型。我们将保存模型的权重,以便我们不必一次又一次地重新训练模型。
所以,让我们定义一个函数来保存模型的权重:
# 保存权重函数
from keras.callbacks import ModelCheckpoint
mcp_save = ModelCheckpoint('weight.hdf5', save_best_only=True, monitor='val_loss', mode='min')
我们将根据验证损失确定最佳模型。请注意,权重将保存为weights.hdf5。如果你愿意,可以重命名该文件。在训练模型之前,我们必须编译它:
# 编译模型
model.compile(loss='categorical_crossentropy',optimizer='Adam',metrics=['accuracy'])
我们使用categorical_crossentropy作为损失函数,优化器是Adam。让我们训练模型:
# 训练模型
model.fit(X_train, y_train, epochs=200, validation_data=(X_test, y_test), callbacks=[mcp_save], batch_size=128)
我们现在有权重,我们将用它来预测新视频。因此,在下一节中,我们将看到此模型在视频分类任务中的表现如何!
评估视频分类模型
让我们打开一个新的Jupyter Notebook来评估模型。评估部分也可以分成多个步骤,以更清楚地理解过程:
-
定义模型结构并加载权重
-
创建测试数据
-
对测试视频进行预测
-
最后,评估模型
定义模型结构并加载权重
导入所需的库:
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.preprocessing import image
import numpy as np
import pandas as pd
from tqdm import tqdm
from keras.applications.vgg16 import VGG16
import cv2
import math
import os
from glob import glob
from scipy import stats as s
接下来,我们将定义模型结构,它与我们在训练模型时的模型结构类似:
base_model = VGG16(weights='imagenet', include_top=False)
这是预先训练好的模型,接下来我们将对其进行微调:
model = Sequential()
model.add(Dense(1024, activation='relu', input_shape=(25088,)))
model.add(Dropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(101, activation='softmax'))
现在,正如我们已经定义了架构,我们现在将加载训练的权重,我们将其存储为weights.hdf5:
#载入权重
model.load_weights("weights.hdf5")
编译模型:
# 编译模型
model.compile(loss='categorical_crossentropy',optimizer='Adam',metrics=['accuracy'])
确保损失函数,优化程序和指标与我们在训练模型时使用的相同。
创建测试数据
你应该根据UCF101数据集的官方文档下载训练/测试集文件。在下载的文件夹中,有一个名为" testlist01.txt " 的文件,其中包含测试视频列表。我们将利用它来创建测试数据:
# 获取测试列表
f = open("testlist01.txt", "r")
temp = f.read()
videos = temp.split('\n')
# 创建dataframe
test = pd.DataFrame()
test['video_name'] = videos
test = test[:-1]
test_videos = test['video_name']
test.head()
我们现在拥有存储在数据框中的所有视频的列表。要将预测类别与实际类别进行映射,我们将使用train_new.csv文件:
# 创建标签
train = pd.read_csv('UCF/train_new.csv')
y = train['class']
y = pd.get_dummies(y)
现在,我们将对测试集中的视频进行预测。
测试视频的预测
让我总结一下在查看代码之前我们将在此步骤中执行的操作。以下步骤将帮助你了解预测部分:
-
首先,我们将创建两个空列表,一个用于存储预测标签,另一个用于存储实际标签
-
然后,我们将从测试集中获取每个视频,提取该视频的帧并将其存储在一个文件夹中(在当前目录中创建一个名为temp的文件夹来存储帧)。我们将在每次迭代时从此文件夹中删除所有其他文件
-
接下来,我们将读取temp文件夹中的所有帧,使用预先训练的模型提取这些帧的特征,进行预测得到标签后将其附加到第一个列表中
-
我们将在第二个列表中为每个视频添加实际标签
让我们编写这些步骤并生成预测:
# 创建两个列表来存储预测的和实际的标签
predict = []
actual = []
# for循环从每个测试视频中提取帧
for i in tqdm(range(test_videos.shape[0])):
count = 0
videoFile = test_videos[i]
cap = cv2.VideoCapture('UCF/'+videoFile.split(' ')[0].split('/')[1]) # 从给定路径获取视频
frameRate = cap.get(5) #帧率
x=1
# 从临时文件夹中删除所有其他文件
files = glob('temp/*')
for f in files:
os.remove(f)
while(cap.isOpened()):
frameId = cap.get(1) #当前帧编号
ret, frame = cap.read()
if (ret != True):
break
if (frameId % math.floor(frameRate) == 0):
# 将此特定视频的帧存储在temp文件夹中
filename ='temp/' + "_frame%d.jpg" % count;count+=1
cv2.imwrite(filename, frame)
cap.release()
# 从临时文件夹中读取所有帧
images = glob("temp/*.jpg")
prediction_images = []
for i in range(len(images)):
img = image.load_img(images[i], target_size=(224,224,3))
img = image.img_to_array(img)
img = img/255
prediction_images.append(img)
# 将测试视频的所有帧转换为numpy数组
prediction_images = np.array(prediction_images)
# extracting features using pre-trained model
prediction_images = base_model.predict(prediction_images)
# 使用预训练模型提取特征
prediction_images = prediction_images.reshape(prediction_images.shape[0], 7*7*512)
# 转换一维数组中的特征
prediction = model.predict_classes(prediction_images)
# 在预测列表中添加预测模式,将标签分配给视频
predict.append(y.columns.values[s.mode(prediction)[0][0]])
# 添加上视频的真实标签
actual.append(videoFile.split('/')[1].split('_')[1])
此步骤需要一些时间,因为测试集中有大约3,800个视频。一旦我们得到预测的结果,我们将用来计算模型的性能。
评估模型
是时候评估我们的模型了。
我们有实际的标签以及我们的模型预测的标签。我们将利用这些来获得准确度分数。在UCF101的官方文档页面上,当前准确率为43.90%。我们的模型可以击败它吗?让我们检查!
from sklearn.metrics import accuracy_score
accuracy_score(predict, actual)*100
输出:44.80570975416337
大!我们的模型的准确率为44.8%,与官方文件中的相似(43.9%)。
你可能想知道为什么我们对50%以下的准确度感到满意。那么,这种低精度背后的原因主要是由于缺乏数据。我们只有大约13,000个视频,甚至持续时间很短。
结束
在本文中,我们介绍了计算机视觉最有趣的应用之一,视频分类。我们首先了解如何处理视频,然后我们提取帧,训练视频分类模型,最后在测试视频上获得44.8%的准确度。
我们现在可以尝试不同的方法,旨在提高模型的性能。我能想到的一些方法是使用可以直接处理视频的3D卷积。
由于视频是一系列帧,我们也可以将其解决为序列问题。所以,可以有更多的解决方案,我建议你可以探索它们。
欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/
欢迎关注PyTorch官方中文教程站:
http://pytorch.panchuang.net/