深度学习教程 : 计算视频中演员的屏幕时间(附Python代码)

徐大白
2个月前 阅读 242 点赞 3

引言

当我开始深度学习之旅时,我学到的第一件事就是图像分类。它是计算机视觉中一个引人入胜的部分,我完全沉浸在它之中!但我有一个奇怪的想法,一旦我掌握了图像分类,我想知道我是否可以将这一学习转移到视频。

有没有办法建立一个模型,在特定的时间间隔内自动识别特定视频中的特定人物?结果是肯定的,我很兴奋和你分享我的方法!

现在,为了给您提供一些我们将要解决的问题的背景,请记住,屏幕时间对于演员来说极其重要。这直接关系到他/她得到的钱。为了让你对这个佣金有所了解,你知道小罗伯特·唐尼在《蜘蛛侠归来》中仅仅15分钟的屏幕时间就拿了1000万美元吗?简直不可思议。

如果我们能拍下任何视频并计算出其中任何一个演员的屏幕时间,那该有多酷啊?

在这篇文章中,我将帮助您了解如何使用深度学习视频数据。要做到这一点,我们将使用非常流行的Tom&Jerry 卡通系列,用来计算任何给定视频中的Tom和Jerry的屏幕时间。

听起来有趣吗?那就继续吧!

注:本文假设您有使用深度学习的图像分类的先验知识。如果没有,我建议您阅读这篇文章,这将帮助您掌握深度学习和图像分类的基本知识。


目录表

  1. 读取视频和提取帧
  2. 如何处理Python中的视频文件
  3. 计算屏幕时间 —— 一个简单的解决方案
  4. 学习心得——为什么成功了,为什么失败了


读取视频和提取帧

听说过翻书吗?如果你没有,你就错过了!看看下面这个:

我们在书的每一页都有不同的图像,当我们翻页时,我们得到了鲨鱼舞蹈的动画。你甚至可以称之为一种视频。翻页越快,可视化效果越好。换句话说,这种视觉是以特定顺序排列的不同图像的集合。

类似地,视频不过是一组图像的集合。这些图像被称为帧,可以组合以获得原始视频。因此,与视频数据相关的问题与图像分类或对象检测问题不同。只有一个额外的步骤从视频中提取帧。

记住,我们的挑战是从给定的视频中计算汤姆和杰瑞的屏幕时间。让我先总结一下我们在本文中要解决的问题:

  1. 导入并读取视频,从中提取帧,并将其保存为图像。
  2. 标记一些图片来训练模型(不用担心,我已经为你做了)
  3. 建立我们的训练数据模型
  4. 对剩余图像进行预测
  5. 计算汤姆和杰瑞的屏幕时间

相信我,遵循这些步骤将有助于你解决许多与视频相关的问题。是时候祭出我们的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_trainX_valid 进行预测,获得特征,然后使用这些特征重新训练模型。

X_train = base_model.predict(X_train)
X_valid = base_model.predict(X_valid)
X_train.shape, X_valid.shape

X_trainX_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个子步骤:

  1. 建立模型
  2. 编译模型
  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

翻译:徐大白

| 3
登录后可评论,马上登录吧~
评论 ( 1 )
像徐大神学习
回复
1个月前