前言
水了幾篇不痛不癢的博客,于是決定把mtcnn的原理記錄下,分享給想要學(xué)習(xí)人臉識(shí)別,但是很糾結(jié)如何開(kāi)始的人。網(wǎng)上關(guān)于mtcnn的教程大同小異,不同不癢,認(rèn)真看完此文,如果還不會(huì)mtcnn,那么請(qǐng)你來(lái)掐死我!
來(lái)看看效果
關(guān)于檢測(cè)
檢測(cè),顧名思義就是找到我們需要的物體,并且標(biāo)注他在圖像中的位置。
- 從mnist數(shù)據(jù)集開(kāi)始
-
大多數(shù)朋友上有的第一個(gè)數(shù)據(jù)集,有著深度學(xué)習(xí)的hello world之稱!我們先看看數(shù)據(jù)集的樣子:
經(jīng)過(guò)我處理后可視化的mnist數(shù)據(jù)集的一部分今天不講如何處理mnist,稍微提一下,我們把每張有數(shù)字的圖片放到神經(jīng)網(wǎng)絡(luò)中,然后會(huì)輸出一個(gè)結(jié)果告訴我們這張圖片是數(shù)字幾,這樣就做到了物體的識(shí)別
同樣地,我們可以把0~9的數(shù)字圖片換成其他,例如貓狗,那我們就可以訓(xùn)練得到區(qū)分貓還是狗的神經(jīng)網(wǎng)絡(luò)。
-
import tensorflow as tf
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
print(train_images.shape) # 6000,28,28,1
- 出現(xiàn)問(wèn)題
貌似我們可以用這個(gè)思路區(qū)分其他的物體,可是,看mnist數(shù)據(jù)集的圖片我們發(fā)現(xiàn),每張圖片只有一個(gè)數(shù)字,也就是,我們訓(xùn)練好的網(wǎng)絡(luò),將來(lái)也只能識(shí)別單個(gè)物體,如果要識(shí)別的圖片有兩個(gè)數(shù)字,我們需要向辦法將兩個(gè)數(shù)字分開(kāi),分別輸入到網(wǎng)絡(luò)進(jìn)行預(yù)測(cè)。那我們有沒(méi)有辦法讓我們訓(xùn)練的網(wǎng)絡(luò)一次可以識(shí)別多個(gè)物體呢? - 解決
滑動(dòng)窗口
先看張圖片:
如果直接把圖片輸入神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)可行嗎?
顯然,大多數(shù)情況下是不可以直接傳入神經(jīng)網(wǎng)絡(luò)獲得預(yù)測(cè)結(jié)果的。問(wèn)題解決了嗎?
我們可以看到,小動(dòng)物的頭我們可以找到,可是小女孩的頭部被分成了兩張圖片了,顯然這樣子我們找不到小女孩。此時(shí),我們可以利用滑動(dòng)窗口的方法最左上角的紅色框框就是我們選定的框框,我們先把紅色框框的圖片截取下來(lái)傳入神經(jīng)網(wǎng)絡(luò)中預(yù)測(cè),然后我們把紅色框框向右平移,我們得到粉紅色的框框,然后我們?cè)侔逊奂t色框框的圖片截取出來(lái)送到網(wǎng)絡(luò)中預(yù)測(cè),以此類推,我們每次將框框向右移動(dòng)一定的長(zhǎng)度,然后獲得下一個(gè)框框,當(dāng)框框移動(dòng)到第一行的最后時(shí)候,也就是和黑色框框重合的時(shí)候,第一行我們?nèi)⊥炅耍缓髮⒖蚩?strong>向下平移,此時(shí)當(dāng)作第二行,簡(jiǎn)單總結(jié)就是將框框按照一定的長(zhǎng)度平移,遍歷整張圖片
此刻,我們可也找到小女孩和小動(dòng)物的位置。
我們暫且叫這個(gè)方法框框滑動(dòng)理論。
我們來(lái)看看這個(gè)方法有什么有缺點(diǎn):
優(yōu)點(diǎn)是可以找到一幅圖像的多個(gè)物體,還能找到大致位置,缺點(diǎn)也很明顯,越是想要找到多的物體,那么框框移動(dòng)的距離就要小,當(dāng)移動(dòng)的距離越小,那么圖片越多,識(shí)別的效率就降低。
MTCNN
我們看了一下單個(gè)物體的檢測(cè)和多個(gè)物體的檢測(cè),接下來(lái)是有關(guān)人臉的檢測(cè)建議看看原論文
- MTCNN由三個(gè)級(jí)聯(lián)的網(wǎng)絡(luò)組成,分別是PNET,RNET,ONET
圖片經(jīng)過(guò)預(yù)處理,先經(jīng)過(guò)pnet網(wǎng)絡(luò),將結(jié)果給rnet網(wǎng)絡(luò),rnet網(wǎng)絡(luò)的輸出再傳入onet網(wǎng)絡(luò),onet最后得到輸出結(jié)果
- Pnet
pnet網(wǎng)絡(luò)結(jié)構(gòu) - 小朋友你現(xiàn)在是否有很多問(wèn)號(hào)
- 12x12不會(huì)太小了嗎?我的臉肯定比12x12大呀QvQ
別急,聽(tīng)我娓娓道來(lái)。
論文對(duì)要進(jìn)行檢測(cè)的圖像做了圖像金字塔
大家在腦海里想一想金字塔長(zhǎng)什么樣子,尖尖的,對(duì)吧!其實(shí)就是將圖像進(jìn)行一定比例,一系列的縮放。。。
(瞬間low了不少)
縮放的極限是到12x12,再小下去的話,框框都比圖像大了,哪怕你再大的頭,總會(huì)有個(gè)縮放比例,把你的頭變小的,此刻應(yīng)該有一波掌聲,原論文就是妙.不妨給我點(diǎn)個(gè)小贊,加個(gè)關(guān)注
下面我將驗(yàn)證我的框框滑動(dòng)理論,將有一大波代碼來(lái)襲,請(qǐng)注意—_—
import cv2 # opencv庫(kù)
import tensorflow as tf
import numpy as np
下面是網(wǎng)絡(luò)結(jié)構(gòu)
def Pnet():
input = tf.keras.Input(shape=[None, None, 3])
x = tf.keras.layers.Conv2D(10, (3, 3), strides=1, padding='valid', name='conv1')(input)
x = tf.keras.layers.PReLU(shared_axes=[1, 2], name='PReLU1')(x)
x = tf.keras.layers.MaxPooling2D()(x)
x = tf.keras.layers.Conv2D(16, (3, 3), strides=1, padding='valid', name='conv2')(x)
x = tf.keras.layers.PReLU(shared_axes=[1, 2], name='PReLU2')(x)
x = tf.keras.layers.Conv2D(32, (3, 3), strides=1, padding='valid', name='conv3')(x)
x = tf.keras.layers.PReLU(shared_axes=[1, 2], name='PReLU3')(x)
classifier = tf.keras.layers.Conv2D(2, (1, 1), activation='softmax',name='conv4-1')(x)
bbox_regress = tf.keras.layers.Conv2D(4, (1, 1), name='conv4-2')(x)
model = tf.keras.models.Model([input], [classifier, bbox_regress])
model.summary()
return model
model = Pnet() # 讀取網(wǎng)絡(luò)結(jié)構(gòu)
model.load_weights("./pnet.h5", by_name=True) # 讀取預(yù)訓(xùn)練權(quán)重,此步驟可以省略
講講我的思路,我將準(zhǔn)備一張12x12x3大小的人臉圖片傳入網(wǎng)絡(luò),看看輸出結(jié)果,之后,我在這張圖片的最后邊添加兩列大小全為255的像素點(diǎn),底下也添加兩行,此時(shí)圖片變成14x14
img = cv2.imread("./face1.jpg") # 用opencv的方法把圖像讀取進(jìn)來(lái)
img = cv2.cvtColor(img, cv2.COLOR_BRG2RGB)
img = (img-127.5)/127.5 # 歸一化,不展開(kāi)
img = img.reshape(1, *img.shape) # shape=1x12x12x3 只有四維才符合輸入格式
- 圖像處理完畢,接下來(lái)傳入網(wǎng)絡(luò)進(jìn)行預(yù)測(cè),我們看看會(huì)得到什么結(jié)果
out = model.predict(img)
對(duì)于最后的Facial landmark, pnet代碼中的代碼中沒(méi)有體現(xiàn),所以就沒(méi)有輸出結(jié)果。
如果沒(méi)有看明白,可以聯(lián)系我,討論學(xué)習(xí)。
- 接下來(lái)讓我把圖片處理一下,變成14x14大小
14x14x3
結(jié)果看著有點(diǎn)小復(fù)雜,不荒,我們研究一下:
同樣只有兩個(gè)array,第一個(gè)array,里面是4x2的矩陣,第二個(gè)array是4x4的矩陣
為什么是4x2和4x4?
- 當(dāng)我們輸入的大小是12x12的時(shí)候,輸出是1x2和1x4
- 當(dāng)我們輸入的大學(xué)是14x14的時(shí)候,輸出是4x2和4x4
由此猜測(cè),當(dāng)輸入16x16的時(shí)候,輸出結(jié)果是9x2和9x4,感興趣可以試試,一定是這個(gè)結(jié)果或者說(shuō)應(yīng)該是3x3x2和3x3x4
- 所以,14x14,輸出大小準(zhǔn)確點(diǎn)應(yīng)該是2x2x2和2x2x4。
(有點(diǎn)小復(fù)雜)
畫(huà)重點(diǎn)
也就是說(shuō),14x14的圖片被分成了4張12x12的小圖片被傳入了網(wǎng)絡(luò),得到的結(jié)果在組合起來(lái),因?yàn)榭蚩虻拇笮∈?2x12,所以可以去到左上角一張圖片右上角一張圖片,左下角和右下角的圖片,有4張,并且位置對(duì)應(yīng)2x2,所以才有這個(gè)結(jié)果
有點(diǎn)饒,多看幾次
換個(gè)角度
我上面已經(jīng)得到了14x14的圖片,我安下面方式截取四張圖片也就是對(duì)應(yīng)于原圖的左上右上左下右下各取12x12大小出來(lái),依次傳入網(wǎng)絡(luò)中
右下
右下
下面我放一下直接傳入14x14的原圖的輸出結(jié)果:自行比對(duì)一下,我不用多說(shuō)
小結(jié)
通過(guò)這種敘事方式,我還沒(méi)有看到敘事得比我詳細(xì)的mtcnn講解,限于篇幅原因,我不會(huì)放大量代碼,也只是講一下我當(dāng)時(shí)最難以理解的地方,后面會(huì)考慮出源碼的解析(當(dāng)然是我自己重構(gòu)后的代碼),剩余部分我會(huì)后面更新完,不過(guò)我覺(jué)得到這里已經(jīng)茶并不多了,后面大同小異,最難的部分已經(jīng)過(guò)去了。
如果覺(jué)得對(duì)你有幫助,可以給我點(diǎn)個(gè)贊,能幫到你我很開(kāi)心,如果有任何疑問(wèn),可以留言,也可以和我聯(lián)系。
加油,有緣人!