最近picojs上了Github Trending,這是一個小巧的人臉檢測庫,200行JS,2K大小,性能很好,效果也還還行。因而我想有沒其餘的能在瀏覽器跑的人臉檢測庫,一查才發現OpenCV已經支持編譯到WebAssembly,也就能夠直接在瀏覽器裏使用了。python
安裝Emscripten SDK:git
git clone https://github.com/juj/emsdk.git cd emsdk ./emsdk update-tags ./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh
Emscripten能夠把C/C++程序編譯成asm.js,而後經過binaryen的asm2wasm轉成WebAssembly。github
接着就能夠編譯OpenCV了:canvas
wget https://github.com/opencv/opencv/archive/3.4.1.zip unzip 3.4.1.zip cd opencv-3.4.1 python ./platforms/js/build_js.py build_wasm --build_wasm
編譯的成果在build_wasm/bin
:瀏覽器
$ ls -lh build_wasm/bin/ total 5.5M -rw-r--r-- 1 lyp lyp 263K Apr 26 22:10 opencv.js -rw-r--r-- 1 lyp lyp 262K Apr 26 22:10 opencv_js.js -rw-r--r-- 1 lyp lyp 5.0M Apr 26 22:10 opencv_js.wasm
咱們只須要其中的opencv.js
和opencv_js.wasm
,能夠複製到其餘地方使用,而opencv_js.js
是中間生成的asm.js,能夠忽略。markdown
咱們能夠直接在HTML頁面裏引用opencv.js
,它會自動加載opencv_js.wasm
而後完成編譯。遇到的第一個問題是,opencv.js
默認會加載根目錄的opencv_js.wasm
,而咱們一般會把js文件放在二級目錄裏。第二個問題是,咱們的代碼必須在OpenCV編譯完成以後才能調用,不會代碼就直接出錯了。async
更新2018-08-20:在Emscripten v1.38.9,locateFile
行爲已經修改,不須要這個hack了。ide
爲了解決以上的問題,要經過Module
進行配置:post
<script> var Module = { locateFile: function (name) { let files = { "opencv_js.wasm": '/opencv/opencv_js.wasm' } return files[name] }, preRun: [() => { Module.FS_createPreloadedFile("/", "face.xml", "data/haarcascade_frontalface_default.xml", true, false); }], postRun: [ run ] }; </script> <script async src="opencv/opencv.js"></script>
Module
是Emscripten生成的全局對象,經過它能夠配置和調用Emscripten的API。例如locateFile
用配置文件的實際URL。性能
preRun
會在初始化前前調用,在這個時候,OpenCV還沒初始化,咱們能夠先用Emscripten的文件系統API預加載以後會用的文件,這裏我加載了一個預訓練好的模型data/haarcascade_frontalface_default.xml
,存放在Emscripten文件系統的"/face.xml"。
postRun
會在初始化完成以後執行,這時候OpenCV編譯完成,能夠使用cv
模塊了。
<video width="640" height="480" id="video" style="display:none"></video> <canvas width="640" height="480" id="outputCanvas"></canvas>
首先咱們須要一個video標籤,而後打開攝像頭:
async function startCamera() { let video = document.getElementById("video"); let stream = await navigator.mediaDevices.getUserMedia({ video: { width: { exact: videoWidth }, height: { exact: videoHeight } }, audio: false }) video.srcObject = stream; video.play(); }
而後咱們就能夠用cv.VideoCapture
來讀取攝像頭了:
// 建立VideoCapture let cap = new cv.VideoCapture(video); // 建立存放圖像的Mat let src = new cv.Mat(videoHeight, videoWidth, cv.CV_8UC4); // 讀一幀圖像 cap.read(src);
建立人臉檢測器:
faceCascade = new cv.CascadeClassifier(); faceCascade.load("face.xml")
接着就能夠循環讀取圖像,檢查人臉,顯示了:
// Capture a frame cap.read(src) // Convert to greyscale cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); // Downsample let downSampled = new cv.Mat(); cv.pyrDown(gray, downSampled); cv.pyrDown(downSampled, downSampled); // Detect faces let faces = new cv.RectVector(); faceCascade.detectMultiScale(downSampled, faces) // Draw boxes let size = downSampled.size(); let xRatio = videoWidth / size.width; let yRatio = videoHeight / size.height; for (let i = 0; i < faces.size(); ++i) { let face = faces.get(i); let point1 = new cv.Point(face.x * xRatio, face.y * yRatio); let point2 = new cv.Point((face.x + face.width) * xRatio, (face.y + face.height) * xRatio); cv.rectangle(src, point1, point2, [255, 0, 0, 255]) } // Show image cv.imshow(outputCanvas, src) // Cleanup downSampled.delete() faces.delete()
性能在30FPS左右,效果要比picojs好,代價是須要加載很大的JS和wasm,初始化慢。
完整代碼:learn_ml/opencv.js