face-recognition.js/Feature_detection/www/js/Photography_with_detected_f...

362 lines
20 KiB
JavaScript

/**
* Photography_with_detected_features.js
*/
'use strict';
var Width_used_for_displaying_canvas = (function () {
return function () {
return 200;
};
})();
var Height_used_for_displaying_canvas = (function () {
return function () {
return 200;
};
})();
var Photography_with_detected_features = function (image /* Image */, image_name /* String */) {
chai.assert.isTrue(image !== undefined && image !== null && image instanceof Image && image.complete, 'Photography_with_detected_features._image');
// 'image_name instanceof String === false' when 'var image_name = "Not constructed through constructor!";'
chai.assert.isTrue(image_name !== undefined && image_name !== null /* && image_name instanceof String */, 'Photography_with_detected_features._image_name');
/** Check image resizing here that has to occur in calling context */
/** Not sure 'image' has to be recorded since it is immediately transformed into canvas: */
this._image = image;
this._image_name = image_name;
/** JSFeat: */
// jsfeat.matrix_t(columns, rows, data_type, data_buffer = undefined);
// jsfeat.U8_t | jsfeat.C1_t; // 1 channel with unsigned char:
this._matrix_char8_C1 = new jsfeat.matrix_t(this._image.width, this._image.height, /*jsfeat.U8_t | jsfeat.C1_t*/ jsfeat.U8C1_t);
/** Canvases */
this.$_image_canvas = $('<canvas>').attr({// Canvas as jQuery object
id: "image-canvas-" + this._image_name
});
// Configuring canvas' size with jQuery is not reliable using 'innerWidth(this._image.width)' or 'outerWidth(this._image.width)':
this.$_image_canvas.get(0).width = this._image.width;
this.$_image_canvas.get(0).height = this._image.height;
this.$_image_canvas.get(0).getContext('2d').drawImage(this._image, 0, 0, this._image.width, this._image.height);
// Display:
this.$_image_canvas.css("width", Width_used_for_displaying_canvas() + "px");
this.$_image_canvas.css("height", Width_used_for_displaying_canvas() + "px");
this.$_image_canvas.get(0).getContext('2d').font = "italic 10px Arial";
this.$_image_canvas.get(0).getContext('2d').strokeText("Original: " + this._image_name, 10, 10);
$("body").prepend(this.$_image_canvas);
this._image_data = this.$_image_canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height);
this._image_data_buffer = new Uint32Array(this._image_data.data.buffer);
this.$_grayscale_canvas = $('<canvas>').attr({// Canvas as jQuery object
id: "grayscale-canvas-" + this._image_name
});
this.$_grayscale_canvas.get(0).width = this._image.width;
this.$_grayscale_canvas.get(0).height = this._image.height;
this._grayscale_image_data = this.$_grayscale_canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height);
this._grayscale_image_data_buffer = new Uint32Array(this._grayscale_image_data.data.buffer);
this.$_gaussian_blur_canvas = $('<canvas>').attr({// Canvas as jQuery object
id: "gaussian_blur-canvas-" + this._image_name
});
this.$_gaussian_blur_canvas.get(0).width = this._image.width;
this.$_gaussian_blur_canvas.get(0).height = this._image.height;
this.$_gaussian_blur_canvas.get(0).getContext('2d').drawImage(this._image, 0, 0, this._image.width, this._image.height);
this._gaussian_blur_image_data = this.$_gaussian_blur_canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height);
this._gaussian_blur_image_data_buffer = new Uint32Array(this._gaussian_blur_image_data.data.buffer);
/** Canny: */
this.$_canny_canvas = $('<canvas>').attr({// Canvas as jQuery object
id: "canny-canvas-" + this._image_name
});
this.$_canny_canvas.get(0).width = this._image.width;
this.$_canny_canvas.get(0).height = this._image.height;
this.$_canny_canvas.get(0).getContext('2d').drawImage(this._image, 0, 0, this._image.width, this._image.height);
this._canny_image_data = this.$_canny_canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height);
this._canny_image_data_buffer = new Uint32Array(this._canny_image_data.data.buffer);
/** Sobel: */
this.$_sobel_canvas = $('<canvas>').attr({
id: "sobel-canvas-" + this._image_name
});
this.$_sobel_canvas.get(0).width = this._image.width;
this.$_sobel_canvas.get(0).height = this._image.height;
this.$_sobel_canvas.get(0).getContext('2d').drawImage(this._image, 0, 0, this._image.width, this._image.height);
this._sobel_image_data = this.$_sobel_canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height);
this._sobel_image_data_buffer = new Uint32Array(this._sobel_image_data.data.buffer);
/** Detection: */
this._gaussian_blur();
this._canny_processing();
this._canny_postprocessing();
this._sobel_processing();
this._sobel_postprocessing();
/** BBF: */
this._brightness_binary_face = null // Sample: '[{"x":44.86460414486096,"y":40.36860185248175,"width":51.39751761778363,"height":51.39751761778363,"neighbors":14,"confidence":8.938965569999997}]'
this._brightness_binary_face_detecting(chroma('blue').hex());
/** Haar: */
this._haar_feature = null;
// Classifiers: 'jsfeat.haar.eye', 'jsfeat.haar.frontalface' and 'jsfeat.haar.mouth'
this._haar_feature_detecting(chroma('green').hex(), jsfeat.haar.frontalface, 1);
this._haar_feature_detecting(chroma('yellow').hex(), jsfeat.haar.mouth, 1);
this._haar_feature_detecting(chroma('magenta').hex(), jsfeat.haar.eye, 2); // Bad result
/** tracking.js */
this._tracking_face();
};
Photography_with_detected_features.prototype._gaussian_blur = function () {
var source = new jsfeat.matrix_t(this._image.width, this._image.height, /*jsfeat.U8_t | jsfeat.C1_t*/ jsfeat.U8C1_t);
var pixel_number = this._image.width * this._image.height;
while (--pixel_number >= 0) {
var pixel = this._image_data_buffer[pixel_number];
source.data[pixel_number] = (pixel << 24) | (pixel << 16) | (pixel << 8) | pixel;
}
var target = new jsfeat.matrix_t(this._image.width, this._image.height, /*jsfeat.U8_t | jsfeat.C1_t*/ jsfeat.U8C1_t);
var sigma = 2; // gui.add(options, 'sigma', 0, 10).step(0.5); // It increases bluring
var radius = 3; // [1 - 11]
var kernel_size = (radius + 1) << 1;
jsfeat.imgproc.gaussian_blur(source, target, kernel_size, sigma);
var pixel_number = target.cols * target.rows;
while (--pixel_number >= 0) {
var pixel = target.data[pixel_number];
this._gaussian_blur_image_data_buffer[pixel_number] = (pixel << 24) | (pixel << 16) | (pixel << 8) | pixel;
}
this.$_gaussian_blur_canvas.get(0).getContext('2d').putImageData(this._gaussian_blur_image_data, 0, 0);
// Display:
this.$_gaussian_blur_canvas.css("width", Width_used_for_displaying_canvas() + "px");
this.$_gaussian_blur_canvas.css("height", Width_used_for_displaying_canvas() + "px");
this.$_gaussian_blur_canvas.get(0).getContext('2d').font = "italic 10px Arial";
this.$_gaussian_blur_canvas.get(0).getContext('2d').strokeText("Gaussian blur: " + this._image_name, 10, 10);
$("body").prepend(this.$_gaussian_blur_canvas);
};
Photography_with_detected_features.prototype._canny_processing = function () {
chai.assert.isNotNull(this._canny_image_data, 'Photography_with_detected_features._canny_image_data');
chai.assert.isNotNull(this._canny_image_data_buffer, 'Photography_with_detected_features._canny_image_data_buffer');
jsfeat.imgproc.grayscale(this._canny_image_data.data, this._image.width, this._image.height, this._matrix_char8_C1);
var blur_radius = 3;
var low_threshold = 40;
var high_threshold = 40;
// Bitwise OR with '0' is equivalent to 'Math.floor()':
var r = blur_radius | 0;
var kernel_size = (r + 1) << 1;
jsfeat.imgproc.gaussian_blur(this._matrix_char8_C1, this._matrix_char8_C1, kernel_size, 0);
// Bitwise OR with '0' is equivalent to 'Math.floor()':
jsfeat.imgproc.canny(this._matrix_char8_C1, this._matrix_char8_C1, low_threshold | 0, high_threshold | 0);
// Render result back to canvas:
var alpha = (0x000000FF << 24); // 'alpha' is set to max., i.e., '255'
var pixel_number = this._matrix_char8_C1.cols * this._matrix_char8_C1.rows;
while (--pixel_number >= 0) {
var pixel = this._matrix_char8_C1.data[pixel_number];
this._canny_image_data_buffer[pixel_number] = alpha | (pixel << 16) | (pixel << 8) | pixel;
}
};
Photography_with_detected_features.prototype._canny_postprocessing = function () {
chai.assert.isNotNull(this._canny_image_data, 'Photography_with_detected_features._canny_image_data');
chai.assert.isNotNull(this._canny_image_data_buffer, 'Photography_with_detected_features._canny_image_data_buffer');
for (var pixel_number = 0; pixel_number < this._canny_image_data_buffer.length; pixel_number++) {
// Décalage sur la droite SANS préservation du signe (i.e., remplissage à gauche par des '0'):
var alpha = this._canny_image_data_buffer[pixel_number] >>> 24; // Most left byte
var blue = (this._canny_image_data_buffer[pixel_number] & 0x00FF0000) >> 16;
var green = (this._canny_image_data_buffer[pixel_number] & 0x0000FF00) >> 8;
var red = this._canny_image_data_buffer[pixel_number] & 0x000000FF; // Most right byte
if (red === 255 && green === 255 && blue === 255) { // white
green = 0;
blue = 0;
alpha = (0x000000FF << 24); // 'alpha' is set to max., i.e., '255'
} else
alpha = (0x00000000 << 24); // 'alpha' is set to min., i.e., '0'
this._canny_image_data_buffer[pixel_number] = alpha | (blue << 16) | (green << 8) | red;
}
this.$_canny_canvas.get(0).getContext('2d').putImageData(this._canny_image_data, 0, 0);
// Display:
this.$_canny_canvas.css("width", Width_used_for_displaying_canvas() + "px");
this.$_canny_canvas.css("height", Height_used_for_displaying_canvas() + "px");
this.$_canny_canvas.get(0).getContext('2d').font = "italic 10px Arial";
this.$_canny_canvas.get(0).getContext('2d').strokeText("Canny: " + this._image_name, 10, 10);
$("body").prepend(this.$_canny_canvas);
};
Photography_with_detected_features.prototype._sobel_processing = function () {
chai.assert.isNotNull(this._sobel_image_data, 'Photography_with_detected_features._sobel_image_data');
chai.assert.isNotNull(this._sobel_image_data_buffer, 'Photography_with_detected_features._sobel_image_data_buffer');
// 2 channels with 32 bit integer:
var matrix_integer32_C2 = new jsfeat.matrix_t(this._image.width, this._image.height, jsfeat.S32C2_t);
// Passe l'image en nuances de gris au lieu des couleurs:
jsfeat.imgproc.grayscale(this._sobel_image_data.data, this._image.width, this._image.height, this._matrix_char8_C1, jsfeat.COLOR_RGBA2GRAY);
// Image source in 'this._matrix_char8_C1', result in 'matrix_integer32_C2'
jsfeat.imgproc.sobel_derivatives(this._matrix_char8_C1, matrix_integer32_C2);
// Render result back to canvas:
// RGBA pixel's features are packed into 32 bits (https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipulation-with-typed-arrays/):
var i = this._sobel_image_data_buffer.length, pixel = 0, gx = 0, gy = 0;
while (--i >= 0) { // 'i' = this._image.width * this._image.height, matrix_integer32_C2.data.length = this._image.width * this._image.height
// First iteration:
// 'i << 1' === matrix_integer32_C2.data.length - 2
// '(i << 1) + 1' === matrix_integer32_C2.data.length - 1
// Gradient de l'intensité sur l'axe x:
gx = Math.abs(matrix_integer32_C2.data[i << 1] >> 2) & 0x000000FF;
// Gradient de l'intensité sur l'axe y:
gy = Math.abs(matrix_integer32_C2.data[(i << 1) + 1] >> 2) & 0x000000FF;
pixel = ((gx + gy) >> 1) & 0x000000FF;
this._sobel_image_data_buffer[i] = (pixel << 24) | (gx << 16) | (0 << 8) | gy;
}
};
Photography_with_detected_features.prototype._sobel_postprocessing = function () {
chai.assert.isNotNull(this._sobel_image_data, 'Photography_with_detected_features._sobel_image_data');
chai.assert.isNotNull(this._sobel_image_data_buffer, 'Photography_with_detected_features._sobel_image_data_buffer');
chai.assert.isNotNull(this._image_data, 'Photography_with_detected_features._image_data');
chai.assert.isNotNull(this._image_data_buffer, 'Photography_with_detected_features._image_data_buffer');
for (var pixel_number = 0; pixel_number < this._sobel_image_data_buffer.length; pixel_number++) {
var alpha = this._sobel_image_data_buffer[pixel_number] >>> 24; // Most left byte
var blue = (this._sobel_image_data_buffer[pixel_number] & 0x00FF0000) >> 16;
var green = (this._sobel_image_data_buffer[pixel_number] & 0x0000FF00) >> 8;
var red = this._sobel_image_data_buffer[pixel_number] & 0x000000FF; // Most right byte
/** Gray scale: red-channel == green-channel == blue-channel */
if (alpha > 10) { // Lower: useless details are kept (outside faces especially) while higher: faces lose quality
red = alpha;
blue = alpha;
green = alpha;
// Change 'alpha'?
} else {
alpha = 0;
}
// Pixel reload:
this._sobel_image_data_buffer[pixel_number] = (alpha << 24) | (blue << 16) | (green << 8) | red;
/** Original image is setup according to Sobel: */
var sobel_alpha = alpha;
alpha = this._image_data_buffer[pixel_number] >>> 24; // Most left byte
blue = (this._image_data_buffer[pixel_number] & 0x00FF0000) >> 16;
green = (this._image_data_buffer[pixel_number] & 0x0000FF00) >> 8;
red = this._image_data_buffer[pixel_number] & 0x000000FF; // Most right byte
if (sobel_alpha === 0) {
// Pixel reload:
//this._image_data_buffer[pixel_number] = (alpha << 24) | (blue << 16) | (green << 8) | red; // Colors are kept
}
}
/** Update pixels into canvas: */
this.$_sobel_canvas.get(0).getContext('2d').putImageData(this._sobel_image_data, 0, 0);
// Display for test only:
this.$_sobel_canvas.css("width", Width_used_for_displaying_canvas() + "px");
this.$_sobel_canvas.css("height", Height_used_for_displaying_canvas() + "px");
this.$_sobel_canvas.get(0).getContext('2d').font = "italic 10px Arial";
this.$_sobel_canvas.get(0).getContext('2d').strokeText("Sobel: " + this._image_name, 10, 10);
$("body").prepend(this.$_sobel_canvas);
};
Photography_with_detected_features.prototype._brightness_binary_face_detecting = function (color) {
var canvas = $('<canvas>').attr({
id: "bbf-canvas-" + this._image_name
});
canvas.get(0).width = this._image.width;
canvas.get(0).height = this._image.height;
canvas.get(0).getContext('2d').drawImage(this._image, 0, 0, this._image.width, this._image.height);
jsfeat.imgproc.grayscale(canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height).data, this._image.width, this._image.height, this._matrix_char8_C1);
// Possible option:
// jsfeat.imgproc.equalize_histogram(img_u8, img_u8);
/*
Build image pyramid using canvas drawImage
src - source grayscale matrix_t (U8_t|C1_t)
min_width - minimum width to scale pyramid to
min_height - minimum height to scale pyramid to
interval - number of original scale levels in pyramid
*/
var pyramid = jsfeat.bbf.build_pyramid(this._matrix_char8_C1, 24 * 2, 24 * 2, 5); // Oscar passe à '5'
/*
This step needed only once to create local copy of features to prevent multiple Array relocation during detection
*/
jsfeat.bbf.prepare_cascade(jsfeat.bbf.face_cascade); // 'bbf_face.js'
/*
Run detection
pyramid - 'pyramid_t' object from 'build_pyramid' method
cascade - cascade data
*/
this._brightness_binary_face = jsfeat.bbf.detect(pyramid, jsfeat.bbf.face_cascade); // 'bbf_face.js'
/*
Groups the object candidate rectangles
this._brightness_binary_face - input candidate objects sequence
min_neighbors - Minimum possible number of rectangles minus 1, the threshold is used in a group of rectangles to retain it
*/
// En augmentant 'min_neighbors', on augmente la qualité de la détection pour ensuite choisir le rectangle:
this._brightness_binary_face = jsfeat.bbf.group_rectangles(this._brightness_binary_face, 1); // '1' par défaut pour 'min_neighbors'
/** JS conventions: */
// All objects are considered to be 'true'
// Strings are considered to be 'false' if they are empty
// 'null' and 'undefined' are considered to be 'false'
// A Number is 'false' if it is zero
if (this._brightness_binary_face.length > 0) {
jsfeat.math.qsort(this._brightness_binary_face, 0, this._brightness_binary_face.length - 1, function (a, b) {
return (b.confidence < a.confidence);
}); // Les rectangles sont triés par ordre de confiance
// Display:
this.$_image_canvas.get(0).getContext('2d').strokeStyle = color;
this.$_image_canvas.get(0).getContext('2d').lineWidth = 3;
var scale = this._image.width / this._matrix_char8_C1.cols;
this.$_image_canvas.get(0).getContext('2d').strokeRect((this._brightness_binary_face[0].x * scale) | 0, (this._brightness_binary_face[0].y * scale) | 0, (this._brightness_binary_face[0].width * scale) | 0, (this._brightness_binary_face[0].height * scale) | 0);
}
};
Photography_with_detected_features.prototype._haar_feature_detecting = function (color, classifier, feature_number) {
var canvas = $('<canvas>').attr({
id: "frontalface-canvas-" + this._image_name
});
canvas.get(0).width = this._image.width;
canvas.get(0).height = this._image.height;
canvas.get(0).getContext('2d').drawImage(this._image, 0, 0, this._image.width, this._image.height);
var edges = new jsfeat.matrix_t(this._image.width, this._image.height, jsfeat.U8_t | jsfeat.C1_t);
var ii_sum = new Int32Array((this._image.width + 1) * (this._image.height + 1));
var ii_sqsum = new Int32Array((this._image.width + 1) * (this._image.height + 1));
var ii_tilted = new Int32Array((this._image.width + 1) * (this._image.height + 1));
var ii_canny = new Int32Array((this._image.width + 1) * (this._image.height + 1));
jsfeat.imgproc.grayscale(canvas.get(0).getContext('2d').getImageData(0, 0, this._image.width, this._image.height).data, this._image.width, this._image.height, this._matrix_char8_C1);
/** option */ jsfeat.imgproc.equalize_histogram(this._matrix_char8_C1, this._matrix_char8_C1);
// jsfeat.imgproc.gaussian_blur(this._matrix_char8_C1, this._matrix_char8_C1, 3);
jsfeat.imgproc.compute_integral_image(this._matrix_char8_C1, ii_sum, ii_sqsum, classifier.tilted ? ii_tilted : null);
/** option */ jsfeat.imgproc.canny(this._matrix_char8_C1, edges, 10, 50);
jsfeat.imgproc.compute_integral_image(edges, ii_canny, null, null);
var min_scale = 1.; // '1.' to '4.' step '0.1'
var scale_factor = 1.15; // '1.1' to '2.' step '0.025'
jsfeat.haar.edges_density = 0.13; // '0.01' to '1.' step '0.005'
this._haar_feature = jsfeat.haar.detect_multi_scale(ii_sum, ii_sqsum, ii_tilted, ii_canny /* 'null' if canny is not used */, this._matrix_char8_C1.cols, this._matrix_char8_C1.rows, classifier, scale_factor, min_scale);
this._haar_feature = jsfeat.haar.group_rectangles(this._haar_feature, 1);
if (this._haar_feature.length > 1) {
jsfeat.math.qsort(this._haar_feature, 0, this._haar_feature.length - 1, function (a, b) {
return (b.confidence < a.confidence);
}); // Les rectangles sont triés par ordre de confiance
}
// Display:
this.$_image_canvas.get(0).getContext('2d').strokeStyle = color;
this.$_image_canvas.get(0).getContext('2d').lineWidth = 3;
var scale = this._image.width / this._matrix_char8_C1.cols;
for (var i = 0; i < Math.min(this._haar_feature.length, feature_number); ++i) {
this.$_image_canvas.get(0).getContext('2d').strokeRect((this._haar_feature[i].x * scale) | 0, (this._haar_feature[i].y * scale) | 0, (this._haar_feature[i].width * scale) | 0, (this._haar_feature[i].height * scale) | 0);
}
};
/** tracking.js */
Photography_with_detected_features.prototype._tracking_face = function () {
var tracker = new tracking.ObjectTracker(['face']);
tracker.setInitialScale(1.); // gui.add(tracker, 'initialScale', 1.0, 10.0).step(0.1);
tracker.setStepSize(1.); // gui.add(tracker, 'stepSize', 1, 5).step(0.1);
tracker.setEdgesDensity(0.1); // gui.add(tracker, 'edgesDensity', 0.1, 0.5).step(0.01);
var display_face = function (event) {
// Display:
this.$_sobel_canvas.get(0).getContext('2d').strokeStyle = chroma('red').hex();
this.$_sobel_canvas.get(0).getContext('2d').lineWidth = 3;
for (var i = 0; i < event.data.length; ++i) {
this.$_sobel_canvas.get(0).getContext('2d').strokeRect(Math.round(event.data[i].x), Math.round(event.data[i].y), Math.round(event.data[i].width), Math.round(event.data[i].height));
}
};
tracker.on('track', display_face.bind(this));
tracking.track(this._image, tracker);
};