最近公司的需求又開始作妖了,說要做使用者人臉識別,要知道照片有幾個臉,和臉部位置。這需求下來讓我這個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方法。
那麼實現效果如下:
輸入照片:
識別照片:
js前端實現
先講一下為什麼要在前端實現,是由於計算量不大不會影響使用者體驗,同時可以節約上傳圖片和下載圖片的消耗,更重要的是實現也非常方便。
js前端實現的話,基本和nodejs差不多,區別在於讀取影象使用canvas和使用ajax請求獲取模型檔案。
<!DOCTYPE html>
Hello OpenCV。js
OpenCV。js is loading。。。
let inputElement = document。getElementById(‘fileInput’);
let faceBtn = document。getElementById(‘getFaceBtn’);
let img = new Image();
inputElement。addEventListener(‘change’, (e) => {
img。src = URL。createObjectURL(e。target。files[0]);
}, false);
img。onload = function() {
let inCanvas = document。getElementById(‘canvasInput’)
let inCanvasCtx = inCanvas。getContext(‘2d’)
inCanvasCtx。drawImage(img,0,0,img。width,img。height,0,0,400,400);
if(img。width!==400 || img。height!=400) {
inCanvas。toBlob(function(blob) {
img。src = URL。createObjectURL(blob);
})
}
};
faceBtn。addEventListener(‘click’, (e) => {
let outCanvas = document。getElementById(‘canvasOutput’)
let outCanvasCtx = outCanvas。getContext(‘2d’);
let src = cv。imread(‘canvasInput’);
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
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));
const offest = 0
let point1 = new cv。Point(faces。get(i)。x, faces。get(i));
let point2 = new cv。Point(faces。get(i)。x + faces。get(i)。width,
faces。get(i)。y + faces。get(i)。height);
outCanvasCtx。drawImage(img,
faces。get(i)。x,
faces。get(i)。y,
faces。get(i)。width,
faces。get(i)。height,
0,0,400,400)
roiGray。delete(); roiSrc。delete();
}
src。delete(); gray。delete(); faceCascade。delete();
faces。delete();
})
function onOpenUtilsReady() {
let utils = new Utils(‘errorMessage’);
utils。loadOpenCv(() => {
document。getElementById(‘status’)。innerHTML = ‘OpenCV。js is ready。’;
let faceCascadeFile = ‘haarcascade_frontalface_default。xml’;
utils。createFileFromUrl(faceCascadeFile, faceCascadeFile, () => {
console。log(‘載入模型成功’)
});
});
}
。inputoutput{
display: inline-block;
}
效果如下:
例項地址展示地址,需翻牆
最後的選型
雖然nodejs服務端和JS前端都實現了扣臉功能,若直接使用前端實現扣臉,可以實現扣臉保證使用者體驗,又節約圖片上傳和下載的頻寬,為使用者和公司節約了資源,充分利用邊緣計算優勢。
但若前端實現,考慮到opencv。js的wasm檔案過大,需要做進度條載入檔案比較麻煩,前端進度很可能來不及,且對於前端複雜度度提升有風險超過前端所能承受的範圍,所以最終還是使用nodejs服務端的載入opencv。js實現。
同時能實現前端和後端的服務,也不禁感嘆一聲WebAssembly牛逼!emscripten牛逼!opencv。js牛逼!Node。js牛逼!JS牛逼!