TensorFlow框架做實時人臉識別小專案(二)
在第一部分中,分析了整個小專案的體系,重點討論了用於人臉檢測對齊的mtcnn網路的實現原理,並利用膝上型電腦自帶的攝像頭進行了測試。今天在這裡要討論的重點是人臉識別中的核心部分——facenet網路。
facenet是Google開源的人臉識別框架,它的作用是把輸入的人臉影象對映為多維特徵向量,相當於對不同的人臉進行了不同的編碼,同一個人臉的影象生成的編碼幾乎一致,不同的人臉影象生成的編碼差異非常大,並以此達到識別的目的。設計一個能夠達到這樣效果的對映的網路是一個很難的問題,我們下面就一步一步來看facenet是怎樣解決這個問題的。
首先,facenet的結構是這樣的:
facenet網路的輸入有多種不同的大小,中間部分是一個深度卷積神經網路,與其他普通CNN沒有多大區別。facenet不一樣的地方在於後面部分,它對深度卷積神經網路的輸出做了一個L2正則化,然後再對輸出進行了embedding,直接將embedding的對映結果作為特徵向量輸出。facenet並沒有像其他一般的CNN用softmax作為損失函式,而是設計了一種新的損失——triplet loss。
在理想的情況下,特徵向量之間的距離可以直接反映人臉的相似度,即:
- 對於同一個人的兩張人臉影象,對應的向量之間的歐幾里得距離比較小
- 對於不同人的兩張影象,對應的向量之間的歐幾里得距離比較大
假設人臉影象為x1和x2,對應的特徵為f(x1)和f(x2)。當x1和x2對應的是同一個人臉時,其距離II f(x1)-f(x2) II應該很小,而當x1和x2對應的是不同的人臉時,其距離II f(x1)-f(x2) II應該很大。
然而事實並非如此。在一般CNN網路中,最後的輸出經過softmax分類器,使用的是softmax損失。這個損失是不同類別間的損失。對於人臉來說,每一個人臉就是一個人。看起來似乎很合理,但是用softmax表示損失,以此區別出不同的人是不可行的。
下面重點看triplet loss的定義及原理
三元組損失(triplet loss)的原理:既然目標是特徵之間的距離應當具備某些性質,那麼就圍繞這個距離來設計損失。具體的,每次都在訓練資料中取出三張人臉影象,第一張影象記為,第二張影象記為,第三張影象記為。在這樣一個三元組中,和對應的是同一個人,是另外一個不同的人。因此,距離應該比較小,而距離應該較大。那麼嚴格的就有以下式子:
即相同人臉距離平方至少要比不同人臉的距離平方小
那麼損失函式就設計為:
三元組損失直接對距離進行優化,因此可以解決人臉的特徵表示問題。
PS:另一種損失:中心損失(center loss)
中心損失的定義:中心損失不對距離進行優化,它保留了原有的分類模型,但又為每一個類指定了一個類別中心。同一個類的影象對應的特徵應該儘量靠近自己的類別中心,不同的類別中心儘量遠離。
輸入的人臉影象為,該人臉的類別為,對每一個類別都規定一個類別中心。希望每個人臉對應的特徵都儘可能的接近其中心,因此中心損失定義為:
多張影象的中心損失就是將單張影象的損失相加:
此外,不能只使用中心損失來訓練分類模型,還需要加入softmax損失,也就是說,最終的損失由兩部分構成,即,其中的是一個超引數。
facenet中的triplet loss定義:
def triplet_loss(anchor, positive, negative, alpha):
"""
引數說明:
anchor: the embeddings for the anchor images.
positive: the embeddings for the positive images.
negative: the embeddings for the negative images.
"""
with tf.variable_scope('triplet_loss'):
pos_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, positive)), 1)
neg_dist = tf.reduce_sum(tf.square(tf.subtract(anchor, negative)), 1)
basic_loss = tf.add(tf.subtract(pos_dist,neg_dist), alpha)
loss = tf.reduce_mean(tf.maximum(basic_loss, 0.0), 0)
facenet中的center loss定義:
def center_loss(features, label, alfa, nrof_classes):
nrof_features = features.get_shape()[1]
centers = tf.get_variable('centers', [nrof_classes, nrof_features], dtype=tf.float32,initializer=tf.constant_initializer(0), trainable=False)
label = tf.reshape(label, [-1])
centers_batch = tf.gather(centers, label)
diff = (1 - alfa) * (centers_batch - features)
centers = tf.scatter_sub(centers, label, diff)
loss = tf.reduce_mean(tf.square(features - centers_batch))
return loss, centers
同樣,facenet網路中的引數需要百萬級的人臉資料進行訓練,才能達到很好的embedding效果。
github上facenet專案的作者David Sandberg,已經通過幾個不同的大型人臉資料集進行預訓練,得到了效果很好的facenet網路模型如下:
這個模型是放在谷歌雲盤上的,國內的網路無法下載,這裡有我下載過來的檔案提供給有需要的小夥伴,在這裡。
使用這個預訓練的facenet網路,首先載入模型,然後向模型中喂入用mtcnn檢測對齊好的人臉crop。實現的部分程式碼如下:
def embed_image(crop):
facenet.load_model('./20180408-102900/20180408-102900.pb')
tf.Graph().as_default()
sess = tf.Session()
# Get input and output tensors
images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")
# Run forward pass to calculate embeddings
#emb_array = np.zeros((160, embedding_size))
feed_dict = {images_placeholder: crop, phase_train_placeholder: False}
emb_array = sess.run(embeddings, feed_dict=feed_dict)
print('embedding:{}'.format(emb_array.shape))
return emb_array
返回的emb_array就是對映後人臉特徵的矩陣。至此,拿到人臉特徵矩陣後就能夠進行識別、分類等應用了。