在本文中,我將向你展示如何使用face-recognition.js執行可靠的人臉檢測和識別 。 我曾經試圖找一個能夠精確識別人臉的Node.js庫,但是沒有找到,因此,我決定自己搞一個!
這個npm包基於dlib實現,因為我發現dlib的識別精度很高。 dlib庫使用深度學習方法,並附帶一些預訓練的模型,這些預置的模型,在LFW人臉識別基准測試上可以達到驚人的准確度:99.38% 。
為什么要搞這個東西?
最近我一直在嘗試使用Node.js來構建一個人臉識別應用程序,以便從《生活大爆炸》劇中來提取和識別人物的面部。最初,我想用OpenCV的人臉識別器來實現,思路類似於我在教程Node.js + OpenCV for Face Recognition中的類似。
但是,雖然這些人臉識別器能夠提供快速的預測結果,但是我發現它們不夠健壯。 更確切地說,雖然他們適用於正面的人臉圖像,但只要人臉姿勢略有不同,就會產生相當不靠譜的預測結果。
因此,我就開始尋找替代方案,然后發現了dlib這個 C ++庫,我用它的Python API試了一下,結果給我留下了深刻的印象,最后我決定: 用Node.js來實現這個功能! 因此,我創建了這個npm包,提供一個簡化的Node.js 的API用於人臉識別。
face-recognition.js是什么?
我開發face-recognitiontion.js的目的,是想提供一個這樣的npm包:
- 有一個簡單的API,可以讓人快速上手人臉識別
- 如果需要的話仍然允許更細粒度的識別控制
- 易於設置(最好敲一行命令就能安裝,例如npm install)
雖然這個軟件包還在開發中,現在已經可以用它做點事情了:
人臉檢測
你可以使用深度神經網絡進行人臉檢測,也可以使用簡單的正面人臉識別器進行快速但但不那么可靠的檢測:
人臉識別
人臉識別器是一個深度神經網絡,它使用我提到的模型來計算一個獨特的人臉描述符。 這個人臉識別器用標注過的人臉圖像進行訓練后,就可以預測輸入人臉圖像的標簽:
人臉特征點檢測
你也可以使用這個包來檢測面部的特征點(5個或68個):
實戰效果!
好吧,正如我所說的,我開始時嘗試用OpenCV,但沒能完成這個任務。 現在我有一堆150x150大小的人臉圖像(分別來自Sheldon 、Raj 、 Lennard 、Howard和Stuart),我將向你展示使用這些數據來訓練人臉識別器、識別新面孔是多么的簡單。 這個例子的代碼可以在這個倉庫上找到。
准備數據
劇中每個角色不同的姿態分別采集大概20張圖像:
我們將分別使用每個人的10個圖像來訓練識別器,其余部分來評估識別器的准確性:
const path = require('path') const fs = require('fs') const fr = require('face-recognition') const dataPath = path.resolve('./data/faces') const classNames = ['sheldon', 'lennard', 'raj', 'howard', 'stuart'] const allFiles = fs.readdirSync(dataPath) const imagesByClass = classNames.map(c => allFiles .filter(f => f.includes(c)) .map(f => path.join(dataPath, f)) .map(fp => fr.loadImage(fp)) ) const numTrainingFaces = 10 const trainDataByClass = imagesByClass.map(imgs => imgs.slice(0, numTrainingFaces)) const testDataByClass = imagesByClass.map(imgs => imgs.slice(numTrainingFaces))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
每個臉部圖像的文件名中包含了人名,所以我們可以簡單地實現圖像和其分類的映射:
['sheldon', 'lennard', 'raj', 'howard', 'stuart']
- 1
你可以使用fr.loadImage(fp)讀取給定文件路徑的圖像。
檢測人臉
正如我所說的,圖像已經提取為150x150的大小,這是我事先用opencv4nodejs完成的。 但是你也可以用下面的代碼來檢測和提取人臉、保存並進行標注:
const image = fr.loadImage('image.png') const detector = fr.FaceDetector() const targetSize = 150 const faceImages = detector.detectFaces(image, targetSize) faceImages.forEach((img, i) => fr.saveImage(img, `face_${i}.png`))
- 1
- 2
- 3
- 4
- 5
訓練識別器
現在我們有了數據,可以開始訓練識別器:
const recognizer = fr.FaceRecognizer() trainDataByClass.forEach((faces, label) => { const name = classNames[label] recognizer.addFaces(faces, name) })
- 1
- 2
- 3
- 4
- 5
- 6
基本上這就是把每張人臉圖像輸入到神經網絡中,神經網絡輸出面部的描述符並存儲指定類別的所有描述符。 你還可以通過將numJitters指定為第三個參數來利用訓練數據生成一些抖動后的圖像,這個參數將應用旋轉 、縮放和鏡像等變換來創建每個輸入人臉的不同版本圖像。 增加抖動版本的數量可能有助於提高預測的准確性,但同時也會增加訓練時間。
此外,我們可以存儲識別器的狀態,這樣就不必在下次要用時重新進行訓練了,只需要簡單地從一個文件中加載訓練好的模型:
保存:
const modelState = recognizer.serialize() fs.writeFileSync('model.json', JSON.stringify(modelState))
- 1
- 2
加載:
const modelState = require('model.json') recognizer.load(modelState)
- 1
- 2
識別新面孔
現在我們可以用剩余的樣本數據來檢查預測精度並記錄結果:
const errors = classNames.map(_ => []) testDataByClass.forEach((faces, label) => { const name = classNames[label] console.log() console.log('testing %s', name) faces.forEach((face, i) => { const prediction = recognizer.predictBest(face) console.log('%s (%s)', prediction.className, prediction.distance) // count number of wrong classifications if (prediction.className !== name) { errors[label] = errors[label] + 1 } }) }) // print the result const result = classNames.map((className, label) => { const numTestFaces = testDataByClass[label].length const numCorrect = numTestFaces - errors[label].length const accuracy = parseInt((numCorrect / numTestFaces) * 10000) / 100 return `${className} ( ${accuracy}% ) : ${numCorrect} of ${numTestFaces} faces have been recognized correctly` }) console.log('result:') console.log(result)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
當前的預測是通過計算輸入人臉圖像的描述符向量到分類中每個描述符的歐式距離來完成的,同時也計算出所有距離的平均值 。 有人可能會說, kmeans聚類或SVM分類器將更適合這個任務,我也可能在將來實現這些算法。 但是現在使用歐幾里德距離的實現,看起來執行速度很快而且效率夠高。
調用predictionBest將輸出具有最小距離(例如最高相似度)的結果。 輸出看起來像這樣:
{className:'sheldon',距離:0.5}
- 1
如果你想獲得所有分類的臉部描述符到輸入的人臉圖像的距離,可以簡單地使用recognitionizer.predict(image) ,它將輸出一個數組,其成員包含輸入圖像到每個分類的距離:
[
{className:'sheldon',距離:0.5}, {className:'raj',距離:0.8}, {className:'howard',距離:0.7}, {className:'lennard',距離:0.69}, {className:'stuart',距離:0.75} ]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
結果
運行上面的例子將給出以下結果。
使用10個面孔進行訓練:
sheldon ( 90.9% ) : 10 of 11 faces have been recognized correctly lennard ( 100% ) : 12 of 12 faces have been recognized correctly raj ( 100% ) : 12 of 12 faces have been recognized correctly howard ( 100% ) : 12 of 12 faces have been recognized correctly stuart ( 100% ) : 3 of 3 faces have been recognized correctly
- 1
- 2
- 3
- 4
- 5
每個訓練只使用5個面孔 :
sheldon ( 100% ) : 16 of 16 faces have been recognized correctly lennard ( 88.23% ) : 15 of 17 faces have been recognized correctly raj ( 100% ) : 17 of 17 faces have been recognized correctly howard ( 100% ) : 17 of 17 faces have been recognized correctly stuart ( 87.5% ) : 7 of 8 faces have been recognized correctly
- 1
- 2
- 3
- 4
- 5
結論
從結果看,即使只用一小組數據進行訓練,也可以獲得相當准確的結果。甚至對這些我從網上抓取的小尺寸圖像(一些提取的臉部非常模糊), 結果也令人非常滿意。