jsfeat
Version:
JavaScript Computer Vision library
395 lines (360 loc) • 19.1 kB
JavaScript
/**
* BBF: Brightness Binary Feature
*
* @author Eugene Zatepyakin / http://inspirit.ru/
*
* this code is a rewrite from https://github.com/liuliu/ccv implementation
* @author Liu Liu / http://liuliu.me/
*
* The original paper refers to: YEF∗ Real-Time Object Detection, Yotam Abramson and Bruno Steux
*/
(function(global) {
"use strict";
//
var bbf = (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;
}
var img_pyr = new jsfeat.pyramid_t(1);
return {
interval: 4,
scale: 1.1486,
next: 5,
scale_to: 1,
// make features local copy
// to avoid array allocation with each scale
// this is strange but array works faster than Int32 version???
prepare_cascade: function(cascade) {
var sn = cascade.stage_classifier.length;
for (var j = 0; j < sn; j++) {
var orig_feature = cascade.stage_classifier[j].feature;
var f_cnt = cascade.stage_classifier[j].count;
var feature = cascade.stage_classifier[j]._feature = new Array(f_cnt);
for (var k = 0; k < f_cnt; k++) {
feature[k] = {"size" : orig_feature[k].size,
"px" : new Array(orig_feature[k].size),
"pz" : new Array(orig_feature[k].size),
"nx" : new Array(orig_feature[k].size),
"nz" : new Array(orig_feature[k].size)};
}
}
},
build_pyramid: function(src, min_width, min_height, interval) {
if (typeof interval === "undefined") { interval = 4; }
var sw=src.cols,sh=src.rows;
var i=0,nw=0,nh=0;
var new_pyr=false;
var src0=src,src1=src;
var data_type = jsfeat.U8_t | jsfeat.C1_t;
this.interval = interval;
this.scale = Math.pow(2, 1 / (this.interval + 1));
this.next = (this.interval + 1)|0;
this.scale_to = (Math.log(Math.min(sw / min_width, sh / min_height)) / Math.log(this.scale))|0;
var pyr_l = ((this.scale_to + this.next * 2) * 4) | 0;
if(img_pyr.levels != pyr_l) {
img_pyr.levels = pyr_l;
img_pyr.data = new Array(pyr_l);
new_pyr = true;
img_pyr.data[0] = src; // first is src
}
for (i = 1; i <= this.interval; ++i) {
nw = (sw / Math.pow(this.scale, i))|0;
nh = (sh / Math.pow(this.scale, i))|0;
src0 = img_pyr.data[i<<2];
if(new_pyr || nw != src0.cols || nh != src0.rows) {
img_pyr.data[i<<2] = new jsfeat.matrix_t(nw, nh, data_type);
src0 = img_pyr.data[i<<2];
}
jsfeat.imgproc.resample(src, src0, nw, nh);
}
for (i = this.next; i < this.scale_to + this.next * 2; ++i) {
src1 = img_pyr.data[(i << 2) - (this.next << 2)];
src0 = img_pyr.data[i<<2];
nw = src1.cols >> 1;
nh = src1.rows >> 1;
if(new_pyr || nw != src0.cols || nh != src0.rows) {
img_pyr.data[i<<2] = new jsfeat.matrix_t(nw, nh, data_type);
src0 = img_pyr.data[i<<2];
}
jsfeat.imgproc.pyrdown(src1, src0);
}
for (i = this.next * 2; i < this.scale_to + this.next * 2; ++i) {
src1 = img_pyr.data[(i << 2) - (this.next << 2)];
nw = src1.cols >> 1;
nh = src1.rows >> 1;
src0 = img_pyr.data[(i<<2)+1];
if(new_pyr || nw != src0.cols || nh != src0.rows) {
img_pyr.data[(i<<2)+1] = new jsfeat.matrix_t(nw, nh, data_type);
src0 = img_pyr.data[(i<<2)+1];
}
jsfeat.imgproc.pyrdown(src1, src0, 1, 0);
//
src0 = img_pyr.data[(i<<2)+2];
if(new_pyr || nw != src0.cols || nh != src0.rows) {
img_pyr.data[(i<<2)+2] = new jsfeat.matrix_t(nw, nh, data_type);
src0 = img_pyr.data[(i<<2)+2];
}
jsfeat.imgproc.pyrdown(src1, src0, 0, 1);
//
src0 = img_pyr.data[(i<<2)+3];
if(new_pyr || nw != src0.cols || nh != src0.rows) {
img_pyr.data[(i<<2)+3] = new jsfeat.matrix_t(nw, nh, data_type);
src0 = img_pyr.data[(i<<2)+3];
}
jsfeat.imgproc.pyrdown(src1, src0, 1, 1);
}
return img_pyr;
},
detect: function(pyramid, cascade) {
var interval = this.interval;
var scale = this.scale;
var next = this.next;
var scale_upto = this.scale_to;
var i=0,j=0,k=0,n=0,x=0,y=0,q=0,sn=0,f_cnt=0,q_cnt=0,p=0,pmin=0,nmax=0,f=0,i4=0,qw=0,qh=0;
var sum=0.0, alpha, feature, orig_feature, feature_k, feature_o, flag = true, shortcut=true;
var scale_x = 1.0, scale_y = 1.0;
var dx = [0, 1, 0, 1];
var dy = [0, 0, 1, 1];
var seq = [];
var pyr=pyramid.data, bpp = 1, bpp2 = 2, bpp4 = 4;
var u8 = [], u8o = [0,0,0];
var step = [0,0,0];
var paddings = [0,0,0];
for (i = 0; i < scale_upto; i++) {
i4 = (i<<2);
qw = pyr[i4 + (next << 3)].cols - (cascade.width >> 2);
qh = pyr[i4 + (next << 3)].rows - (cascade.height >> 2);
step[0] = pyr[i4].cols * bpp;
step[1] = pyr[i4 + (next << 2)].cols * bpp;
step[2] = pyr[i4 + (next << 3)].cols * bpp;
paddings[0] = (pyr[i4].cols * bpp4) - (qw * bpp4);
paddings[1] = (pyr[i4 + (next << 2)].cols * bpp2) - (qw * bpp2);
paddings[2] = (pyr[i4 + (next << 3)].cols * bpp) - (qw * bpp);
sn = cascade.stage_classifier.length;
for (j = 0; j < sn; j++) {
orig_feature = cascade.stage_classifier[j].feature;
feature = cascade.stage_classifier[j]._feature;
f_cnt = cascade.stage_classifier[j].count;
for (k = 0; k < f_cnt; k++) {
feature_k = feature[k];
feature_o = orig_feature[k];
q_cnt = feature_o.size|0;
for (q = 0; q < q_cnt; q++) {
feature_k.px[q] = (feature_o.px[q] * bpp) + feature_o.py[q] * step[feature_o.pz[q]];
feature_k.pz[q] = feature_o.pz[q];
feature_k.nx[q] = (feature_o.nx[q] * bpp) + feature_o.ny[q] * step[feature_o.nz[q]];
feature_k.nz[q] = feature_o.nz[q];
}
}
}
u8[0] = pyr[i4].data; u8[1] = pyr[i4 + (next<<2)].data;
for (q = 0; q < 4; q++) {
u8[2] = pyr[i4 + (next<<3) + q].data;
u8o[0] = (dx[q]*bpp2) + dy[q] * (pyr[i4].cols*bpp2);
u8o[1] = (dx[q]*bpp) + dy[q] * (pyr[i4 + (next<<2)].cols*bpp);
u8o[2] = 0;
for (y = 0; y < qh; y++) {
for (x = 0; x < qw; x++) {
sum = 0;
flag = true;
sn = cascade.stage_classifier.length;
for (j = 0; j < sn; j++) {
sum = 0;
alpha = cascade.stage_classifier[j].alpha;
feature = cascade.stage_classifier[j]._feature;
f_cnt = cascade.stage_classifier[j].count;
for (k = 0; k < f_cnt; k++) {
feature_k = feature[k];
pmin = u8[feature_k.pz[0]][u8o[feature_k.pz[0]] + feature_k.px[0]];
nmax = u8[feature_k.nz[0]][u8o[feature_k.nz[0]] + feature_k.nx[0]];
if (pmin <= nmax) {
sum += alpha[k << 1];
} else {
shortcut = true;
q_cnt = feature_k.size;
for (f = 1; f < q_cnt; f++) {
if (feature_k.pz[f] >= 0) {
p = u8[feature_k.pz[f]][u8o[feature_k.pz[f]] + feature_k.px[f]];
if (p < pmin) {
if (p <= nmax) {
shortcut = false;
break;
}
pmin = p;
}
}
if (feature_k.nz[f] >= 0) {
n = u8[feature_k.nz[f]][u8o[feature_k.nz[f]] + feature_k.nx[f]];
if (n > nmax) {
if (pmin <= n) {
shortcut = false;
break;
}
nmax = n;
}
}
}
sum += (shortcut) ? alpha[(k << 1) + 1] : alpha[k << 1];
}
}
if (sum < cascade.stage_classifier[j].threshold) {
flag = false;
break;
}
}
if (flag) {
seq.push({"x" : (x * 4 + dx[q] * 2) * scale_x,
"y" : (y * 4 + dy[q] * 2) * scale_y,
"width" : cascade.width * scale_x,
"height" : cascade.height * scale_y,
"neighbor" : 1,
"confidence" : sum});
++x;
u8o[0] += bpp4;
u8o[1] += bpp2;
u8o[2] += bpp;
}
u8o[0] += bpp4;
u8o[1] += bpp2;
u8o[2] += bpp;
}
u8o[0] += paddings[0];
u8o[1] += paddings[1];
u8o[2] += paddings[2];
}
}
scale_x *= scale;
scale_y *= scale;
}
return seq;
},
// 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.bbf = bbf;
})(jsfeat);