Skip to content

Instantly share code, notes, and snippets.

@ttezel
Last active August 29, 2015 14:27
Show Gist options
  • Save ttezel/b20fbd216ec53b3a0481 to your computer and use it in GitHub Desktop.
Save ttezel/b20fbd216ec53b3a0481 to your computer and use it in GitHub Desktop.
/**
* @author Eugene Zatepyakin / http://inspirit.ru/
*
* this code is a rewrite from https://github.com/mtschirs/js-objectdetect implementation
* @author Martin Tschirsich / http://www.tu-darmstadt.de/~m_t
*/
(function(global) {
"use strict";
//
var haar = (function() {
var _group_func = function(r1, r2) {
var distance = (r1.width * 0.25 + 0.5)|0;
return r2.x <= r1.x + distance &&
r2.x >= r1.x - distance &&
r2.y <= r1.y + distance &&
r2.y >= r1.y - distance &&
r2.width <= (r1.width * 1.5 + 0.5)|0 &&
(r2.width * 1.5 + 0.5)|0 >= r1.width;
}
return {
edges_density: 0.07,
detect_single_scale: function(int_sum, int_sqsum, int_tilted, int_canny_sum, width, height, scale, classifier) {
var win_w = (classifier.size[0] * scale)|0,
win_h = (classifier.size[1] * scale)|0,
step_x = (0.5 * scale + 1.5)|0,
step_y = step_x;
var i,j,k,x,y,ex=(width-win_w)|0,ey=(height-win_h)|0;
var w1=(width+1)|0,edge_dens,mean,variance,std;
var inv_area = 1.0 / (win_w * win_h);
var stages,stage,trees,tree,sn,tn,fn,found=true,stage_thresh,stage_sum,tree_sum,feature,features;
var fi_a,fi_b,fi_c,fi_d,fw,fh;
var ii_a=0,ii_b=win_w,ii_c=win_h*w1,ii_d=ii_c+win_w;
var edges_thresh = ((win_w*win_h) * 0xff * this.edges_density)|0;
// if too much gradient we also can skip
//var edges_thresh_high = ((win_w*win_h) * 0xff * 0.3)|0;
var rects = [];
for(y = 0; y < ey; y += step_y) {
ii_a = y * w1;
for(x = 0; x < ex; x += step_x, ii_a += step_x) {
mean = int_sum[ii_a]
- int_sum[ii_a+ii_b]
- int_sum[ii_a+ii_c]
+ int_sum[ii_a+ii_d];
// canny prune
if(int_canny_sum) {
edge_dens = (int_canny_sum[ii_a]
- int_canny_sum[ii_a+ii_b]
- int_canny_sum[ii_a+ii_c]
+ int_canny_sum[ii_a+ii_d]);
if(edge_dens < edges_thresh || mean < 20) {
x += step_x, ii_a += step_x;
continue;
}
}
mean *= inv_area;
variance = (int_sqsum[ii_a]
- int_sqsum[ii_a+ii_b]
- int_sqsum[ii_a+ii_c]
+ int_sqsum[ii_a+ii_d]) * inv_area - mean * mean;
std = variance > 0. ? Math.sqrt(variance) : 1;
stages = classifier.complexClassifiers;
sn = stages.length;
found = true;
for(i = 0; i < sn; ++i) {
stage = stages[i];
stage_thresh = stage.threshold;
trees = stage.simpleClassifiers;
tn = trees.length;
stage_sum = 0;
for(j = 0; j < tn; ++j) {
tree = trees[j];
tree_sum = 0;
features = tree.features;
fn = features.length;
if(tree.tilted === 1) {
for(k=0; k < fn; ++k) {
feature = features[k];
fi_a = ~~(x + feature[0] * scale) + ~~(y + feature[1] * scale) * w1;
fw = ~~(feature[2] * scale);
fh = ~~(feature[3] * scale);
fi_b = fw * w1;
fi_c = fh * w1;
tree_sum += (int_tilted[fi_a]
- int_tilted[fi_a + fw + fi_b]
- int_tilted[fi_a - fh + fi_c]
+ int_tilted[fi_a + fw - fh + fi_b + fi_c]) * feature[4];
}
} else {
for(k=0; k < fn; ++k) {
feature = features[k];
fi_a = ~~(x + feature[0] * scale) + ~~(y + feature[1] * scale) * w1;
fw = ~~(feature[2] * scale);
fh = ~~(feature[3] * scale);
fi_c = fh * w1;
tree_sum += (int_sum[fi_a]
- int_sum[fi_a+fw]
- int_sum[fi_a+fi_c]
+ int_sum[fi_a+fi_c+fw]) * feature[4];
}
}
stage_sum += (tree_sum * inv_area < tree.threshold * std) ? tree.left_val : tree.right_val;
}
if (stage_sum < stage_thresh) {
found = false;
break;
}
}
if(found) {
rects.push({"x" : x,
"y" : y,
"width" : win_w,
"height" : win_h,
"neighbor" : 1,
"confidence" : stage_sum});
x += step_x, ii_a += step_x;
}
}
}
return rects;
},
detect_multi_scale: function(int_sum, int_sqsum, int_tilted, int_canny_sum, width, height, classifier, scale_factor, scale_min) {
if (typeof scale_factor === "undefined") { scale_factor = 1.2; }
if (typeof scale_min === "undefined") { scale_min = 1.0; }
var win_w = classifier.size[0];
var win_h = classifier.size[1];
var rects = [];
while (scale_min * win_w < width && scale_min * win_h < height) {
rects = rects.concat(this.detect_single_scale(int_sum, int_sqsum, int_tilted, int_canny_sum, width, height, scale_min, classifier));
scale_min *= scale_factor;
}
return rects;
},
// OpenCV method to group detected rectangles
group_rectangles: function(rects, min_neighbors) {
if (typeof min_neighbors === "undefined") { min_neighbors = 1; }
var i, j, n = rects.length;
var node = [];
for (i = 0; i < n; ++i) {
node[i] = {"parent" : -1,
"element" : rects[i],
"rank" : 0};
}
for (i = 0; i < n; ++i) {
if (!node[i].element)
continue;
var root = i;
while (node[root].parent != -1)
root = node[root].parent;
for (j = 0; j < n; ++j) {
if( i != j && node[j].element && _group_func(node[i].element, node[j].element)) {
var root2 = j;
while (node[root2].parent != -1)
root2 = node[root2].parent;
if(root2 != root) {
if(node[root].rank > node[root2].rank)
node[root2].parent = root;
else {
node[root].parent = root2;
if (node[root].rank == node[root2].rank)
node[root2].rank++;
root = root2;
}
/* compress path from node2 to the root: */
var temp, node2 = j;
while (node[node2].parent != -1) {
temp = node2;
node2 = node[node2].parent;
node[temp].parent = root;
}
/* compress path from node to the root: */
node2 = i;
while (node[node2].parent != -1) {
temp = node2;
node2 = node[node2].parent;
node[temp].parent = root;
}
}
}
}
}
var idx_seq = [];
var class_idx = 0;
for(i = 0; i < n; i++) {
j = -1;
var node1 = i;
if(node[node1].element) {
while (node[node1].parent != -1)
node1 = node[node1].parent;
if(node[node1].rank >= 0)
node[node1].rank = ~class_idx++;
j = ~node[node1].rank;
}
idx_seq[i] = j;
}
var comps = [];
for (i = 0; i < class_idx+1; ++i) {
comps[i] = {"neighbors" : 0,
"x" : 0,
"y" : 0,
"width" : 0,
"height" : 0,
"confidence" : 0};
}
// count number of neighbors
for(i = 0; i < n; ++i) {
var r1 = rects[i];
var idx = idx_seq[i];
if (comps[idx].neighbors == 0)
comps[idx].confidence = r1.confidence;
++comps[idx].neighbors;
comps[idx].x += r1.x;
comps[idx].y += r1.y;
comps[idx].width += r1.width;
comps[idx].height += r1.height;
comps[idx].confidence = Math.max(comps[idx].confidence, r1.confidence);
}
var seq2 = [];
// calculate average bounding box
for(i = 0; i < class_idx; ++i) {
n = comps[i].neighbors;
if (n >= min_neighbors)
seq2.push({"x" : (comps[i].x * 2 + n) / (2 * n),
"y" : (comps[i].y * 2 + n) / (2 * n),
"width" : (comps[i].width * 2 + n) / (2 * n),
"height" : (comps[i].height * 2 + n) / (2 * n),
"neighbors" : comps[i].neighbors,
"confidence" : comps[i].confidence});
}
var result_seq = [];
n = seq2.length;
// filter out small face rectangles inside large face rectangles
for(i = 0; i < n; ++i) {
var r1 = seq2[i];
var flag = true;
for(j = 0; j < n; ++j) {
var r2 = seq2[j];
var distance = (r2.width * 0.25 + 0.5)|0;
if(i != j &&
r1.x >= r2.x - distance &&
r1.y >= r2.y - distance &&
r1.x + r1.width <= r2.x + r2.width + distance &&
r1.y + r1.height <= r2.y + r2.height + distance &&
(r2.neighbors > Math.max(3, r1.neighbors) || r1.neighbors < 3)) {
flag = false;
break;
}
}
if(flag)
result_seq.push(r1);
}
return result_seq;
}
};
})();
global.haar = haar;
})(jsfeat);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment