人臉表情識別
簡介
這是科賽網曾今的一次計算機視覺類的分類賽,屬於一個視覺基礎任務。關於人臉表情識別,Kaggle等平臺也舉辦過相關的比賽,難度並不算大,但是對技巧的要求比較高。本文詳述該比賽的主要步驟,並構建多個模型對比效果。
數據探索
這部分主要是對數據集的簡單探索,由於計算機視覺類比賽數據多爲圖片或者視頻格式,無需像挖掘賽那樣進行比較複雜的EDA。這部分的代碼可以在文末給出的Github倉庫的EDA.ipynb
文件中找到。
數據集的目錄形式如下(數據集分享於百度雲,提取碼爲2fb9),由於本比賽只需要將五種區分度比較明顯的表情識別出來,所以共五個文件夾,每個文件夾內的圖片爲一類。
首先,生成了如下的csv格式的數據集說明文件如下,包含兩列,分別爲文件的相對路徑和標籤。對該說明文件進行分析更加的方便,因爲這個文件包含大多數數據集的信息,且Pandas提供了很多分析的API,很容易對錶格數據處理。
首先觀察類別的分佈,很遺憾,數據的類別分佈如下。這是因爲有的表情數據確實很少,儘管我們確實希望數據的分佈是均衡的,但是這裏的影響也不是很大,後面通過數據增強,會對這個情況有所改善。
隨後,隨機採樣10張圖片展示並顯示標籤,結果如下。通過這一步的觀察,不難發現,其實圖片中不僅僅是目標-人臉表情,有很多背景干擾因素,這就要求需要進行人臉檢測及圖片裁減了,也就是輸入深度模型中的不應該是原圖而應該是裁減後的人臉圖片。
數據預處理
這部分主要對圖片進行人臉截取,實現的原理是基於目標檢測的思路,分爲傳統方法和深度方法,爲了更快得到結果,這裏使用軟件包face_recognition
進行人臉檢測(注意處理單圖片多人臉情況),這是一個非常簡單的封裝好的人臉識別庫,可以通過pip安裝(若安裝出錯一般是dlib問題,安裝dlib的我回來文件即可)。
其人臉檢測的結果保存到本地後結果如下。注意,這個數據生成的過程時間較長。
核心代碼如下,具體代碼見文末Github。
def preprocess():
categories = os.listdir(data_folder)
for category in categories:
in_path = os.path.join(data_folder, category)
out_path = os.path.join(generate_folder, category + '_face')
if not os.path.exists(out_path):
os.mkdir(out_path)
for file in glob(in_path + '/*.jpg'):
file_name = file.split('\\')[-1]
print(file_name)
img = face_recognition.load_image_file(file)
if max(img.shape) > 2000:
if img.shape[0] > img.shape[1]:
img = cv2.resize(img, (2000, int(2000 * img.shape[1] / img.shape[0])))
else:
img = cv2.resize(img, (int(2000 * img.shape[0] / img.shape[1]), 2000))
locations = face_recognition.face_locations(img) # 人臉檢測,大部分爲單個,但也有多個檢測結果
if len(locations) <= 0:
print("no face")
else:
for i, (a, b, c, d) in enumerate(locations):
image_split = img[a:c, d:b, :]
image_split = scale_img(image_split)
Image.fromarray(image_split).save(os.path.join(out_path, file_name + '_{}.png'.format(i)))
數據加載
由於數據集已經被處理爲很規範的數據集格式,這裏直接調用TF2中Keras接口解析數據集(自己寫迭代器也是合適的,這裏爲了開發的速度採用封裝好的API),返回一個迭代器,同時在該接口中設置一些數據增強手段,這裏主要使用水平翻轉。
具體代碼如下,關於如何使用Keras的數據加載接口可以見我之前的博客。
代碼如下,具體整個項目的代碼見文末Github。
class DataSet(object):
def __init__(self, root_folder):
self.folder = root_folder
self.df_desc = pd.read_csv(self.folder + 'description.csv', encoding="utf8")
def get_generator(self, batch_size=32, da=True):
if da:
# 數據增強
train_gen = ImageDataGenerator(rescale=1 / 255., validation_split=0.2, horizontal_flip=True, shear_range=0.2,
width_shift_range=0.1)
else:
train_gen = ImageDataGenerator(rescale=1 / 255., validation_split=0.2, horizontal_flip=False)
img_size = (64, 64)
train_generator = train_gen.flow_from_dataframe(dataframe=self.df_desc,
directory='.',
x_col='file_id',
y_col='label',
batch_size=batch_size,
class_mode='categorical',
target_size=img_size,
subset='training')
valid_generator = train_gen.flow_from_dataframe(dataframe=self.df_desc,
directory=".",
x_col="file_id",
y_col="label",
batch_size=batch_size,
class_mode="categorical",
target_size=img_size,
subset='validation')
return train_generator, valid_generator
模型構建
主要嘗試了構建一個簡單的CNN模型進行訓練和預測,這是模型較淺,這是考慮到可能的部署後的速度而採取的設計方法;此外,使用ResNet50模型結構嘗試訓練和預測。本文采用TensorFlow2.0構建模型進行訓練,這是TF2是一個很不錯的深度學習框架,相比於TF1很容易學習和使用,具體的可以查看我TensorFlow2系列的教程博客。
def CNN(input_shape=(224, 224, 3), n_classes=5):
# input
input_layer = Input(shape=input_shape)
x = Conv2D(32, (1, 1), strides=1, padding='same', activation='relu')(input_layer)
# block1
x = Conv2D(64, (3, 3), strides=1, padding='same')(x)
x = PReLU()(x)
x = Conv2D(64, (5, 5), strides=1, padding='same')(x)
x = PReLU()(x)
x = MaxPooling2D(pool_size=(2, 2), strides=2)(x)
# fc
x = Flatten()(x)
x = Dense(2048, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(1024, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(n_classes, activation='softmax')(x)
model = Model(inputs=input_layer, outputs=x)
return model
def ResNet_pretrained(input_shape=(224, 224, 3), n_classes=5):
input_layer = Input(shape=input_shape)
densenet121 = ResNet50(include_top=False, weights=None, input_tensor=input_layer)
x = GlobalAveragePooling2D()(densenet121.output)
x = Dropout(0.5)(x)
x = Dense(n_classes, activation='softmax')(x)
model = Model(input_layer, x)
return model
對比兩個模型的效果如下圖,訓練集很快達到接近100%的準確率,驗證集卻幾乎不怎麼變化,說明模型的效果還是比較一般的,這主要是因爲不同類的數據量差距太大(有的類別幾百個樣本有的類別幾萬個樣本),模型很難訓練,或者說很快達到極限。
這裏注意,表情識別比賽只是一個簡單的分類賽,採用合適的深度特徵提取網絡即可取得不錯的效果,對於這種分類問題一般採用端到端的模型設計即可,即使用分類損失交叉熵進行預測結果衡量,優化器選擇的是Adam,當然,爲了更加適應任務可以自己設計合適的損失函數如focal損失,當然,這也只是錦上添花而已,真正需要優化的還是模型的結構。此外,也可以考慮RGB轉爲Gray圖從而減少冗餘信息等手段,或者引入更多的數據集進行訓練。
補充說明
真正構建實際系統是可以在模型應用的預測端進行一個增廣預測,將得到的結果進行加權從而得到真正的預測結果,具體可以參考我之前的項目。所有代碼開源於我的Github,歡迎Star或者Fork。