在Face Alignment中,傳統方法其實能夠取得不錯的效果。包括AAM,ASM,CLM還有之前說的基于全局顯式回歸的ESR,3000FPS等。
但是!傳統算法在大姿态、極端表情上基本無能為力。在我的了解中,他們都是對于訓練集的某些特征進行降維後記憶回歸的結果(了解有錯請指出)。嘻嘻,而且不去接觸神經網絡都要Out了!
背景
基于由儉入奢的原則,論文方面我們選取了CNN在人臉對齊方向比較前期的一篇論文
CVPR _2013《Deep Convolutional Network Cascade for Facial Point Detection》
。在實作環境方面,我們選擇了社群比較成熟的而且背後大佬比較牛逼的
Tensorflow 1.3.0
。
去網上搜羅了一些CNN基本知識,因為卷積的Gif實在是太生動了。也很容易了解CNN卷積層中W和b的含義。
模型
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9MWbihGayIGao1WZxBXblZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TO4IzMxkDN4EjMykDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
我們隻挑選了其中的Level1來進行實作,是以代碼量也比較少。作為入門使用也是可以的。
Leve1由三個CNN網絡組成[F1,EN1,NM1]。F1預測了面部五個點的位置,EN1預測其中的雙眼、鼻子,NM1預測鼻子、嘴角。最後整個Leve1的輸出是三個網絡輸出的平均值。
根據論文表格,描述了所有網絡的詳細結構:
我們準備實作的F1為S0結構,EN1及NM1為S1結構。在論文中,作者使用非共享卷積層。對于每一個map,分為p x q的區域。如S0的Layer1:CR(4,20,2,2),CR表示使用Abs(tanh(x))作為激活函數,卷積核大小為4x4,輸出20個通道map,每個map在10x10的區域内權值共享。
但由于tensorflow并不原生支援unshare weights,是以我們直接按照标準的共享權值CNN來實作,在精度上會有一定損失。
實作細節
基于最基礎的思想,利用Tensorflow中已有的Api,我們來直接實作Level 1網絡。訓練集及測試集均可以在論文的官網下載下傳到。
預處理
首先,按照論文的說法,我們對原始訓練集進行資料增強。對原始圖像進行鏡像、少量随機旋轉和平移然後抽取人臉檢測包圍盒中的圖檔,将其縮放至39x39備用。相比于利用API進行後續網絡的搭建,反而感覺這一步是最麻煩的。哈哈哈
搭模組化型
利用Tensorflow的進階layer函數,我們可以非常友善的搭建整個網絡。需要用到的api:
tf.layers.conv2d #卷積層
tf.layers.max_pooling2d #最大池化層 我們這裡沒有像論文一樣給池化層增權重值及bias
tf.contrib.layers.flatten #展平資料
tf.layers.dense #全連接配接層
在選擇激活函數的時候,我們選擇于論文一緻的 tanh 來當做激活函數。一開始我直接使用Tensorflow自帶的權值初始方法,發現收斂非常慢甚至于收斂不了。經過查資料和詢問朋友發現,tanh 搭配 xavier 初始化有奇效。這點我目前還沒有徹底了解,留後。資料:資料
注:後來發現為手誤,初始的也能收斂。
So,我們現在搭一個CNN太簡單了!
def NewS0(name):
with tf.name_scope('S0' + name):
x = tf.placeholder(tf.float32, shape=[None, , ,])
y = tf.placeholder(tf.float32, shape=[None, ])
xImage = tf.reshape(x,[-, , , ])
Conv1 = tf.abs(tf.layers.conv2d(inputs=xImage,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool1 = tf.layers.max_pooling2d(inputs=Conv1,pool_size=,strides=,padding='same')
Conv2 = tf.abs(tf.layers.conv2d(inputs=Pool1,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool2 = tf.layers.max_pooling2d(inputs=Conv2,pool_size=,strides=,padding='same')
Conv3 = tf.abs(tf.layers.conv2d(inputs=Pool2,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool3 = tf.layers.max_pooling2d(inputs=Conv3,pool_size=,strides=,padding='same')
Conv4 = tf.abs(tf.layers.conv2d(inputs=Pool3,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool3_Flat = tf.contrib.layers.flatten(Pool3)
Conv4_Flat = tf.contrib.layers.flatten(Conv4)
Concat = tf.concat([Pool3_Flat,Conv4_Flat],)
Fc1 = tf.layers.dense(Concat,,activation=tf.nn.tanh)
Fc2 = tf.layers.dense(Fc1,,activation=tf.nn.tanh)
Cost = tf.reduce_mean(tf.square(Fc2 - y)) /
tf.summary.scalar(name + "/Cost",Cost)
Optimizer = tf.train.AdamOptimizer().minimize(Cost)
return Optimizer,Fc2,x,y,Cost
def NewS1(name,isEN1):
with tf.name_scope('S1' + name):
x = tf.placeholder(tf.float32, shape=[None, ,,])
y = tf.placeholder(tf.float32, shape=[None, ])
xImage = tf.reshape(x,[-, , , ])
if isEN1:
xImage = tf.slice(xImage,[,,,],[-,,,])
y_In = tf.slice(y,[,],[-,])
else:
xImage = tf.slice(xImage,[,,,],[-,,,])
y_In = tf.slice(y,[,],[-,])
Conv1 = tf.abs(tf.layers.conv2d(inputs=xImage,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool1 = tf.layers.max_pooling2d(inputs=Conv1,pool_size=,strides=,padding='same')
Conv2 = tf.abs(tf.layers.conv2d(inputs=Pool1,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool2 = tf.layers.max_pooling2d(inputs=Conv2,pool_size=,strides=,padding='same')
Conv3 = tf.abs(tf.layers.conv2d(inputs=Pool2,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool3 = tf.layers.max_pooling2d(inputs=Conv3,pool_size=,strides=,padding='same')
Conv4 = tf.abs(tf.layers.conv2d(inputs=Pool3,filters=,kernel_size=,strides=,activation=tf.nn.tanh))
Pool3_Flat = tf.contrib.layers.flatten(Pool3)
Conv4_Flat = tf.contrib.layers.flatten(Conv4)
Concat = tf.concat([Pool3_Flat,Conv4_Flat],)
Fc1 = tf.layers.dense(Concat,,activation=tf.nn.tanh)
Fc2 = tf.layers.dense(Fc1,,activation=tf.nn.tanh)
Cost = tf.reduce_mean(tf.square(Fc2 - y_In)) /
tf.summary.scalar(name + "/Cost",Cost)
Optimizer = tf.train.AdamOptimizer().minimize(Cost)
return Optimizer,Fc2,x,y,Cost
順便一說,我并沒有使用論文中的随機梯度下降來優化參數,因為。。實在太慢了。我使用了Tensorflow中提供的優化器Adam。
最後,在測試集上的雙眼間距錯誤率為 4%,應該說我們的網絡基本已經實作了。雖然精度可能沒有論文中
那麼好,但也差不多啦哈哈哈哈!OK,CNN的第一次應用就算完成了。接下來去解決後續論文了。