深度学习教程计算视频中演员的屏幕时间(使用Python代码)
当我开始深度学习之旅时,我学到的第一件事就是图像分类。一旦我掌握了图像分类,我想知道我是否可以将这种学习转移到视频上。
有没有办法建立一个模型,以特定的时间间隔自动识别给定视频中的特定人?事实证明,有,我很高兴与你分享我的方法!
在本文中,我将帮助您了解如何在视频数据上使用深度学习。为此,我们将使用来自流行的TOM和JERRY卡通系列的视频。目的是计算任何给定视频中TOM和JERRY的屏幕时间。
阅读视频并提取帧
有没有听说过翻书?如果你还没有,你错过了!看看下面的一个:
我们在书的每一页上都有不同的图像,当我们翻阅这些页面时,我们会得到一个鲨鱼舞动画。你甚至可以把它称为一种视频。我们翻页的速度越快,可视化效果越好。换句话说,该视觉是以特定顺序排列的不同图像的集合。
同样,视频只是一组图像的集合。这些图像称为帧,可以组合以获得原始视频。因此,与视频数据相关的问题与图像分类或对象检测问题没有区别。从视频中提取帧只需要一个额外的步骤。
请记住,我们的挑战是根据给定视频计算Tom和Jerry的屏幕时间。让我首先总结一下我们将在本文中遵循的步骤来解决这个问题:
- 导入和读取视频,从中提取帧,并将其保存为图像
- 标记一些图像以训练模型
- 建立我们的训练数据模型
- 预测剩余的图像
- 计算TOM和JERRY的屏幕时间
如何在Python中处理视频文件
让我们从导入所有必要的Python库开始。
- Numpy
- Pandas
- Matplotlib
- Keras
- Skimage
- OpenCV
import cv2 # for capturing videos import math # for mathematical operations import matplotlib.pyplot as plt # for plotting the images %matplotlib inline import pandas as pd from keras.preprocessing import image # for preprocessing the images import numpy as np # for mathematical operations from keras.utils import np_utils from skimage.transform import resize # for resizing images
步骤1:阅读视频,从中提取帧并将其保存为图像
现在我们将加载视频并将其转换为帧。您可以从此链接下载用于此示例的视频(https://drive.google.com/file/d/1_DcwBhYo15j7AU-v2gN61qGGd1ZablGK/view?usp=sharing)。我们将首先使用VideoCapture()函数从给定目录中捕获视频,然后我们将从视频中提取帧并使用imwrite()函数将它们保存为图像。我们来看看Python编码:
count = 0 videoFile = "Tom and jerry.mp4" cap = cv2.VideoCapture(videoFile) # capturing the video from the given path frameRate = cap.get(5) #frame rate x=1 while(cap.isOpened()): frameId = cap.get(1) #current frame number ret, frame = cap.read() if (ret != True): break if (frameId % math.floor(frameRate) == 0): filename ="frame%d.jpg" % count;count+=1 cv2.imwrite(filename, frame) cap.release() print ("Done!")
让我们尝试可视化图像(帧)。我们将首先使用matplotlib的imread()函数读取图像,然后使用imshow()函数绘制它。
img = plt.imread('frame0.jpg') # reading image using its name plt.imshow(img)
这是视频的第一帧。我们从视频的整个过程中,每秒钟提取一个帧。由于视频时长为4分58分(298秒),我们现在总共有298张图片。
我们的任务是确定哪幅图像是TOM的,哪幅图像是JERRY的。如果我们提取的图像与当前流行的Imagenet数据集中的图像相似,那么这个挑战可能是轻而易举的。我们可以简单地使用在Imagenet数据上预先训练的模型,并获得高准确度的分数!
我们的是卡通形象,没有任何一个预训练好的模特能在一个视频中识别出TOM和JERRY。
步骤2:标记一些图像以训练模型
那么我们该如何处理呢?一种可能的解决方案是手动为一些图像添加标签并在其上训练模型。一旦模型学会了模式,我们就可以用它来预测一组以前没见过的图像。
我们将其视为一个多类别的分类问题。我定义的类是:
- 0:JERRY和TOM都没有
- 1:JERRY
- 2:TOM
别担心,我已经标记了所有图像,所以你没有必要!继续下载mapping.csv文件(https://drive.google.com/file/d/1NbU8Sdj_YNF5Dl_zbdeBcnqEyU3Xw9TU/view?usp=sharing),其中包含每个图像名称及其对应的类(0或1或2)。
data = pd.read_csv('mapping.csv') # reading the csv file data.head() # printing first five rows of the file
映射文件包含两列:
- Image_ID:包含每个图像的名称
- Class. Image_ID:包含每个图像的对应类
下一步是读取图像,我们将根据它们的名称(即Image_ID列)来读取这些图像。
X = [ ] # creating an empty array for img_name in data.Image_ID: img = plt.imread('' + img_name) X.append(img) # storing each image in array X X = np.array(X) # converting list to array
我们现在有了我们的图像。请记住,我们需要两件事来训练我们的模型:
- 训练图像
- 他们对应的类
由于有三个类,我们将使用keras.utils的to_categorical()函数对它们进行one hot编码。
y = data.Class dummy_y = np_utils.to_categorical(y) # one hot encoding Classes
我们将使用一个VGG16预训练模型,它接受的输入图像(224 X 224 X 3)。我们将使用skimage的resize()函数。
image = [] for i in range(0,X.shape[0]): a = resize(X[i], preserve_range=True, output_shape=(224,224)).astype(int) # reshaping to 224*224*3 image.append(a) X = np.array(image)
所有图像都已重新整形为224 X 224 X 3.但在将任何输入传递给模型之前,我们必须根据模型的要求对其进行预处理。否则,模型将无法运行良好。使用keras.applications.vgg16的preprocess_input()函数执行此步骤。
from keras.applications.vgg16 import preprocess_input X = preprocess_input(X, mode='tf') # preprocessing the input data
我们还需要一个验证集来检查模型在未出现过的图像上的性能。我们将利用sklearn.model_selection模块的train_test_split()函数将图像随机分成训练和验证集。
from sklearn.model_selection import train_test_split X_train, X_valid, y_train, y_valid = train_test_split(X, dummy_y, test_size=0.3, random_state=42) # preparing the validation set
第3步:构建模型
下一步是构建我们的模型。如上所述,我们将使用VGG16预训练模型完成此任务。让我们首先导入所需的Python库来构建模型:
from keras.models import Sequential from keras.applications.vgg16 import VGG16 from keras.layers import Dense, InputLayer, Dropout
我们现在将加载VGG16预训练模型并将其存储为base_model:
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) # include_top=False to remove the top layer
我们将使用此模型对X_train和X_valid进行预测,获取特征,然后使用这些特征重新训练模型。
X_train = base_model.predict(X_train) X_valid = base_model.predict(X_valid) X_train.shape, X_valid.shape
X_train和X_valid的shape分别是(208,7,7,512),(90,7,7,512)。为了将它传递给我们的神经网络,我们必须将其reshape 为1-D。
X_train = X_train.reshape(208, 7*7*512) # converting to 1-D X_valid = X_valid.reshape(90, 7*7*512)
现在,我们将对图像进行预处理,使其为zero-centered,这有助于模型更快地收敛。
train = X_train/X_train.max() # centering the data X_valid = X_valid/X_train.max()
最后,我们将构建我们的模型。这一步可分为3个子步骤:
- 建立模型
- 编译模型
- 训练模型
# i. Building the model model = Sequential() model.add(InputLayer((7*7*512,))) # input layer model.add(Dense(units=1024, activation='sigmoid')) # hidden layer model.add(Dense(3, activation='sigmoid')) # output layer
让我们使用summary()函数检查模型的摘要:
model.summary()
我们有一个隐藏层,有1,024个神经元,一个输出层有3个神经元(因为我们有3个类可以预测)。现在我们将编译我们的模型:
# ii. Compiling the model model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
在最后一步中,我们将拟合模型并同时检查其在性能,即验证图像:
# iii. Training the model model.fit(train, y_train, epochs=100, validation_data=(X_valid, y_valid))
我们可以看到它在训练和验证图像上的表现非常好。获得了大约85%的准确度。这就是我们如何在视频数据上训练模型以获得每帧的预测。
计算屏幕时间 - 一个简单的解决方案
首先,从这里下载我们将在本节中使用的视频(https://drive.google.com/file/d/1MQHRosZmeYpK2onCWr_A9p5SI93pEDw0/view?usp=sharing)。完成后,继续加载视频并从中提取帧。我们将按照与上述相同的步骤操作:
count = 0 videoFile = "Tom and Jerry 3.mp4" cap = cv2.VideoCapture(videoFile) frameRate = cap.get(5) #frame rate x=1 while(cap.isOpened()): frameId = cap.get(1) #current frame number ret, frame = cap.read() if (ret != True): break if (frameId % math.floor(frameRate) == 0): filename ="test%d.jpg" % count;count+=1 cv2.imwrite(filename, frame) cap.release() print ("Done!")
从新视频中提取帧后,我们现在将加载test.csv文件,其中包含每个提取帧的名称。下载test.csv文件并加载它(https://drive.google.com/open?id=1uIAXp_2WHwb_SLZF3fwpW9lbo9eRaTtp):
test = pd.read_csv('test.csv')
接下来,我们将导入图像进行测试,然后根据上述预训练模型的要求对其进行reshape :
test_image = [] for img_name in test.Image_ID: img = plt.imread('' + img_name) test_image.append(img) test_img = np.array(test_image) test_image = [] for i in range(0,test_img.shape[0]): a = resize(test_img[i], preserve_range=True, output_shape=(224,224)).astype(int) test_image.append(a) test_image = np.array(test_image)
我们需要对这些图像进行更改,类似于我们对训练图像所做的更改。我们将预处理图像,使用base_model.predict()函数使用VGG16预训练模型从这些图像中提取特征,将这些图像reshape为1-D形式,并使它们zero-centered:
# preprocessing the images test_image = preprocess_input(test_image, mode='tf') # extracting features from the images using pretrained model test_image = base_model.predict(test_image) # converting the images to 1-D form test_image = test_image.reshape(186, 7*7*512) # zero centered images test_image = test_image/test_image.max()
由于我们之前已经训练过该模型,因此我们将利用该模型对这些图像进行预测。
步骤-4:对剩余图像进行预测
predictions = model.predict_classes(test_image)
步骤-5计算TOM和JERRY的屏幕时间
Class'1'表示存在JERRY,而Class'2'表示存在TOM。我们将利用上述预测来计算屏幕时间:
print("The screen time of JERRY is", predictions[predictions==1].shape[0], "seconds") print("The screen time of TOM is", predictions[predictions==2].shape[0], "seconds")
我学到的东西
在本节中,我将详细介绍我所面临的一些困难,以及我如何解决它们。之后,我为最终的模型提供了完整的代码,给了我最好的准确性。
首先,我尝试使用预训练模型而不删除top层。结果并不令人满意。可能的原因:可能是这些是卡通图像,我们的预训练模型是在实际图像上训练的,因此无法对这些卡通图像进行分类。为了解决这个问题,我使用少量标记图像重新训练了预训练模型,结果与之前的结果相比更好。
即使对标记图像进行训练,准确率仍不理想。该模型不能很好地处理训练图像本身。所以,我试着增加层的数量。增加层数被证明是提高训练精度的好方法,但是训练和验证精度之间没有同步。模型过度拟合,在不可见数据上的性能不理想。所以我在每一层密集层之后都添加了一个drop - out层,这样在训练和验证精度之间就有了很好的同步。
我注意到类不平衡。TOM有更多的屏幕时间,所以预测是由它主导的,大多数帧被预测为TOM。为了克服这个问题并使类平衡,我使用了sklearn.utils.class_weight模块的compute_class_weight()函数。与具有较高值计数的类相比,它为具有较低值计数的类分配较高权重。
我还使用模型检查点来保存最好的模型,即产生最低验证损失的模型,然后使用该模型进行最后的预测。我将总结上述所有步骤并给出最终代码。可以在testing.csv文件中找到测试图像的实际类。
import cv2 import math import matplotlib.pyplot as plt import pandas as pd %matplotlib inline from keras.preprocessing import image import numpy as np from skimage.transform import resize count = 0 videoFile = "Tom and jerry.mp4" cap = cv2.VideoCapture(videoFile) frameRate = cap.get(5) #frame rate x=1 while(cap.isOpened()): frameId = cap.get(1) #current frame number ret, frame = cap.read() if (ret != True): break if (frameId % math.floor(frameRate) == 0): filename ="frame%d.jpg" % count;count+=1 cv2.imwrite(filename, frame) cap.release() print ("Done!")
Done!
count = 0 videoFile = "Tom and Jerry 3.mp4" cap = cv2.VideoCapture(videoFile) frameRate = cap.get(5) #frame rate x=1 while(cap.isOpened()): frameId = cap.get(1) #current frame number ret, frame = cap.read() if (ret != True): break if (frameId % math.floor(frameRate) == 0): filename ="test%d.jpg" % count;count+=1 cv2.imwrite(filename, frame) cap.release() print ("Done!")
Done!
data = pd.read_csv('mapping.csv') test = pd.read_csv('testing.csv') X = [] for img_name in data.Image_ID: img = plt.imread('' + img_name) X.append(img) X = np.array(X) test_image = [] for img_name in test.Image_ID: img = plt.imread('' + img_name) test_image.append(img) test_img = np.array(test_image) from keras.utils import np_utils train_y = np_utils.to_categorical(data.Class) test_y = np_utils.to_categorical(test.Class) image = [] for i in range(0,X.shape[0]): a = resize(X[i], preserve_range=True, output_shape=(224,224,3)).astype(int) image.append(a) X = np.array(image) test_image = [] for i in range(0,test_img.shape[0]): a = resize(test_img[i], preserve_range=True, output_shape=(224,224)).astype(int) test_image.append(a) test_image = np.array(test_image) from keras.applications.vgg16 import preprocess_input X = preprocess_input(X, mode='tf') test_image = preprocess_input(test_image, mode='tf') from sklearn.model_selection import train_test_split X_train, X_valid, y_train, y_valid = train_test_split(X, train_y, test_size=0.3, random_state=42) from keras.models import Sequential from keras.applications.vgg16 import VGG16 from keras.layers import Dense, InputLayer, Dropout base_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) X_train = base_model.predict(X_train) X_valid = base_model.predict(X_valid) test_image = base_model.predict(test_image) X_train = X_train.reshape(208, 7*7*512) X_valid = X_valid.reshape(90, 7*7*512) test_image = test_image.reshape(186, 7*7*512) train = X_train/X_train.max() X_valid = X_valid/X_train.max() test_image = test_image/test_image.max() model = Sequential() model.add(InputLayer((7*7*512,))) # input layer model.add(Dense(units=1024, activation='sigmoid')) # hidden layer model.add(Dropout(0.5)) # adding dropout model.add(Dense(units=512, activation='sigmoid')) # hidden layer model.add(Dropout(0.5)) # adding dropout model.add(Dense(units=256, activation='sigmoid')) # hidden layer model.add(Dropout(0.5)) # adding dropout model.add(Dense(3, activation='sigmoid')) # output layer model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) from sklearn.utils.class_weight import compute_class_weight, compute_sample_weight class_weights = compute_class_weight('balanced',np.unique(data.Class), data.Class) # computing weights of different classes from keras.callbacks import ModelCheckpoint filepath="weights.best.hdf5" checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_best_only=True, mode='min') callbacks_list = [checkpoint] # model check pointing based on validation loss model.fit(train, y_train, epochs=100, validation_data=(X_valid, y_valid), class_weight=class_weights, callbacks=callbacks_list)
model.load_weights("weights.best.hdf5") model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) scores = model.evaluate(test_image, test_y) print("%s: %.2f%%" % (model.metrics_names[1], scores[1]*100))
结论
使用此模型,我们在验证数据上获得了大约88%的准确率,在测试数据上获得了64%的准确度。
在测试数据上获得低精度的一个可能原因可能是缺乏训练数据。由于该模型对TOM和JERRY这样的卡通形象知之甚少,因此我们必须在训练过程中为其提供更多图像。我的建议是从不同的TOM和JERRY视频中提取更多帧,相应地标记它们,并使用它们来训练模型。一旦模型看到更多图像,就很有可能导致更好的分类结果。
这些模型可以帮助我们在各个领域:
- 我们可以计算电影中特定演员的屏幕时间
- 计算你最喜欢的超级英雄的屏幕时间等。
这些只是可以使用此技术的几个示例。你可以自己想出更多这样的应用程序!