簡單實現一個虛擬形象系統

前言

上週啓動居家開會的時候,看到有人通過「虛擬形象」功能,給自己帶上了口罩、眼鏡之類,於是想到了是不是也可以搞一個簡單的虛擬形象系統。

大致想來,分爲以下幾個部分:

卷積神經網絡 (CNN)

下面講解一下三層 CNN 網絡模型:

卷積層——提取特徵

卷積層的運算過程如下圖,用一個卷積核掃完整張圖片:

通過動圖能夠更好的理解卷積過程,使用一個卷積核(過濾器)來過濾圖像的各個小區域,從而得到這些小區域的特徵值。

在具體應用中,往往有多個卷積核,每個卷積核代表了一種圖像模式(特徵規則),如果某個圖像塊與此卷積核卷積出的值大,則認爲此圖像塊十分接近於此卷積核。如果有 N 個卷積核,那麼就認爲圖像中有 N 種底層紋理(特徵),即用這 N 種基礎紋理就能描繪出一副圖像。

總結: 卷積 層的通過卷積核的過濾提取出圖片中局部的特徵。

疑問:上圖卷積後,存在邊緣數據特徵提取減少,大家能想到什麼方式處理呢?

池化層(下采樣)——數據降維,避免過擬合

池化層通常也被叫做下采樣,目的是降低數據的維度,減少數據處理量。其過程大致如下:

上圖輸入時是 20×20 的,先進行卷積採樣,卷積核爲 10×10,採用最大池化的方式,輸出爲一個 2×2 大小的特徵圖。這樣可將數據維度減少了 10 倍,方便後續模塊處理。

總結:池化層相比 卷積 層可以更有效的降低數據維度,不僅可減少運算量,還可以避免 過擬合

過擬合是指訓練誤差和測試誤差之間的差距太大。換句換說,就是模型複雜度高於實際問題,模型在訓練集上表現很好,但在測試集上卻表現很差。模型對訓練集 "死記硬背"(記住了不適用於測試集的訓練集性質或特點),沒有理解數據背後的規律,泛化能力差。

全連接層——輸出結果

全鏈接層是將我們最後一個池化層的輸出連接到最終的輸出節點上。假設,上述 CNN 的最後一個池化層的輸出大小爲 [5×5×4],即 5×5×4=100 個節點。對於當前任務(僅識別🐱、🐶、🐍),我們的輸出會是一個三維向量,輸出層共 3 個節點,如輸出 [0.89, 0.1, 0.001],表示 0.89 的概率爲貓。在實際應用中,通常全連接層的節點數會逐層遞減,最終變爲 n 維向量。

舉個例子🌰

假設我們有 2 個檢測的特徵爲「水平邊緣」和「垂直邊緣」。「垂直邊緣」卷積過程如下:

最終結果如下:

Q&A 環節

沒錯啦,前面的問題的答案就是邊緣填充。

face-api.js

face-api.js 是基於 tensorflow.js 實現的,內置了一些訓練好的模型,這些模型應該是這個方案的核心,通過這些預先訓練好的模型,我們可以直接使用而不需要自己再去標註、訓練,極大的降低了成本。

主要提供的功能如下:

人臉檢測

針對人臉檢測,face-api 提供了 SSD Mobilenet V1 和 The Tiny Face Detector 兩個人臉檢測模型:

人臉特徵檢測

針對人臉特徵檢測, 提供了 68 點人臉特徵檢測模型,檢測這 68 個點的作用是爲了後續的人臉對齊,爲後續人臉識別做準備,這裏提供了兩個大小的模型供選擇:350kb 和 80kb,大的模型肯定是更準確,小的模型適合對精確度要求不高,對資源要求佔用不高的場景。其輸出的區域特徵點區間固定如下:

dgXvDD

人臉識別

經過人臉檢測以及人臉對齊以後,將檢測到的人臉輸入到人臉識別網絡進行識別,從而獲得一個 128 維的人臉特徵向量。通過計算兩個向量之間的距離(餘弦值),就可以判斷相似度。

虛擬形象系統

獲取人臉圖像

目前主流瀏覽器提供了 WebRTC 能力,我們可以調用getUserMedia方法指定設備採集音視頻數據。其中constrains詳情參考 MediaTrackConstraints - Web APIs | MDN[1]。

const constraints = { audio: true, video: { width: 1280, height: 720 } };
const setLocalMediaStream = (mediaStream: MediaStream) ={
    videoRef.current.srcObject = mediaStream;
}
navigator
    .mediaDevices
    .getUserMedia(constraints)
    .then(setLocalMediaStream)

獲取人臉特徵

