作者 | Vincent Mühle
編譯 | 姍姍
出品 | 人工智能頭條(公眾號ID:AI_Thinker)
【導讀】隨着深度學習方法的應用,瀏覽器調用人臉識別技術已經得到了更廣泛的應用與提升。在實際過程中也具有其特有的優勢,通過集成與人臉檢測與識別相關的API,通過更為簡單的coding就可以實現。今天將為大家介紹一個用於人臉檢測、人臉識別和人臉特征檢測的 JavaScript API,通過在瀏覽器中利用 tensorflow.js 進行人臉檢測和人臉識別。大家不僅可以更快速學習這個,對有人臉識別技術需求的 JS 開發者來說更是一件值得開心的事。
▌前言
對於 JS 開發者來說這將是一件很開心的事,那就是終於可以在瀏覽器中進行人臉識別了!通過接下來的這篇文章,將為大家介紹 face-api.js,一個構建在 tensorflow.js core 上的 javascript 模塊,實現了人臉檢測、人臉識別和人臉特征檢測三種 CNNs (卷積神經網絡)。
我們將通過研究一個簡單的代碼示例,只用幾行代碼就可以試着使用這個包。
▌第一個人臉識別包 face-recognition.js,現在又來了一個包?
如果讀過我的另一篇關於人臉識別的文章 Node.js + face-recognition.js : Simple and Robust Face Recognition using Deep Learning,你可能會了解到在不久前,我組裝了一個類似的包, face-recognition.js,用 nodejs 來進行人臉識別。
起初,我沒有想到在 javascript 社區對人臉識別包的需求會如此之高。對很多人來說,face-recognition.js 就像微軟或亞馬遜所提供的,似乎是一個不錯的可免費使用且開源的替代付費服務的人臉識別服務。但我經常也會被問到一個問題,在瀏覽器中是或否可以完全運行完整的人臉識別管道。
對此要感謝 tensorflow.js !我使用 tfjs-core 實現了部分類似的工具,得到與 face-recognition.js 幾乎相同的結果,但,是在瀏覽器中實現的!而最棒的一點是,它不需要設置任何外部依賴關系,就可以直接使用。還有一個意外的獎勵 —— 在 WebGL 上運行操作 ,GPU 的加速。
這足以讓我相信,javascript 社區需要這樣一個包!這也將留給你們足夠的想象空間,你們可以用它來構建各種各樣的應用。
▌如何用深度學習解決人臉識別問題
如果你是希望盡快開始,你可以跳過這一部分,直接跳到編碼中。但是為了更好地理解 face-api.js 使用的方法。要實現人臉識別,強烈建議參與一起學習,因為我經常會被問到這個問題。
簡單來說,我們實際上想要實現的是,識別給出的一個人的面部圖像,用作輸入圖像(input image)。
我們的方法是,給出想識別的那個人的一張或多張圖片,並給此人的名字打上標簽,用作參考數據(reference data)。現在將輸入圖片與引用數據進行對比並找出最相似的參考圖片。如果兩個圖像足夠相似,我們將會輸出此人的名字,否則我們輸出結果為 “unknow”。
聽起來不錯吧!然而這其中還存在兩個問題。首先,如果一張照片中有多人並且我們想把所有人都識別出來該怎么辦?其次,我們需要能夠計算出兩張人臉圖像的相似度度量,以便比較它們。
▌人臉檢測
對於第一個問題的答案是通過人臉檢測來解決。簡單地說,我們首先定位輸入圖像中的所有面孔。人臉檢測,face-api.js 實現了一個 SSD 算法,它基本上是基於 MobileNetV1 的 CNN,在網絡的頂部有一些額外的盒預測層。
網絡返回每張面孔的邊界框與相應的分數,即顯示面孔的每個邊界框的概率。這些分數用於篩選邊界區域,因為圖像中可能根本不包含任何面孔。注意,即使只有一個人要檢索邊界框,人臉檢測也應該執行。
▌人臉特征檢測和人臉對齊
第一個問題解決了!但是,我們想要對齊邊界框,這樣我們就可以在傳遞給人臉識別網絡之前,在每個區域的人臉中心提取出圖像,這將使人臉識別更加准確!
為此 face-api.js 實現了一個簡單的 CNN 網絡,此網絡返回給定人臉圖像的 68 個點的面部特征。
根據特征點的位置,邊界區域可以集中在面部中心。在下圖中你可以看到人臉檢測的結果(左)與對齊的人臉圖像(右)
▌人臉識別
現在我們可以將提取和對齊的人臉圖像輸入到人臉識別網絡中,該網絡是基於類似 ResNet-34 的架構,基本上對應於 dlib 中實現的架構。該網絡已經被訓練學習將人臉的特征映射到人臉描述符(一個有128個值的矢量)中,通常也被稱為人臉嵌入。
現在回到比較兩個人臉時的原始問題:我們將使用提取的每張人臉圖像的描述符,並將它們與參考數據的人臉描述符進行比較。更准確地說,我們可以計算兩張人臉描述符之間的歐式距離,根據閾值判斷兩個人臉是否相似(對於 150×150 幅人臉圖像,0.6 是一個很好的閾值)。使用歐幾里得距離方法非常有效,當然你也可以選擇任意類型的分類器。下面的 gif 圖像例子就是通過歐幾里得距離來比較的兩張人臉圖像:
在學過了人臉識別的理論之后,我們開始 coding ~~
▌編碼
在這個簡短的示例中,我們將逐步看到如何在下面這張多人的輸入圖像上進行人臉識別:
▌腳本
首先,從 dist / face - api .js上或者 dist/face-ap.min.js 的minifed版本中獲取 latest build ,包括腳本:
<script src="face-api.js"></script>
如果用 npm :
npm i face-api.js
▌加載模型數據
根據你的應用需求,可以專門加載需要的模型,但是要運行一個完整的端到端示例,我們需要加載人臉檢測、人臉特征檢測和人臉識別這三個模型。模型文件在 repo 上可用,可在此找到。
鏈接:
https://github.com/justadudewhohacks/faceapi.js/tree/master/weights
和原始模型相比,已經量化了模型的權重,模型文件大小減少了75%,以允許客戶端只加載所需要的最小數據。此外,模型的權重被分割成最大 4 MB的組塊,使瀏覽器可以緩存這些文件,這樣只需要加載一次。
模型文件可以作為靜態資源來提供在給 web 應用程序,也可以在其他地方托管它們,通過指定文件的路徑或 url 來加載它們。假設在 public/models 下一個模型目錄中提供它們。
const MODEL_URL = '/models'await faceapi.loadModels(MODEL_URL)
如果你只想加載特殊模塊:
const MODEL_URL = '/models'
await faceapi.loadFaceDetectionModel(MODEL_URL)
await faceapi.loadFaceLandmarkModel(MODEL_URL)
await faceapi.loadFaceRecognitionModel(MODEL_URL)
▌從輸入圖像接收所有面孔的完整描述
神經網絡接受 HTML 圖像、畫布或視頻元素作為輸入。簡單地說,要檢測輸入的人臉的邊界,只需使 Score > minScore
const minConfidence = 0.8const fullFaceDescriptions = await faceapi.allFaces(input, minConfidence)
完整的臉部描述包含檢測結果(邊界框+分數)、臉部特征和計算描述符。faceapi.js 做了之前討論所有的問題。也可以手動獲取人臉位置和特征。github repo上有這樣的示例。
注意,邊界和特征與原始圖像/媒體大小相關。如果顯示的圖像大小與原始圖像大小不一致,可以調整它們的大小:
const resized = fullFaceDescriptions.map(fd => fd.forSize(width, height))
可以通過畫出邊界區域來可視化檢測結果
fullFaceDescription.forEach((fd, i) => { faceapi.drawDetection(canvas, fd.detection, { withScore: true })})
人間特征按照下面的方法顯示出來
fullFaceDescription.forEach((fd, i) => {
faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true })
})
▌人臉識別
知道了如何在輸入圖像中檢索所有人臉的位置和描述符,我們將得到一些分別顯示一個人的圖像,並計算人臉描述符。這些描述符就是我們的參考數據。
假設我們有一些示例圖像,我們首先用 url 獲取圖像。
然后使用 faceapi.bufferToImage 從數據緩存區中創建 HTML 圖像元素:
// fetch images from url as blobsconst blobs = await Promise.all( ['sheldon.png' 'raj.png', 'leonard.png', 'howard.png'].map( uri => (await fetch(uri)).blob() ))// convert blobs (buffers) to HTMLImage elementsconst images = await Promise.all(blobs.map( blob => await faceapi.bufferToImage(blob)))
接下來,對於每個圖像,定位被試的臉並計算面部描述符,就像之前對輸入圖像所做的處理那樣:
const refDescriptions = await Promsie.all(images.map(
img => (await faceapi.allFaces(img))[0]
))
const refDescriptors = refDescriptions.map(fd => fd.descriptor)
現在,剩下要做的就是循環遍歷輸入圖像的人臉描述,找到與參考數據歐式距離最小的描述符:
const sortAsc = (a, b) => a - bconst labels = ['sheldon', 'raj', 'leonard', 'howard']const results = fullFaceDescription.map((fd, i) => { const bestMatch = refDescriptors.map( refDesc => ({ label: labels[i], distance: faceapi.euclideanDistance(fd.descriptor, refDesc) }) ).sort(sortAsc)[0] return { detection: fd.detection, label: bestMatch.label, distance: bestMatch.distance }})
正如前面講到的,在這里用效果很好的歐氏距離作為相似度度量。最終得到了在輸入圖像中每個人的最佳匹配。
最后可以將邊界框和它們的標簽一起畫出來並顯示結果:
// 0.6 is a good distance threshold value to judge
// whether the descriptors match or not
const maxDistance = 0.6
results.forEach(result => {
faceapi.drawDetection(canvas, result.detection, { withScore: false })
const text = `${result.distance < maxDistance ? result.className : 'unkown'} (${result.distance})`
const { x, y, height: boxHeight } = detection.getBox()
faceapi.drawText(
canvas.getContext('2d'),
x,
y + boxHeight,
text
)
})
這就是這次的學習之旅!至此希望大家已經學會如何使用這個 api,並且建議大家看一下 repo 中的其他示例。
——【完】——