face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

張倩發表於2018-07-16

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

號外!號外!現在人們終於可以在瀏覽器中進行人臉識別了!本文將為大家介紹「face-api.js」,這是一個建立在「tensorflow.js」核心上的 javascript 模組,它實現了三種卷積神經網路(CNN)架構,用於完成人臉檢測、識別和特徵點檢測任務。

  • face-api.js:https://github.com/justadudewhohacks/face-api.js

  • TensorFlow.js:https://github.com/tensorflow/tfjs-core

像往常一樣,我們將檢視一個簡單的程式碼示例,這將使你能立即通過短短几行程式碼中的程式包開始瞭解這個 API。讓我們開始吧!

我們已經有了「face-recognition.js」,現在又來了另一個同樣的程式包?

如果你閱讀過本文作者另一篇關於「node.js」環境下進行人臉識別的文章《Node.js + face-recognition.js : Simple and Robust Face Recognition using Deep Learning》(Node.js + face-recognition.js:通過深度學習實現簡單而魯棒的人臉識別)(https://medium.com/@muehler.v/node-js-face-recognition-js-simple-and-robust-face-recognition-using-deep-learning-ea5ba8e852),你就會知道他在之前組裝過一個類似的程式包,例如「face-recgnition.js」,從而為「node.js」引入了人臉識別功能。

起初,作者並沒有預見到 JavaScript 社群對與人臉識別程式包的需求程度如此之高。對許多人而言,「face-recognition.js」似乎是一個不錯的、能夠免費試用的開源選項,它可以替代由微軟或亞馬遜等公司提供的付費人臉識別服務。但是作者曾多次被問道:是否有可能在瀏覽器中執行完整的人臉識別的工作流水線?

多虧了「tensorflow.js」,這種設想最終變為了現實!作者設法使用「tf.js

」核心實現了部分類似的工具,它們能得到和「face-recognition.js」幾乎相同的結果,但是作者是在瀏覽器中完成的這項工作!而且最棒的是,這套工具不需要建立任何的外部依賴,使用它非常方便。並且這套工具還能通過 GPU 進行加速,相關操作可以使用 WebGL 執行。

這足以讓我相信,JavaScript 社群需要這樣的一個為瀏覽器環境而編寫的程式包!可以設想一下你能通過它構建何種應用程式。

如何利用深度學習解決人臉識別問題

如果想要儘快開始實戰部分,那麼你可以跳過這一章,直接跳到程式碼分析部分去。但是為了更好地理解「face-api.js」中為了實現人臉識別所使用的方法,我強烈建議你順著這個章節閱讀下去,因為我常常被人們問到這個問題。

為簡單起見,我們實際想要實現的目標是在給定一張人臉的影象時,識別出影象中的人。為了實現這個目標,我們需要為每一個我們想要識別的人提供一張(或更多)他們的人臉影象,並且給這些影象打上人臉主人姓名的標籤作為參考資料。現在,我們將輸入影象和參考資料進行對比,找到與輸入影象最相似的參考影象。如果有兩張影象都與輸入足夠相似,那麼我們輸出人名,否則輸出「unknown」(未知)。

聽起來確實是個好主意!然而,這個方案仍然存在兩個問題。首先,如果我們有一張顯示了多人的影象,並且我們需要識別出其中所有的人,將會怎樣呢?其次,我們需要建立一種相似度度量手段,用來比較兩張人臉影象。

人臉檢測

我們可以從人臉檢測技術中找到第一個問題的答案。簡單地說,我們將首先定位輸入影象中的所有人臉。「face-api.js」針對人臉檢測工作實現了一個 SSD(Single Shot Multibox Detector)演算法,它本質上是一個基於 MobileNetV1 的卷積神經網路(CNN),在網路的頂層加入了一些人臉邊框預測層。

該網路將返回每張人臉的邊界框,並返回每個邊框相應的分數,即每個邊界框表示一張人臉的概率。這些分數被用於過濾邊界框,因為可能存在一張圖片並不包含任何一張人臉的情況。請注意,為了對邊界框進行檢索,即使影象中僅僅只有一個人,也應該執行人臉檢測過程。

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

人臉特徵點檢測及人臉對齊

在上文中,我們已經解決了第一個問題!然而,我想要指出的是,我們需要對齊邊界框,從而抽取出每個邊界框中的人臉居中的影象,接著將其作為輸入傳給人臉識別網路,因為這樣可以使人臉識別更加準確!

為了實現這個目標,「face-api.js」實現了一個簡單的卷積神經網路(CNN),它將返回給定影象的 68 個人臉特徵點:

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

從特徵點位置上看,邊界框可以將人臉居中。你可以從下圖中看到人臉檢測結果(左圖)與對齊後的人臉影象(右圖)的對比:

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

人臉識別

現在,我們可以將提取出的對齊後的人臉影象輸入到人臉識別網路中,該網路基於一個類似於 ResNet-34 的架構,基本上與 dlib(https://github.com/davisking/dlib/blob/master/examples/dnn_face_recognition_ex.cpp)中實現的架構一致。該網路已經被訓練去學習出人臉特徵到人臉描述符的對映(一個包含 128 個值的特徵向量),這個過程通常也被稱為人臉嵌入。

現在讓我們回到最初對比兩張人臉影象的問題:我們將使用每張抽取出的人臉影象的人臉描述符,並且將它們與參考資料的人臉描述符進行對比。更確切地說,我們可以計算兩個人臉描述符之間的歐氏距離,並根據閾值判斷兩張人臉影象是否相似(對於 150*150 的影象來說,0.6 是一個很好的閾值)。使用歐氏距離的效果驚人的好,當然,你也可以選用任何一種分類器。下面的 gif 動圖視覺化了通過歐氏距離比較兩張人臉影象的過程:face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

至此,我們已經對人臉識別的理論有所瞭解。接下來讓我們開始編寫一個程式碼示例。

是時候開始程式設計了!

在這個簡短的示例中,我們將看到如何一步步地執行人臉識別程式,識別出如下所示的輸入影象中的多個人物:

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

匯入指令碼

首先,從 dist/face-api.js 獲得最新的版本(https://github.com/justadudewhohacks/face-api.js/tree/master/dist),或者從 dist/face-api.min.js 獲得縮減版,並且匯入指令碼:

<script src="face-api.js"></script>

如果你使用 npm 包管理工具,可以輸入如下指令:

npm i face-api.js

載入模型資料

你可以根據應用程式的要求載入你需要的特定模型。但是如果要執行一個完整的端到端的示例,我們還需要載入人臉檢測、人臉特徵點檢測和人臉識別模型。相關的模型檔案可以在程式碼倉庫中找到,連結如下:https://github.com/justadudewhohacks/face-api.js/tree/master/weights。

其中,模型的權重已經被量化,檔案大小相對於初始模型減小了 75%,使你的客戶端僅僅需要載入所需的最少的資料。此外,模型的權重被分到了最大為 4 MB 的資料塊中,使瀏覽器能夠快取這些檔案,這樣它們就只需要被載入一次。

模型檔案可以直接作為你的 web 應用中的靜態資源被使用,或者你可以將它們存放在另外的主機上,通過指定的路徑或檔案的 url 連結來載入。假如你將它們與你在 public/models 資料夾下的資產共同存放在一個 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 影象、畫布、視訊元素或張量(tensor)作為輸入。為了檢測出輸入影象中分數(score)大於最小閾值(minScore)的人臉邊界框,我們可以使用下面的簡單操作:

const minConfidence = 0.8
const fullFaceDescriptions = await faceapi.allFaces(input, minConfidence)

一個完整的人臉描述符包含了檢測結果(邊界框+分數),人臉特徵點以及計算出的描述符。正如你所看到的,「faceapi.allFaces」在底層完成了本文前面的章節所討論的所有工作。然而,你也可以手動地獲取人臉定位和特徵點。如果這是你的目的,你可以參考 github repo 中的幾個示例。

請注意,邊界框和特徵點的位置與原始影象/媒體檔案的尺寸有關。當顯示出的影象尺寸與原始影象的尺寸不相符時,你可以簡單地通過下面的方法重新調整它們的大小:

const resized = fullFaceDescriptions.map(fd => fd.forSize(width, height))

我們可以通過將邊界框在畫布上繪製出來對檢測結果進行視覺化:

fullFaceDescription.forEach((fd, i) => {
  faceapi.drawDetection(canvas, fd.detection, { withScore: true })
})

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

可以通過下面的方法將人臉特徵點顯示出來:

fullFaceDescription.forEach((fd, i) => {
  faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true })
})

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

通常,我會在 img 元素的頂層覆蓋一個具有相同寬度和高度的絕對定位的畫布(想獲取更多資訊,請參閱 github 上的示例)。

人臉識別

當我們知道了如何得到給定的影象中所有人臉的位置和描述符後,我們將得到一些每張圖片顯示一個人的影象,並且計算出它們的人臉描述符。這些描述符將作為我們的參考資料。

假設我們有一些可以用的示例圖片,我們首先從一個 url 連結處獲取圖片,然後使用「faceapi.bufferToImage」從它們的資料快取中建立 HTML 影象元素:

// fetch images from url as blobs
const blobs = await Promise.all(
  ['sheldon.png' 'raj.png', 'leonard.png', 'howard.png'].map(
    uri => (await fetch(uri)).blob()
  )
)

// convert blobs (buffers) to HTMLImage elements
const 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 - b
const 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
  )
})

face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

至此,我希望你對如何使用這個 API 有了一個初步的認識。同時,我也建議你看看文中給出的程式碼倉庫中的其它示例。好好地把這個程式包玩個痛快吧!face-api.js:一個在瀏覽器中進行人臉識別的 JavaScript 介面

原文連結:https://itnext.io/face-api-js-javascript-api-for-face-recognition-in-the-browser-with-tensorflow-js-bcc2a6c4cf07

相關文章