根據官方文檔介紹,The Tiny Face Detector模型與人臉特徵識別模型組合的效果更好,故本文使用的人臉檢測模型是The Tiny Face Detector

這個模型有兩個參數可以調整,包括 inputSizescoreThreshold,默認值是 416 和 0.5。

  1. 首先我們要選擇並加載模型(這裏使用官網訓練好的模型和權重參數)
// 加載人臉檢測模型
await faceApi.nets.tinyFaceDetector.loadFromUri(
    'xxx/weights/',
);
// 加載特徵檢測模型
await faceApi.nets.faceLandmark68Net.loadFromUri(
    'xxx/weights/',
);
  1. 轉換人臉檢測模型。face-api 的人臉檢測模型默認是 SSD Mobilenet v1,這裏需要顯式調整爲The Tiny Face Detector模型。
const options = new faceApi.TinyFaceDetectorOptions({
  inputSize,
  scoreThreshold,
});

// 人臉68點位特徵集
const result = await faceApi
  .detectSingleFace(videoEl, options) // 人臉檢測
  .withFaceLandmarks(); // 特徵檢測

形象繪製

經過上述計算,我們已經拿到了人臉 68 點位特徵集。需要先計算點位相對座標信息,然後進行形象繪製。

const canvas = canvasRef.current;
const canvasCtx = canvas.getContext('2d');
const dims = faceApi.matchDimensions(canvas, videoEl, true);
const resizedResult = faceApi.resizeResults(result, dims);

本文使用的是一張 256*256 的口罩圖片,選取 1 號和 16 號點位繪製口罩,根據兩點位之間的距離縮放口罩大小。

這裏主要調研了兩種方式,分別是 canvas 繪製和媒體流繪製。

canvas 繪製

首先想到的一種方式,video 和 canvas 大小和位置固定,定時抓取 video 媒體流中圖片,進行識別人臉,然後繪製在 canvas 上。

const { positions } = resizedResult.landmarks;
const leftPoint = positions[0];
const rightPoint = positions[16];
const length = Math.sqrt(
    Math.pow(leftPoint.x - rightPoint.x, 2) +
      Math.pow(leftPoint.y - rightPoint.y, 2),
);
canvasCtx?.drawImage(
    mask,
    0,
    0,
    265,
    265,
    leftPoint.x,
    leftPoint.y,
    length,
    length,
);

媒體流繪製

canvas 提供了一個 api 叫做 captureStream[2],會返回一個繼承 MediaStream 的實例,實時視頻捕獲畫布上的內容(媒體流)。我們可以在 canvas 上以固定幀率進行圖像繪製,獲取視頻軌道。

這樣我們僅需保證 video 和 canvas 大小一致,位置無需固定,甚至 canvas 可以離屏不渲染。

const stream = canvasRef.current.captureStream()!;
  mediaStream = res[0].clone();
  mediaStream.addTrack(stream.getVideoTracks()[0]);
  videoRef.current!.srcObject = mediaStream;

對比

實際效果

因爲這裏僅使用了 2 個點位的信息,所以效果一般般。我們完全可以充分利用 68 個點位全面換膚,實現各種效果。

延伸思考

  1. 測評場景下:判斷人臉數量、是否是用戶本人,自動提醒用戶,異常狀態記錄日誌,監控人員可以後臺查看

  2. 學習場景下:判斷用戶是否離開屏幕等,提醒用戶返回學習狀態。

  3. 彈幕場景下:檢測人臉,解決彈幕遮擋問題

參考資料

[1] MediaTrackConstraints - Web APIs | MDN: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints

[2] captureStream: https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/captureStream

[3] 一文看懂卷積神經網絡 - CNN(基本原理 + 獨特價值 + 實際應用)- 產品經理的人工智能學習庫: https://easyai.tech/ai-definition/cnn/

[4] 基於 face-api.js 實現人臉識別的實踐和總結: https://zhuanlan.zhihu.com/p/330540757

[5] face-api.js:在瀏覽器中進行人臉識別的 JS 接口: https://zhuanlan.zhihu.com/p/39918438

[6] 卷積神經網絡: https://github.com/bighuang624/Andrew-Ng-Deep-Learning-notes/blob/master/docs/Convolutional_Neural_Networks/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C.md

[7] CNN Explainer: https://poloclub.github.io/cnn-explainer/

[8] face-api.js: https://github.com/justadudewhohacks/face-api.js/

[9] 卷積神經網絡 (Convolutional Neural Network, CNN) - Leo Van | 範葉亮: https://leovan.me/cn/2018/08/cnn/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/SoEiE8f9KDe79tnuhN-5Cw