├── README.md └── src ├── about-us.html ├── asm-worker.js ├── cv-asm.js ├── cv-wasm.data ├── cv-wasm.js ├── cv-wasm.wasm ├── cv.data ├── dat.gui.js ├── favicon.ico ├── haar-detector.min.js ├── haar-detector.noDOM.js ├── haar-detector.noDOM.min.js ├── haarcascade_eye.js ├── haarcascade_frontalface_alt.js ├── headshots ├── brian.jpg ├── brianR.png ├── deb.jpg ├── debR.png ├── edie.jpg ├── edieR.png ├── github.png ├── linkedIn.png ├── mark.jpg ├── markR.png └── red_Rect.png ├── index-video.html ├── index.html ├── js-worker.js ├── logoFinal.png ├── main-video.js ├── main.js ├── package.json ├── parallel.min.js ├── styles.css ├── wasm-worker.js └── worker.js /README.md: -------------------------------------------------------------------------------- 1 | WebSight demonstrates a comparison of performance between JavaScript, asm.js, and WebAssembly. A user uploaded static image or live video is displayed for each target. Performance is measured by the length of time it takes to detect face(s) or eyes in the image or video. 2 | 3 | Each target is run in its own web worker. The popular open source computer vision library, OpenCV, was compiled using Emscripten to asm.js and wasm (WebAssembly module) to utilize the Viola-Jones algorithm for object detection. HAAR.js was modified to remove the DOM manipulation, so it could be used in the JavaScript web worker. 4 | 5 | Thanks to WebDSP for the inspiration, and to uscisysarch, njor, and foo123 for giving us a starting point. 6 | 7 | Contributors to this project are BrianJFeldman, DebraD, MarkGeeRomano and YervantB. 8 | -------------------------------------------------------------------------------- /src/about-us.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About Us 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |

Brian Feldman

31 | 39 |
40 |
41 |
42 |
43 | 44 | 45 |
46 |
47 |

Debra Do

48 | 56 |
57 |
58 |
59 |
60 | 61 | 62 |
63 |
64 |

Yervant "Edie" Bastikian

65 | 73 |
74 |
75 |
76 |
77 | 78 | 79 |
80 |
81 |

Mark Romano

