硬貨 | 手把手帶你構建影片分類模型(附Python演練))
- 2019 年 10 月 4 日
- 筆記
譯者 | 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卷積。
由於影片是一系列幀,我們也可以將其解決為序列問題。所以,可以有更多的解決方案,我建議你可以探索它們。