├── 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 |
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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | WebAssembly
41 |
42 | Asm.js
43 |
44 | JavaScript
45 |
46 |
47 |
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 |
46 |
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