├── .gitignore ├── LICENSE.md ├── README.md ├── client ├── index.html ├── receiver.js ├── sender.js └── styles.css └── server ├── lib ├── config │ └── server.js └── routes │ ├── index.js │ └── socket.js ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Esther Jun Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # face-detection-browser-opencv 2 | 3 | Real-time face detection using WebRTC, OpenCV, Node.js, and WebSockets. 4 | 5 | Similar to [face-detection-node-opencv](https://github.com/claudiopetrini/face-detection-node-opencv) but this time: 6 | 7 | - camera acquisition is made with WebRTC on the browser 8 | - the image is sent to the server via web socket for processing 9 | - the processed image is rendered on the client 10 | 11 | ## Requirements 12 | 13 | * [Node.js](http://nodejs.org/) 14 | * [OpenCV 2.4.9 - 2.4.11](http://opencv.org/) 15 | * A webcam, e.g. laptop-integrated webcam, USB webcam 16 | 17 | ## Installing Node.js packages 18 | 19 | * Navigate to the `server` directory 20 | * To install the packages: `npm install` 21 | 22 | ## Running the demo 23 | 24 | * Make sure you are still in the `server` directory 25 | * To run the server: `node server.js` 26 | * To run the demo locally, open a browser and go to `localhost:8080` 27 | 28 | The app should be up and running! 29 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | face-detection-node-opencv 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /client/receiver.js: -------------------------------------------------------------------------------- 1 | // MODIFY THIS TO THE APPROPRIATE URL IF IT IS NOT BEING RUN LOCALLY 2 | var socket = io.connect('http://localhost:8080'); 3 | 4 | var canvasFace = document.getElementById('canvas-face'); 5 | var context = canvasFace.getContext('2d'); 6 | var img = new Image(); 7 | 8 | // show loading notice 9 | context.fillStyle = '#333'; 10 | context.fillText('Loading...', canvasFace.width / 2 - 30, canvasFace.height / 3); 11 | 12 | socket.on('frame', function (data) { 13 | // Reference: http://stackoverflow.com/questions/24107378/socket-io-began-to-support-binary-stream-from-1-0-is-there-a-complete-example-e/24124966#24124966 14 | var uint8Arr = new Uint8Array(data.buffer); 15 | var str = String.fromCharCode.apply(null, uint8Arr); 16 | var base64String = btoa(str); 17 | 18 | img.onload = function () { 19 | context.drawImage(this, 0, 0, canvasFace.width, canvasFace.height); 20 | }; 21 | img.src = 'data:image/png;base64,' + base64String; 22 | }); -------------------------------------------------------------------------------- /client/sender.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var canvas = document.getElementById('canvas'); 3 | var width = 640; 4 | var height = 480; 5 | 6 | function takepicture(video) { 7 | return function () { 8 | 9 | var context = canvas.getContext('2d'); 10 | if (width && height) { 11 | canvas.width = width; 12 | canvas.height = height; 13 | context.drawImage(video, 0, 0, width, height); 14 | var jpgQuality = 0.6; 15 | var theDataURL = canvas.toDataURL('image/jpeg', jpgQuality); 16 | socket.emit('img', theDataURL); 17 | } 18 | } 19 | } 20 | 21 | navigator.getMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia); 22 | 23 | navigator.getMedia( 24 | // constraints 25 | { 26 | video: true, 27 | audio: false 28 | }, 29 | 30 | // success callback 31 | function (mediaStream) { 32 | var video = document.getElementsByTagName('video')[0]; 33 | video.src = window.URL.createObjectURL(mediaStream); 34 | video.play(); 35 | setInterval(takepicture(video), 1000 / 10) 36 | 37 | }, 38 | //handle error 39 | function (error) { 40 | console.log(error); 41 | }) 42 | })(); -------------------------------------------------------------------------------- /client/styles.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding-top: 50px; 3 | } 4 | 5 | .center { 6 | text-align: center; 7 | } 8 | 9 | #canvas-video { 10 | width: 640px; 11 | height: 480px; 12 | border: 1px solid #ccc; 13 | } -------------------------------------------------------------------------------- /server/lib/config/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | httpPort: 8080, 5 | staticFolder: path.join(__dirname + '/../../../client') 6 | }; -------------------------------------------------------------------------------- /server/lib/routes/index.js: -------------------------------------------------------------------------------- 1 | // Load the page 2 | // NB: This needs to be the last route added 3 | exports.serveIndex = function (app, staticFolder) { 4 | app.get('*', function (req, res) { 5 | res.sendFile('index.html', { root: staticFolder }); 6 | }); 7 | }; -------------------------------------------------------------------------------- /server/lib/routes/socket.js: -------------------------------------------------------------------------------- 1 | var cv = require('opencv'), 2 | async = require('async'); 3 | 4 | module.exports = function (socket) { 5 | socket.on('img', function (base64) { 6 | var output = base64.replace(/^data:image\/(png|jpeg);base64,/, ""); 7 | var buffer = new Buffer(output, 'base64'); 8 | 9 | async.auto({ 10 | readFromSocket: readFromSocket(buffer), 11 | face: ['readFromSocket', detect(cv.FACE_CASCADE)], 12 | eyes: ['readFromSocket', detect('./node_modules/opencv/data/haarcascade_mcs_eyepair_small.xml')] 13 | }, emitFrame(socket)); 14 | }) 15 | } 16 | 17 | var readFromSocket = function (buffer) { 18 | return function (callback) { 19 | cv.readImage(buffer, function (err, mat) { 20 | callback(err, mat); 21 | }); 22 | } 23 | } 24 | 25 | var detect = function (haarfile) { 26 | return function (callback, results) { 27 | var im = results['readFromSocket']; 28 | im.detectObject(haarfile, {}, function (err, faces) { 29 | if (err) callback(err); 30 | 31 | for (var i = 0; i < faces.length; i++) { 32 | face = faces[i]; 33 | im.ellipse(face.x + face.width / 2, face.y + face.height / 2, face.width / 2, face.height / 2); 34 | } 35 | callback(null, im); 36 | }); 37 | } 38 | } 39 | 40 | var emitFrame = function (socket) { 41 | return function (err, results) { 42 | if (err) { 43 | 44 | } else { 45 | var im = results['eyes']; 46 | socket.emit('frame', { 47 | buffer: im.toBuffer() 48 | }); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edi-cam", 3 | "private": true, 4 | "scripts": { 5 | "start": "node server.js" 6 | }, 7 | "dependencies": { 8 | "async": "^1.0.0", 9 | "express": "4.12.4", 10 | "morgan": "1.4.1", 11 | "opencv": "3.0.0", 12 | "socket.io": "1.3.2" 13 | } 14 | } -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // modules 2 | var express = require('express') 3 | , http = require('http') 4 | , morgan = require('morgan'); 5 | 6 | // configuration files 7 | var configServer = require('./lib/config/server'); 8 | 9 | // app parameters 10 | var app = express(); 11 | app.set('port', configServer.httpPort); 12 | app.use(express.static(configServer.staticFolder)); 13 | app.use(morgan('dev')); 14 | 15 | // serve index 16 | require('./lib/routes').serveIndex(app, configServer.staticFolder); 17 | 18 | // HTTP server 19 | var server = http.createServer(app); 20 | server.listen(app.get('port'), function () { 21 | console.log('HTTP server listening on port ' + app.get('port')); 22 | }); 23 | 24 | // WebSocket server 25 | var io = require('socket.io')(server); 26 | io.on('connection', require('./lib/routes/socket')); 27 | 28 | module.exports.app = app; --------------------------------------------------------------------------------