引言
当我开始深度学习之旅时,我学到的第一件事就是图像分类。它是计算机视觉中一个引人入胜的部分,我完全沉浸在它之中!但我有一个奇怪的想法,一旦我掌握了图像分类,我想知道我是否可以将这一学习转移到视频。
有没有办法建立一个模型,在特定的时间间隔内自动识别特定视频中的特定人物?结果是肯定的,我很兴奋和你分享我的方法!
现在,为了给您提供一些我们将要解决的问题的背景,请记住,屏幕时间对于演员来说极其重要。这直接关系到他/她得到的钱。为了让你对这个佣金有所了解,你知道小罗伯特·唐尼在《蜘蛛侠归来》中仅仅15分钟的屏幕时间就拿了1000万美元吗?简直不可思议。
如果我们能拍下任何视频并计算出其中任何一个演员的屏幕时间,那该有多酷啊?
在这篇文章中,我将帮助您了解如何使用深度学习视频数据。要做到这一点,我们将使用非常流行的Tom&Jerry 卡通系列,用来计算任何给定视频中的Tom和Jerry的屏幕时间。
听起来有趣吗?那就继续吧!
注:本文假设您有使用深度学习的图像分类的先验知识。如果没有,我建议您阅读这篇文章,这将帮助您掌握深度学习和图像分类的基本知识。
目录表
- 读取视频和提取帧
- 如何处理Python中的视频文件
- 计算屏幕时间 —— 一个简单的解决方案
- 学习心得——为什么成功了,为什么失败了
读取视频和提取帧
听说过翻书吗?如果你没有,你就错过了!看看下面这个:
我们在书的每一页都有不同的图像,当我们翻页时,我们得到了鲨鱼舞蹈的动画。你甚至可以称之为一种视频。翻页越快,可视化效果越好。换句话说,这种视觉是以特定顺序排列的不同图像的集合。
类似地,视频不过是一组图像的集合。这些图像被称为帧,可以组合以获得原始视频。因此,与视频数据相关的问题与图像分类或对象检测问题不同。只有一个额外的步骤从视频中提取帧。
记住,我们的挑战是从给定的视频中计算汤姆和杰瑞的屏幕时间。让我先总结一下我们在本文中要解决的问题:
- 导入并读取视频,从中提取帧,并将其保存为图像。
- 标记一些图片来训练模型(不用担心,我已经为你做了)
- 建立我们的训练数据模型
- 对剩余图像进行预测
- 计算汤姆和杰瑞的屏幕时间
相信我,遵循这些步骤将有助于你解决许多与视频相关的问题。是时候祭出我们的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:读取视频,从中提取帧,并将其保存为图像。
现在我们将加载视频并将其转换成帧。你可以从这个链接下载这个例子中使用的视频。我们将首先使用VideoCapture() 函数从给定目录捕获视频,然后从视频中提取帧,并使用imwrite() 函数将它们保存为图像。看看代码:
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的IfDead()函数读取图像,然后使用imSub()函数绘制它。
img = plt.imread('frame0.jpg') # reading image using its name
plt.imshow(img)
激动不?
这是视频中的第一帧。我们从视频的整个持续时间中提取了每一帧。由于视频的持续时间是4'58"(298秒),我们现在总共有298张图像。
我们的任务是确定哪个图像有汤姆,哪个图像有杰瑞。如果我们提取的图像与流行的Imagenet数据集中的图像类似,那么这个挑战可能只是小菜一碟。怎样?我们可以简单地使用在IMAGENET数据上预先训练的模型,并获得高精度的分数!但那有什么好玩的呢?
我们有卡通图像,所以很难(如果不是不可能的话)用任何预先训练的模型来识别给定视频中的TOM和JERRY。
步骤2:为训练模型标注一些图像
那么我们该怎么处理呢?一种可能的解决方案是手动给几个图像标签,并在它们上训练模型。一旦模型学会了模式,我们就可以用它来预测以前看不见的图像集。
请记住,当汤姆和杰瑞都不在场时,可能会有框架。因此,我们将其视为一个多类分类问题。我定义的类是:
0——既不是杰瑞也不是汤姆
1 -杰瑞
2 -汤姆
别担心,我把所有的图像都贴上了标签,所以你不必这么做!继续下载包含每个图像名称及其对应类(0或1或2)的MAPPING.CSV文件。
data = pd.read_csv('mapping.csv') # reading the csv file
data.head() # printing first five rows of the file
映射文件包含两列:
- IMAGE_ID:包含每个图像的名称
- CLASS.IMAGE_ID:包含每个图像的对应类
我们的下一步是读取图像,我们将根据他们的名字,aka,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() 函数对它们进行热编码。
y = data.Class
dummy_y = np_utils.to_categorical(y) # one hot encoding Classes
我们将使用VGG16预训练模型,该模型采用形状(224×224×3)的输入图像。因为我们的图像大小不同,所以我们需要重塑它们。我们将使用 skimage.transform 的 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×224×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预训练模型来完成这项任务。让我们首先导入所需的库来构建模型:
from keras.models import Sequential
from keras.applications.vgg16 import VGG16
from keras.layers import Dense, InputLayer, Dropout
现在我们将加载VGG16预训练模型并将其存储为Basic模型:
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 的形状分别为(208, 7, 7,512),(90, 7, 7,512)。为了把它传递给我们的神经网络,我们必须把它改造成1-D。
X_train = X_train.reshape(208, 7*7*512) # converting to 1-D
X_valid = X_valid.reshape(90, 7*7*512)
现在,我们将预处理图像,使它们零中心,这有助于模型收敛更快。
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()
有一个隐含层,有1024个神经元,一个输出层有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%的准确度。这就是我们如何训练一个视频数据模型来预测每个帧。
在下一节中,我们将尝试计算一个新视频中的Tom和Jerry的屏幕时间。
计算屏幕时间——一个简单的解决方案
首先,从这里下载我们将在本节中使用的视频。一旦完成,继续加载视频并从中提取帧。我们将遵循与上述相同的步骤:
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文件并载入:
test = pd.read_csv('test.csv')
接下来,我们将导入图像进行测试,然后根据上述预训练模型的要求对它们进行整形:
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预训练模型从这些图像中提取特征,将这些图像重塑为一维形式,并使它们以零为中心:
# 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:计算汤姆和杰瑞的屏幕时间
回想一下,“1”代表了杰瑞的存在,而“2”代表了汤姆的存在。我们将利用上述预测来计算这两个传奇人物的银幕时间:
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")
好啦!我们有汤姆和杰瑞在诶定视频中的总屏幕时间。
学习心得——为什么我成功了,为什么又失败了
我尝试和测试了很多东西来应对这个挑战——一些人运行得非常好,而有些人则最终失败了。在这一节中,我将详细阐述我所面临的一些困难,然后我如何解决它们。之后,我提供了最后一个模型的全部代码,这给了我最好的准确度。
首先,我尝试使用预先训练的模型,而不去除顶层。结果不令人满意。可能的原因是这些是卡通图像,并且我们的预训练模型是在实际图像上训练的,因此它不能对这些卡通图像进行分类。为了解决这个问题,我使用很少的标记图像重新训练了预模型,结果比以前的结果更好。
即使在对标记图像进行训练之后,精度也不令人满意。该模型不能在训练图像本身上表现良好。所以,我试着增加层数。增加层数被证明是提高训练精度的良好解决方案,但是训练和验证精度之间没有同步。模型过于拟合,其在未观测数据上的表现并不理想。因此,在每一个稠密层之后,我添加了一个丢失层,然后在训练和验证准确性之间有很好的同步。
我注意到这些类是不平衡的。汤姆有更多的屏幕时间,所以预测占主导地位,它的大部分帧被预测为汤姆。为了克服这一问题,使类平衡,我使用 sklearn.utils.class_weight 模块的 compute_class_weight() function of 函数。与具有较高值计数的类相比,它向具有较低值计数的类分配更高的权重。
我还使用 Model Checkpointing 来保存最好的模型,即产生最低验证损失的模型,然后使用该模型做出最终预测。我将总结所有上述步骤,并给出最终的代码现在。测试图像的实际类可以在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!")
搞定!
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!")
搞定!
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视频中提取更多的帧,相应地标记它们,并使用它们来训练模型。一旦模型已经看到这两个字符的大量图像,就有很好的机会获得更好的分类结果。
这样的模型可以帮助我们在各个领域:
- 我们可以计算电影中特定演员的屏幕时间。
- 计算你最喜欢的超级英雄的屏幕时间等。
这些只是几个例子,在这里可以使用这种技术。你可以自己想出更多这样的应用程序!请在下面的评论部分分享你的想法和反馈。
原文:Deep Learning Tutorial to Calculate the Screen Time of Actors in any Video (with Python codes)
作者:Pulkit Sharma
翻译:徐大白
注意:本文归作者所有,未经作者允许,不得转载