├── .gitignore ├── tests ├── assets │ └── faces.jpg ├── test-runner-dist.html ├── test-runner.html └── tests.js ├── examples ├── assets │ ├── video.mp4 │ ├── video.ogv │ ├── video.webm │ └── picture.jpg ├── img │ ├── background.jpg │ ├── tryit.svg │ └── logo.svg ├── css │ └── styles.css ├── picture.html └── video.html ├── .travis.yml ├── bower.json ├── package.json ├── LICENSE.md ├── README.md ├── src ├── jquery.facedetection.js └── ccv.js └── gulpfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dev/* 3 | tmp/* -------------------------------------------------------------------------------- /tests/assets/faces.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaysalvat/jquery.facedetection/HEAD/tests/assets/faces.jpg -------------------------------------------------------------------------------- /examples/assets/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaysalvat/jquery.facedetection/HEAD/examples/assets/video.mp4 -------------------------------------------------------------------------------- /examples/assets/video.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaysalvat/jquery.facedetection/HEAD/examples/assets/video.ogv -------------------------------------------------------------------------------- /examples/assets/video.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaysalvat/jquery.facedetection/HEAD/examples/assets/video.webm -------------------------------------------------------------------------------- /examples/assets/picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaysalvat/jquery.facedetection/HEAD/examples/assets/picture.jpg -------------------------------------------------------------------------------- /examples/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaysalvat/jquery.facedetection/HEAD/examples/img/background.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 5.11.1 4 | 5 | before_script: 6 | - npm install -g gulp 7 | 8 | script: "gulp test" -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.facedetection", 3 | "version": "2.0.3", 4 | "homepage": "https://facedetection.jaysalvat.com", 5 | "main": "./dist/jquery.facedetection.min.js", 6 | "dependencies": { 7 | "jquery": ">=1.10" 8 | } 9 | } -------------------------------------------------------------------------------- /tests/test-runner-dist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery FaceDetection QUnit 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/test-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery FaceDetection QUnit 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/css/styles.css: -------------------------------------------------------------------------------- 1 | /* Base */ 2 | 3 | body { 4 | background: url(../img/background.jpg); 5 | } 6 | 7 | a { 8 | color: inherit; 9 | text-decoration: none; 10 | } 11 | 12 | .logo { 13 | display: block; 14 | margin: 20px auto; 15 | width: 400px; 16 | } 17 | 18 | .button-try { 19 | position: relative; 20 | display: block; 21 | margin: -60px auto 20px; 22 | width: 200px; 23 | } 24 | 25 | .button-visit { 26 | display: block; 27 | margin: 50px auto; 28 | width: 160px; 29 | text-transform: uppercase; 30 | padding: 20px; 31 | border: 3px solid #fff; 32 | border-radius: 10px; 33 | text-align: center; 34 | color: #fff; 35 | font: 0.8em Arial, sans-serif; 36 | } 37 | .button-visit:hover { 38 | background: rgba(255, 255, 255, 0.1); 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.facedetection", 3 | "version": "2.0.3", 4 | "description": "A jQuery plugin to detect faces on images, videos and canvases.", 5 | "homepage": "http://facedetection.jaysalvat.com", 6 | "main": "./dist/jquery.facedetection.min.js", 7 | "scripts": { 8 | "test": "gulp test", 9 | "build": "gulp build" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jaysalvat/jquery.facedetection" 14 | }, 15 | "keywords": [ 16 | "jquery", 17 | "face", 18 | "detection", 19 | "image", 20 | "picture", 21 | "video", 22 | "canvas", 23 | "jquery-plugin" 24 | ], 25 | "author": { 26 | "name": "Jay Salvat", 27 | "url": "http://jaysalvat.com" 28 | }, 29 | "contributors": [ 30 | { 31 | "name": "Liu Liu", 32 | "url": "http://liuliu.me" 33 | } 34 | ], 35 | "license": "MIT", 36 | "devDependencies": { 37 | "del": "^2.2.2", 38 | "exec": "^0.1.2", 39 | "gulp": "^3.9.1", 40 | "gulp-bump": "^2.5.0", 41 | "gulp-header": "^1.8.8", 42 | "gulp-jshint": "^2.0.2", 43 | "gulp-qunit": "^1.5.0", 44 | "gulp-replace": "^0.5.4", 45 | "gulp-sync": "^0.1.4", 46 | "gulp-uglifyjs": "^0.6.2", 47 | "gulp-util": "^3.0.7", 48 | "gulp-zip": "^3.2.0", 49 | "jshint": "^2.9.4", 50 | "yargs": "^6.3.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | FaceDetection jQuery Plugin 2 | Copyright (c) 2010-2021, Jay Salvat 3 | http://www.jaysalvat.com 4 | All rights reserved. 5 | 6 | src/ccv.js and src/cascade.js 7 | Copyright (c) 2010-2021, Liu Liu 8 | http://liuliu.me/ 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 12 | 13 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | * Neither the name of the authors nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | FaceDetection jQuery Plugin 3 | Copyright (c) 2014 Jay Salvat 4 | */ 5 | 6 | /* global jQuery:true, QUnit:true */ 7 | 8 | (function ($) { 9 | "use strict"; 10 | 11 | QUnit.test('Plugin', function (assert) { 12 | assert.ok($("#img").faceDetection().addClass("testing"), "can be chained"); 13 | }); 14 | 15 | QUnit.test('Detection in image', function (assert) { 16 | $("#img").faceDetection(function (faces) { 17 | assert.equal(faces.length, 2, "2 faces found (callback)"); 18 | }); 19 | 20 | $("#img").faceDetection({ 21 | complete: function (faces) { 22 | assert.equal(faces.length, 2, "2 faces found (options)"); 23 | } 24 | }); 25 | }); 26 | 27 | QUnit.test('Detection in canvas', function (assert) { 28 | var img = document.getElementById('img'), 29 | canvas = document.getElementById('canvas'), 30 | ctx = canvas.getContext('2d'); 31 | 32 | canvas.setAttribute('width', img.width); 33 | canvas.setAttribute('height',img.height); 34 | 35 | ctx.drawImage(img, 0, 0); 36 | 37 | // TODO Async test on image load 38 | 39 | $("#canvas").faceDetection(function (faces) { 40 | assert.equal(faces.length, 2, "2 faces found"); 41 | }); 42 | }); 43 | 44 | QUnit.test('Errors', function (assert) { 45 | $("#div").faceDetection({ 46 | complete: function (faces) { 47 | assert.equal(faces.length, 0, "no faces found"); 48 | }, 49 | error: function (code, message) { 50 | assert.equal(code, 1, "returns error code 1"); 51 | assert.ok(/images, videos and canvas only/.test(message), "contains message"); 52 | } 53 | }); 54 | }); 55 | 56 | QUnit.asyncTest('Async Mode', function (assert) { 57 | assert.expect(1); 58 | 59 | $("#img").faceDetection({ 60 | worker: true, 61 | complete: function(faces) { 62 | assert.equal(faces.length, 2, "2 faces found"); 63 | QUnit.start(); 64 | } 65 | }); 66 | }); 67 | })(jQuery); 68 | -------------------------------------------------------------------------------- /examples/picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jQuery FaceDetection Examples 7 | 8 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | Visit the website 43 | 44 | 45 | 46 | 79 | 80 | -------------------------------------------------------------------------------- /examples/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jQuery FaceDetection Examples 7 | 8 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | Visit the website 47 | 48 | 49 | 50 | 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery Face Detection Plugin 2 | ============================ 3 | 4 | [![Build Status](https://travis-ci.org/jaysalvat/jquery.facedetection.png?branch=master)](https://travis-ci.org/jaysalvat/https://travis-ci.org/jaysalvat/jquery.facedetection) 5 | [![NPM version](https://badge.fury.io/js/jquery.facedetection.svg)](http://badge.fury.io/js/jquery.facedetection) 6 | [![Bower version](https://badge.fury.io/bo/jquery.facedetection.svg)](http://badge.fury.io/bo/jquery.facedetection) 7 | 8 | A jQuery/Zepto plugin to detect faces on images, videos and canvases to get theirs coordinates. 9 | 10 | **Importante note:** This plugin uses an algorithm by [Liu Liu](http://liuliu.me/). 11 | 12 | Demos 13 | ----- 14 | 15 | Website and demo here: 16 | 17 | [http://facedetection.jaysalvat.com/](http://facedetection.jaysalvat.com/) 18 | 19 | Get started 20 | ----------- 21 | 22 | Download the plugin with the method of your choice. 23 | 24 | - Download the [last release](http://jaysalvat.github.io/jquery.facedetection/releases/latest/jquery.facedetection.zip) manually 25 | - Or install it with [Bower](http://bower.io/). 26 | 27 | bower install jquery.facedetection 28 | 29 | - Or install it with [NPM](https://www.npmjs.org/package/jquery.facedetection). 30 | 31 | npm install jquery.facedetection 32 | 33 | Include [jQuery](https://code.jquery.com/jquery-3.2.1.min.js) and the plugin. 34 | 35 | 36 | 37 | 38 | Set a picture with some faces in your HTML page. 39 | 40 | 41 | 42 | Apply the plugin to this image and get the face coordinates. 43 | 44 | 51 | 52 | Results 53 | ------- 54 | 55 | Returns an array of found faces object: 56 | 57 | - **x** — X coord of the face in the picture 58 | - **y** — Y coord of the face in the picture 59 | - **width** — Width of the face 60 | - **height** — Height of the face 61 | - **positionX** — X position relative to the document 62 | - **positionY** — Y position relative to the document 63 | - **offsetX** — X position relative to the offset parent 64 | - **offsetY** — Y position relative to the offset parent 65 | - **scaleX** — Ratio between original image width and displayed width 66 | - **scaleY** — Ratio between original image height and displayed height 67 | - **confidence** — Level of confidence 68 | 69 | Settings 70 | -------- 71 | - **interval** — Interval (default 4) 72 | - **minNeighbors** — Minimum neighbors threshold which sets the cutoff level for discarding rectangle groups as face (default 1) 73 | - **confidence** — Minimum confidence (default null) 74 | - **async** — Async mode if Worker available (default false). The async mode uses Workers and needs the script to be on the same domain. 75 | - **grayscale** — Convert to grayscale before processing (default true) 76 | - **complete** — Callback function trigged after the detection is completed 77 | 78 | complete: function (faces) { 79 | // ... 80 | } 81 | 82 | - **error** — Callback function trigged on errors 83 | 84 | error: function (code, message) { 85 | // ... 86 | } 87 | -------------------------------------------------------------------------------- /src/jquery.facedetection.js: -------------------------------------------------------------------------------- 1 | /* 2 | FaceDetection jQuery Plugin 3 | Copyright (c) 2016 Jay Salvat 4 | */ 5 | 6 | /* global $, ccv, cascade */ 7 | 8 | $.fn.faceDetection = function (settingsOrCallback) { 9 | "use strict"; 10 | 11 | var time; 12 | 13 | var options = { 14 | interval: 4, 15 | minNeighbors: 1, 16 | grayscale: true, 17 | confidence: null, 18 | async: false, 19 | complete: function () {}, // (faces) 20 | error: function () {} // (code, message) 21 | }; 22 | 23 | if ($.isFunction(settingsOrCallback)) { 24 | options.complete = settingsOrCallback; 25 | } else { 26 | $.extend(options, settingsOrCallback); 27 | } 28 | 29 | return this.each(function() { 30 | var $$ = $(this), 31 | offset = $$.offset(), 32 | position = $$.position(), 33 | scaleX = ($$.width() / (this.naturalWidth || this.videoWidth )) || 1, 34 | scaleY = ($$.height() / (this.naturalHeight || this.videoHeight)) || 1; 35 | 36 | if (!$$.is('img, video, canvas')) { 37 | options.error.apply($$, [ 1, 'Face detection is possible on images, videos and canvas only.' ]); 38 | options.complete.apply($$, [ [] ]); 39 | 40 | return; 41 | } 42 | 43 | function detect() { 44 | var source, canvas; 45 | 46 | time = new Date().getTime(); 47 | 48 | if ($$.is('img')) { 49 | source = new Image(); 50 | source.src = $$.attr('src'); 51 | source.crossOrigin = $$.attr('crossorigin'); 52 | 53 | canvas = ccv.pre(source); 54 | } else if ($$.is('video') || $$.is('canvas')) { 55 | var copy, context; 56 | 57 | source = $$[0]; 58 | 59 | copy = document.createElement('canvas'); 60 | copy.setAttribute('width', source.videoWidth || source.width); 61 | copy.setAttribute('height', source.videoHeight || source.height); 62 | 63 | context = copy.getContext("2d"); 64 | context.drawImage(source, 0, 0); 65 | 66 | canvas = ccv.pre(copy); 67 | } 68 | 69 | if (options.grayscale) { 70 | canvas = ccv.grayscale(canvas); 71 | } 72 | 73 | try { 74 | if (options.async && window.Worker) { 75 | ccv.detect_objects({ 76 | "canvas": canvas, 77 | "cascade": cascade, 78 | "interval": options.interval, 79 | "min_neighbors": options.minNeighbors, 80 | "worker": 1, 81 | "async": true 82 | })(done); 83 | } else { 84 | done(ccv.detect_objects({ 85 | "canvas": canvas, 86 | "cascade": cascade, 87 | "interval": options.interval, 88 | "min_neighbors": options.minNeighbors 89 | })); 90 | } 91 | } catch (e) { 92 | options.error.apply($$, [ 2, e.message ]); 93 | options.complete.apply($$, [ false ]); 94 | } 95 | } 96 | 97 | function done(faces) { 98 | var n = faces.length, 99 | data = []; 100 | 101 | for (var i = 0; i < n; ++i) { 102 | if (options.confidence !== null && faces[i].confidence <= options.confidence) { 103 | continue; 104 | } 105 | 106 | faces[i].positionX = position.left + faces[i].x; 107 | faces[i].positionY = position.top + faces[i].y; 108 | faces[i].offsetX = offset.left + faces[i].x; 109 | faces[i].offsetY = offset.top + faces[i].y; 110 | faces[i].scaleX = scaleX; 111 | faces[i].scaleY = scaleY; 112 | 113 | data.push(faces[i]); 114 | } 115 | 116 | data.time = new Date().getTime() - time; 117 | 118 | options.complete.apply($$, [ data ]); 119 | } 120 | 121 | return detect(); 122 | }); 123 | }; 124 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* Utlimate Jay Mega Gulpfile */ 2 | /* jshint node: true */ 3 | 4 | (function () { 5 | "use strict"; 6 | 7 | var pkg = require("./package.json"), 8 | del = require("del"), 9 | yargs = require("yargs"), 10 | exec = require("exec"), 11 | fs = require("fs"), 12 | gulp = require("gulp"), 13 | bump = require("gulp-bump"), 14 | header = require("gulp-header"), 15 | qunit = require("gulp-qunit"), 16 | uglify = require("gulp-uglifyjs"), 17 | jshint = require('gulp-jshint'), 18 | gutil = require("gulp-util"), 19 | zip = require("gulp-zip"), 20 | replace = require("gulp-replace"), 21 | gsync = require("gulp-sync"), 22 | sync = gsync(gulp).sync; 23 | // TODO: 24 | // Use gulp-load-plugins 25 | 26 | var version = yargs.argv.type || "patch"; 27 | 28 | var settings = { 29 | banner: { 30 | content: [ 31 | '/*! ----------------------------------------------------------------------------', 32 | ' * <%= pkg.description %>', 33 | ' * v<%= pkg.version %> released <%= datetime %>', 34 | ' * <%= pkg.homepage %>', 35 | ' * Copyright (c) 2010-<%= year %>, Jay Salvat', 36 | ' * http://jaysalvat.com/', 37 | ' * ----------------------------------------------------------------------------', 38 | ' * ccv.js and cascade.js', 39 | ' * Copyright (c) 2010-<%= year %>, Liu Liu', 40 | ' * http://liuliu.me/', 41 | ' * ----------------------------------------------------------------------------', 42 | ' */', 43 | '', 44 | ].join("\n"), 45 | vars: { 46 | pkg: pkg, 47 | datetime: gutil.date("yyyy-mm-dd HH:MM"), 48 | year: gutil.date("yyyy") 49 | } 50 | }, 51 | files: { 52 | in: [ 53 | './src/cascade.js', 54 | './src/ccv.js', 55 | './src/jquery.facedetection.js' 56 | ], 57 | out: 'jquery.facedetection' 58 | }, 59 | enclose: { 60 | // jQuery / Zepto / Worker mode 61 | '("function" === typeof jQuery) ? jQuery : ("function" === typeof Zepto) ? Zepto : { fn: {} }': '$' 62 | } 63 | }; 64 | 65 | var getPackageJson = function () { 66 | return JSON.parse(fs.readFileSync('./package.json')); 67 | }; 68 | 69 | gulp.task("clean", function (cb) { 70 | return del([ "./dist" ], cb); 71 | }); 72 | 73 | gulp.task("tmp-clean", function (cb) { 74 | return del([ "./tmp" ], cb); 75 | }); 76 | 77 | gulp.task("tmp-create", function (cb) { 78 | return exec("mkdir -p ./tmp", cb); 79 | }); 80 | 81 | gulp.task("tmp-copy", [ "tmp-create" ], function () { 82 | return gulp.src("./dist/*") 83 | .pipe(gulp.dest("./tmp")); 84 | }); 85 | 86 | gulp.task("zip", [ "tmp-create" ], function () { 87 | var filename = settings.files.out + ".zip"; 88 | 89 | return gulp.src("./dist/*") 90 | .pipe(zip(filename)) 91 | .pipe(gulp.dest("./tmp")); 92 | }); 93 | 94 | gulp.task("fail-if-dirty", function (cb) { 95 | return exec('git diff-index HEAD --', function (err, output) { // err, output, code 96 | if (err) { 97 | return cb(err); 98 | } 99 | if (output) { 100 | return cb("Repository is dirty"); 101 | } 102 | return cb(); 103 | }); 104 | }); 105 | 106 | gulp.task("fail-if-not-master", function (cb) { 107 | exec('git symbolic-ref -q HEAD', function (err, output) { // err, output, code 108 | if (err) { 109 | return cb(err); 110 | } 111 | if (!/refs\/heads\/master/.test(output)) { 112 | return cb("Branch is not Master"); 113 | } 114 | return cb(); 115 | }); 116 | }); 117 | 118 | gulp.task("git-tag", function (cb) { 119 | var message = "v" + getPackageJson().version; 120 | 121 | return exec('git tag ' + message, cb); 122 | }); 123 | 124 | gulp.task("git-add", function (cb) { 125 | return exec('git add -A', cb); 126 | }); 127 | 128 | gulp.task("git-commit", [ "git-add" ], function (cb) { 129 | var message = "Build v" + getPackageJson().version; 130 | 131 | return exec('git commit -m "' + message + '"', cb); 132 | }); 133 | 134 | gulp.task("git-pull", function (cb) { 135 | return exec('git pull origin master', function (err, output, code) { 136 | if (code !== 0) { 137 | return cb(err + output); 138 | } 139 | return cb(); 140 | }); 141 | }); 142 | 143 | gulp.task("git-push", [ "git-commit" ], function (cb) { 144 | return exec('git push origin master --tags', function (err, output, code) { 145 | if (code !== 0) { 146 | return cb(err + output); 147 | } 148 | return cb(); 149 | }); 150 | }); 151 | 152 | gulp.task("meta", [ "tmp-create" ], function (cb) { 153 | var metadata = { 154 | date: gutil.date("yyyy-mm-dd HH:MM"), 155 | version: "v" + getPackageJson().version 156 | }, 157 | json = JSON.stringify(metadata, null, 4); 158 | 159 | fs.writeFileSync("tmp/metadata.json", json); 160 | fs.writeFileSync("tmp/metadata.js", "__metadata(" + json + ");"); 161 | 162 | return cb(); 163 | }); 164 | 165 | gulp.task("bump", function () { 166 | return gulp.src([ "package.json", "bower.json", "facedetection.jquery.json" ]) 167 | .pipe(bump({ 168 | type: version 169 | })) 170 | .pipe(gulp.dest(".")); 171 | }); 172 | 173 | gulp.task("license", function () { 174 | return gulp.src([ "./LICENSE.md", "./README.md" ]) 175 | .pipe(replace(/( 2010-)(\d{4})/g, "$1" + gutil.date("yyyy"))) 176 | .pipe(gulp.dest(".")); 177 | }); 178 | 179 | gulp.task('lint', function() { 180 | return gulp.src('./src/jquery.facedetection.js') 181 | .pipe(jshint()) 182 | .pipe(jshint.reporter('default')); 183 | }); 184 | 185 | gulp.task("test-dev", function () { 186 | return gulp.src("./tests/test-runner.html") 187 | .pipe(qunit()); 188 | }); 189 | 190 | gulp.task("test-dist", function () { 191 | return gulp.src("./tests/test-runner-dist.html") 192 | .pipe(qunit()); 193 | }); 194 | 195 | gulp.task("uglify", function () { 196 | settings.banner.vars.pkg = getPackageJson(); 197 | 198 | return gulp.src(settings.files.in) 199 | .pipe(header(settings.banner.content, settings.banner.vars )) 200 | .pipe(uglify(settings.files.out + '.min.js', { 201 | enclose: settings.enclose, 202 | compress: { 203 | warnings: false 204 | }, 205 | mangle: true, 206 | outSourceMap: true 207 | })) 208 | .pipe(gulp.dest('./dist/')); 209 | }); 210 | 211 | gulp.task("beautify", function () { 212 | settings.banner.vars.pkg = getPackageJson(); 213 | 214 | return gulp.src(settings.files.in) 215 | .pipe(header(settings.banner.content, settings.banner.vars )) 216 | .pipe(uglify(settings.files.out + '.js', { 217 | enclose: settings.enclose, 218 | compress: { 219 | warnings: false 220 | }, 221 | output: { 222 | beautify: true 223 | }, 224 | mangle: false 225 | })) 226 | .pipe(gulp.dest('./dist/')); 227 | }); 228 | 229 | // gulp.task("header", function () { 230 | // settings.banner.vars.pkg = getPackageJson(); 231 | 232 | // return gulp.src('./dist/*.js') 233 | // .pipe(header(settings.banner.content, settings.banner.vars )) 234 | // .pipe(gulp.dest('./dist/')); 235 | // }); 236 | 237 | gulp.task("gh-pages", function (cb) { 238 | version = getPackageJson().version; 239 | 240 | exec([ 'git checkout gh-pages', 241 | 'rm -rf releases/' + version, 242 | 'mkdir -p releases/' + version, 243 | 'cp -r tmp/* releases/' + version, 244 | 'git add -A releases/' + version, 245 | 'rm -rf releases/latest', 246 | 'mkdir -p releases/latest', 247 | 'cp -r tmp/* releases/latest', 248 | 'git add -A releases/latest', 249 | 'git commit -m "Publish release v' + version + '."', 250 | 'git push origin gh-pages', 251 | 'git checkout -', 252 | ].join(" && "), 253 | function (err, output, code) { 254 | if (code !== 0) { 255 | return cb(err + output); 256 | } 257 | return cb(); 258 | } 259 | ); 260 | }); 261 | 262 | gulp.task("npm-publish", function (cb) { 263 | exec('npm publish', function (err, output, code) { 264 | if (code !== 0) { 265 | return cb(err + output); 266 | } 267 | return cb(); 268 | } 269 | ); 270 | }); 271 | 272 | gulp.task("test", sync([ 273 | "lint", 274 | "test-dev" 275 | ], 276 | "building")); 277 | 278 | gulp.task("build", sync([ 279 | "lint", 280 | "test-dev", 281 | "clean", 282 | "beautify", 283 | "uglify", 284 | "test-dist" 285 | ], 286 | "building")); 287 | 288 | gulp.task("release", sync([ 289 | [ "fail-if-not-master", "fail-if-dirty" ], 290 | "git-pull", 291 | "lint", 292 | "test-dev", 293 | "bump", 294 | "license", 295 | "clean", 296 | "beautify", 297 | "uglify", 298 | "test-dist", 299 | "git-add", 300 | "git-commit", 301 | "git-tag", 302 | "git-push", 303 | "publish", 304 | "npm-publish" 305 | ], 306 | "releasing")); 307 | 308 | gulp.task("publish", sync([ 309 | [ "fail-if-not-master", "fail-if-dirty" ], 310 | "tmp-create", 311 | "tmp-copy", 312 | "meta", 313 | "zip", 314 | "gh-pages", 315 | "tmp-clean" 316 | ], 317 | "publising")); 318 | })(); 319 | 320 | /* 321 | 322 | NOTES 323 | ===== 324 | 325 | Gh-pages creation 326 | ----------------- 327 | 328 | git checkout --orphan gh-pages 329 | git rm -rf . 330 | rm -fr 331 | echo "Welcome" > index.html 332 | git add index.html 333 | git commit -a -m "First commit" 334 | git push origin gh-pages 335 | git checkout - 336 | 337 | */ 338 | -------------------------------------------------------------------------------- /examples/img/tryit.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 167 | -------------------------------------------------------------------------------- /src/ccv.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2010, Liu Liu 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | * Neither the name of the authors nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | */ 11 | 12 | /* Added by Jay Salvat: Get Script Path */ 13 | try { 14 | var getScriptPath = function () { 15 | "use strict"; 16 | 17 | var scripts = document.getElementsByTagName('script'); 18 | 19 | for (var i = 0; i < scripts.length; i++) { 20 | if (scripts[i].src.match(/(jquery\.facedetection(\.min)?\.js)|\/ccv\.js/)) { 21 | return scripts[i].src; 22 | } 23 | } 24 | }; 25 | 26 | var scriptPath = getScriptPath(); 27 | } catch (e) {} 28 | /* End Jay Salvat */ 29 | 30 | if (parallable === undefined) { 31 | var parallable = function (file, funct) { 32 | "use strict"; 33 | 34 | parallable.core[funct.toString()] = funct().core; 35 | 36 | return function () { 37 | var i; 38 | var async, worker_num, params; 39 | if (arguments.length > 1) { 40 | async = arguments[arguments.length - 2]; 41 | worker_num = arguments[arguments.length - 1]; 42 | params = new Array(arguments.length - 2); 43 | for (i = 0; i < arguments.length - 2; i++) 44 | params[i] = arguments[i]; 45 | } else { 46 | async = arguments[0].async; 47 | worker_num = arguments[0].worker; 48 | params = arguments[0]; 49 | delete params["async"]; 50 | delete params["worker"]; 51 | params = [params]; 52 | } 53 | var scope = { "shared" : {} }; 54 | var ctrl = funct.apply(scope, params); 55 | if (async) { 56 | return function (complete, error) { 57 | var executed = 0; 58 | var outputs = new Array(worker_num); 59 | var inputs = ctrl.pre.apply(scope, [worker_num]); 60 | /* sanitize scope shared because for Chrome/WebKit, worker only support JSONable data */ 61 | for (i in scope.shared) 62 | /* delete function, if any */ 63 | if (typeof scope.shared[i] == "function") 64 | delete scope.shared[i]; 65 | /* delete DOM object, if any */ 66 | else if (scope.shared[i].tagName !== undefined) 67 | delete scope.shared[i]; 68 | for (i = 0; i < worker_num; i++) { 69 | var worker = new Worker(file); 70 | worker.onmessage = (function (i) { 71 | return function (event) { 72 | outputs[i] = (typeof event.data == "string") ? JSON.parse(event.data) : event.data; 73 | executed++; 74 | if (executed == worker_num) 75 | complete(ctrl.post.apply(scope, [outputs])); 76 | } 77 | })(i); 78 | var msg = { "input" : inputs[i], 79 | "name" : funct.toString(), 80 | "shared" : scope.shared, 81 | "id" : i, 82 | "worker" : params.worker_num, 83 | "from" : "jquery.facedetection" }; 84 | try { 85 | worker.postMessage(msg); 86 | } catch (e) { 87 | worker.postMessage(JSON.stringify(msg)); 88 | } 89 | } 90 | } 91 | } else { 92 | return ctrl.post.apply(scope, [[ctrl.core.apply(scope, [ctrl.pre.apply(scope, [1])[0], 0, 1])]]); 93 | } 94 | } 95 | }; 96 | parallable.core = {}; 97 | } 98 | 99 | function get_named_arguments(params, names) { 100 | if (params.length > 1) { 101 | var new_params = {}; 102 | for (var i = 0; i < names.length; i++) 103 | new_params[names[i]] = params[i]; 104 | return new_params; 105 | } else if (params.length == 1) { 106 | return params[0]; 107 | } else { 108 | return {}; 109 | } 110 | } 111 | 112 | var ccv = { 113 | pre : function (image) { 114 | if (image.tagName.toLowerCase() == "img") { 115 | var canvas = document.createElement("canvas"); 116 | document.body.appendChild(image); 117 | canvas.width = image.offsetWidth; 118 | canvas.style.width = image.offsetWidth.toString() + "px"; 119 | canvas.height = image.offsetHeight; 120 | canvas.style.height = image.offsetHeight.toString() + "px"; 121 | document.body.removeChild(image); 122 | var ctx = canvas.getContext("2d"); 123 | ctx.drawImage(image, 0, 0); 124 | return canvas; 125 | } 126 | return image; 127 | }, 128 | 129 | grayscale : function (canvas) { 130 | var ctx = canvas.getContext("2d"); 131 | var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 132 | var data = imageData.data; 133 | var pix1, pix2, pix = canvas.width * canvas.height * 4; 134 | while (pix > 0) 135 | data[pix -= 4] = data[pix1 = pix + 1] = data[pix2 = pix + 2] = (data[pix] * 0.3 + data[pix1] * 0.59 + data[pix2] * 0.11); 136 | ctx.putImageData(imageData, 0, 0); 137 | return canvas; 138 | }, 139 | 140 | array_group : function (seq, gfunc) { 141 | var i, j; 142 | var node = new Array(seq.length); 143 | for (i = 0; i < seq.length; i++) 144 | node[i] = {"parent" : -1, 145 | "element" : seq[i], 146 | "rank" : 0}; 147 | for (i = 0; i < seq.length; i++) { 148 | if (!node[i].element) 149 | continue; 150 | var root = i; 151 | while (node[root].parent != -1) 152 | root = node[root].parent; 153 | for (j = 0; j < seq.length; j++) { 154 | if( i != j && node[j].element && gfunc(node[i].element, node[j].element)) { 155 | var root2 = j; 156 | 157 | while (node[root2].parent != -1) 158 | root2 = node[root2].parent; 159 | 160 | if(root2 != root) { 161 | if(node[root].rank > node[root2].rank) 162 | node[root2].parent = root; 163 | else { 164 | node[root].parent = root2; 165 | if (node[root].rank == node[root2].rank) 166 | node[root2].rank++; 167 | root = root2; 168 | } 169 | 170 | /* compress path from node2 to the root: */ 171 | var temp, node2 = j; 172 | while (node[node2].parent != -1) { 173 | temp = node2; 174 | node2 = node[node2].parent; 175 | node[temp].parent = root; 176 | } 177 | 178 | /* compress path from node to the root: */ 179 | node2 = i; 180 | while (node[node2].parent != -1) { 181 | temp = node2; 182 | node2 = node[node2].parent; 183 | node[temp].parent = root; 184 | } 185 | } 186 | } 187 | } 188 | } 189 | var idx = new Array(seq.length); 190 | var class_idx = 0; 191 | for(i = 0; i < seq.length; i++) { 192 | j = -1; 193 | var node1 = i; 194 | if(node[node1].element) { 195 | while (node[node1].parent != -1) 196 | node1 = node[node1].parent; 197 | if(node[node1].rank >= 0) 198 | node[node1].rank = ~class_idx++; 199 | j = ~node[node1].rank; 200 | } 201 | idx[i] = j; 202 | } 203 | return {"index" : idx, "cat" : class_idx}; 204 | }, 205 | 206 | detect_objects : parallable(scriptPath, function (canvas, cascade, interval, min_neighbors) { 207 | if (this.shared !== undefined) { 208 | var params = get_named_arguments(arguments, ["canvas", "cascade", "interval", "min_neighbors"]); 209 | this.shared.canvas = params.canvas; 210 | this.shared.interval = params.interval; 211 | this.shared.min_neighbors = params.min_neighbors; 212 | this.shared.cascade = params.cascade; 213 | this.shared.scale = Math.pow(2, 1 / (params.interval + 1)); 214 | this.shared.next = params.interval + 1; 215 | this.shared.scale_upto = Math.floor(Math.log(Math.min(params.canvas.width / params.cascade.width, params.canvas.height / params.cascade.height)) / Math.log(this.shared.scale)); 216 | var i; 217 | for (i = 0; i < this.shared.cascade.stage_classifier.length; i++) 218 | this.shared.cascade.stage_classifier[i].orig_feature = this.shared.cascade.stage_classifier[i].feature; 219 | } 220 | 221 | function pre(worker_num) { 222 | var canvas = this.shared.canvas; 223 | var interval = this.shared.interval; 224 | var scale = this.shared.scale; 225 | var next = this.shared.next; 226 | var scale_upto = this.shared.scale_upto; 227 | var pyr = new Array((scale_upto + next * 2) * 4); 228 | var ret = new Array((scale_upto + next * 2) * 4); 229 | pyr[0] = canvas; 230 | ret[0] = { "width" : pyr[0].width, 231 | "height" : pyr[0].height, 232 | "data" : pyr[0].getContext("2d").getImageData(0, 0, pyr[0].width, pyr[0].height).data }; 233 | var i; 234 | for (i = 1; i <= interval; i++) { 235 | pyr[i * 4] = document.createElement("canvas"); 236 | pyr[i * 4].width = Math.floor(pyr[0].width / Math.pow(scale, i)); 237 | pyr[i * 4].height = Math.floor(pyr[0].height / Math.pow(scale, i)); 238 | pyr[i * 4].getContext("2d").drawImage(pyr[0], 0, 0, pyr[0].width, pyr[0].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height); 239 | ret[i * 4] = { "width" : pyr[i * 4].width, 240 | "height" : pyr[i * 4].height, 241 | "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data }; 242 | } 243 | for (i = next; i < scale_upto + next * 2; i++) { 244 | pyr[i * 4] = document.createElement("canvas"); 245 | pyr[i * 4].width = Math.floor(pyr[i * 4 - next * 4].width / 2); 246 | pyr[i * 4].height = Math.floor(pyr[i * 4 - next * 4].height / 2); 247 | pyr[i * 4].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 0, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height); 248 | ret[i * 4] = { "width" : pyr[i * 4].width, 249 | "height" : pyr[i * 4].height, 250 | "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data }; 251 | } 252 | for (i = next * 2; i < scale_upto + next * 2; i++) { 253 | pyr[i * 4 + 1] = document.createElement("canvas"); 254 | pyr[i * 4 + 1].width = Math.floor(pyr[i * 4 - next * 4].width / 2); 255 | pyr[i * 4 + 1].height = Math.floor(pyr[i * 4 - next * 4].height / 2); 256 | pyr[i * 4 + 1].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 0, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4 + 1].width - 2, pyr[i * 4 + 1].height); 257 | ret[i * 4 + 1] = { "width" : pyr[i * 4 + 1].width, 258 | "height" : pyr[i * 4 + 1].height, 259 | "data" : pyr[i * 4 + 1].getContext("2d").getImageData(0, 0, pyr[i * 4 + 1].width, pyr[i * 4 + 1].height).data }; 260 | pyr[i * 4 + 2] = document.createElement("canvas"); 261 | pyr[i * 4 + 2].width = Math.floor(pyr[i * 4 - next * 4].width / 2); 262 | pyr[i * 4 + 2].height = Math.floor(pyr[i * 4 - next * 4].height / 2); 263 | pyr[i * 4 + 2].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 1, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height - 2); 264 | ret[i * 4 + 2] = { "width" : pyr[i * 4 + 2].width, 265 | "height" : pyr[i * 4 + 2].height, 266 | "data" : pyr[i * 4 + 2].getContext("2d").getImageData(0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height).data }; 267 | pyr[i * 4 + 3] = document.createElement("canvas"); 268 | pyr[i * 4 + 3].width = Math.floor(pyr[i * 4 - next * 4].width / 2); 269 | pyr[i * 4 + 3].height = Math.floor(pyr[i * 4 - next * 4].height / 2); 270 | pyr[i * 4 + 3].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 1, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 3].width - 2, pyr[i * 4 + 3].height - 2); 271 | ret[i * 4 + 3] = { "width" : pyr[i * 4 + 3].width, 272 | "height" : pyr[i * 4 + 3].height, 273 | "data" : pyr[i * 4 + 3].getContext("2d").getImageData(0, 0, pyr[i * 4 + 3].width, pyr[i * 4 + 3].height).data }; 274 | } 275 | return [ret]; 276 | }; 277 | 278 | function core(pyr, id, worker_num) { 279 | var cascade = this.shared.cascade; 280 | var interval = this.shared.interval; 281 | var scale = this.shared.scale; 282 | var next = this.shared.next; 283 | var scale_upto = this.shared.scale_upto; 284 | var i, j, k, x, y, q; 285 | var scale_x = 1, scale_y = 1; 286 | var dx = [0, 1, 0, 1]; 287 | var dy = [0, 0, 1, 1]; 288 | var seq = []; 289 | for (i = 0; i < scale_upto; i++) { 290 | var qw = pyr[i * 4 + next * 8].width - Math.floor(cascade.width / 4); 291 | var qh = pyr[i * 4 + next * 8].height - Math.floor(cascade.height / 4); 292 | var step = [pyr[i * 4].width * 4, pyr[i * 4 + next * 4].width * 4, pyr[i * 4 + next * 8].width * 4]; 293 | var paddings = [pyr[i * 4].width * 16 - qw * 16, 294 | pyr[i * 4 + next * 4].width * 8 - qw * 8, 295 | pyr[i * 4 + next * 8].width * 4 - qw * 4]; 296 | for (j = 0; j < cascade.stage_classifier.length; j++) { 297 | var orig_feature = cascade.stage_classifier[j].orig_feature; 298 | var feature = cascade.stage_classifier[j].feature = new Array(cascade.stage_classifier[j].count); 299 | for (k = 0; k < cascade.stage_classifier[j].count; k++) { 300 | feature[k] = {"size" : orig_feature[k].size, 301 | "px" : new Array(orig_feature[k].size), 302 | "pz" : new Array(orig_feature[k].size), 303 | "nx" : new Array(orig_feature[k].size), 304 | "nz" : new Array(orig_feature[k].size)}; 305 | for (q = 0; q < orig_feature[k].size; q++) { 306 | feature[k].px[q] = orig_feature[k].px[q] * 4 + orig_feature[k].py[q] * step[orig_feature[k].pz[q]]; 307 | feature[k].pz[q] = orig_feature[k].pz[q]; 308 | feature[k].nx[q] = orig_feature[k].nx[q] * 4 + orig_feature[k].ny[q] * step[orig_feature[k].nz[q]]; 309 | feature[k].nz[q] = orig_feature[k].nz[q]; 310 | } 311 | } 312 | } 313 | for (q = 0; q < 4; q++) { 314 | var u8 = [pyr[i * 4].data, pyr[i * 4 + next * 4].data, pyr[i * 4 + next * 8 + q].data]; 315 | var u8o = [dx[q] * 8 + dy[q] * pyr[i * 4].width * 8, dx[q] * 4 + dy[q] * pyr[i * 4 + next * 4].width * 4, 0]; 316 | for (y = 0; y < qh; y++) { 317 | for (x = 0; x < qw; x++) { 318 | var sum = 0; 319 | var flag = true; 320 | for (j = 0; j < cascade.stage_classifier.length; j++) { 321 | sum = 0; 322 | var alpha = cascade.stage_classifier[j].alpha; 323 | var feature = cascade.stage_classifier[j].feature; 324 | for (k = 0; k < cascade.stage_classifier[j].count; k++) { 325 | var feature_k = feature[k]; 326 | var p, pmin = u8[feature_k.pz[0]][u8o[feature_k.pz[0]] + feature_k.px[0]]; 327 | var n, nmax = u8[feature_k.nz[0]][u8o[feature_k.nz[0]] + feature_k.nx[0]]; 328 | if (pmin <= nmax) { 329 | sum += alpha[k * 2]; 330 | } else { 331 | var f, shortcut = true; 332 | for (f = 0; f < feature_k.size; f++) { 333 | if (feature_k.pz[f] >= 0) { 334 | p = u8[feature_k.pz[f]][u8o[feature_k.pz[f]] + feature_k.px[f]]; 335 | if (p < pmin) { 336 | if (p <= nmax) { 337 | shortcut = false; 338 | break; 339 | } 340 | pmin = p; 341 | } 342 | } 343 | if (feature_k.nz[f] >= 0) { 344 | n = u8[feature_k.nz[f]][u8o[feature_k.nz[f]] + feature_k.nx[f]]; 345 | if (n > nmax) { 346 | if (pmin <= n) { 347 | shortcut = false; 348 | break; 349 | } 350 | nmax = n; 351 | } 352 | } 353 | } 354 | sum += (shortcut) ? alpha[k * 2 + 1] : alpha[k * 2]; 355 | } 356 | } 357 | if (sum < cascade.stage_classifier[j].threshold) { 358 | flag = false; 359 | break; 360 | } 361 | } 362 | if (flag) { 363 | seq.push({"x" : (x * 4 + dx[q] * 2) * scale_x, 364 | "y" : (y * 4 + dy[q] * 2) * scale_y, 365 | "width" : cascade.width * scale_x, 366 | "height" : cascade.height * scale_y, 367 | "neighbor" : 1, 368 | "confidence" : sum}); 369 | } 370 | u8o[0] += 16; 371 | u8o[1] += 8; 372 | u8o[2] += 4; 373 | } 374 | u8o[0] += paddings[0]; 375 | u8o[1] += paddings[1]; 376 | u8o[2] += paddings[2]; 377 | } 378 | } 379 | scale_x *= scale; 380 | scale_y *= scale; 381 | } 382 | return seq; 383 | }; 384 | 385 | function post(seq) { 386 | var min_neighbors = this.shared.min_neighbors; 387 | var cascade = this.shared.cascade; 388 | var interval = this.shared.interval; 389 | var scale = this.shared.scale; 390 | var next = this.shared.next; 391 | var scale_upto = this.shared.scale_upto; 392 | var i, j; 393 | for (i = 0; i < cascade.stage_classifier.length; i++) 394 | cascade.stage_classifier[i].feature = cascade.stage_classifier[i].orig_feature; 395 | seq = seq[0]; 396 | if (!(min_neighbors > 0)) 397 | return seq; 398 | else { 399 | var result = ccv.array_group(seq, function (r1, r2) { 400 | var distance = Math.floor(r1.width * 0.25 + 0.5); 401 | 402 | return r2.x <= r1.x + distance && 403 | r2.x >= r1.x - distance && 404 | r2.y <= r1.y + distance && 405 | r2.y >= r1.y - distance && 406 | r2.width <= Math.floor(r1.width * 1.5 + 0.5) && 407 | Math.floor(r2.width * 1.5 + 0.5) >= r1.width; 408 | }); 409 | var ncomp = result.cat; 410 | var idx_seq = result.index; 411 | var comps = new Array(ncomp + 1); 412 | for (i = 0; i < comps.length; i++) 413 | comps[i] = {"neighbors" : 0, 414 | "x" : 0, 415 | "y" : 0, 416 | "width" : 0, 417 | "height" : 0, 418 | "confidence" : 0}; 419 | 420 | // count number of neighbors 421 | for(i = 0; i < seq.length; i++) 422 | { 423 | var r1 = seq[i]; 424 | var idx = idx_seq[i]; 425 | 426 | if (comps[idx].neighbors == 0) 427 | comps[idx].confidence = r1.confidence; 428 | 429 | ++comps[idx].neighbors; 430 | 431 | comps[idx].x += r1.x; 432 | comps[idx].y += r1.y; 433 | comps[idx].width += r1.width; 434 | comps[idx].height += r1.height; 435 | comps[idx].confidence = Math.max(comps[idx].confidence, r1.confidence); 436 | } 437 | 438 | var seq2 = []; 439 | // calculate average bounding box 440 | for(i = 0; i < ncomp; i++) 441 | { 442 | var n = comps[i].neighbors; 443 | if (n >= min_neighbors) 444 | seq2.push({"x" : (comps[i].x * 2 + n) / (2 * n), 445 | "y" : (comps[i].y * 2 + n) / (2 * n), 446 | "width" : (comps[i].width * 2 + n) / (2 * n), 447 | "height" : (comps[i].height * 2 + n) / (2 * n), 448 | "neighbors" : comps[i].neighbors, 449 | "confidence" : comps[i].confidence}); 450 | } 451 | 452 | var result_seq = []; 453 | // filter out small face rectangles inside large face rectangles 454 | for(i = 0; i < seq2.length; i++) 455 | { 456 | var r1 = seq2[i]; 457 | var flag = true; 458 | for(j = 0; j < seq2.length; j++) 459 | { 460 | var r2 = seq2[j]; 461 | var distance = Math.floor(r2.width * 0.25 + 0.5); 462 | 463 | if(i != j && 464 | r1.x >= r2.x - distance && 465 | r1.y >= r2.y - distance && 466 | r1.x + r1.width <= r2.x + r2.width + distance && 467 | r1.y + r1.height <= r2.y + r2.height + distance && 468 | (r2.neighbors > Math.max(3, r1.neighbors) || r1.neighbors < 3)) 469 | { 470 | flag = false; 471 | break; 472 | } 473 | } 474 | 475 | if(flag) 476 | result_seq.push(r1); 477 | } 478 | return result_seq; 479 | } 480 | }; 481 | return { "pre" : pre, "core" : core, "post" : post }; 482 | }) 483 | } 484 | 485 | // Monkey patch to avoid overriding window's onmessage handler. 486 | var originalOnMessage = window.onmessage || function () {}; 487 | onmessage = function (event) { 488 | var data; 489 | try { 490 | data = (typeof event.data == "string") ? JSON.parse(event.data) : event.data; 491 | if (data.type === "jquery.facedetection") { 492 | // This is the event that is intended for jquery.facedetection 493 | var scope = { "shared" : data.shared }; 494 | var result = parallable.core[data.name].apply(scope, [data.input, data.id, data.worker]); 495 | try { 496 | postMessage(result); 497 | } catch (e) { 498 | postMessage(JSON.stringify(result)); 499 | } 500 | } else { 501 | // Nope. This is not the event that should be handled by jquery.facedetection 502 | var args = Array.prototype.slice.call(arguments); 503 | originalOnMessage.apply(window, args); 504 | } 505 | } catch (e) { 506 | // `event.data` is string, but too bad it is not in JSON format. 507 | // so just pass it to window's original onmessage handler 508 | var args = Array.prototype.slice.call(arguments); 509 | originalOnMessage.apply(window, args); 510 | } 511 | } 512 | -------------------------------------------------------------------------------- /examples/img/logo.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 341 | 342 | --------------------------------------------------------------------------------