A Pen by Ridley Neslon on CodePen.
Created
March 18, 2019 14:13
-
-
Save theorium-0/efd1f94bfaa381954c273a70e522e1b1 to your computer and use it in GitHub Desktop.
OpenCV.js Face Detection (WebAssembly)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | |
<body> | |
<div id="container"> | |
<canvas class="center-block" id="canvasOutput" width=320 height=240></canvas> | |
</div> | |
<div class="text-center"> | |
<input type="checkbox" id="face" name="classifier" value="face" checked> | |
<label for="face">face</label> | |
<input type="checkbox" id="eye" name="cascade" value="eye"> | |
<label for="eye">eye</label> | |
</div> | |
<div class="invisible"> | |
<video id="video" class="hidden">Your browser does not support the video tag.</video> | |
</div> | |
</div> | |
</body> | |
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> | |
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.min.js"></script> | |
<script> | |
var Module = { | |
wasmBinaryFile: 'https://huningxin.github.io/opencv.js/build/wasm/opencv_js.wasm', | |
preRun: [function() { | |
Module.FS_createPreloadedFile('/', 'haarcascade_eye.xml', 'https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_eye.xml', true, false); | |
Module.FS_createPreloadedFile('/', 'haarcascade_frontalface_default.xml', 'https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml', true, false); | |
Module.FS_createPreloadedFile('/', 'haarcascade_profileface.xml', 'https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_profileface.xml', true, false); | |
}], | |
_main: function() {opencvIsReady();} | |
}; | |
</script> | |
<script async src="https://huningxin.github.io/opencv.js/build/wasm/opencv.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let videoWidth, videoHeight; | |
// whether streaming video from the camera. | |
let streaming = false; | |
let video = document.getElementById('video'); | |
let canvasOutput = document.getElementById('canvasOutput'); | |
let canvasOutputCtx = canvasOutput.getContext('2d'); | |
let stream = null; | |
let detectFace = document.getElementById('face'); | |
let detectEye = document.getElementById('eye'); | |
function startCamera() { | |
if (streaming) return; | |
navigator.mediaDevices.getUserMedia({video: true, audio: false}) | |
.then(function(s) { | |
stream = s; | |
video.srcObject = s; | |
video.play(); | |
}) | |
.catch(function(err) { | |
console.log("An error occured! " + err); | |
}); | |
video.addEventListener("canplay", function(ev){ | |
if (!streaming) { | |
videoWidth = video.videoWidth; | |
videoHeight = video.videoHeight; | |
video.setAttribute("width", videoWidth); | |
video.setAttribute("height", videoHeight); | |
canvasOutput.width = videoWidth; | |
canvasOutput.height = videoHeight; | |
streaming = true; | |
} | |
startVideoProcessing(); | |
}, false); | |
} | |
let faceClassifier = null; | |
let eyeClassifier = null; | |
let src = null; | |
let dstC1 = null; | |
let dstC3 = null; | |
let dstC4 = null; | |
let canvasInput = null; | |
let canvasInputCtx = null; | |
let canvasBuffer = null; | |
let canvasBufferCtx = null; | |
function startVideoProcessing() { | |
if (!streaming) { console.warn("Please startup your webcam"); return; } | |
stopVideoProcessing(); | |
canvasInput = document.createElement('canvas'); | |
canvasInput.width = videoWidth; | |
canvasInput.height = videoHeight; | |
canvasInputCtx = canvasInput.getContext('2d'); | |
canvasBuffer = document.createElement('canvas'); | |
canvasBuffer.width = videoWidth; | |
canvasBuffer.height = videoHeight; | |
canvasBufferCtx = canvasBuffer.getContext('2d'); | |
srcMat = new cv.Mat(videoHeight, videoWidth, cv.CV_8UC4); | |
grayMat = new cv.Mat(videoHeight, videoWidth, cv.CV_8UC1); | |
faceClassifier = new cv.CascadeClassifier(); | |
faceClassifier.load('haarcascade_frontalface_default.xml'); | |
eyeClassifier = new cv.CascadeClassifier(); | |
eyeClassifier.load('haarcascade_eye.xml'); | |
requestAnimationFrame(processVideo); | |
} | |
function processVideo() { | |
stats.begin(); | |
canvasInputCtx.drawImage(video, 0, 0, videoWidth, videoHeight); | |
let imageData = canvasInputCtx.getImageData(0, 0, videoWidth, videoHeight); | |
srcMat.data.set(imageData.data); | |
cv.cvtColor(srcMat, grayMat, cv.COLOR_RGBA2GRAY); | |
let faces = []; | |
let eyes = []; | |
let size; | |
if (detectFace.checked) { | |
let faceVect = new cv.RectVector(); | |
let faceMat = new cv.Mat(); | |
if (detectEye.checked) { | |
cv.pyrDown(grayMat, faceMat); | |
size = faceMat.size(); | |
} else { | |
cv.pyrDown(grayMat, faceMat); | |
cv.pyrDown(faceMat, faceMat); | |
size = faceMat.size(); | |
} | |
faceClassifier.detectMultiScale(faceMat, faceVect); | |
for (let i = 0; i < faceVect.size(); i++) { | |
let face = faceVect.get(i); | |
faces.push(new cv.Rect(face.x, face.y, face.width, face.height)); | |
if (detectEye.checked) { | |
let eyeVect = new cv.RectVector(); | |
let eyeMat = faceMat.getRoiRect(face); | |
eyeClassifier.detectMultiScale(eyeMat, eyeVect); | |
for (let i = 0; i < eyeVect.size(); i++) { | |
let eye = eyeVect.get(i); | |
eyes.push(new cv.Rect(face.x + eye.x, face.y + eye.y, eye.width, eye.height)); | |
} | |
eyeMat.delete(); | |
eyeVect.delete(); | |
} | |
} | |
faceMat.delete(); | |
faceVect.delete(); | |
} else { | |
if (detectEye.checked) { | |
let eyeVect = new cv.RectVector(); | |
let eyeMat = new cv.Mat(); | |
cv.pyrDown(grayMat, eyeMat); | |
size = eyeMat.size(); | |
eyeClassifier.detectMultiScale(eyeMat, eyeVect); | |
for (let i = 0; i < eyeVect.size(); i++) { | |
let eye = eyeVect.get(i); | |
eyes.push(new cv.Rect(eye.x, eye.y, eye.width, eye.height)); | |
} | |
eyeMat.delete(); | |
eyeVect.delete(); | |
} | |
} | |
canvasOutputCtx.drawImage(canvasInput, 0, 0, videoWidth, videoHeight); | |
drawResults(canvasOutputCtx, faces, 'red', size); | |
drawResults(canvasOutputCtx, eyes, 'yellow', size); | |
stats.end(); | |
requestAnimationFrame(processVideo); | |
} | |
function drawResults(ctx, results, color, size) { | |
for (let i = 0; i < results.length; ++i) { | |
let rect = results[i]; | |
let xRatio = videoWidth/size.width; | |
let yRatio = videoHeight/size.height; | |
ctx.lineWidth = 3; | |
ctx.strokeStyle = color; | |
ctx.strokeRect(rect.x*xRatio, rect.y*yRatio, rect.width*xRatio, rect.height*yRatio); | |
} | |
} | |
function stopVideoProcessing() { | |
if (src != null && !src.isDeleted()) src.delete(); | |
if (dstC1 != null && !dstC1.isDeleted()) dstC1.delete(); | |
if (dstC3 != null && !dstC3.isDeleted()) dstC3.delete(); | |
if (dstC4 != null && !dstC4.isDeleted()) dstC4.delete(); | |
} | |
function stopCamera() { | |
if (!streaming) return; | |
stopVideoProcessing(); | |
document.getElementById("canvasOutput").getContext("2d").clearRect(0, 0, width, height); | |
video.pause(); | |
video.srcObject=null; | |
stream.getVideoTracks()[0].stop(); | |
streaming = false; | |
} | |
function initUI() { | |
stats = new Stats(); | |
stats.showPanel(0); | |
document.getElementById('container').appendChild(stats.dom); | |
} | |
function opencvIsReady() { | |
console.log('OpenCV.js is ready'); | |
initUI(); | |
startCamera(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
canvas { | |
border: 1px solid black; | |
} | |
.invisible { | |
display: none; | |
} | |
.text-center { | |
text-align: center; | |
} | |
div { | |
margin: 10px; | |
} | |
.center-block { | |
display: block; | |
margin: auto; | |
} | |
label { | |
padding-right: 10px; | |
width: 25%; | |
vertical-align: top; | |
font: 16px 'Lucida Grande', sans-serif; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment