最近公司的需求又開始作妖了,說要做使用者人臉識別,要知道照片有幾個臉,和臉部位置。這需求下來讓我這個CURD-BOY有點慌了,果然這個重任又落到了我身上。所以開始研究扣臉技術,之前使用過opencv做過盲水印技術,所以這次就打算繼續選取opencv來做這個。

但由於很久沒有接觸opencv了,之前還是基於2。4做的,現在都4。3了,果然還是逝者如斯夫不捨晝夜。既然如此,重新看官方文件來一遍。

發現新大陸

這個時候居然發現了

opencv。js

,不看不知道一看高興壞了。原來

opencv。js

是opencv利用了emscripten將原本的C++版本編譯成了WebAssembly,讓js可以直接呼叫C++版本的opencv方法。

這下省事了,線上部署也方便了。要知道在之前如果要用,線上伺服器還要裝opencv的開發套還要編寫C++擴充套件,這樣非常容易出問題,如果是docker,新增安裝指令碼前期工作量能讓你爆炸。如果是主機,則很容易因為線上linux版本問題和環境問題,導致呼叫opencv出錯。但現在有

WebAssembly

版本的

opencv。js

一切都變的不一樣了。

所以今天我打算透過opencv。js來實現扣臉技術。

獲取opencv。js

獲取opencv。js有兩種途徑

使用原始碼構建(教程地址)

直接下載構建好的版本(下載地址)

兩者都可以直接使用在nodejs或js上,區別是原始碼構建先需要先有emscripten環境,步驟比較麻煩。下載則版本固定且方便,但如果你要修改opencv原始碼實現特殊功能,那就不行了。

首先實現nodejs服務端版本

其實在官網例子Face Detection using Haar Cascades(例子地址)就有這個例子,但區別是服務端讀取圖片方式不同,在例子中使用的是前端的canvas讀取,後端讀取圖片主要是使用了jimp庫來讀取圖片。

同時其實在人臉識別中,opencv有一個自帶的訓練模型haarcascade_frontalface_default。xml,這個模型可以在opencv的程式碼庫中找到(程式碼庫地址)。

既然方法有了,模型也有了,是不是可以直接開擼u,很方便就能實現?

Module = {

async onRuntimeInitialized() {

console。log(cv。getBuildInformation())

await getFace()

}

}

const cv = require(‘。/opencv。js’);

const fs = require(‘fs’);

const Jimp = require(‘jimp’);

const path = require(‘path’);

async function getFace() {

var jimpSrc = await Jimp。read(path。join(__dirname,‘gx。jpg’));

let src = cv。matFromImageData(jimpSrc。bitmap);

let gray = new cv。Mat();

cv。cvtColor(src, gray, cv。COLOR_RGBA2GRAY, 0);

let faces = new cv。RectVector();

let faceCascade = new cv。CascadeClassifier();

// load pre-trained classifiers

cv。FS_createDataFile(

‘/’, ‘haarcascade_frontalface_default。xml’,

fs。readFileSync(path。join(__dirname,‘haarcascade_frontalface_default。xml’)),

true, false, false

);

faceCascade。load(‘haarcascade_frontalface_default。xml’);

// // detect faces

let msize = new cv。Size(0, 0);

faceCascade。detectMultiScale(gray, faces, 1。1, 3, 0, msize, msize);

for (let i = 0; i < faces。size(); ++i) {

let roiGray = gray。roi(faces。get(i));

let roiSrc = src。roi(faces。get(i));

let point1 = new cv。Point(faces。get(i)。x, faces。get(i)。y);

let point2 = new cv。Point(faces。get(i)。x + faces。get(i)。width,

faces。get(i)。y + faces。get(i)。height);

cv。rectangle(src, point1, point2, [255, 0, 0, 255]);

roiGray。delete(); roiSrc。delete();

}

new Jimp({

width: src。cols,

height: src。rows,

data: Buffer。from(src。data)

})。write(‘gxOutput。png’);

src。delete(); gray。delete(); faceCascade。delete();

faces。delete();

}

nodejs實現也就花了45行程式碼,其中為什麼Module要在require(’。/opencv。js’)之前是因為在’。/opencv。js’檔案中執行了全域性的Module。onRuntimeInitialized方法。

那麼實現效果如下:

輸入照片:

使用WebAssembly版本opencv實現人臉識別

識別照片:

使用WebAssembly版本opencv實現人臉識別

js前端實現

先講一下為什麼要在前端實現,是由於計算量不大不會影響使用者體驗,同時可以節約上傳圖片和下載圖片的消耗,更重要的是實現也非常方便。

js前端實現的話,基本和nodejs差不多,區別在於讀取影象使用canvas和使用ajax請求獲取模型檔案。

<!DOCTYPE html>

Hello OpenCV。js

Hello OpenCV。js

OpenCV。js is loading。。。

輸入圖片

輸出圖片

效果如下:

使用WebAssembly版本opencv實現人臉識別

例項地址展示地址,需翻牆

最後的選型

雖然nodejs服務端和JS前端都實現了扣臉功能,若直接使用前端實現扣臉,可以實現扣臉保證使用者體驗,又節約圖片上傳和下載的頻寬,為使用者和公司節約了資源,充分利用邊緣計算優勢。

但若前端實現,考慮到opencv。js的wasm檔案過大,需要做進度條載入檔案比較麻煩,前端進度很可能來不及,且對於前端複雜度度提升有風險超過前端所能承受的範圍,所以最終還是使用nodejs服務端的載入opencv。js實現。

同時能實現前端和後端的服務,也不禁感嘆一聲WebAssembly牛逼!emscripten牛逼!opencv。js牛逼!Node。js牛逼!JS牛逼!