82 | 90 |
91 |
92 |
93 |
94 | 102 | -------------------------------------------------------------------------------- /src/asm-worker.js: -------------------------------------------------------------------------------- 1 | var Module = {}; 2 | Module['onRuntimeInitialized'] = function() { 3 | postMessage({msg: 'asm'}); 4 | } 5 | importScripts('cv-asm.js', 'worker.js'); 6 | -------------------------------------------------------------------------------- /src/cv-wasm.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/cv-wasm.data -------------------------------------------------------------------------------- /src/cv-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/cv-wasm.wasm -------------------------------------------------------------------------------- /src/cv.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/cv.data -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/favicon.ico -------------------------------------------------------------------------------- /src/haar-detector.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * HAAR.js Feature Detection Library based on Viola-Jones Haar Detection algorithm 4 | * port of jViolaJones for Java (http://code.google.com/p/jviolajones/) to JavaScript and Node 5 | * 6 | * https://github.com/foo123/HAAR.js 7 | * @version: 0.4.8 8 | * 9 | * Supports parallel "map-reduce" computation both in browser and node using parallel.js library 10 | * https://github.com/adambom/parallel.js (included) 11 | * 12 | **/ 13 | /** 14 | * 15 | * HAAR.js Feature Detection Library based on Viola-Jones Haar Detection algorithm 16 | * port of jViolaJones for Java (http://code.google.com/p/jviolajones/) to JavaScript and Node 17 | * 18 | * https://github.com/foo123/HAAR.js 19 | * @version: 0.4.8 20 | * 21 | * Supports parallel "map-reduce" computation both in browser and node using parallel.js library 22 | * https://github.com/adambom/parallel.js (included) 23 | * 24 | **/ 25 | ! function(e, n, t) { 26 | "use strict"; 27 | var i, a = "object" == typeof module && module.exports, 28 | l = "function" == typeof define && define.amd; 29 | a ? module.exports = (module.$deps = module.$deps || {})[n] = module.$deps[n] || t.call(e, { 30 | NODE: module 31 | }) || 1 : l && "function" == typeof require && "function" == typeof require.specified && require.specified(n) ? define(n, ["require", "exports", "module"], function(n, i, a) { 32 | return t.call(e, { 33 | AMD: a 34 | }) 35 | }) : n in e || (e[n] = i = t.call(e, {}) || 1) && l && define(n, [], function() { 36 | return i 37 | }) 38 | }(this, "HAAR", function(e) { 39 | return ! function(e, n) { 40 | "use strict"; 41 | 42 | function t(e, n, t) { 43 | var i, a, l, r, o, h, u, c = e.length, 44 | s = n * t, 45 | d = new y(s), 46 | g = new f(s), 47 | w = new f(s), 48 | v = new f(s); 49 | for (r = 0, l = 0, i = a = 0; n > r;) u = 4899 * e[l] + 9617 * e[l + 1] + 1868 * e[l + 2] + 8192 >>> 14, u &= 255, i += u, a += u * u, d[r] = u, g[r] = i, w[r] = a, v[r] = u, r++, l += 4; 50 | for (o = 0, h = 1, r = n, l = n << 2, i = a = 0; c > l;) u = 4899 * e[l] + 9617 * e[l + 1] + 1868 * e[l + 2] + 8192 >>> 14, u &= 255, i += u, a += u * u, d[r] = u, g[r] = g[r - n] + i, w[r] = w[r - n] + a, v[r] = v[r + 1 - n] + (u + d[r - n]) + (h > 1 ? v[r - n - n] : 0) + (o > 0 ? v[r - 1 - n] : 0), o++, r++, l += 4, o >= n && (o = 0, h++, i = a = 0); 51 | return { 52 | gray: d, 53 | integral: g, 54 | squares: w, 55 | tilted: v 56 | } 57 | } 58 | 59 | function i(e, n, t) { 60 | var i, a, l, r, o, h, u, c, s, d, g, v = e.length, 61 | x = new y(v), 62 | m = new f(v); 63 | for (i = 0; n > i; i++) x[i] = 0, x[i + n] = 0, x[i + v - n] = 0, x[i + v - n - n] = 0, m[i] = 0, m[i + v - n] = 0; 64 | for (a = 0, l = 0; t > a; a++, l += n) x[0 + l] = 0, x[1 + l] = 0, x[n - 1 + l] = 0, x[n - 2 + l] = 0, m[0 + l] = 0, m[n - 1 + l] = 0; 65 | for (i = 2; n - 2 > i; i++) 66 | for (r = 0, a = 2, l = n << 1; t - 2 > a; a++, l += n) u = i + l, c = u + n, s = c + n, d = u - n, g = d - n, r = 0 + (e[g - 2] << 1) + (e[d - 2] << 2) + (e[u - 2] << 2) + e[u - 2] + (e[c - 2] << 2) + (e[s - 2] << 1) + (e[g - 1] << 2) + (e[d - 1] << 3) + e[d - 1] + (e[u - 1] << 4) - (e[u - 1] << 2) + (e[c - 1] << 3) + e[c - 1] + (e[s - 1] << 2) + (e[g] << 2) + e[g] + (e[d] << 4) - (e[d] << 2) + (e[u] << 4) - e[u] + (e[c] << 4) - (e[c] << 2) + (e[s] << 2) + e[s] + (e[g + 1] << 2) + (e[d + 1] << 3) + e[d + 1] + (e[u + 1] << 4) - (e[u + 1] << 2) + (e[c + 1] << 3) + e[c + 1] + (e[s + 1] << 2) + (e[g + 2] << 1) + (e[d + 2] << 2) + (e[u + 2] << 2) + e[u + 2] + (e[c + 2] << 2) + (e[s + 2] << 1), x[u] = ((103 * r + 8192 & 4294967295) >>> 14 & 255) >>> 0; 67 | for (i = 1; n - 1 > i; i++) 68 | for (a = 1, l = n; t - 1 > a; a++, l += n) u = l + i, c = u + n, d = u - n, o = 0 - x[d - 1] + x[d + 1] - x[u - 1] - x[u - 1] + x[u + 1] + x[u + 1] - x[c - 1] + x[c + 1], h = 0 + x[d - 1] + x[d] + x[d] + x[d + 1] - x[c - 1] - x[c] - x[c] - x[c + 1], m[u] = w(o) + w(h); 69 | for (i = 0, r = 0; n > i;) r += m[i], m[i] = r, i++; 70 | for (i = n, l = 0, r = 0; v > i;) r += m[i], m[i] = m[i - n] + r, i++, l++, l >= n && (l = 0, r = 0); 71 | return m 72 | } 73 | 74 | function a(e, n, t) { 75 | var i, a, r, o, h, u, c, d = e.length, 76 | g = new Array(d), 77 | f = [], 78 | y = 0, 79 | w = !1; 80 | for (r = 0; d > r; r++) g[r] = 0; 81 | for (r = 0; d > r; r++) { 82 | for (w = !1, o = 0; r > o; o++) e[o].almostEqual(e[r]) && (w = !0, g[r] = g[o]); 83 | w || (g[r] = y, y++) 84 | } 85 | for (i = new Array(y), a = new Array(y), r = 0; y > r; r++) i[r] = 0, a[r] = new s; 86 | for (r = 0; d > r; r++) c = g[r], i[c]++, a[c].add(e[r]); 87 | for (r = 0; y > r; r++) h = i[r], h >= n && (u = 1 / (h + h), c = new s(u * (2 * a[r].x + h), u * (2 * a[r].y + h), u * (2 * a[r].width + h), u * (2 * a[r].height + h)), f.push(c)); 88 | for (1 != t && (t = 1 / t), d = f.length, r = 0; d > r; r++) 89 | for (o = r + 1; d > o; o++) !f[r].isInside && f[r].inside(f[o]) ? f[r].isInside = !0 : !f[o].isInside && f[o].inside(f[r]) && (f[o].isInside = !0); 90 | for (r = d; --r >= 0;) f[r].isInside ? f.splice(r, 1) : (1 != t && f[r].scale(t), f[r].round().computeArea()); 91 | return f.sort(l) 92 | } 93 | 94 | function l(e, n) { 95 | return e.area - n.area 96 | } 97 | 98 | function r(e, n) { 99 | return e.index - n.index 100 | } 101 | 102 | function o(e) { 103 | return e[1].length && (e[0] = e[0].concat(e[1]).sort(r)), e[0] 104 | } 105 | 106 | function h(e) { 107 | var n, t, i, a, l, r, o, h, u, c, s, d, g, f, y, w, v, x, m, p, S, I, C, q, R, A, H, b, _, D, P, T, j, x, M, L, W, z, E, k, F, O, $, N, U, V, B, G, J, K, Q, X, Y, Z = Z || Math.sqrt, 108 | en = [], 109 | nn = e.haardata, 110 | tn = nn.stages, 111 | an = e.scaledSelection, 112 | ln = an.width, 113 | rn = an.height, 114 | on = ln * rn, 115 | hn = on - 1, 116 | un = nn.size1, 117 | cn = nn.size2, 118 | sn = an.x, 119 | dn = an.y, 120 | gn = tn.length, 121 | fn = e.cannyLow, 122 | yn = e.cannyHigh, 123 | wn = e.canny, 124 | vn = e.integral, 125 | xn = e.squares, 126 | mn = e.tilted, 127 | pn = e.scale, 128 | Sn = e.increment, 129 | In = e.i || 0, 130 | Cn = e.doCannyPruning; 131 | for (m = 0, p = ln - 1, S = 0, I = on - ln, i = ~~(pn * un), n = ~~(i * Sn), a = ~~(pn * cn), t = ~~(a * Sn), u = a * ln, c = t * ln, l = dn * c, y = ln - i, w = rn - a, C = i * a, q = 1 / C, o = dn, h = l; w > o; o += t, h += c) 132 | for (r = sn; y > r; r += n) 133 | if (s = r - 1 + h - ln, d = s + i, g = s + u, f = g + i, s = 0 > s ? 0 : s > hn ? hn : s, d = 0 > d ? 0 : d > hn ? hn : d, g = 0 > g ? 0 : g > hn ? hn : g, f = 0 > f ? 0 : f > hn ? hn : f, !(Cn && (b = q * (wn[f] - wn[g] - wn[d] + wn[s]), fn > b || b > yn))) { 134 | for (R = q * (vn[f] - vn[g] - vn[d] + vn[s]), A = q * (xn[f] - xn[g] - xn[d] + xn[s]), H = A - R * R, H = H > 1 ? Z(H) : 1, _ = !0, v = 0; gn > v; v++) { 135 | for (D = tn[v], P = D.thres, T = D.trees, j = T.length, Y = 0, x = 0; j > x; x++) 136 | for (W = T[x].feats, M = 0;;) { 137 | if (z = W[M], E = z.rects, k = E.length, F = z.thres, O = 0, z.tilt) 138 | for ($ = 0; k > $; $++) N = E[$], U = r + ~~(pn * N[0]), V = (o - 1 + ~~(pn * N[1])) * ln, B = r + ~~(pn * (N[0] + N[2])), G = (o - 1 + ~~(pn * (N[1] + N[2]))) * ln, J = r + ~~(pn * (N[0] - N[3])), K = (o - 1 + ~~(pn * (N[1] + N[3]))) * ln, Q = r + ~~(pn * (N[0] + N[2] - N[3])), X = (o - 1 + ~~(pn * (N[1] + N[2] + N[3]))) * ln, U = m > U ? m : U > p ? p : U, B = m > B ? m : B > p ? p : B, J = m > J ? m : J > p ? p : J, Q = m > Q ? m : Q > p ? p : Q, V = S > V ? S : V > I ? I : V, G = S > G ? S : G > I ? I : G, K = S > K ? S : K > I ? I : K, X = S > X ? S : X > I ? I : X, O += N[4] * (mn[Q + X] - mn[J + K] - mn[B + G] + mn[U + V]); 139 | else 140 | for ($ = 0; k > $; $++) N = E[$], U = r - 1 + ~~(pn * N[0]), B = r - 1 + ~~(pn * (N[0] + N[2])), V = ln * (o - 1 + ~~(pn * N[1])), G = ln * (o - 1 + ~~(pn * (N[1] + N[3]))), U = m > U ? m : U > p ? p : U, B = m > B ? m : B > p ? p : B, V = S > V ? S : V > I ? I : V, G = S > G ? S : G > I ? I : G, O += N[4] * (vn[B + G] - vn[U + G] - vn[B + V] + vn[U + V]); 141 | if (L = F * H > O * q ? 0 : 1) { 142 | if (z.has_r) { 143 | Y += z.r_val; 144 | break 145 | } 146 | M = z.r_node 147 | } else { 148 | if (z.has_l) { 149 | Y += z.l_val; 150 | break 151 | } 152 | M = z.l_node 153 | } 154 | } 155 | if (_ = Y > P ? !0 : !1, !_) break 156 | } 157 | _ && en.push({ 158 | index: In, 159 | x: r, 160 | y: o, 161 | width: i, 162 | height: a 163 | }) 164 | } 165 | return en 166 | } 167 | 168 | function u(e, n) { 169 | for (var t = 0, i = n.length; i > t; t++) n[t] = new s(n[t]); 170 | e.objects = a(n, e.min_neighbors, e.Ratio, e.Selection), e.Ready = !0, e.onComplete && e.onComplete.call(e) 171 | } 172 | var c, s, d = e.HAAR = { 173 | VERSION: "0.4.8" 174 | }, 175 | g = "prototype", 176 | f = "undefined" != typeof Float32Array ? Float32Array : Array, 177 | y = "undefined" != typeof Uint8Array ? Uint8Array : Array, 178 | w = Math.abs, 179 | v = Math.max, 180 | x = Math.min, 181 | m = (Math.floor, Math.round), 182 | p = (Math.sqrt, Array[g].slice); 183 | c = d.Detector = function(e, n) { 184 | var t = this; 185 | t.haardata = e || null, t.Ready = !1, t.doCannyPruning = !1, t.Canvas = null, t.Selection = null, t.scaledSelection = null, t.objects = null, t.TimeInterval = null, t.DetectInterval = 30, t.Ratio = .5, t.cannyLow = 20, t.cannyHigh = 100, t.Parallel = n || null, t.onComplete = null 186 | }, c[g] = { 187 | constructor: c, 188 | haardata: null, 189 | Canvas: null, 190 | objects: null, 191 | Selection: null, 192 | scaledSelection: null, 193 | Ratio: .5, 194 | origWidth: 0, 195 | origHeight: 0, 196 | width: 0, 197 | height: 0, 198 | DetectInterval: 30, 199 | TimeInterval: null, 200 | doCannyPruning: !1, 201 | cannyLow: 20, 202 | cannyHigh: 100, 203 | canny: null, 204 | integral: null, 205 | squares: null, 206 | tilted: null, 207 | Parallel: null, 208 | Ready: !1, 209 | onComplete: null, 210 | dispose: function() { 211 | var e = this; 212 | return e.DetectInterval && clearTimeout(e.DetectInterval), e.DetectInterval = null, e.TimeInterval = null, e.haardata = null, e.Canvas = null, e.objects = null, e.Selection = null, e.scaledSelection = null, e.Ratio = null, e.origWidth = null, e.origHeight = null, e.width = null, e.height = null, e.doCannyPruning = null, e.cannyLow = null, e.cannyHigh = null, e.canny = null, e.integral = null, e.squares = null, e.tilted = null, e.Parallel = null, e.Ready = null, e.onComplete = null, e 213 | }, 214 | clearCache: function() { 215 | var e = this; 216 | return e.haardata = null, e.canny = null, e.integral = null, e.squares = null, e.tilted = null, e.Selection = null, e.scaledSelection = null, e 217 | }, 218 | cascade: function(e) { 219 | return this.haardata = e || null, this 220 | }, 221 | parallel: function(e) { 222 | return this.Parallel = e || null, this 223 | }, 224 | image: function(e, a, l) { 225 | var r = this; 226 | if (e) { 227 | var o, h, u, c, s, d, g, f, y, w = e instanceof HTMLVideoElement; 228 | r.Canvas || (r.Canvas = l || document.createElement("canvas")), y = r.Canvas, f = r.Ratio = n === a ? .5 : a, r.Ready = !1, c = r.origWidth = w ? e.videoWidth : e.width, s = r.origHeight = w ? e.videoHeight : e.height, d = r.width = y.width = m(f * c), g = r.height = y.height = m(f * s), o = y.getContext("2d"), o.drawImage(e, 0, 0, c, s, 0, 0, d, g), h = o.getImageData(0, 0, d, g), u = t(h.data, h.width, h.height), r.integral = u.integral, r.squares = u.squares, r.tilted = u.tilted, r.canny = i(u.gray, d, g), u.gray = null, u.integral = null, u.squares = null, u.tilted = null, u = null 229 | } 230 | return r 231 | }, 232 | interval: function(e) { 233 | return e > 0 && (this.DetectInterval = e), this 234 | }, 235 | cannyThreshold: function(e) { 236 | return e && n !== e.low && (this.cannyLow = e.low), e && n !== e.high && (this.cannyHigh = e.high), this 237 | }, 238 | select: function() { 239 | var e = p.call(arguments), 240 | n = e.length; 241 | return this.Selection = 1 == n && "auto" == e[0] || !n ? null : s[g].data.apply(new s, e), this 242 | }, 243 | complete: function(e) { 244 | return this.onComplete = e || null, this 245 | }, 246 | detect: function(e, t, i, a, l) { 247 | var c, d, g, f, y = this, 248 | w = y.haardata, 249 | v = w.size1, 250 | m = w.size2, 251 | p = y.width, 252 | S = y.height, 253 | I = y.origWidth, 254 | C = y.origHeight, 255 | q = y.integral, 256 | R = y.squares, 257 | A = y.tilted, 258 | H = y.canny, 259 | b = y.cannyLow, 260 | _ = y.cannyHigh; 261 | y.Selection || (y.Selection = new s(0, 0, I, C)), c = y.Selection, c.x = "auto" == c.x ? 0 : c.x, c.y = "auto" == c.y ? 0 : c.y, c.width = "auto" == c.width ? I : c.width, c.height = "auto" == c.height ? C : c.height, d = y.scaledSelection = c.clone().scale(y.Ratio).round(), e = n === e ? 1 : e, t = n === t ? 1.25 : t, i = n === i ? .5 : i, a = n === a ? 1 : a, l = y.doCannyPruning = n === l ? !0 : l, g = y.maxScale = x(p / v, S / m), f = y.scale = e, y.min_neighbors = a, y.scale_inc = t, y.increment = i, y.Ready = !1; 262 | var D = y.Parallel; 263 | if (D && D.isSupported()) { 264 | var P, T = [], 265 | j = 0; 266 | for (P = e; g >= P; P *= t) T.push({ 267 | haardata: w, 268 | width: p, 269 | height: S, 270 | scaledSelection: { 271 | x: d.x, 272 | y: d.y, 273 | width: d.width, 274 | height: d.height 275 | }, 276 | integral: q, 277 | squares: R, 278 | tilted: A, 279 | doCannyPruning: l, 280 | canny: l ? H : null, 281 | cannyLow: b, 282 | cannyHigh: _, 283 | maxScale: g, 284 | min_neighbors: a, 285 | scale_inc: t, 286 | increment: i, 287 | scale: P, 288 | i: j++ 289 | }); 290 | new D(T, { 291 | synchronous: !1 292 | }).require(r, h, o).map(h).reduce(o).then(function(e) { 293 | u(y, e) 294 | }) 295 | } else { 296 | var M = [], 297 | L = function() { 298 | y.scale <= y.maxScale ? (M = M.concat(h(y)), y.scale *= y.scale_inc, y.TimeInterval = setTimeout(L, y.DetectInterval)) : (clearTimeout(y.TimeInterval), u(y, M)) 299 | }; 300 | y.TimeInterval = setTimeout(L, y.DetectInterval) 301 | } 302 | return y 303 | }, 304 | detectSync: function(e, n, t, i, l) { 305 | var r, o, u = this, 306 | c = u.haardata.size1, 307 | d = u.haardata.size2; 308 | u.Selection || (u.Selection = new s(0, 0, u.origWidth, u.origHeight)), u.Selection.x = "auto" == u.Selection.x ? 0 : u.Selection.x, u.Selection.y = "auto" == u.Selection.y ? 0 : u.Selection.y, u.Selection.width = "auto" == u.Selection.width ? u.origWidth : u.Selection.width, u.Selection.height = "auto" == u.Selection.height ? u.origHeight : u.Selection.height, u.scaledSelection = u.Selection.clone().scale(u.Ratio).round(), e = "undefined" == typeof e ? 1 : e, n = "undefined" == typeof n ? 1.25 : n, t = "undefined" == typeof t ? .5 : t, i = "undefined" == typeof i ? 1 : i, u.doCannyPruning = "undefined" == typeof l ? !0 : l, o = u.maxScale = x(u.width / c, u.height / d), r = u.scale = e, u.min_neighbors = i, u.scale_inc = n, u.increment = t, u.Ready = !1; 309 | for (var g = []; o >= r;) g = g.concat(h(u)), u.scale = r *= n; 310 | for (var f = 0, y = g.length; y > f; f++) g[f] = new s(g[f]); 311 | return u.objects = a(g, u.min_neighbors, u.Ratio, u.Selection), u.Ready = !0, u.objects 312 | } 313 | }, c[g].selection = c[g].select, s = d.Feature = function(e, n, t, i, a) { 314 | this.data(e, n, t, i, a) 315 | }, s[g] = { 316 | constructor: s, 317 | index: 0, 318 | x: 0, 319 | y: 0, 320 | width: 0, 321 | height: 0, 322 | area: 0, 323 | isInside: !1, 324 | data: function(e, n, t, i, a) { 325 | var l = this; 326 | return e && e instanceof s ? l.copy(e) : e && e instanceof Object ? (l.x = e.x || 0, l.y = e.y || 0, l.width = e.width || 0, l.height = e.height || 0, l.index = e.index || 0, l.area = e.area || 0, l.isInside = e.isInside || !1) : (l.x = e || 0, l.y = n || 0, l.width = t || 0, l.height = i || 0, l.index = a || 0, l.area = 0, l.isInside = !1), l 327 | }, 328 | add: function(e) { 329 | var n = this; 330 | return n.x += e.x, n.y += e.y, n.width += e.width, n.height += e.height, n 331 | }, 332 | scale: function(e) { 333 | var n = this; 334 | return n.x *= e, n.y *= e, n.width *= e, n.height *= e, n 335 | }, 336 | round: function() { 337 | var e = this; 338 | return e.x = ~~(e.x + .5), e.y = ~~(e.y + .5), e.width = ~~(e.width + .5), e.height = ~~(e.height + .5), e 339 | }, 340 | computeArea: function() { 341 | var e = this; 342 | return e.area = e.width * e.height, e.area 343 | }, 344 | inside: function(e) { 345 | var n = this; 346 | return !!(n.x >= e.x && n.y >= e.y && n.x + n.width <= e.x + e.width && n.y + n.height <= e.y + e.height) 347 | }, 348 | contains: function(e) { 349 | return e.inside(this) 350 | }, 351 | equal: function(e) { 352 | var n = this; 353 | return !(e.x !== n.x || e.y !== n.y || e.width !== n.width || e.height !== n.height) 354 | }, 355 | almostEqual: function(e) { 356 | var n = this, 357 | t = .2 * v(e.width, n.width), 358 | i = .2 * v(e.height, n.height); 359 | return !!(w(n.x - e.x) <= t && w(n.y - e.y) <= i && w(n.width - e.width) <= t && w(n.height - e.height) <= i) 360 | }, 361 | clone: function() { 362 | var e = this, 363 | n = new s; 364 | return n.x = e.x, n.y = e.y, n.width = e.width, n.height = e.height, n.index = e.index, n.area = e.area, n.isInside = e.isInside, n 365 | }, 366 | copy: function(e) { 367 | var n = this; 368 | return e && e instanceof s && (n.x = e.x, n.y = e.y, n.width = e.width, n.height = e.height, n.index = e.index, n.area = e.area, n.isInside = e.isInside), n 369 | }, 370 | toString: function() { 371 | return ["[ x:", this.x, "y:", this.y, "width:", this.width, "height:", this.height, "]"].join(" ") 372 | } 373 | } 374 | }(e), e.HAAR 375 | }); 376 | // !function(e,n,t){"use strict";var i,a="object"==typeof module&&module.exports,l="function"==typeof define&&define.amd;a?module.exports=(module.$deps=module.$deps||{})[n]=module.$deps[n]||t.call(e,{NODE:module})||1:l&&"function"==typeof require&&"function"==typeof require.specified&&require.specified(n)?define(n,["require","exports","module"],function(n,i,a){return t.call(e,{AMD:a})}):n in e||(e[n]=i=t.call(e,{})||1)&&l&&define(n,[],function(){return i})}(this,"HAAR",function(e){return!function(e,n){"use strict";function t(e,n,t){var i,a,l,r,o,h,u,c=e.length,s=n*t,d=new y(s),g=new f(s),w=new f(s),v=new f(s);for(r=0,l=0,i=a=0;n>r;)u=4899*e[l]+9617*e[l+1]+1868*e[l+2]+8192>>>14,u&=255,i+=u,a+=u*u,d[r]=u,g[r]=i,w[r]=a,v[r]=u,r++,l+=4;for(o=0,h=1,r=n,l=n<<2,i=a=0;c>l;)u=4899*e[l]+9617*e[l+1]+1868*e[l+2]+8192>>>14,u&=255,i+=u,a+=u*u,d[r]=u,g[r]=g[r-n]+i,w[r]=w[r-n]+a,v[r]=v[r+1-n]+(u+d[r-n])+(h>1?v[r-n-n]:0)+(o>0?v[r-1-n]:0),o++,r++,l+=4,o>=n&&(o=0,h++,i=a=0);return{gray:d,integral:g,squares:w,tilted:v}}function i(e,n,t){var i,a,l,r,o,h,u,c,s,d,g,v=e.length,x=new y(v),m=new f(v);for(i=0;n>i;i++)x[i]=0,x[i+n]=0,x[i+v-n]=0,x[i+v-n-n]=0,m[i]=0,m[i+v-n]=0;for(a=0,l=0;t>a;a++,l+=n)x[0+l]=0,x[1+l]=0,x[n-1+l]=0,x[n-2+l]=0,m[0+l]=0,m[n-1+l]=0;for(i=2;n-2>i;i++)for(r=0,a=2,l=n<<1;t-2>a;a++,l+=n)u=i+l,c=u+n,s=c+n,d=u-n,g=d-n,r=0+(e[g-2]<<1)+(e[d-2]<<2)+(e[u-2]<<2)+e[u-2]+(e[c-2]<<2)+(e[s-2]<<1)+(e[g-1]<<2)+(e[d-1]<<3)+e[d-1]+(e[u-1]<<4)-(e[u-1]<<2)+(e[c-1]<<3)+e[c-1]+(e[s-1]<<2)+(e[g]<<2)+e[g]+(e[d]<<4)-(e[d]<<2)+(e[u]<<4)-e[u]+(e[c]<<4)-(e[c]<<2)+(e[s]<<2)+e[s]+(e[g+1]<<2)+(e[d+1]<<3)+e[d+1]+(e[u+1]<<4)-(e[u+1]<<2)+(e[c+1]<<3)+e[c+1]+(e[s+1]<<2)+(e[g+2]<<1)+(e[d+2]<<2)+(e[u+2]<<2)+e[u+2]+(e[c+2]<<2)+(e[s+2]<<1),x[u]=((103*r+8192&4294967295)>>>14&255)>>>0;for(i=1;n-1>i;i++)for(a=1,l=n;t-1>a;a++,l+=n)u=l+i,c=u+n,d=u-n,o=0-x[d-1]+x[d+1]-x[u-1]-x[u-1]+x[u+1]+x[u+1]-x[c-1]+x[c+1],h=0+x[d-1]+x[d]+x[d]+x[d+1]-x[c-1]-x[c]-x[c]-x[c+1],m[u]=w(o)+w(h);for(i=0,r=0;n>i;)r+=m[i],m[i]=r,i++;for(i=n,l=0,r=0;v>i;)r+=m[i],m[i]=m[i-n]+r,i++,l++,l>=n&&(l=0,r=0);return m}function a(e,n,t){var i,a,r,o,h,u,c,d=e.length,g=new Array(d),f=[],y=0,w=!1;for(r=0;d>r;r++)g[r]=0;for(r=0;d>r;r++){for(w=!1,o=0;r>o;o++)e[o].almostEqual(e[r])&&(w=!0,g[r]=g[o]);w||(g[r]=y,y++)}for(i=new Array(y),a=new Array(y),r=0;y>r;r++)i[r]=0,a[r]=new s;for(r=0;d>r;r++)c=g[r],i[c]++,a[c].add(e[r]);for(r=0;y>r;r++)h=i[r],h>=n&&(u=1/(h+h),c=new s(u*(2*a[r].x+h),u*(2*a[r].y+h),u*(2*a[r].width+h),u*(2*a[r].height+h)),f.push(c));for(1!=t&&(t=1/t),d=f.length,r=0;d>r;r++)for(o=r+1;d>o;o++)!f[r].isInside&&f[r].inside(f[o])?f[r].isInside=!0:!f[o].isInside&&f[o].inside(f[r])&&(f[o].isInside=!0);for(r=d;--r>=0;)f[r].isInside?f.splice(r,1):(1!=t&&f[r].scale(t),f[r].round().computeArea());return f.sort(l)}function l(e,n){return e.area-n.area}function r(e,n){return e.index-n.index}function o(e){return e[1].length&&(e[0]=e[0].concat(e[1]).sort(r)),e[0]}function h(e){var n,t,i,a,l,r,o,h,u,c,s,d,g,f,y,w,v,x,m,p,S,I,C,q,R,A,H,b,_,D,P,T,j,x,M,L,W,z,E,k,F,O,$,N,U,V,B,G,J,K,Q,X,Y,Z=Z||Math.sqrt,en=[],nn=e.haardata,tn=nn.stages,an=e.scaledSelection,ln=an.width,rn=an.height,on=ln*rn,hn=on-1,un=nn.size1,cn=nn.size2,sn=an.x,dn=an.y,gn=tn.length,fn=e.cannyLow,yn=e.cannyHigh,wn=e.canny,vn=e.integral,xn=e.squares,mn=e.tilted,pn=e.scale,Sn=e.increment,In=e.i||0,Cn=e.doCannyPruning;for(m=0,p=ln-1,S=0,I=on-ln,i=~~(pn*un),n=~~(i*Sn),a=~~(pn*cn),t=~~(a*Sn),u=a*ln,c=t*ln,l=dn*c,y=ln-i,w=rn-a,C=i*a,q=1/C,o=dn,h=l;w>o;o+=t,h+=c)for(r=sn;y>r;r+=n)if(s=r-1+h-ln,d=s+i,g=s+u,f=g+i,s=0>s?0:s>hn?hn:s,d=0>d?0:d>hn?hn:d,g=0>g?0:g>hn?hn:g,f=0>f?0:f>hn?hn:f,!(Cn&&(b=q*(wn[f]-wn[g]-wn[d]+wn[s]),fn>b||b>yn))){for(R=q*(vn[f]-vn[g]-vn[d]+vn[s]),A=q*(xn[f]-xn[g]-xn[d]+xn[s]),H=A-R*R,H=H>1?Z(H):1,_=!0,v=0;gn>v;v++){for(D=tn[v],P=D.thres,T=D.trees,j=T.length,Y=0,x=0;j>x;x++)for(W=T[x].feats,M=0;;){if(z=W[M],E=z.rects,k=E.length,F=z.thres,O=0,z.tilt)for($=0;k>$;$++)N=E[$],U=r+~~(pn*N[0]),V=(o-1+~~(pn*N[1]))*ln,B=r+~~(pn*(N[0]+N[2])),G=(o-1+~~(pn*(N[1]+N[2])))*ln,J=r+~~(pn*(N[0]-N[3])),K=(o-1+~~(pn*(N[1]+N[3])))*ln,Q=r+~~(pn*(N[0]+N[2]-N[3])),X=(o-1+~~(pn*(N[1]+N[2]+N[3])))*ln,U=m>U?m:U>p?p:U,B=m>B?m:B>p?p:B,J=m>J?m:J>p?p:J,Q=m>Q?m:Q>p?p:Q,V=S>V?S:V>I?I:V,G=S>G?S:G>I?I:G,K=S>K?S:K>I?I:K,X=S>X?S:X>I?I:X,O+=N[4]*(mn[Q+X]-mn[J+K]-mn[B+G]+mn[U+V]);else for($=0;k>$;$++)N=E[$],U=r-1+~~(pn*N[0]),B=r-1+~~(pn*(N[0]+N[2])),V=ln*(o-1+~~(pn*N[1])),G=ln*(o-1+~~(pn*(N[1]+N[3]))),U=m>U?m:U>p?p:U,B=m>B?m:B>p?p:B,V=S>V?S:V>I?I:V,G=S>G?S:G>I?I:G,O+=N[4]*(vn[B+G]-vn[U+G]-vn[B+V]+vn[U+V]);if(L=F*H>O*q?0:1){if(z.has_r){Y+=z.r_val;break}M=z.r_node}else{if(z.has_l){Y+=z.l_val;break}M=z.l_node}}if(_=Y>P?!0:!1,!_)break}_&&en.push({index:In,x:r,y:o,width:i,height:a})}return en}function u(e,n){for(var t=0,i=n.length;i>t;t++)n[t]=new s(n[t]);e.objects=a(n,e.min_neighbors,e.Ratio,e.Selection),e.Ready=!0,e.onComplete&&e.onComplete.call(e)}var c,s,d=e.HAAR={VERSION:"0.4.8"},g="prototype",f="undefined"!=typeof Float32Array?Float32Array:Array,y="undefined"!=typeof Uint8Array?Uint8Array:Array,w=Math.abs,v=Math.max,x=Math.min,m=(Math.floor,Math.round),p=(Math.sqrt,Array[g].slice);c=d.Detector=function(e,n){var t=this;t.haardata=e||null,t.Ready=!1,t.doCannyPruning=!1,t.Canvas=null,t.Selection=null,t.scaledSelection=null,t.objects=null,t.TimeInterval=null,t.DetectInterval=30,t.Ratio=.5,t.cannyLow=20,t.cannyHigh=100,t.Parallel=n||null,t.onComplete=null},c[g]={constructor:c,haardata:null,Canvas:null,objects:null,Selection:null,scaledSelection:null,Ratio:.5,origWidth:0,origHeight:0,width:0,height:0,DetectInterval:30,TimeInterval:null,doCannyPruning:!1,cannyLow:20,cannyHigh:100,canny:null,integral:null,squares:null,tilted:null,Parallel:null,Ready:!1,onComplete:null,dispose:function(){var e=this;return e.DetectInterval&&clearTimeout(e.DetectInterval),e.DetectInterval=null,e.TimeInterval=null,e.haardata=null,e.Canvas=null,e.objects=null,e.Selection=null,e.scaledSelection=null,e.Ratio=null,e.origWidth=null,e.origHeight=null,e.width=null,e.height=null,e.doCannyPruning=null,e.cannyLow=null,e.cannyHigh=null,e.canny=null,e.integral=null,e.squares=null,e.tilted=null,e.Parallel=null,e.Ready=null,e.onComplete=null,e},clearCache:function(){var e=this;return e.haardata=null,e.canny=null,e.integral=null,e.squares=null,e.tilted=null,e.Selection=null,e.scaledSelection=null,e},cascade:function(e){return this.haardata=e||null,this},parallel:function(e){return this.Parallel=e||null,this},image:function(e,a,l){var r=this;if(e){var o,h,u,c,s,d,g,f,y,w=e instanceof HTMLVideoElement;r.Canvas||(r.Canvas=l||document.createElement("canvas")),y=r.Canvas,f=r.Ratio=n===a?.5:a,r.Ready=!1,c=r.origWidth=w?e.videoWidth:e.width,s=r.origHeight=w?e.videoHeight:e.height,d=r.width=y.width=m(f*c),g=r.height=y.height=m(f*s),o=y.getContext("2d"),o.drawImage(e,0,0,c,s,0,0,d,g),h=o.getImageData(0,0,d,g),u=t(h.data,h.width,h.height),r.integral=u.integral,r.squares=u.squares,r.tilted=u.tilted,r.canny=i(u.gray,d,g),u.gray=null,u.integral=null,u.squares=null,u.tilted=null,u=null}return r},interval:function(e){return e>0&&(this.DetectInterval=e),this},cannyThreshold:function(e){return e&&n!==e.low&&(this.cannyLow=e.low),e&&n!==e.high&&(this.cannyHigh=e.high),this},select:function(){var e=p.call(arguments),n=e.length;return this.Selection=1==n&&"auto"==e[0]||!n?null:s[g].data.apply(new s,e),this},complete:function(e){return this.onComplete=e||null,this},detect:function(e,t,i,a,l){var c,d,g,f,y=this,w=y.haardata,v=w.size1,m=w.size2,p=y.width,S=y.height,I=y.origWidth,C=y.origHeight,q=y.integral,R=y.squares,A=y.tilted,H=y.canny,b=y.cannyLow,_=y.cannyHigh;y.Selection||(y.Selection=new s(0,0,I,C)),c=y.Selection,c.x="auto"==c.x?0:c.x,c.y="auto"==c.y?0:c.y,c.width="auto"==c.width?I:c.width,c.height="auto"==c.height?C:c.height,d=y.scaledSelection=c.clone().scale(y.Ratio).round(),e=n===e?1:e,t=n===t?1.25:t,i=n===i?.5:i,a=n===a?1:a,l=y.doCannyPruning=n===l?!0:l,g=y.maxScale=x(p/v,S/m),f=y.scale=e,y.min_neighbors=a,y.scale_inc=t,y.increment=i,y.Ready=!1;var D=y.Parallel;if(D&&D.isSupported()){var P,T=[],j=0;for(P=e;g>=P;P*=t)T.push({haardata:w,width:p,height:S,scaledSelection:{x:d.x,y:d.y,width:d.width,height:d.height},integral:q,squares:R,tilted:A,doCannyPruning:l,canny:l?H:null,cannyLow:b,cannyHigh:_,maxScale:g,min_neighbors:a,scale_inc:t,increment:i,scale:P,i:j++});new D(T,{synchronous:!1}).require(r,h,o).map(h).reduce(o).then(function(e){u(y,e)})}else{var M=[],L=function(){y.scale<=y.maxScale?(M=M.concat(h(y)),y.scale*=y.scale_inc,y.TimeInterval=setTimeout(L,y.DetectInterval)):(clearTimeout(y.TimeInterval),u(y,M))};y.TimeInterval=setTimeout(L,y.DetectInterval)}return y},detectSync:function(e,n,t,i,l){var r,o,u=this,c=u.haardata.size1,d=u.haardata.size2;u.Selection||(u.Selection=new s(0,0,u.origWidth,u.origHeight)),u.Selection.x="auto"==u.Selection.x?0:u.Selection.x,u.Selection.y="auto"==u.Selection.y?0:u.Selection.y,u.Selection.width="auto"==u.Selection.width?u.origWidth:u.Selection.width,u.Selection.height="auto"==u.Selection.height?u.origHeight:u.Selection.height,u.scaledSelection=u.Selection.clone().scale(u.Ratio).round(),e="undefined"==typeof e?1:e,n="undefined"==typeof n?1.25:n,t="undefined"==typeof t?.5:t,i="undefined"==typeof i?1:i,u.doCannyPruning="undefined"==typeof l?!0:l,o=u.maxScale=x(u.width/c,u.height/d),r=u.scale=e,u.min_neighbors=i,u.scale_inc=n,u.increment=t,u.Ready=!1;for(var g=[];o>=r;)g=g.concat(h(u)),u.scale=r*=n;for(var f=0,y=g.length;y>f;f++)g[f]=new s(g[f]);return u.objects=a(g,u.min_neighbors,u.Ratio,u.Selection),u.Ready=!0,u.objects}},c[g].selection=c[g].select,s=d.Feature=function(e,n,t,i,a){this.data(e,n,t,i,a)},s[g]={constructor:s,index:0,x:0,y:0,width:0,height:0,area:0,isInside:!1,data:function(e,n,t,i,a){var l=this;return e&&e instanceof s?l.copy(e):e&&e instanceof Object?(l.x=e.x||0,l.y=e.y||0,l.width=e.width||0,l.height=e.height||0,l.index=e.index||0,l.area=e.area||0,l.isInside=e.isInside||!1):(l.x=e||0,l.y=n||0,l.width=t||0,l.height=i||0,l.index=a||0,l.area=0,l.isInside=!1),l},add:function(e){var n=this;return n.x+=e.x,n.y+=e.y,n.width+=e.width,n.height+=e.height,n},scale:function(e){var n=this;return n.x*=e,n.y*=e,n.width*=e,n.height*=e,n},round:function(){var e=this;return e.x=~~(e.x+.5),e.y=~~(e.y+.5),e.width=~~(e.width+.5),e.height=~~(e.height+.5),e},computeArea:function(){var e=this;return e.area=e.width*e.height,e.area},inside:function(e){var n=this;return!!(n.x>=e.x&&n.y>=e.y&&n.x+n.width<=e.x+e.width&&n.y+n.height<=e.y+e.height)},contains:function(e){return e.inside(this)},equal:function(e){var n=this;return!(e.x!==n.x||e.y!==n.y||e.width!==n.width||e.height!==n.height)},almostEqual:function(e){var n=this,t=.2*v(e.width,n.width),i=.2*v(e.height,n.height);return!!(w(n.x-e.x)<=t&&w(n.y-e.y)<=i&&w(n.width-e.width)<=t&&w(n.height-e.height)<=i)},clone:function(){var e=this,n=new s;return n.x=e.x,n.y=e.y,n.width=e.width,n.height=e.height,n.index=e.index,n.area=e.area,n.isInside=e.isInside,n},copy:function(e){var n=this;return e&&e instanceof s&&(n.x=e.x,n.y=e.y,n.width=e.width,n.height=e.height,n.index=e.index,n.area=e.area,n.isInside=e.isInside),n},toString:function(){return["[ x:",this.x,"y:",this.y,"width:",this.width,"height:",this.height,"]"].join(" ")}}}(e),e.HAAR}); -------------------------------------------------------------------------------- /src/haar-detector.noDOM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * HAAR.js Feature Detection Library based on Viola-Jones Haar Detection algorithm 4 | * port of jViolaJones for Java (http://code.google.com/p/jviolajones/) to JavaScript and Node 5 | * 6 | * https://github.com/foo123/HAAR.js 7 | * @version: @@VERSION@@ 8 | * 9 | * Supports parallel "map-reduce" computation both in browser and node using parallel.js library 10 | * https://github.com/adambom/parallel.js (included) 11 | * 12 | **/ 13 | "use strict"; 14 | 15 | // the export object 16 | var HAAR = { VERSION : "@@VERSION@@" }, Detector, Feature, proto = 'prototype', undef = undefined; 17 | 18 | var // typed arrays substitute 19 | Array32F = (typeof Float32Array !== "undefined") ? Float32Array : Array, 20 | Array8U = (typeof Uint8Array !== "undefined") ? Uint8Array : Array, 21 | /* 22 | Array64F = (typeof Float64Array !== "undefined") ? Float64Array : Array, 23 | Array8I = (typeof Int8Array !== "undefined") ? Int8Array : Array, 24 | Array16I = (typeof Int16Array !== "undefined") ? Int16Array : Array, 25 | Array32I = (typeof Int32Array !== "undefined") ? Int32Array : Array, 26 | Array16U = (typeof Uint16Array !== "undefined") ? Uint16Array : Array, 27 | Array32U = (typeof Uint32Array !== "undefined") ? Uint32Array : Array, 28 | */ 29 | 30 | // math functions brevity 31 | Abs=Math.abs, Max=Math.max, Min=Math.min, Floor=Math.floor, Round=Math.round, Sqrt=Math.sqrt, 32 | slice=Array[proto].slice 33 | ; 34 | 35 | 36 | // 37 | // Private methods for detection 38 | // 39 | 40 | // compute grayscale image, integral image (SAT) and squares image (Viola-Jones) 41 | function integralImage(im, w, h/*, selection*/) 42 | { 43 | var imLen=im.length, count=w*h 44 | , sum, sum2, i, j, k, y, g 45 | , gray = new Array8U(count) 46 | // Viola-Jones 47 | , integral = new Array32F(count), squares = new Array32F(count) 48 | // Lienhart et al. extension using tilted features 49 | , tilted = new Array32F(count) 50 | ; 51 | 52 | // first row 53 | j=0; i=0; sum=sum2=0; 54 | while (j>> 14; 60 | 61 | g &= 255; 62 | sum += g; 63 | sum2 += /*(*/(g*g); //&0xFFFFFFFF) >>> 0; 64 | 65 | // SAT(-1, y) = SAT(x, -1) = SAT(-1, -1) = 0 66 | // SAT(x, y) = SAT(x, y-1) + SAT(x-1, y) + I(x, y) - SAT(x-1, y-1) <-- integral image 67 | 68 | // RSAT(-1, y) = RSAT(x, -1) = RSAT(x, -2) = RSAT(-1, -1) = RSAT(-1, -2) = 0 69 | // RSAT(x, y) = RSAT(x-1, y-1) + RSAT(x+1, y-1) - RSAT(x, y-2) + I(x, y) + I(x, y-1) <-- rotated(tilted) integral image at 45deg 70 | gray[j] = g; 71 | integral[j] = sum; 72 | squares[j] = sum2; 73 | tilted[j] = g; 74 | 75 | j++; i+=4; 76 | } 77 | // other rows 78 | k=0; y=1; j=w; i=(w<<2); sum=sum2=0; 79 | while (i>> 14; 85 | 86 | g &= 255; 87 | sum += g; 88 | sum2 += /*(*/(g*g); //&0xFFFFFFFF) >>> 0; 89 | 90 | // SAT(-1, y) = SAT(x, -1) = SAT(-1, -1) = 0 91 | // SAT(x, y) = SAT(x, y-1) + SAT(x-1, y) + I(x, y) - SAT(x-1, y-1) <-- integral image 92 | 93 | // RSAT(-1, y) = RSAT(x, -1) = RSAT(x, -2) = RSAT(-1, -1) = RSAT(-1, -2) = 0 94 | // RSAT(x, y) = RSAT(x-1, y-1) + RSAT(x+1, y-1) - RSAT(x, y-2) + I(x, y) + I(x, y-1) <-- rotated(tilted) integral image at 45deg 95 | gray[j] = g; 96 | integral[j] = integral[j-w] + sum; 97 | squares[j] = squares[j-w] + sum2; 98 | tilted[j] = tilted[j+1-w] + (g + gray[j-w]) + ((y>1) ? tilted[j-w-w] : 0) + ((k>0) ? tilted[j-1-w] : 0); 99 | 100 | k++; j++; i+=4; if (k>=w) { k=0; y++; sum=sum2=0; } 101 | } 102 | 103 | return {gray:gray, integral:integral, squares:squares, tilted:tilted}; 104 | } 105 | 106 | // compute Canny edges on gray-scale image to speed up detection if possible 107 | function integralCanny(gray, w, h) 108 | { 109 | var i, j, k, sum, grad_x, grad_y, 110 | ind0, ind1, ind2, ind_1, ind_2, count=gray.length, 111 | lowpass = new Array8U(count), canny = new Array32F(count) 112 | ; 113 | 114 | // first, second rows, last, second-to-last rows 115 | for (i=0; i>> 0; 195 | 196 | /* 197 | Original Code 198 | 199 | grad[ind0] = sum/159 = sum*0.0062893081761006; 200 | */ 201 | 202 | // sum of coefficients = 159, factor = 1/159 = 0,0062893081761006 203 | // 2^14 = 16384, 16384/2 = 8192 204 | // 2^14/159 = 103,0440251572322304 =~ 103 +/- 2^13 205 | //grad[ind0] = (( ((sum << 6)&0xFFFFFFFF)>>>0 + ((sum << 5)&0xFFFFFFFF)>>>0 + ((sum << 3)&0xFFFFFFFF)>>>0 + ((8192-sum)&0xFFFFFFFF)>>>0 ) >>> 14) >>> 0; 206 | lowpass[ind0] = ((((103*sum + 8192)&0xFFFFFFFF) >>> 14)&0xFF) >>> 0; 207 | } 208 | } 209 | 210 | // sobel gradient 211 | for (i=1; i=w) { k=0; sum=0; } 261 | } 262 | 263 | return canny; 264 | } 265 | 266 | // merge the detected features if needed 267 | function merge(rects, min_neighbors, ratio, selection) 268 | { 269 | var rlen=rects.length, ref = new Array(rlen), feats=[], 270 | nb_classes = 0, neighbors, r, found=false, i, j, n, t, ri; 271 | 272 | // original code 273 | // find number of neighbour classes 274 | for (i = 0; i < rlen; i++) ref[i] = 0; 275 | for (i = 0; i < rlen; i++) 276 | { 277 | found = false; 278 | for (j = 0; j < i; j++) 279 | { 280 | if (rects[j].almostEqual(rects[i])) 281 | { 282 | found = true; 283 | ref[i] = ref[j]; 284 | } 285 | } 286 | 287 | if (!found) 288 | { 289 | ref[i] = nb_classes; 290 | nb_classes++; 291 | } 292 | } 293 | 294 | // merge neighbor classes 295 | neighbors = new Array(nb_classes); r = new Array(nb_classes); 296 | for (i = 0; i < nb_classes; i++) { neighbors[i] = 0; r[i] = new Feature(); } 297 | for (i = 0; i < rlen; i++) { ri=ref[i]; neighbors[ri]++; r[ri].add(rects[i]); } 298 | for (i = 0; i < nb_classes; i++) 299 | { 300 | n = neighbors[i]; 301 | if (n >= min_neighbors) 302 | { 303 | t=1/(n + n); 304 | ri = new Feature( 305 | t*(r[i].x * 2 + n), t*(r[i].y * 2 + n), 306 | t*(r[i].width * 2 + n), t*(r[i].height * 2 + n) 307 | ); 308 | 309 | feats.push(ri); 310 | } 311 | } 312 | 313 | if (ratio != 1) { ratio=1.0/ratio; } 314 | 315 | // filter inside rectangles 316 | rlen=feats.length; 317 | for (i=0; i= 0) 327 | { 328 | if (feats[i].isInside) 329 | { 330 | feats.splice(i, 1); 331 | } 332 | else 333 | { 334 | // scaled down, scale them back up 335 | if (ratio != 1) feats[i].scale(ratio); 336 | //feats[i].x+=selection.x; feats[i].y+=selection.y; 337 | feats[i].round().computeArea(); 338 | } 339 | } 340 | 341 | // sort according to size 342 | // (a deterministic way to present results under different cases) 343 | return feats.sort(byArea); 344 | } 345 | 346 | // area used as compare func for sorting 347 | function byArea(a, b) { return a.area-b.area; } 348 | 349 | // serial index used as compare func for sorting 350 | function byOrder(a, b) { return a.index-b.index; } 351 | 352 | /* 353 | splice subarray (not used) 354 | http://stackoverflow.com/questions/1348178/a-better-way-to-splice-an-array-into-an-array-in-javascript 355 | Array.prototype.splice.apply(d[0], [prev, 0].concat(d[1])); 356 | */ 357 | 358 | // used for parallel "reduce" computation 359 | function mergeSteps(d) 360 | { 361 | // concat and sort according to serial ordering 362 | if (d[1].length) d[0]=d[0].concat(d[1]).sort(byOrder); 363 | return d[0]; 364 | } 365 | 366 | // used for parallel, asynchronous and/or synchronous computation 367 | function detectSingleStep(self) 368 | { 369 | var Sqrt = Sqrt || Math.sqrt, ret = [], 370 | haar = self.haardata, haar_stages = haar.stages, scaledSelection = self.scaledSelection, 371 | w = scaledSelection.width, h = scaledSelection.height, imArea=w*h, imArea1=imArea-1, 372 | sizex = haar.size1, sizey = haar.size2, xstep, ystep, xsize, ysize, 373 | startx = scaledSelection.x, starty = scaledSelection.y, startty, 374 | x, y, ty, tyw, tys, sl = haar_stages.length, 375 | p0, p1, p2, p3, xl, yl, s, t, 376 | bx1, bx2, by1, by2, 377 | swh, inv_area, total_x, total_x2, vnorm, 378 | edges_density, pass, cL = self.cannyLow, cH = self.cannyHigh, 379 | 380 | stage, threshold, trees, tl, 381 | canny = self.canny, integral = self.integral, squares = self.squares, tilted = self.tilted, 382 | t, cur_node_ind, where, features, feature, rects, nb_rects, thresholdf, 383 | rect_sum, kr, r, x1, y1, x2, y2, x3, y3, x4, y4, rw, rh, yw, yh, sum, 384 | scale = self.scale, increment = self.increment, index = self.i||0, doCanny = self.doCannyPruning 385 | 386 | ; 387 | 388 | bx1=0; bx2=w-1; by1=0; by2=imArea-w; 389 | 390 | xsize = ~~(scale * sizex); 391 | xstep = ~~(xsize * increment); 392 | ysize = ~~(scale * sizey); 393 | ystep = ~~(ysize * increment); 394 | //ysize = xsize; ystep = xstep; 395 | tyw = ysize*w; 396 | tys = ystep*w; 397 | startty = starty*tys; 398 | xl = w-xsize; 399 | yl = h-ysize; 400 | swh = xsize*ysize; 401 | inv_area = 1.0/swh; 402 | 403 | 404 | for (y=starty, ty=startty; yimArea1) ? imArea1 : p0; 413 | p1 = (p1<0) ? 0 : (p1>imArea1) ? imArea1 : p1; 414 | p2 = (p2<0) ? 0 : (p2>imArea1) ? imArea1 : p2; 415 | p3 = (p3<0) ? 0 : (p3>imArea1) ? imArea1 : p3; 416 | 417 | if (doCanny) 418 | { 419 | // avoid overflow 420 | edges_density = inv_area * (canny[p3] - canny[p2] - canny[p1] + canny[p0]); 421 | if (edges_density < cL || edges_density > cH) continue; 422 | } 423 | 424 | // pre-compute some values for speed 425 | 426 | // avoid overflow 427 | total_x = inv_area * (integral[p3] - integral[p2] - integral[p1] + integral[p0]); 428 | // avoid overflow 429 | total_x2 = inv_area * (squares[p3] - squares[p2] - squares[p1] + squares[p0]); 430 | 431 | vnorm = total_x2 - total_x * total_x; 432 | vnorm = (vnorm > 1) ? Sqrt(vnorm) : /*vnorm*/ 1 ; 433 | 434 | pass = true; 435 | for (s = 0; s < sl; s++) 436 | { 437 | // Viola-Jones HAAR-Stage evaluator 438 | stage = haar_stages[s]; 439 | threshold = stage.thres; 440 | trees = stage.trees; tl = trees.length; 441 | sum=0; 442 | 443 | for (t = 0; t < tl; t++) 444 | { 445 | // 446 | // inline the tree and leaf evaluators to avoid function calls per-loop (faster) 447 | // 448 | 449 | // Viola-Jones HAAR-Tree evaluator 450 | features = trees[t].feats; 451 | cur_node_ind = 0; 452 | while (true) 453 | { 454 | feature = features[cur_node_ind]; 455 | 456 | // Viola-Jones HAAR-Leaf evaluator 457 | rects = feature.rects; 458 | nb_rects = rects.length; 459 | thresholdf = feature.thres; 460 | rect_sum = 0; 461 | 462 | if (feature.tilt) 463 | { 464 | // tilted rectangle feature, Lienhart et al. extension 465 | for (kr = 0; kr < nb_rects; kr++) 466 | { 467 | r = rects[kr]; 468 | 469 | // this produces better/larger features, possible rounding effects?? 470 | x1 = x + ~~(scale * r[0]); 471 | y1 = (y-1 + ~~(scale * r[1])) * w; 472 | x2 = x + ~~(scale * (r[0] + r[2])); 473 | y2 = (y-1 + ~~(scale * (r[1] + r[2]))) * w; 474 | x3 = x + ~~(scale * (r[0] - r[3])); 475 | y3 = (y-1 + ~~(scale * (r[1] + r[3]))) * w; 476 | x4 = x + ~~(scale * (r[0] + r[2] - r[3])); 477 | y4 = (y-1 + ~~(scale * (r[1] + r[2] + r[3]))) * w; 478 | 479 | // clamp 480 | x1 = (x1bx2) ? bx2 : x1; 481 | x2 = (x2bx2) ? bx2 : x2; 482 | x3 = (x3bx2) ? bx2 : x3; 483 | x4 = (x4bx2) ? bx2 : x4; 484 | y1 = (y1by2) ? by2 : y1; 485 | y2 = (y2by2) ? by2 : y2; 486 | y3 = (y3by2) ? by2 : y3; 487 | y4 = (y4by2) ? by2 : y4; 488 | 489 | // RSAT(x-h+w, y+w+h-1) + RSAT(x, y-1) - RSAT(x-h, y+h-1) - RSAT(x+w, y+w-1) 490 | // x4 y4 x1 y1 x3 y3 x2 y2 491 | rect_sum+= r[4] * (tilted[x4 + y4] - tilted[x3 + y3] - tilted[x2 + y2] + tilted[x1 + y1]); 492 | } 493 | } 494 | else 495 | { 496 | // orthogonal rectangle feature, Viola-Jones original 497 | for (kr = 0; kr < nb_rects; kr++) 498 | { 499 | r = rects[kr]; 500 | 501 | // this produces better/larger features, possible rounding effects?? 502 | x1 = x-1 + ~~(scale * r[0]); 503 | x2 = x-1 + ~~(scale * (r[0] + r[2])); 504 | y1 = (w) * (y-1 + ~~(scale * r[1])); 505 | y2 = (w) * (y-1 + ~~(scale * (r[1] + r[3]))); 506 | 507 | // clamp 508 | x1 = (x1bx2) ? bx2 : x1; 509 | x2 = (x2bx2) ? bx2 : x2; 510 | y1 = (y1by2) ? by2 : y1; 511 | y2 = (y2by2) ? by2 : y2; 512 | 513 | // SAT(x-1, y-1) + SAT(x+w-1, y+h-1) - SAT(x-1, y+h-1) - SAT(x+w-1, y-1) 514 | // x1 y1 x2 y2 x1 y1 x2 y1 515 | rect_sum+= r[4] * (integral[x2 + y2] - integral[x1 + y2] - integral[x2 + y1] + integral[x1 + y1]); 516 | } 517 | } 518 | 519 | where = (rect_sum * inv_area < thresholdf * vnorm) ? 0 : 1; 520 | // END Viola-Jones HAAR-Leaf evaluator 521 | 522 | if (where) 523 | { 524 | if (feature.has_r) { sum += feature.r_val; break; } 525 | else { cur_node_ind = feature.r_node; } 526 | } 527 | else 528 | { 529 | if (feature.has_l) { sum += feature.l_val; break; } 530 | else { cur_node_ind = feature.l_node; } 531 | } 532 | } 533 | // END Viola-Jones HAAR-Tree evaluator 534 | 535 | } 536 | pass = (sum > threshold) ? true : false; 537 | // END Viola-Jones HAAR-Stage evaluator 538 | 539 | if (!pass) break; 540 | } 541 | 542 | if (pass) 543 | { 544 | ret.push({ 545 | index: index, 546 | x: x, y: y, 547 | width: xsize, height: ysize 548 | }); 549 | } 550 | } 551 | } 552 | 553 | // return any features found in this step 554 | return ret; 555 | } 556 | 557 | // called when detection ends, calls user-defined callback if any 558 | function detectEnd(self, rects) 559 | { 560 | for (var i=0, l=rects.length; i0) this.DetectInterval = it; 788 | return this; 789 | }, 790 | 791 | // customize canny prunning thresholds for best results 792 | /**[DOC_MARKDOWN] 793 | * __cannyThreshold({low: lowThreshold, high: highThreshold})__ 794 | * ```javascript 795 | * detector.cannyThreshold({low: lowThreshold, high: highThreshold}); 796 | * ``` 797 | * 798 | * Set the thresholds when Canny Pruning is used, for extra fine-tuning. 799 | * Canny Pruning detects the number/density of edges in a given region. A region with too few or too many edges is unlikely to be a feature. 800 | * Default values work fine in most cases, however depending on image size and the specific feature, some fine tuning could be needed 801 | * 802 | * __Explanation of parameters__ 803 | * 804 | * * _low_ : (Optional) The low threshold (default __20__ ). 805 | * * _high_ : (Optional) The high threshold (default __100__ ). 806 | [/DOC_MARKDOWN]**/ 807 | cannyThreshold: function(thres) { 808 | (thres && undef!==thres.low) && (this.cannyLow = thres.low); 809 | (thres && undef!==thres.high) && (this.cannyHigh = thres.high); 810 | return this; 811 | }, 812 | 813 | // set custom detection region as selection 814 | /**[DOC_MARKDOWN] 815 | * __select|selection('auto'|object|feature|x [,y, width, height])__ 816 | * ```javascript 817 | * detector.selection('auto'|object|feature|x [,y, width, height]); 818 | * ``` 819 | * 820 | * Allow to set a custom region in the image to confine the detection process only in that region (eg detect nose while face already detected) 821 | * 822 | * __Explanation of parameters__ 823 | * 824 | * * _1st parameter_ : This can be the string 'auto' which sets the whole image as the selection, or an object ie: {x:10, y:'auto', width:100, height:'auto'} (every param set as 'auto' will take the default image value) or a detection rectangle/feature, or a x coordinate (along with rest coordinates). 825 | * * _y_ : (Optional) the selection start y coordinate, can be an actual value or 'auto' (y=0) 826 | * * _width_ : (Optional) the selection width, can be an actual value or 'auto' (width=image.width) 827 | * * _height_ : (Optional) the selection height, can be an actual value or 'auto' (height=image.height) 828 | * 829 | * The actual selection rectangle/feature is available as this.Selection or detector.Selection 830 | [/DOC_MARKDOWN]**/ 831 | select: function(/* ..variable args here.. */) { 832 | var args = slice.call(arguments), argslen=args.length; 833 | 834 | if (1==argslen && 'auto'==args[0] || !argslen) this.Selection = null; 835 | else this.Selection = Feature[proto].data.apply(new Feature(), args); 836 | return this; 837 | }, 838 | 839 | // detector on complete callback 840 | /**[DOC_MARKDOWN] 841 | * __complete(callback)__ 842 | * ```javascript 843 | * detector.complete(callback); 844 | * ``` 845 | * 846 | * Set the callback handler when detection completes (for parallel and asynchronous detection) 847 | * 848 | * __Explanation of parameters__ 849 | * 850 | * * _callback_ : The user-defined callback function (will be called within the detectors scope, the value of 'this' will be the detector instance). 851 | [/DOC_MARKDOWN]**/ 852 | complete: function(callback) { 853 | this.onComplete = callback || null; 854 | return this; 855 | }, 856 | 857 | // Detector detect method to start detection 858 | /**[DOC_MARKDOWN] 859 | * __detect(baseScale, scale_inc, increment, min_neighbors, doCannyPruning)__ 860 | * ```javascript 861 | * detector.detect(baseScale, scale_inc, increment, min_neighbors, doCannyPruning); 862 | * ``` 863 | * 864 | * __Explanation of parameters__ ([JViolaJones Parameters](http://code.google.com/p/jviolajones/wiki/Parameters)) 865 | * 866 | * * *baseScale* : The initial ratio between the window size and the Haar classifier size (default __1__ ). 867 | * * *scale_inc* : The scale increment of the window size, at each step (default __1.25__ ). 868 | * * *increment* : The shift of the window at each sub-step, in terms of percentage of the window size (default __0.5__ ). 869 | * * *min_neighbors* : The minimum numbers of similar rectangles needed for the region to be considered as a feature (avoid noise) (default __1__ ) 870 | * * *doCannyPruning* : enable Canny Pruning to pre-detect regions unlikely to contain features, in order to speed up the execution (optional default __true__ ). 871 | [/DOC_MARKDOWN]**/ 872 | detect: function(baseScale, scale_inc, increment, min_neighbors, doCannyPruning) { 873 | var self = this; 874 | var haardata = self.haardata, 875 | sizex = haardata.size1, sizey = haardata.size2, 876 | selection, scaledSelection, 877 | width = self.width, height = self.height, 878 | origWidth = self.origWidth, origHeight = self.origHeight, 879 | maxScale, scale, 880 | integral = self.integral, squares = self.squares, tilted = self.tilted, canny = self.canny, 881 | cannyLow = self.cannyLow, cannyHigh = self.cannyHigh 882 | ; 883 | 884 | if (!self.Selection) self.Selection = new Feature(0, 0, origWidth, origHeight); 885 | selection = self.Selection; 886 | selection.x = ('auto'==selection.x) ? 0 : selection.x; 887 | selection.y = ('auto'==selection.y) ? 0 : selection.y; 888 | selection.width = ('auto'==selection.width) ? origWidth : selection.width; 889 | selection.height = ('auto'==selection.height) ? origHeight : selection.height; 890 | scaledSelection = self.scaledSelection = selection.clone().scale(self.Ratio).round(); 891 | 892 | baseScale = (undef === baseScale) ? 1.0 : baseScale; 893 | scale_inc = (undef === scale_inc) ? 1.25 : scale_inc; 894 | increment = (undef === increment) ? 0.5 : increment; 895 | min_neighbors = (undef === min_neighbors) ? 1 : min_neighbors; 896 | doCannyPruning = self.doCannyPruning = (undef === doCannyPruning) ? true : doCannyPruning; 897 | 898 | maxScale = self.maxScale = Min(width/sizex, height/sizey); 899 | scale = self.scale = baseScale; 900 | self.min_neighbors = min_neighbors; 901 | self.scale_inc = scale_inc; 902 | self.increment = increment; 903 | self.Ready = false; 904 | 905 | // needs parallel.js library (included) 906 | var parallel = self.Parallel; 907 | if (parallel && parallel.isSupported()) 908 | { 909 | var data=[], sc, i=0; 910 | 911 | for (sc=baseScale; sc<=maxScale; sc*=scale_inc) 912 | { 913 | data.push({ 914 | haardata : haardata, 915 | 916 | width : width, 917 | height : height, 918 | scaledSelection : {x:scaledSelection.x, y:scaledSelection.y, width:scaledSelection.width, height:scaledSelection.height}, 919 | 920 | integral : integral, 921 | squares : squares, 922 | tilted : tilted, 923 | 924 | doCannyPruning : doCannyPruning, 925 | canny : (doCannyPruning) ? canny : null, 926 | cannyLow : cannyLow, 927 | cannyHigh : cannyHigh, 928 | 929 | maxScale : maxScale, 930 | min_neighbors : min_neighbors, 931 | scale_inc : scale_inc, 932 | increment : increment, 933 | scale : sc, // current scale to check 934 | i : i++ // serial ordering 935 | }); 936 | } 937 | 938 | // needs parallel.js library (included) 939 | // parallelize the detection, using map-reduce 940 | // should also work in Nodejs (using child processes) 941 | new parallel(data, {synchronous: false}) 942 | .require( byOrder, detectSingleStep, mergeSteps ) 943 | .map( detectSingleStep ).reduce( mergeSteps ) 944 | .then( function(rects){ detectEnd(self, rects); } ) 945 | ; 946 | } 947 | else 948 | { 949 | // else detect asynchronously using fixed intervals 950 | var rects = [], 951 | detectAsync = function() { 952 | if (self.scale <= self.maxScale) 953 | { 954 | rects = rects.concat( detectSingleStep(self) ); 955 | // increase scale 956 | self.scale *= self.scale_inc; 957 | self.TimeInterval = setTimeout(detectAsync, self.DetectInterval); 958 | } 959 | else 960 | { 961 | clearTimeout( self.TimeInterval ); 962 | detectEnd(self, rects); 963 | } 964 | } 965 | ; 966 | 967 | self.TimeInterval = setTimeout(detectAsync, self.DetectInterval); 968 | } 969 | return self; 970 | }, 971 | 972 | /**[DOC_MARKDOWN] 973 | * __detectSync(baseScale, scale_inc, increment, min_neighbors, doCannyPruning)__ 974 | * ```javascript 975 | * var features = detector.detectSync(baseScale, scale_inc, increment, min_neighbors, doCannyPruning); 976 | * ``` 977 | * 978 | * Run detector synchronously in one step, instead of parallel or asynchronously. Can be useful when immediate results are needed. Due to massive amount of processing the UI thread may be blocked. 979 | * 980 | * __Explanation of parameters__ (similar to *detect* method) 981 | * 982 | [/DOC_MARKDOWN]**/ 983 | detectSync: function(baseScale, scale_inc, increment, min_neighbors, doCannyPruning) { 984 | var self = this, scale, maxScale, 985 | sizex = self.haardata.size1, sizey = self.haardata.size2; 986 | 987 | if (!self.Selection) self.Selection = new Feature(0, 0, self.origWidth, self.origHeight); 988 | self.Selection.x = ('auto'==self.Selection.x) ? 0 : self.Selection.x; 989 | self.Selection.y = ('auto'==self.Selection.y) ? 0 : self.Selection.y; 990 | self.Selection.width = ('auto'==self.Selection.width) ? self.origWidth : self.Selection.width; 991 | self.Selection.height = ('auto'==self.Selection.height) ? self.origHeight : self.Selection.height; 992 | self.scaledSelection = self.Selection.clone().scale(self.Ratio).round(); 993 | 994 | baseScale = (typeof baseScale == 'undefined') ? 1.0 : baseScale; 995 | scale_inc = (typeof scale_inc == 'undefined') ? 1.25 : scale_inc; 996 | increment = (typeof increment == 'undefined') ? 0.5 : increment; 997 | min_neighbors = (typeof min_neighbors == 'undefined') ? 1 : min_neighbors; 998 | self.doCannyPruning = (typeof doCannyPruning == 'undefined') ? true : doCannyPruning; 999 | 1000 | maxScale = self.maxScale = Min(self.width/sizex, self.height/sizey); 1001 | scale = self.scale = baseScale; 1002 | self.min_neighbors = min_neighbors; 1003 | self.scale_inc = scale_inc; 1004 | self.increment = increment; 1005 | self.Ready = false; 1006 | 1007 | // detect synchronously 1008 | var rects = []; 1009 | // detection loop 1010 | while (scale <= maxScale) 1011 | { 1012 | rects = rects.concat( detectSingleStep(self) ); 1013 | // increase scale 1014 | self.scale = scale *= scale_inc; 1015 | } 1016 | 1017 | // merge any features found 1018 | for (var i=0, l=rects.length; i= f.x) && 1118 | (self.y >= f.y) && 1119 | (self.x+self.width <= f.x+f.width) && 1120 | (self.y+self.height <= f.y+f.height) 1121 | ); 1122 | }, 1123 | 1124 | contains: function(f) { 1125 | return f.inside(this); 1126 | }, 1127 | 1128 | equal: function(f) { 1129 | var self = this; 1130 | return !!( 1131 | (f.x === self.x) && 1132 | (f.y === self.y) && 1133 | (f.width === self.width) && 1134 | (f.height === self.height) 1135 | ); 1136 | }, 1137 | 1138 | almostEqual: function(f) { 1139 | var self = this, d1=Max(f.width, self.width)*0.2, d2=Max(f.height, self.height)*0.2; 1140 | //var d1=Max(f.width, this.width)*0.5, d2=Max(f.height, this.height)*0.5; 1141 | //var d2=d1=Max(f.width, this.width, f.height, this.height)*0.4; 1142 | return !!( 1143 | Abs(self.x-f.x) <= d1 && 1144 | Abs(self.y-f.y) <= d2 && 1145 | Abs(self.width-f.width) <= d1 && 1146 | Abs(self.height-f.height) <= d2 1147 | ); 1148 | }, 1149 | 1150 | clone: function() { 1151 | var self = this, f = new Feature(); 1152 | f.x = self.x; 1153 | f.y = self.y; 1154 | f.width = self.width; 1155 | f.height = self.height; 1156 | f.index = self.index; 1157 | f.area = self.area; 1158 | f.isInside = self.isInside; 1159 | return f; 1160 | }, 1161 | 1162 | copy: function(f) { 1163 | var self = this; 1164 | if ( f && (f instanceof Feature) ) 1165 | { 1166 | self.x = f.x; 1167 | self.y = f.y; 1168 | self.width = f.width; 1169 | self.height = f.height; 1170 | self.index = f.index; 1171 | self.area = f.area; 1172 | self.isInside = f.isInside; 1173 | } 1174 | return self; 1175 | }, 1176 | 1177 | toString: function() { 1178 | return ['[ x:', this.x, 'y:', this.y, 'width:', this.width, 'height:', this.height, ']'].join(' '); 1179 | } 1180 | }; 1181 | -------------------------------------------------------------------------------- /src/haar-detector.noDOM.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function integralImage(im,w,h){var sum,sum2,i,j,k,y,g,imLen=im.length,count=w*h,gray=new Array8U(count),integral=new Array32F(count),squares=new Array32F(count),tilted=new Array32F(count);for(j=0,i=0,sum=sum2=0;j>>14,sum+=g&=255,sum2+=g*g,gray[j]=g,integral[j]=sum,squares[j]=sum2,tilted[j]=g,j++,i+=4;for(k=0,y=1,j=w,i=w<<2,sum=sum2=0;i>>14,sum+=g&=255,sum2+=g*g,gray[j]=g,integral[j]=integral[j-w]+sum,squares[j]=squares[j-w]+sum2,tilted[j]=tilted[j+1-w]+(g+gray[j-w])+(y>1?tilted[j-w-w]:0)+(k>0?tilted[j-1-w]:0),j++,i+=4,++k>=w&&(k=0,y++,sum=sum2=0);return{gray:gray,integral:integral,squares:squares,tilted:tilted}}function integralCanny(gray,w,h){var i,j,k,sum,grad_x,grad_y,ind0,ind1,ind2,ind_1,ind_2,count=gray.length,lowpass=new Array8U(count),canny=new Array32F(count);for(i=0;i>>14&255)>>>0;for(i=1;i=w&&(k=0,sum=0);return canny}function merge(rects,min_neighbors,ratio,selection){var neighbors,r,i,j,n,t,ri,rlen=rects.length,ref=new Array(rlen),feats=[],nb_classes=0,found=!1;for(i=0;i=min_neighbors&&(ri=new Feature((t=1/(n+n))*(2*r[i].x+n),t*(2*r[i].y+n),t*(2*r[i].width+n),t*(2*r[i].height+n)),feats.push(ri));for(1!=ratio&&(ratio=1/ratio),rlen=feats.length,i=0;i=0;)feats[i].isInside?feats.splice(i,1):(1!=ratio&&feats[i].scale(ratio),feats[i].round().computeArea());return feats.sort(byArea)}function byArea(a,b){return a.area-b.area}function byOrder(a,b){return a.index-b.index}function mergeSteps(d){return d[1].length&&(d[0]=d[0].concat(d[1]).sort(byOrder)),d[0]}function detectSingleStep(self){var xstep,ystep,xsize,ysize,x,y,ty,tyw,tys,p0,p1,p2,p3,xl,yl,s,bx2,by2,inv_area,total_x,vnorm,edges_density,pass,stage,threshold,trees,tl,t,cur_node_ind,features,feature,rects,nb_rects,thresholdf,rect_sum,kr,r,x1,y1,x2,y2,x3,y3,x4,y4,sum,Sqrt=Sqrt||Math.sqrt,ret=[],haar=self.haardata,haar_stages=haar.stages,scaledSelection=self.scaledSelection,w=scaledSelection.width,h=scaledSelection.height,imArea=w*h,imArea1=imArea-1,sizex=haar.size1,sizey=haar.size2,startx=scaledSelection.x,starty=scaledSelection.y,sl=haar_stages.length,cL=self.cannyLow,cH=self.cannyHigh,canny=self.canny,integral=self.integral,squares=self.squares,tilted=self.tilted,scale=self.scale,increment=self.increment,index=self.i||0,doCanny=self.doCannyPruning;for(0,bx2=w-1,0,by2=imArea-w,xstep=~~((xsize=~~(scale*sizex))*increment),tyw=(ysize=~~(scale*sizey))*w,xl=w-xsize,yl=h-ysize,inv_area=1/(xsize*ysize),y=starty,ty=starty*(tys=(ystep=~~(ysize*increment))*w);yimArea1?imArea1:p0,p1=p1<0?0:p1>imArea1?imArea1:p1,p2=p2<0?0:p2>imArea1?imArea1:p2,p3=p3<0?0:p3>imArea1?imArea1:p3,!doCanny||!((edges_density=inv_area*(canny[p3]-canny[p2]-canny[p1]+canny[p0]))cH)){for(total_x=inv_area*(integral[p3]-integral[p2]-integral[p1]+integral[p0]),vnorm=(vnorm=inv_area*(squares[p3]-squares[p2]-squares[p1]+squares[p0])-total_x*total_x)>1?Sqrt(vnorm):1,pass=!0,s=0;sbx2?bx2:x1,x2=x2<0?0:x2>bx2?bx2:x2,x3=x3<0?0:x3>bx2?bx2:x3,x4=x4<0?0:x4>bx2?bx2:x4,y1=y1<0?0:y1>by2?by2:y1,y2=y2<0?0:y2>by2?by2:y2,y3=y3<0?0:y3>by2?by2:y3,y4=y4<0?0:y4>by2?by2:y4,rect_sum+=r[4]*(tilted[x4+y4]-tilted[x3+y3]-tilted[x2+y2]+tilted[x1+y1]);else for(kr=0;krbx2?bx2:x1,x2=x2<0?0:x2>bx2?bx2:x2,y1=y1<0?0:y1>by2?by2:y1,y2=y2<0?0:y2>by2?by2:y2,rect_sum+=r[4]*(integral[x2+y2]-integral[x1+y2]-integral[x2+y1]+integral[x1+y1]);if(rect_sum*inv_areathreshold))break}pass&&ret.push({index:index,x:x,y:y,width:xsize,height:ysize})}return ret}function detectEnd(self,rects){for(var i=0,l=rects.length;i0&&(this.DetectInterval=it),this},cannyThreshold:function(thres){return thres&&undef!==thres.low&&(this.cannyLow=thres.low),thres&&undef!==thres.high&&(this.cannyHigh=thres.high),this},select:function(){var args=slice.call(arguments),argslen=args.length;return 1==argslen&&"auto"==args[0]||!argslen?this.Selection=null:this.Selection=Feature[proto].data.apply(new Feature,args),this},complete:function(callback){return this.onComplete=callback||null,this},detect:function(baseScale,scale_inc,increment,min_neighbors,doCannyPruning){var selection,scaledSelection,maxScale,self=this,haardata=self.haardata,sizex=haardata.size1,sizey=haardata.size2,width=self.width,height=self.height,origWidth=self.origWidth,origHeight=self.origHeight,integral=self.integral,squares=self.squares,tilted=self.tilted,canny=self.canny,cannyLow=self.cannyLow,cannyHigh=self.cannyHigh;self.Selection||(self.Selection=new Feature(0,0,origWidth,origHeight)),(selection=self.Selection).x="auto"==selection.x?0:selection.x,selection.y="auto"==selection.y?0:selection.y,selection.width="auto"==selection.width?origWidth:selection.width,selection.height="auto"==selection.height?origHeight:selection.height,scaledSelection=self.scaledSelection=selection.clone().scale(self.Ratio).round(),baseScale=undef===baseScale?1:baseScale,scale_inc=undef===scale_inc?1.25:scale_inc,increment=undef===increment?.5:increment,min_neighbors=undef===min_neighbors?1:min_neighbors,doCannyPruning=self.doCannyPruning=undef===doCannyPruning||doCannyPruning,maxScale=self.maxScale=Min(width/sizex,height/sizey),self.scale=baseScale,self.min_neighbors=min_neighbors,self.scale_inc=scale_inc,self.increment=increment,self.Ready=!1;var parallel=self.Parallel;if(parallel&¶llel.isSupported()){var sc,data=[],i=0;for(sc=baseScale;sc<=maxScale;sc*=scale_inc)data.push({haardata:haardata,width:width,height:height,scaledSelection:{x:scaledSelection.x,y:scaledSelection.y,width:scaledSelection.width,height:scaledSelection.height},integral:integral,squares:squares,tilted:tilted,doCannyPruning:doCannyPruning,canny:doCannyPruning?canny:null,cannyLow:cannyLow,cannyHigh:cannyHigh,maxScale:maxScale,min_neighbors:min_neighbors,scale_inc:scale_inc,increment:increment,scale:sc,i:i++});new parallel(data,{synchronous:!1}).require(byOrder,detectSingleStep,mergeSteps).map(detectSingleStep).reduce(mergeSteps).then(function(rects){detectEnd(self,rects)})}else{var rects=[],detectAsync=function(){self.scale<=self.maxScale?(rects=rects.concat(detectSingleStep(self)),self.scale*=self.scale_inc,self.TimeInterval=setTimeout(detectAsync,self.DetectInterval)):(clearTimeout(self.TimeInterval),detectEnd(self,rects))};self.TimeInterval=setTimeout(detectAsync,self.DetectInterval)}return self},detectSync:function(baseScale,scale_inc,increment,min_neighbors,doCannyPruning){var scale,maxScale,self=this,sizex=self.haardata.size1,sizey=self.haardata.size2;self.Selection||(self.Selection=new Feature(0,0,self.origWidth,self.origHeight)),self.Selection.x="auto"==self.Selection.x?0:self.Selection.x,self.Selection.y="auto"==self.Selection.y?0:self.Selection.y,self.Selection.width="auto"==self.Selection.width?self.origWidth:self.Selection.width,self.Selection.height="auto"==self.Selection.height?self.origHeight:self.Selection.height,self.scaledSelection=self.Selection.clone().scale(self.Ratio).round(),baseScale=void 0===baseScale?1:baseScale,scale_inc=void 0===scale_inc?1.25:scale_inc,increment=void 0===increment?.5:increment,min_neighbors=void 0===min_neighbors?1:min_neighbors,self.doCannyPruning=void 0===doCannyPruning||doCannyPruning,maxScale=self.maxScale=Min(self.width/sizex,self.height/sizey),scale=self.scale=baseScale,self.min_neighbors=min_neighbors,self.scale_inc=scale_inc,self.increment=increment,self.Ready=!1;for(var rects=[];scale<=maxScale;)rects=rects.concat(detectSingleStep(self)),self.scale=scale*=scale_inc;for(var i=0,l=rects.length;i=f.x&&self.y>=f.y&&self.x+self.width<=f.x+f.width&&self.y+self.height<=f.y+f.height)},contains:function(f){return f.inside(this)},equal:function(f){var self=this;return!(f.x!==self.x||f.y!==self.y||f.width!==self.width||f.height!==self.height)},almostEqual:function(f){var self=this,d1=.2*Max(f.width,self.width),d2=.2*Max(f.height,self.height);return!!(Abs(self.x-f.x)<=d1&&Abs(self.y-f.y)<=d2&&Abs(self.width-f.width)<=d1&&Abs(self.height-f.height)<=d2)},clone:function(){var self=this,f=new Feature;return f.x=self.x,f.y=self.y,f.width=self.width,f.height=self.height,f.index=self.index,f.area=self.area,f.isInside=self.isInside,f},copy:function(f){var self=this;return f&&f instanceof Feature&&(self.x=f.x,self.y=f.y,self.width=f.width,self.height=f.height,self.index=f.index,self.area=f.area,self.isInside=f.isInside),self},toString:function(){return["[ x:",this.x,"y:",this.y,"width:",this.width,"height:",this.height,"]"].join(" ")}}; -------------------------------------------------------------------------------- /src/headshots/brian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/brian.jpg -------------------------------------------------------------------------------- /src/headshots/brianR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/brianR.png -------------------------------------------------------------------------------- /src/headshots/deb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/deb.jpg -------------------------------------------------------------------------------- /src/headshots/debR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/debR.png -------------------------------------------------------------------------------- /src/headshots/edie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/edie.jpg -------------------------------------------------------------------------------- /src/headshots/edieR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/edieR.png -------------------------------------------------------------------------------- /src/headshots/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/github.png -------------------------------------------------------------------------------- /src/headshots/linkedIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/linkedIn.png -------------------------------------------------------------------------------- /src/headshots/mark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/mark.jpg -------------------------------------------------------------------------------- /src/headshots/markR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/markR.png -------------------------------------------------------------------------------- /src/headshots/red_Rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/headshots/red_Rect.png -------------------------------------------------------------------------------- /src/index-video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSight, A WebAssembly Application 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 | 29 | 30 |
31 | Face 32 | Eyes 33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 |
WebAssembly
41 |
42 |
Asm.js
43 |
44 |
JavaScript
45 | 46 |
48 |
49 | 50 |
51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OpenCV JS Binding - Image Processing Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 27 |
Reset
28 |
Face Detection
29 |
Eye Detection
30 | 31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 |

WebAssembly

39 |
40 | 41 |
42 |
43 |
44 |

ASM

45 |
46 |
47 |
48 |
49 |

JS

50 |
51 |
52 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/js-worker.js: -------------------------------------------------------------------------------- 1 | importScripts('haarcascade_frontalface_alt.js'); 2 | importScripts('haar-detector.noDOM.min.js'); 3 | importScripts('haarcascade_eye.js'); 4 | 5 | function featureDetect(imageData, feature) { 6 | // console.log('running jsworker') 7 | let trainingSet, trainingSet1; 8 | 9 | switch (feature) { 10 | case 'face': 11 | trainingSet = haarcascade_frontalface_alt; 12 | break; 13 | case 'eyes': 14 | trainingSet = haarcascade_eye; 15 | trainingSet1 = haarcascade_frontalface_alt 16 | break; 17 | default: 18 | console.log(`${feature} not found`); 19 | postMessage({ message: null, length: 0 }); 20 | return; 21 | } 22 | let rects = []; 23 | if (trainingSet1) { 24 | new HAAR.Detector(trainingSet1, false) 25 | .image(imageData, 1) 26 | .interval(20) 27 | .selection('auto') 28 | .complete(function () { 29 | let rect; 30 | let l = this.objects.length; 31 | for (let i = 0; i < l; i++) { 32 | rect = this.objects[i]; 33 | rects.push({ x: rect.x, y: rect.y, width: rect.width, height: rect.height }) 34 | } 35 | }) 36 | .cannyThreshold({ low: 60, high: 200 }) 37 | .detect(1, 1.1, 0.12, 1, true); 38 | } 39 | 40 | new HAAR.Detector(trainingSet, false) 41 | .image(imageData, 1) 42 | .interval(20) 43 | .selection('auto') 44 | .complete(function () { 45 | let rect; 46 | let l = this.objects.length; 47 | for (let i = 0; i < l; i++) { 48 | rect = this.objects[i]; 49 | rects.push({ x: rect.x, y: rect.y, width: rect.width, height: rect.height }) 50 | } 51 | postMessage({ features: rects }); 52 | }) 53 | .cannyThreshold({ low: 60, high: 200 }) 54 | .detect(1, 1.1, 0.12, 1, true); 55 | } 56 | 57 | self.onmessage = function (e) { 58 | if (e.data.cmd === 'faceDetect') { 59 | featureDetect(e.data.img, 'face'); 60 | } 61 | else if (e.data.cmd === 'eyesDetect') { 62 | featureDetect(e.data.img, 'eyes'); 63 | } 64 | } 65 | self.onerror = function (e) { 66 | console.log(e); 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/logoFinal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Sight/WebSight/ae2e1333ef42ef7fdc69118190ec31b5088e13a7/src/logoFinal.png -------------------------------------------------------------------------------- /src/main-video.js: -------------------------------------------------------------------------------- 1 | let asmWorker = new Worker('asm-worker.js'); 2 | let wasmWorker = new Worker('wasm-worker.js'); 3 | let jsWorker = new Worker('js-worker.js'); 4 | 5 | let video = document.querySelector("#videoElement"); 6 | let objType = 'faceDetect'; 7 | 8 | 9 | // check for getUserMedia support 10 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia; 11 | 12 | if (navigator.getUserMedia) { 13 | // get webcam feed if available 14 | navigator.getUserMedia({ video: true }, handleVideo, () => console.log('error with webcam')); 15 | // setTimeout(detect, 8000) 16 | } 17 | 18 | document.addEventListener('DOMContentLoaded', function () { 19 | console.log('dom loaded') 20 | }, false); 21 | 22 | function handleVideo(stream) { 23 | video.srcObject = stream; 24 | } 25 | 26 | let canvases = {}; 27 | canvases.running = false; 28 | canvases.ready = false; 29 | canvases.wasm = {}; 30 | canvases.asm = {}; 31 | canvases.js = {}; 32 | 33 | canvases.wasm.fps = 0; 34 | canvases.asm.fps = 0; 35 | canvases.js.fps = 0; 36 | 37 | canvases.wasm.lastTime = +new Date; 38 | canvases.asm.lastTime = +new Date; 39 | canvases.js.lastTime = +new Date; 40 | 41 | canvases.wasm.fpsArr = []; 42 | canvases.asm.fpsArr = []; 43 | canvases.js.fpsArr = []; 44 | 45 | canvases.wasm.color = 'rgba(255, 0, 0, 1)'; 46 | canvases.asm.color = 'rgba(0, 191, 255, 1)'; 47 | canvases.js.color = 'rgba(0, 255, 0, 1)'; 48 | canvases.width = 320; 49 | canvases.height = 240; 50 | canvases.scale = 2; 51 | 52 | canvases.wasm.canvas = document.getElementById('wasm'); 53 | canvases.wasm.context = canvases.wasm.canvas.getContext('2d'); 54 | canvases.wasm.canvas.width = canvases.width; 55 | canvases.wasm.canvas.height = canvases.height; 56 | 57 | canvases.asm.canvas = document.getElementById('asm'); 58 | canvases.asm.context = canvases.asm.canvas.getContext('2d'); 59 | canvases.asm.canvas.width = canvases.width; 60 | canvases.asm.canvas.height = canvases.height; 61 | 62 | canvases.js.canvas = document.getElementById('js'); 63 | canvases.js.context = canvases.js.canvas.getContext('2d'); 64 | canvases.js.canvas.width = canvases.width; 65 | canvases.js.canvas.height = canvases.height; 66 | 67 | canvases.dummy = {}; 68 | canvases.dummy.canvas = document.getElementById('dummy'); 69 | canvases.dummy.context = canvases.dummy.canvas.getContext('2d'); 70 | canvases.dummy.canvas.width = canvases.width; 71 | canvases.dummy.canvas.height = canvases.height; 72 | 73 | canvases.chart = {}; 74 | canvases.chart.canvas = document.getElementById('graph'); 75 | canvases.chart.context = canvases.chart.canvas.getContext('2d'); 76 | canvases.chart.canvas.width = canvases.width; 77 | canvases.chart.canvas.height = canvases.height; 78 | 79 | function detect(type) { 80 | if (!canvases.running) { 81 | canvases.running = true; 82 | startWorker(canvases.wasm.context.getImageData(0, 0, canvases.wasm.canvas.width, canvases.wasm.canvas.height), objType, 'wasm'); 83 | startWorker(canvases.asm.context.getImageData(0, 0, canvases.asm.canvas.width, canvases.asm.canvas.height), objType, 'asm'); 84 | startWorker(canvases.js.context.getImageData(0, 0, canvases.js.canvas.width, canvases.js.canvas.height), objType, 'js'); 85 | } 86 | } 87 | 88 | function startWorker(imageData, command, type) { 89 | if (type == 'wasm') 90 | canvases.dummy.context.drawImage(wasm, 0, 0, imageData.width, imageData.height, 0, 0, Math.round(imageData.width/ canvases.scale), Math.round(imageData.height/canvases.scale)); 91 | let message = { 92 | cmd: command, 93 | img: canvases.dummy.context.getImageData(0, 0, Math.round(imageData.width/ canvases.scale), Math.round(imageData.height/canvases.scale)) 94 | }; 95 | if (type == 'wasm') wasmWorker.postMessage(message); 96 | else if (type == 'asm') asmWorker.postMessage(message); 97 | else if (type == 'js') jsWorker.postMessage(message); 98 | } 99 | 100 | function selectObj(type) { 101 | if (type == 'face') { 102 | objType = 'faceDetect'; 103 | document.getElementById('radio-face').checked = true; 104 | document.getElementById('radio-eyes').checked = false; 105 | } 106 | else { 107 | objType = 'eyesDetect'; 108 | document.getElementById('radio-eyes').checked = true; 109 | document.getElementById('radio-face').checked = false; 110 | } 111 | return; 112 | } 113 | 114 | function updateCanvas(e, targetCanvas, plot) { 115 | targetCanvas.context.drawImage(video, 0, 0, targetCanvas.canvas.width, targetCanvas.canvas.height); 116 | targetCanvas.context.strokeStyle = targetCanvas.color; 117 | targetCanvas.context.lineWidth = 2; 118 | let fps = 1000 / (targetCanvas.startTime - targetCanvas.lastTime) 119 | if (fps) { 120 | targetCanvas.fpsArr.push(fps); 121 | } 122 | if (plot.displayPoints.length > 10) { 123 | plot.displayPoints.shift(); 124 | } 125 | if (canvases.js.fpsArr.length === 1 || canvases.asm.fpsArr.length === 2 || canvases.wasm.fpsArr.length === 4 ) { 126 | targetCanvas.context.fps = Math.round((targetCanvas.fpsArr.reduce((a, b) => a + b) / targetCanvas.fpsArr.length) * 100) / 100; 127 | if ( targetCanvas.context.fps > myChart.controller.options.scales.yAxes[0].ticks.max) { 128 | myChart.controller.options.scales.yAxes[0].ticks.max = targetCanvas.context.fps; 129 | } 130 | plot.displayPoints.push(targetCanvas.context.fps) 131 | targetCanvas.fpsArr = []; 132 | } 133 | myChart.update(); 134 | targetCanvas.context.fillStyle = 'rgba(255,255,255,.5)'; 135 | targetCanvas.context.fillRect(0, 0, 90, 30) 136 | targetCanvas.context.font = "normal 14pt Arial"; 137 | targetCanvas.context.fillStyle = targetCanvas.color; 138 | targetCanvas.context.fillText(targetCanvas.context.fps + " fps", 5, 20); 139 | targetCanvas.lastTime = targetCanvas.startTime; 140 | for (let i = 0; i < e.data.features.length; i++) { 141 | let rect = e.data.features[i]; 142 | targetCanvas.context.strokeRect(rect.x * canvases.scale, rect.y * canvases.scale, rect.width * canvases.scale, rect.height * canvases.scale); 143 | } 144 | } 145 | 146 | wasmWorker.onmessage = function (e) { 147 | if (e.data.msg == 'wasm') { 148 | if (canvases.ready) { 149 | detect(); 150 | } 151 | else { 152 | canvases.ready = true 153 | } 154 | } 155 | else { 156 | updateCanvas(e, canvases.wasm, wasmGraph); 157 | requestAnimationFrame((wasmTime) => { 158 | canvases.wasm.startTime = wasmTime; 159 | startWorker(canvases.wasm.context.getImageData(0, 0, canvases.wasm.canvas.width, canvases.wasm.canvas.height), objType, 'wasm') 160 | }) 161 | } 162 | } 163 | 164 | asmWorker.onmessage = function (e) { 165 | if (e.data.msg == 'asm') { 166 | if (canvases.ready) { 167 | detect(); 168 | } 169 | else { 170 | canvases.ready = true 171 | } 172 | } 173 | else { 174 | updateCanvas(e, canvases.asm, asmGraph); 175 | requestAnimationFrame((asmTime) => { 176 | canvases.asm.startTime = asmTime; 177 | startWorker(canvases.asm.context.getImageData(0, 0, canvases.asm.canvas.width, canvases.asm.canvas.height), objType, 'asm') 178 | }); 179 | } 180 | } 181 | 182 | jsWorker.onmessage = function (e) { 183 | updateCanvas(e, canvases.js, jsGraph); 184 | requestAnimationFrame((jsTime) => { 185 | canvases.js.startTime = jsTime; 186 | startWorker(canvases.js.context.getImageData(0, 0, canvases.js.canvas.width, canvases.js.canvas.height), objType, 'js') 187 | }); 188 | } 189 | 190 | window.onerror = function (event) { 191 | console.log(event) 192 | }; 193 | 194 | Chart.defaults.global.tooltips.enabled = false; 195 | Chart.defaults.global.scalesLineColor = "rgba(0,0,0,0)"; 196 | Chart.defaults.global.defaultFontFamily = '"Palatino Linotype", "Book Antiqua", Palatino, serif'; 197 | 198 | const graphEle = document.getElementById("graph"); 199 | graphEle.height = 80; 200 | const ctx = graphEle.getContext('2d'); 201 | 202 | Chart.pluginService.register({ 203 | beforeDraw: function (chart, easing) { 204 | if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) { 205 | var helpers = Chart.helpers; 206 | var ctx = chart.chart.ctx; 207 | var chartArea = chart.chartArea; 208 | 209 | ctx.save(); 210 | ctx.fillStyle = chart.config.options.chartArea.backgroundColor; 211 | ctx.fillRect(chartArea.left, chartArea.top, chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); 212 | } 213 | } 214 | }); 215 | 216 | let myChart = Chart.Line(ctx, { 217 | responsive: true, 218 | options: { 219 | legend: { 220 | display: true, 221 | labels: { 222 | fontColor: "#F16327" 223 | } 224 | }, 225 | chartArea: { 226 | backgroundColor: "#D1D1D1" 227 | }, 228 | elements: { 229 | point: { 230 | radius: 0 231 | } 232 | }, 233 | animation: false, 234 | scales: { 235 | fontColor: "#FFFFFF", 236 | xAxes: [{ 237 | gridLines: { 238 | display: false, 239 | color: "rgba(0,0,0,1)" 240 | }, 241 | ticks: { 242 | display:false, 243 | fontColor: "#F16327" 244 | }, 245 | display: true, 246 | scaleLabel: { 247 | display: true, 248 | labelString: "Inputs", 249 | fontColor: "#F16327" 250 | }, 251 | }], 252 | yAxes: [ 253 | { 254 | display: true, 255 | ticks: { 256 | min: 0, 257 | max: 30, 258 | stepSize: 10, 259 | fontColor: "#F16327" 260 | }, 261 | scaleLabel: { 262 | display: true, 263 | labelString: "FPS", 264 | fontColor: "#F16327" 265 | }, 266 | gridLines: { 267 | color: "rgba(0,0,0,1)", 268 | } 269 | }], 270 | }, 271 | labels: { 272 | fontColor: "blue" 273 | } 274 | }, 275 | data: { 276 | labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 277 | datasets: [ 278 | { 279 | fill: false, 280 | label: "wasm", 281 | borderColor: "rgba(255,0,0,1)", 282 | backgroundColor: "rgba(255,0,0,1)", 283 | data: [] 284 | }, 285 | { 286 | fill: false, 287 | label: "asm", 288 | borderColor: "rgba(0,191,255,1)", 289 | backgroundColor: "rgba(0,191,255,1)", 290 | data: [] 291 | }, 292 | { 293 | fill: false, 294 | label: "js", 295 | borderColor: "rgba(0,255,0,1)", 296 | backgroundColor: "rgba(0,255,0,1)", 297 | data: [] 298 | } 299 | ] 300 | } 301 | }) 302 | 303 | let wasmGraph = { displayPoints: myChart.config.data.datasets[0].data, holder: [] }; 304 | let asmGraph = { displayPoints: myChart.config.data.datasets[1].data, holder: [] }; 305 | let jsGraph = { displayPoints: myChart.config.data.datasets[2].data, holder: [] }; 306 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | 2 | let wasmWorker = new Worker('wasm-worker.js'); 3 | let jsWorker = new Worker('js-worker.js'); 4 | let asmWorker = new Worker('asm-worker.js'); 5 | 6 | let wasmCanvas = { color: 'rgba(255, 0, 0, 1)', scale: 1 }, 7 | asmCanvas = { color: 'rgba(0,191,255,1)', scale: 1 }, 8 | jsCanvas = { color: 'rgba(75,221,17,1)', scale: 1 }; 9 | 10 | let perfwasm0, perfasm0, perfJS0, perfwasm1, perfasm1, perfjs1, wasmRan, asmRan, jsRan; 11 | let img = new Image(); 12 | 13 | function reset() { 14 | wasmCanvas.holder.context.drawImage(img, 0, 0); 15 | asmCanvas.holder.context.drawImage(img, 0, 0); 16 | jsCanvas.holder.context.drawImage(img, 0, 0); 17 | wasmCanvas.real.context.drawImage(img, 0, 0); 18 | asmCanvas.real.context.drawImage(img, 0, 0); 19 | jsCanvas.real.context.drawImage(img, 0, 0); 20 | 21 | document.getElementById("wasm-speed").innerText = ''; 22 | document.getElementById("asm-speed").innerText = ''; 23 | document.getElementById("js-speed").innerText = ''; 24 | } 25 | 26 | function detectFace() { 27 | if (document.getElementById("wasm-speed").innerText) { 28 | reset() 29 | } 30 | perfasm0 = performance.now(); 31 | startAsmWorker(asmCanvas.real.context.getImageData(0, 0, asmCanvas.real.canvas.width || 200, asmCanvas.real.canvas.height || 200), 'faceDetect'); 32 | perfwasm0 = performance.now(); 33 | startWasmWorker(wasmCanvas.real.context.getImageData(0, 0, wasmCanvas.real.canvas.width || 200, wasmCanvas.real.canvas.height || 200), 'faceDetect'); 34 | perfJS0 = performance.now(); 35 | startJSWorker(jsCanvas.real.context.getImageData(0, 0, jsCanvas.real.canvas.width || 200, jsCanvas.real.canvas.height || 200), 'faceDetect'); 36 | } 37 | 38 | function detectEyes() { 39 | if (document.getElementById("wasm-speed").innerText) { 40 | reset() 41 | } 42 | perfasm0 = performance.now(); 43 | startAsmWorker(asmCanvas.real.context.getImageData(0, 0, asmCanvas.real.canvas.width || 200, asmCanvas.real.canvas.height || 200), 'eyesDetect'); 44 | perfwasm0 = performance.now(); 45 | startWasmWorker(wasmCanvas.real.context.getImageData(0, 0, wasmCanvas.real.canvas.width || 200, wasmCanvas.real.canvas.height || 200), 'eyesDetect'); 46 | perfJS0 = performance.now(); 47 | startJSWorker(jsCanvas.real.context.getImageData(0, 0, jsCanvas.real.canvas.width || 200, jsCanvas.real.canvas.height || 200), 'eyesDetect'); 48 | } 49 | 50 | function startWasmWorker(imageData, command) { 51 | wasmCanvas.holder.context.drawImage(img, 0, 0, imageData.width, imageData.height, 0, 0, imageData.width, imageData.height); 52 | let message = { cmd: command, img: wasmCanvas.holder.context.getImageData(0, 0, imageData.width, imageData.height) }; 53 | 54 | wasmWorker.postMessage(message); 55 | } 56 | 57 | function startAsmWorker(imageData, command) { 58 | asmCanvas.holder.context.drawImage(img, 0, 0, imageData.width, imageData.height, 0, 0, imageData.width, imageData.height); 59 | let message = { cmd: command, img: asmCanvas.holder.context.getImageData(0, 0, imageData.width, imageData.height) }; 60 | 61 | asmWorker.postMessage(message); 62 | } 63 | 64 | function startJSWorker(imageData, command) { 65 | jsCanvas.holder.context.drawImage(img, 0, 0, imageData.width, imageData.height, 0, 0, imageData.width, imageData.height); 66 | let message = { cmd: command, img: jsCanvas.holder.context.getImageData(0, 0, imageData.width, imageData.height) }; 67 | jsWorker.postMessage(message); 68 | } 69 | 70 | function updateCanvas(e, canvas) { 71 | canvas.real.context.strokeStyle = canvas.color; 72 | canvas.real.context.lineWidth = 2; 73 | for (let i = 0; i < e.data.features.length; i++) { 74 | let rect = e.data.features[i]; 75 | canvas.real.context.strokeRect(rect.x * canvas.scale, rect.y * canvas.scale, rect.width * canvas.scale, rect.height * canvas.scale); 76 | } 77 | } 78 | 79 | wasmWorker.onmessage = function (e) { 80 | updateCanvas(e, wasmCanvas); 81 | perfwasm1 = performance.now(); 82 | console.log(`WASM: ${perfwasm1 - perfwasm0}`); 83 | if (!wasmRan) { 84 | wasmRan = true; 85 | } else { 86 | document.getElementById("wasm-speed").innerText = String((perfwasm1 - perfwasm0).toFixed(2)) + " MS"; 87 | } 88 | } 89 | 90 | asmWorker.onmessage = function (e) { 91 | updateCanvas(e, asmCanvas); 92 | perfasm1 = performance.now(); 93 | console.log(`ASM: ${perfasm1 - perfasm0}`); 94 | if (!asmRan) { 95 | asmRan = true; 96 | } else { 97 | document.getElementById("asm-speed").innerText = String((perfasm1 - perfasm0).toFixed(2)) + " MS"; 98 | } 99 | } 100 | 101 | jsWorker.onmessage = function (e) { 102 | updateCanvas(e, jsCanvas); 103 | perfjs1 = performance.now(); 104 | console.log(`JS: ${perfjs1 - perfJS0}`) 105 | if (!jsRan) { 106 | jsRan = true; 107 | } else { 108 | document.getElementById("js-speed").innerText = String((perfjs1 - perfJS0).toFixed(2)) + " MS"; 109 | } 110 | } 111 | 112 | let inputElement = document.getElementById('input'); 113 | inputElement.addEventListener('change', handleFiles); 114 | 115 | function handleFiles(e) { 116 | wasmCanvas.holder = { canvas: document.getElementById('canvas-wasm') }; 117 | asmCanvas.holder = { canvas: document.getElementById('canvas-asm') }; 118 | jsCanvas.holder = { canvas: document.getElementById('canvas-js') }; 119 | wasmCanvas.real = { canvas: document.getElementById('real-wasm') }; 120 | asmCanvas.real = { canvas: document.getElementById('real-asm') }; 121 | jsCanvas.real = { canvas: document.getElementById('real-js') }; 122 | 123 | wasmCanvas.holder.context = wasmCanvas.holder.canvas.getContext('2d'); 124 | asmCanvas.holder.context = asmCanvas.holder.canvas.getContext('2d'); 125 | jsCanvas.holder.context = jsCanvas.holder.canvas.getContext('2d'); 126 | wasmCanvas.real.context = wasmCanvas.real.canvas.getContext('2d'); 127 | asmCanvas.real.context = asmCanvas.real.canvas.getContext('2d'); 128 | jsCanvas.real.context = jsCanvas.real.canvas.getContext('2d'); 129 | let url = URL.createObjectURL(e.target.files[0]); 130 | img.onload = function () { 131 | wasmCanvas.real.canvas.width 132 | = asmCanvas.real.canvas.width 133 | = jsCanvas.real.canvas.width 134 | = wasmCanvas.holder.canvas.width 135 | = asmCanvas.holder.canvas.width 136 | = jsCanvas.holder.canvas.width 137 | = img.width; 138 | 139 | wasmCanvas.real.canvas.height 140 | = asmCanvas.real.canvas.height 141 | = jsCanvas.real.canvas.height 142 | = wasmCanvas.holder.canvas.height 143 | = asmCanvas.holder.canvas.height 144 | = jsCanvas.holder.canvas.height 145 | = img.height; 146 | 147 | reset(); 148 | } 149 | 150 | img.src = url; 151 | } 152 | 153 | window.onload = function () { 154 | setTimeout(function () { 155 | wasmCanvas.holder = { canvas: document.getElementById('canvas-wasm') }; 156 | asmCanvas.holder = { canvas: document.getElementById('canvas-asm') }; 157 | jsCanvas.holder = { canvas: document.getElementById('canvas-js') }; 158 | wasmCanvas.real = { canvas: document.getElementById('real-wasm') }; 159 | asmCanvas.real = { canvas: document.getElementById('real-asm') }; 160 | jsCanvas.real = { canvas: document.getElementById('real-js') }; 161 | 162 | wasmCanvas.holder.context = wasmCanvas.holder.canvas.getContext('2d'); 163 | asmCanvas.holder.context = asmCanvas.holder.canvas.getContext('2d'); 164 | jsCanvas.holder.context = jsCanvas.holder.canvas.getContext('2d'); 165 | wasmCanvas.real.context = wasmCanvas.real.canvas.getContext('2d'); 166 | asmCanvas.real.context = asmCanvas.real.canvas.getContext('2d'); 167 | jsCanvas.real.context = jsCanvas.real.canvas.getContext('2d'); 168 | 169 | wasmCanvas.real.canvas.width 170 | = asmCanvas.real.canvas.width 171 | = jsCanvas.real.canvas.width 172 | = wasmCanvas.holder.canvas.width 173 | = asmCanvas.holder.canvas.width 174 | = jsCanvas.holder.canvas.width 175 | = img.width; 176 | 177 | wasmCanvas.real.canvas.height 178 | = asmCanvas.real.canvas.height 179 | = jsCanvas.real.canvas.height 180 | = wasmCanvas.holder.canvas.height 181 | = asmCanvas.holder.canvas.height 182 | = jsCanvas.holder.canvas.height 183 | = img.height; 184 | 185 | reset(); 186 | 187 | detectFace(); 188 | }, 3000); 189 | } 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "facedetectmodule", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "jquery": "^3.2.1", 8 | "jquery.facedetection": "^2.0.2", 9 | "mocha": "^3.4.2", 10 | "nodemon": "^1.11.0" 11 | }, 12 | "devDependencies": { 13 | "mocha": "^3.4.2" 14 | }, 15 | "scripts": { 16 | "test": "mocha" 17 | }, 18 | "author": "", 19 | "license": "ISC" 20 | } 21 | -------------------------------------------------------------------------------- /src/parallel.min.js: -------------------------------------------------------------------------------- 1 | !function(){function t(t,e){e||(e={});for(var r in t)void 0===e[r]&&(e[r]=t[r]);return e}function e(){this._callbacks=[],this._errCallbacks=[],this._resolved=0,this._result=null}function r(r,o){this.data=r,this.options=t(l,o),this.operation=new e,this.operation.resolve(null,this.data),this.requiredScripts=[],this.requiredFunctions=[]}var o="undefined"!=typeof global&&"[object global]"=={}.toString.call(global),n=n||function(t){setTimeout(t,0)},s=o?require(__dirname+"/Worker.js"):self.Worker,a="undefined"!=typeof self?self.URL?self.URL:self.webkitURL:null,i=o||self.Worker?!0:!1;e.prototype.resolve=function(t,e){if(t){this._resolved=2,this._result=t;for(var r=0;r1&&(++o,n._spawnReduceWorker([n.data[0],n.data[1]],t,r),n.data.splice(0,2))}if(!this.data.length)throw new Error("Can't reduce non-array data");var o=0,n=this,s=new e;return this.operation.then(function(){if(1===n.data.length)s.resolve(null,n.data[0]);else{for(var e=0;e