From 73f86e838aac8ecf561451c46b1a9e19c8b472f6 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 3 Nov 2016 15:01:51 +0100 Subject: [PATCH] Correction [SobelFilter] + [CannyFilter] inspired by examples on `jsfeat` demo website --- .../js/Photography_with_detected_features.js | 36 +-- public_html/js/action-script.js | 116 +++++----- public_html/js/lib/min/reactive-filter.js | 27 ++- public_html/js/lib/reactive-filter.js | 211 ++++++++++++------ public_html/js/min/action-script.js | 17 +- public_html/permanent-storage/storage.json | 2 +- 6 files changed, 245 insertions(+), 164 deletions(-) diff --git a/Feature_detection/www/js/Photography_with_detected_features.js b/Feature_detection/www/js/Photography_with_detected_features.js index c3fd212..c772ed5 100644 --- a/Feature_detection/www/js/Photography_with_detected_features.js +++ b/Feature_detection/www/js/Photography_with_detected_features.js @@ -18,15 +18,15 @@ var Height_used_for_displaying_canvas = (function () { 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!";' + // '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: + // 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 = $('').attr({// Canvas as jQuery object @@ -141,13 +141,13 @@ Photography_with_detected_features.prototype._canny_processing = function () { var blur_radius = 3; var low_threshold = 40; var high_threshold = 40; -// Bitwise OR with '0' is equivalent to 'Math.floor()': + // 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()': + // 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: + // 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) { @@ -160,7 +160,7 @@ 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'): + // 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; @@ -185,20 +185,20 @@ Photography_with_detected_features.prototype._canny_postprocessing = function () 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: + // 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: + // 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: + // 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; @@ -257,7 +257,7 @@ Photography_with_detected_features.prototype._brightness_binary_face_detecting = 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: + // Possible option: // jsfeat.imgproc.equalize_histogram(img_u8, img_u8); /* Build image pyramid using canvas drawImage @@ -288,10 +288,10 @@ Photography_with_detected_features.prototype._brightness_binary_face_detecting = // 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 + // 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); diff --git a/public_html/js/action-script.js b/public_html/js/action-script.js index 4f3c866..b9fac63 100644 --- a/public_html/js/action-script.js +++ b/public_html/js/action-script.js @@ -40,8 +40,7 @@ var Controller, init, - featureZones, - faceZones, + zones = {feature: [], face: []}, track, featureTracker, faceTracker, @@ -75,18 +74,18 @@ ControllerRememberer.fetch(function(loaded_data){ }; /* (2) Gestion de tracking.js */ - featureZones = []; - faceZones = []; + zones.feature = []; + zones.face = []; /* (3) Gestion du track de l'image */ track = { trackFeatures: function(){ - featureZones = []; + zones.feature = []; featureTrackerTask = tracking.track(_CAN, featureTracker); }, trackFace: function(){ - faceZones = []; + zones.face = []; faceTrackerTask = tracking.track(_CAN, faceTracker); } }; @@ -95,15 +94,19 @@ ControllerRememberer.fetch(function(loaded_data){ faceTracker = new tracking.ObjectTracker(['face']); featureTracker = new tracking.ObjectTracker(['eye', 'mouth']); - faceTracker.setStepSize(1.5); - featureTracker.setStepSize(1.5); + faceTracker.setInitialScale(1.0); + featureTracker.setInitialScale(1.0); + faceTracker.setStepSize(1.2); + featureTracker.setStepSize(1.2); + faceTracker.setEdgesDensity(0.1); + featureTracker.setEdgesDensity(0.1); - trackerCallback = function(zones, e){ + trackerCallback = function(z, e){ - zones.length = 0; + zones[z].length = 0; e.data.forEach(function(rect){ - zones.push({ + zones[z].push({ x: rect.x / _CAN.width, y: rect.y / _CAN.height, w: rect.width / _CAN.width, @@ -111,15 +114,18 @@ ControllerRememberer.fetch(function(loaded_data){ }); }); - // On enregistre dans `featureZones` les featureZones trackées - log('Recognition done', '[Tracking.js]'); + // On enregistre dans `zones.feature` les zones.feature trackées + if( zones[z].length > 0 ) + log(z+' recognition done', '[Tracking.js]'); + else + log(z+' recognition failed', '[Tracking.js]'); - // On met à jour le rendu (affichage des featureZones) + // On met à jour le rendu (affichage des zones.feature) process.apply(DOM.imageLoader); } - faceTracker.on('track', function(e){ return trackerCallback.apply(this, [faceZones, e]); }); - featureTracker.on('track', function(e){ return trackerCallback.apply(this, [featureZones, e]); }); + faceTracker.on('track', function(e){ return trackerCallback.apply(this, ['face', e]); }); + featureTracker.on('track', function(e){ return trackerCallback.apply(this, ['feature', e]); }); } @@ -136,8 +142,8 @@ ControllerRememberer.fetch(function(loaded_data){ /* [0.0] Gestion du changement d'image =========================================================*/ if( this.src != last ){ - faceZones = []; - featureZones = []; + zones.face = []; + zones.feature = []; exec = false; last = this.src; } @@ -176,13 +182,16 @@ ControllerRememberer.fetch(function(loaded_data){ /* (1) Contraste */ filterManager.get('contrast').apply(); - /* (2) Sobel */ + /* (2) Contraste */ + filterManager.get('grayscale').apply(); + + /* (3) Sobel */ filterManager.get('sobel').apply(); - /* (3) Gaussian Filter */ + /* (4) Gaussian Filter */ filterManager.get('gaussian').apply(); - /* (4) Canny Filter */ + /* (5) Canny Filter */ filterManager.get('canny').apply(); } @@ -192,36 +201,28 @@ ControllerRememberer.fetch(function(loaded_data){ /* (1) On reporte chaque zone trackée sur le `` */ var i, x, y, w, h; - for( i in featureZones ){ - x = featureZones[i].x * _CAN.width; - y = featureZones[i].y * _CAN.height; - w = featureZones[i].w * _CAN.width; - h = featureZones[i].h * _CAN.height; - _CON.beginPath(); - _CON.moveTo( x, y ); - _CON.lineTo( x, y+h ); - _CON.lineTo( x+w, y+h ); - _CON.lineTo( x+w, y ); - _CON.lineTo( x, y ); + for( i in zones.feature ){ + x = zones.feature[i].x * _CAN.width; + y = zones.feature[i].y * _CAN.height; + w = zones.feature[i].w * _CAN.width; + h = zones.feature[i].h * _CAN.height; + _CON.lineWidth = 5; _CON.strokeStyle = '#f00'; - _CON.stroke(); + + _CON.strokeRect(x, y, w, h); } - for( i in faceZones ){ - x = faceZones[i].x * _CAN.width; - y = faceZones[i].y * _CAN.height; - w = faceZones[i].w * _CAN.width; - h = faceZones[i].h * _CAN.height; - _CON.beginPath(); - _CON.moveTo( x, y ); - _CON.lineTo( x, y+h ); - _CON.lineTo( x+w, y+h ); - _CON.lineTo( x+w, y ); - _CON.lineTo( x, y ); + for( i in zones.face ){ + x = zones.face[i].x * _CAN.width; + y = zones.face[i].y * _CAN.height; + w = zones.face[i].w * _CAN.width; + h = zones.face[i].h * _CAN.height; + _CON.lineWidth = 5; _CON.strokeStyle = '#ff0'; - _CON.stroke(); + + _CON.strokeRect(x, y, w, h); } } @@ -245,6 +246,7 @@ ControllerRememberer.fetch(function(loaded_data){ /* (2) Ajout des filtres */ filterManager.add('resolution', reactiveResolution); filterManager.add('contrast', reactiveContrast); + filterManager.add('grayscale', reactiveGrayscale); filterManager.add('sobel', reactiveSobel); filterManager.add('gaussian', reactiveGaussianBlur); filterManager.add('canny', reactiveCanny); @@ -252,6 +254,7 @@ ControllerRememberer.fetch(function(loaded_data){ /* (3) Gestion des backups */ Controller.remember(filterManager.get('resolution')); Controller.remember(filterManager.get('contrast')); + Controller.remember(filterManager.get('grayscale')); Controller.remember(filterManager.get('sobel')); Controller.remember(filterManager.get('canny')); Controller.remember(filterManager.get('gaussian')); @@ -261,16 +264,23 @@ ControllerRememberer.fetch(function(loaded_data){ Controller.add(track, 'trackFace'); Controller.add(track, 'trackFeatures'); Controller.addFolder('Image Resolution'); - Controller.add(filterManager.get('resolution'), 'width', 0, 2).step(0.1);//listen(); - Controller.add(filterManager.get('resolution'), 'height', 0, 2).step(0.1);//listen(); + Controller.add(filterManager.get('resolution'), 'width', 0, 2).step(0.1); + Controller.add(filterManager.get('resolution'), 'height', 0, 2).step(0.1); Controller.addFolder('Basic Image Processing'); - Controller.add(filterManager.get('contrast'), 'contrast', 0, 100);//listen(); + Controller.add(filterManager.get('contrast'), 'contrast', 0, 100); + Controller.add(filterManager.get('grayscale'), 'grayscale'); Controller.addFolder('Gaussian Blur'); - Controller.add(filterManager.get('gaussian'), 'sigma', 0, 10).step(0.5);//listen(); - Controller.add(filterManager.get('gaussian'), 'radius', 1, 11).step(1);//listen(); - Controller.addFolder('Convolution Filters'); - Controller.add(filterManager.get('sobel'), 'sobelActive');//listen(); - Controller.add(filterManager.get('canny'), 'canny_radius');//listen(); + Controller.add(filterManager.get('gaussian'), 'sigma', 0, 10).step(0.5); + Controller.add(filterManager.get('gaussian'), 'radius', 1, 11).step(1); + Controller.addFolder('Sobel Filter'); + Controller.add(filterManager.get('sobel'), 'sobelActive'); + Controller.addFolder('Canny Filter'); + Controller.add(filterManager.get('canny'), 'active'); + Controller.add(filterManager.get('canny'), 'radius', 0, 4).step(1); + Controller.add(filterManager.get('canny'), 'low_threshold', 1, 127).step(1); + Controller.add(filterManager.get('canny'), 'high_threshold', 1, 127).step(1); + Controller.addFolder('Process'); + Controller.add({render: process}, 'render'); /* (5) Gestion du @PermanentStorage */ diff --git a/public_html/js/lib/min/reactive-filter.js b/public_html/js/lib/min/reactive-filter.js index cde3d04..98c4995 100644 --- a/public_html/js/lib/min/reactive-filter.js +++ b/public_html/js/lib/min/reactive-filter.js @@ -1,13 +1,16 @@ -var ReactiveFilter=function(a){this._manager={_process:function(){}};this._attr=a instanceof Object?a:{};for(var b in this._attr)this.__defineGetter__(b,function(a){return this._attr[a]}.bind(this,b)),this.__defineSetter__(b,function(a,b){return function(c){a._attr[b]=c;a._manager.process()}}(this,b));this.apply=function(){}},ReactiveFilterManager=function(a,b,d){this._target=a instanceof HTMLImageElement?a:null;if(!this._target)throw Error("Param 1 expected to be an HTMLImageElement (), but "+ -a.constructor.name+" received");this._canvas=b instanceof HTMLCanvasElement?b:null;if(!this._canvas)throw Error("Param 2 expected to be an HTMLCanvasElement (), but "+b.constructor.name+" received");this._context=this._canvas.getContext("2d");this._process=d instanceof Function?d:null;if(!this._process)throw Error("Param 3 expected to be a Function, but "+d.constructor.name+" received");this._filter={}}; -ReactiveFilterManager.prototype.add=function(a,b){a="string"===typeof a?a:null;if(!a)throw Error("Param 1 expected to be a `string`, but "+a.constructor.name+" received");b=b instanceof ReactiveFilter?b:null;if(!b)throw Error("Param 2 expected to be a `ReactiveFilter`, but "+b.constructor.name+" received");if(null!=this._filter[a])return!0;this._filter[a]=b;b._manager=this}; +var ReactiveFilter=function(a){this._manager={_process:function(){}};this._attr=a instanceof Object?a:{};for(var c in this._attr)this.__defineGetter__(c,function(a){return this._attr[a]}.bind(this,c)),this.__defineSetter__(c,function(a,b){return function(c){a._attr[b]=c;a._manager.process()}}(this,c));this.apply=function(){}},ReactiveFilterManager=function(a,c,d){this._target=a instanceof HTMLImageElement?a:null;if(!this._target)throw Error("Param 1 expected to be an HTMLImageElement (), but "+ +a.constructor.name+" received");this._canvas=c instanceof HTMLCanvasElement?c:null;if(!this._canvas)throw Error("Param 2 expected to be an HTMLCanvasElement (), but "+c.constructor.name+" received");this._context=this._canvas.getContext("2d");this._process=d instanceof Function?d:null;if(!this._process)throw Error("Param 3 expected to be a Function, but "+d.constructor.name+" received");this._filter={}}; +ReactiveFilterManager.prototype.add=function(a,c){a="string"===typeof a?a:null;if(!a)throw Error("Param 1 expected to be a `string`, but "+a.constructor.name+" received");c=c instanceof ReactiveFilter?c:null;if(!c)throw Error("Param 2 expected to be a `ReactiveFilter`, but "+c.constructor.name+" received");if(null!=this._filter[a])return!0;this._filter[a]=c;c._manager=this}; ReactiveFilterManager.prototype.get=function(a){a="string"===typeof a?a:null;if(!a)throw Error("Param 1 expected to be a `string`, but "+a.constructor.name+" received");return null!=this._filter[a]?this._filter[a]:!1};ReactiveFilterManager.prototype.process=function(){this._process.bind(this._target)()}; -ConvolutionFilter=function(a,b,d,e){var c,f,h,g,p=parseInt(e.length/2),q=parseInt(e[0].length/2),l=d.slice(0),n,k,m,r,t,u;for(k=p;k=127+e&&(d[c]=255),d[c]<=127-e&&(d[c]=0));b.putImageData(a,0,0)}};var reactiveSobel=new ReactiveFilter({sobelActive:!1}); -reactiveSobel.apply=function(){if(this._manager instanceof ReactiveFilterManager&&this.sobelActive){var a=this._manager._canvas,b=this._manager._context,d=b.getImageData(0,0,a.width,a.height),e=new Uint32Array(d.buffer);SobelFilter(a.width,a.height,e);b.putImageData(d,0,0)}};var reactiveGaussianBlur=new ReactiveFilter({sigma:0,radius:3}); -reactiveGaussianBlur.apply=function(){if(this._manager instanceof ReactiveFilterManager){var a=this._manager._target,b=this._manager._canvas,d=this._manager._context,e=d.getImageData(0,0,b.width,b.height),c=new Uint32Array(e.buffer),b=document.createElement("canvas").getContext("2d").createImageData(b.width,b.height);b.data.set(e.data.slice(0));tBuf=new Uint32Array(b.buffer);for(var f=new jsfeat.matrix_t(a.width,a.height,jsfeat.U8C1_t),h=new jsfeat.matrix_t(a.width,a.height,jsfeat.U8C1_t),a=a.width* -a.height,g;0<=--a;)g=c[a],f.data[a]=g<<24|g<<16|g<<8|g;jsfeat.imgproc.gaussian_blur(f,h,this.radius+1<<1,this.sigma);for(a=h.cols*h.rows;0<=--a;)g=h.data[a],tBuf[a]=g<<24|g<<16|g<<8|g;e.data.set(b.data);d.putImageData(b,0,0)}};var reactiveCanny=new ReactiveFilter({canny_radius:3}); -reactiveCanny.apply=function(){if(this._manager instanceof ReactiveFilterManager){var a=this._manager._target,b=this._manager._canvas,d=this._manager._context,b=d.getImageData(0,0,b.width,b.height),e=new Uint32Array(b.buffer),a=new jsfeat.matrix_t(a.width,a.height,jsfeat.U8C1_t),c,f=e.length;c={};jsfeat.imgproc.canny(a,a,40,40);for(i=0;i>16,g:(c&65280)>>8,r:c&255},255===c.r&&255===c.g&&255===c.b?c={r:c.r,g:0,b:0,a:-16777216}:c.a=0,e[i]= -c.a|c.b<<16|c.g<<8|c.r;d.putImageData(b,0,0)}}; +ConvolutionFilter=function(a,c,d,b){var e,g,f,h,q=parseInt(b.length/2),p=parseInt(b[0].length/2),l=d.slice(0),k,n,m,r,t,u;for(n=q;n=127+b&&(d[e]=255),d[e]<=127-b&&(d[e]=0));c.putImageData(a,0,0)}};var reactiveGrayscale=new ReactiveFilter({grayscale:!1}); +reactiveGrayscale.apply=function(){if(this._manager instanceof ReactiveFilterManager&&this.grayscale){for(var a=this._manager._canvas,c=this._manager._context,a=c.getImageData(0,0,a.width,a.height),d=a.data,b=this.contrast;1>31)-(f>>31)|0,agy=(h^h>>31)-(h>>31)|0,0agy?(m=k[a+p],r=k[a+p+-l],f=k[a-p],h=k[a-p+l],m=(agx-agy)*m+agy*r,f=(agx-agy)*f+agy*h,h=k[a]*agx,n[a]=h>=m&&h>f?agx&255:0):(m=k[a+-l],r=k[a+p+-l],f=k[a+l],h=k[a-p+l],m=(agy-agx)*m+agx*r,f=(agy-agx)*f+agx* +h,h=k[a]*agy,n[a]=h>=m&&h>f?agy&255:0);data_u32=new Uint32Array(d.data.buffer);for(a=b.cols*b.rows;0<=--a;)data_u32[a]=-16777216|n[a]<<16|n[a]<<8|n[a];c.putImageData(d,0,0)}};var reactiveGaussianBlur=new ReactiveFilter({sigma:0,radius:3}); +reactiveGaussianBlur.apply=function(){if(this._manager instanceof ReactiveFilterManager){for(var a=this._manager._target,c=this._manager._canvas,d=this._manager._context,c=d.getImageData(0,0,c.width,c.height),b=new Uint32Array(c.buffer),e=new jsfeat.matrix_t(a.width,a.height,jsfeat.U8C1_t),g=new jsfeat.matrix_t(a.width,a.height,jsfeat.U8C1_t),a=a.width*a.height,f;0<=--a;)f=b[a],e.data[a]=f<<24|f<<16|f<<8|f;jsfeat.imgproc.gaussian_blur(e,g,this.radius+1<<1,this.sigma);for(a=g.cols*g.rows;0<=--a;)f= +g.data[a],b[a]=f<<24|f<<16|f<<8|f;d.putImageData(c,0,0)}};var reactiveCanny=new ReactiveFilter({active:!1,low_threshold:1,high_threshold:1,radius:3}); +reactiveCanny.apply=function(){if(this._manager instanceof ReactiveFilterManager&&this.active){var a=this._manager._canvas,c=this._manager._context,d=c.getImageData(0,0,a.width,a.height),b=new jsfeat.matrix_t(a.width,a.height,jsfeat.U8C1_t),e,g;jsfeat.imgproc.grayscale(d.data,a.width,a.height,b);jsfeat.imgproc.gaussian_blur(b,b,(this.radius|0)+1<<1,0);jsfeat.imgproc.canny(b,b,this.low_threshold|0,this.high_threshold|0);a=new Uint32Array(d.data.buffer);e=b.cols*b.rows;for(pix=0;0<=--e;)g=b.data[e], +a[e]=-16777216|g<<16|g<<8|g;c.putImageData(d,0,0)}}; diff --git a/public_html/js/lib/reactive-filter.js b/public_html/js/lib/reactive-filter.js index f0a1e31..230a1aa 100644 --- a/public_html/js/lib/reactive-filter.js +++ b/public_html/js/lib/reactive-filter.js @@ -256,7 +256,7 @@ SobelFilter = function(width, height, buff32){ dIndex = (y*width + x); // indice destination convolved = Math.abs(convolved); - buff32[dIndex] = convolved << 32 | convolved << 16 | convolved << 8 | 0xff; + buff32[dIndex] = (convolved << 32) + (convolved << 16) + (convolved << 8) | 0xff000000; // buff32[dIndex+1] = Math.abs(convolved); // buff32[dIndex+2] = Math.abs(convolved); // buff32[dIndex+3] = 0xff; @@ -344,6 +344,56 @@ reactiveContrast.apply = function(){ }; +/************************************************ +**** Gestion du grayscale **** +************************************************/ +var reactiveGrayscale = new ReactiveFilter({ grayscale: false }); +reactiveGrayscale.apply = function(){ + /* [1] Si pas de manager, on exit + =========================================================*/ + if( !(this._manager instanceof ReactiveFilterManager) ) + return; + + if( !this.grayscale ) + return; + + /* [2] On recupère notre back-buffer (8Uint) + =========================================================*/ + var o = { + image: this._manager._target, + canvas: this._manager._canvas, + context: this._manager._context + }; + + var imageData = o.context.getImageData(0, 0, o.canvas.width, o.canvas.height); + var buffer = imageData.data; + // var buffer32 = new Uint32Array( imageData.imageData ); + + + /* [3] On effectue notre modification + =========================================================*/ + /* (1) On récupère la valeur du contraste */ + var thresold = this.contrast; + + while( thresold > 1 ) thresold /= 100; + + thresold = (1-thresold) * 127; + + /* (1) Gestion du contraste */ + var g; + for( var i = 0, len = buffer.length ; i < len ; i++ ){ + // si pas pixel rouge, on ne fait rien + if( i%4 > 0 ) continue; + + // Grayscale + g = Math.round(0.298 * buffer[i] + 0.586 * buffer[i+1] + 0.114 * buffer[i+2]); + buffer[i] = buffer[i+1] = buffer[i+2] = g; + } + + /* (2) Copie le résultat sur le `` */ + o.context.putImageData(imageData, 0, 0); +}; + /************************************************ **** Gestion du Sobel **** @@ -368,41 +418,78 @@ reactiveSobel.apply = function(){ }; var imageData = o.context.getImageData(0, 0, o.canvas.width, o.canvas.height); - // var buffer = imageData.data; - var buffer32 = new Uint32Array( imageData.buffer ); + var buffer32 = new Uint32Array( imageData.buffer ); + var img_u8 = new jsfeat.matrix_t(o.canvas.width, o.canvas.height, jsfeat.U8C1_t), + img_gxgy = new jsfeat.matrix_t(o.canvas.width, o.canvas.height, jsfeat.S32C2_t), + img_mag = new jsfeat.matrix_t(o.canvas.width, o.canvas.height, jsfeat.S32C1_t); + var i, gx, gy, x, y, dx, dy, gd, mag, id, a1, a2, b1, b2, A, B, point, data_32, a, p; /* [3] On effectue notre modification =========================================================*/ - SobelFilter(o.canvas.width, o.canvas.height, buffer32); - /* (1) On calcule la convolution horizontale */ - // var Gx = buffer.slice(0); - // ConvolutionFilter(o.canvas.width, o.canvas.height, Gx, [ - // [-1, 0, 1], - // [-2, 0, 2], - // [-1, 0, 1] - // ]); - // - // /* (2) On calcule la convolution verticale */ - // var Gy = buffer.slice(0); - // ConvolutionFilter(o.canvas.width, o.canvas.height, Gy, [ - // [1, 2, 1], - // [0, 0, 0], - // [-1, -2, -1] - // ]); - // - // /* (3) On mixe les 2 */ - // for( var i = 0, len = buffer.length ; i < len ; i += 4 ){ - // - // /* (4.1) On ajoute la convolution horizontale en rouge */ - // buffer[i] = Math.abs(Gy[i]); - // - // /* (4.2) On ajoute la convolution verticale en vert */ - // buffer[i+1] = Math.abs(Gx[i+1]); - // - // buffer[i+2] = 0x00; - // buffer[i+3] = 0xff; - // } + jsfeat.imgproc.grayscale(imageData.data, o.canvas.width, o.canvas.height, img_u8); + jsfeat.imgproc.gaussian_blur(img_u8, img_u8, 3); + jsfeat.imgproc.sobel_derivatives(img_u8, img_gxgy); + + i = img_u8.cols*img_u8.rows; + gx = gy = x = y = dx = dy = agx = agy = 0; + gd = img_gxgy.data; + mag = img_mag.data; + id = img_u8.data; + + while( --i >= 0 ){ + gx = gd[ i<<1 ]; + gy = gd[ (i<<1) + 1 ]; + mag[i] = gx*gx + gy*gy; + } + + for( y = 1 ; y < img_u8.rows - 1 ; ++y ){ + i = (y * img_u8.cols + 1) | 0; + + for( x = 1 ; x < img_u8.cols - 1 ; ++x, ++i){ + + gx = gd[ i<<1 ]; + gy = gd[ (i<<1) + 1 ]; + agx = ((gx ^ (gx >> 31)) - (gx >> 31))|0; + agy = ((gy ^ (gy >> 31)) - (gy >> 31))|0; + + (gx > 0) && (dx = 1) || (dx = -1); + + (gy > 0) && (dy = img_u8.cols) || (dy = -img_u8.cols); + + if( agx > agy ){ + + a1 = mag[i+dx]; + a2 = mag[i+dx+(-dy)]; + b1 = mag[i-dx]; + b2 = mag[i-dx+dy]; + A = (agx - agy)*a1 + agy*a2; + B = (agx - agy)*b1 + agy*b2; + point = mag[i] * agx; + id[i] = (point >= A && point > B) ? agx&0xff : 0x0; + + } else { + a1 = mag[i+(-dy)]; + a2 = mag[i+dx+(-dy)]; + b1 = mag[i+dy]; + b2 = mag[i-dx+dy]; + A = (agy - agx)*a1 + agx*a2; + B = (agy - agx)*b1 + agx*b2; + point = mag[i] * agy; + + id[i] = (point >= A && point > B) ? agy&0xff : 0x0; + + } + } + } + + // render result back to canvas + data_u32 = new Uint32Array(imageData.data.buffer); + a = (0xff << 24); + p = 0; + i = img_u8.cols*img_u8.rows; + while( --i >= 0 ) + data_u32[i] = a | (id[i] << 16) | (id[i] << 8) | id[i]; /* (2) Copie le résultat sur le `` */ @@ -434,12 +521,6 @@ reactiveGaussianBlur.apply = function(){ // var buffer = imageData.data; var buffer32 = new Uint32Array( imageData.buffer ); - /* (2) Create target */ - var tCan = document.createElement('canvas'); - var tmp = tCan.getContext('2d').createImageData(o.canvas.width, o.canvas.height); - tmp.data.set( imageData.data.slice(0) ); - tBuf = new Uint32Array( tmp.buffer ); - /* [3] On effectue notre modification =========================================================*/ @@ -460,25 +541,26 @@ reactiveGaussianBlur.apply = function(){ p_num = target.cols * target.rows; while( --p_num >= 0 ){ pixel = target.data[p_num]; - tBuf[p_num] = (pixel << 24) | (pixel << 16) | (pixel << 8) | pixel; + buffer32[p_num] = (pixel << 24) | (pixel << 16) | (pixel << 8) | pixel; } /* (2) Copie le résultat sur le `` */ - imageData.data.set( tmp.data ); - o.context.putImageData(tmp, 0, 0); + o.context.putImageData(imageData, 0, 0); }; /************************************************ **** Gestion du Canny Filter **** ************************************************/ -var reactiveCanny = new ReactiveFilter({ canny_radius: 3 }); +var reactiveCanny = new ReactiveFilter({ active: false, low_threshold: 1, high_threshold: 1, radius: 3 }); reactiveCanny.apply = function(){ /* [1] Si pas de manager, on exit =========================================================*/ if( !(this._manager instanceof ReactiveFilterManager) ) return; + if( !this.active ) + return; /* [2] On recupère notre back-buffer (8Uint) =========================================================*/ @@ -490,43 +572,28 @@ reactiveCanny.apply = function(){ /* (1) Get source */ var imageData = o.context.getImageData(0, 0, o.canvas.width, o.canvas.height); - // var buffer = imageData.data; - var buffer32 = new Uint32Array( imageData.buffer ); - + var img_u8 = new jsfeat.matrix_t(o.canvas.width, o.canvas.height, jsfeat.U8C1_t); + var r, kernel_size, buffer32, a, i, p; /* [3] On effectue notre modification =========================================================*/ - var matrix = new jsfeat.matrix_t(o.image.width, o.image.height, jsfeat.U8C1_t); - var pixel, - kernel_size = (this.canny_radius + 1) << 1, - alpha = (0x000000FF << 24), - p_num = buffer32.length, - low_threshold = 40, - high_threshold = 40, - x = {}, - cur; + jsfeat.imgproc.grayscale(imageData.data, o.canvas.width, o.canvas.height, img_u8); - // jsfeat.imgproc.gaussian_blur(matrix, matrix, kernel_size, 0); + r = this.radius|0; + kernel_size = (r+1) << 1; + jsfeat.imgproc.gaussian_blur(img_u8, img_u8, kernel_size, 0); + jsfeat.imgproc.canny(img_u8, img_u8, this.low_threshold|0, this.high_threshold|0); - jsfeat.imgproc.canny(matrix, matrix, low_threshold | 0, high_threshold | 0); - - for( i = 0 ; i < p_num ; i++ ){ - pixel = matrix.data[i]; - cur = (pixel << 16) | (pixel << 8) | pixel; - x = { - a: alpha, - b: (cur & 0x00FF0000) >> 16, - g: (cur & 0x0000FF00) >> 8, - r: cur & 0x000000FF - }; - if( x.r === 255 && x.g === 255 && x.b === 255 ) // white - x = { r: x.r, g: 0, b: 0, a: (0x000000FF << 24) }; - else - x.a = (0x00000000 << 24); // 'alpha' is set to min., i.e., '0' - - buffer32[i] = x.a | (x.b << 16) | (x.g << 8) | x.r; + // render result back to canvas + buffer32 = new Uint32Array( imageData.data.buffer ); + a = (0xff << 24); + i = img_u8.cols*img_u8.rows, pix = 0; + while( --i >= 0 ){ + p = img_u8.data[i]; + buffer32[i] = a | (p << 16) | (p << 8) | p; } + /* (2) Copie le résultat sur le `` */ o.context.putImageData(imageData, 0, 0); diff --git a/public_html/js/min/action-script.js b/public_html/js/min/action-script.js index 85bdc7a..6c4c1ab 100644 --- a/public_html/js/min/action-script.js +++ b/public_html/js/min/action-script.js @@ -1,9 +1,10 @@ -var DOM={body:$("body"),canvas:$("canvas"),imageLoader:$("#image-loader")},_CAN=DOM.canvas;_CAN.width=_CAN.height=1E3;var _CON=_CAN.getContext("2d"),iL,filterManager,process=function(){},exec=!1,last,featureTrackerTask,faceTrackerTask,trackerCallback,Controller,init,featureZones,faceZones,track,featureTracker,faceTracker,initialized=!1,ControllerRememberer=new PermanentStorage; -ControllerRememberer.fetch(function(f){log("Preset loaded.","[PermanentStorage]");Controller=new dat.GUI({load:JSON.parse(f),preset:"default"});init=function(){last=this.src="front/male/1.jpg";initialized=!0;Controller.addFolder("Source Picture");Controller.remember(this);Controller.add(this,"src",this._images).listen()};featureZones=[];faceZones=[];track={trackFeatures:function(){featureZones=[];featureTrackerTask=tracking.track(_CAN,featureTracker)},trackFace:function(){faceZones=[];faceTrackerTask= -tracking.track(_CAN,faceTracker)}};faceTracker=new tracking.ObjectTracker(["face"]);featureTracker=new tracking.ObjectTracker(["eye","mouth"]);faceTracker.setStepSize(1.5);featureTracker.setStepSize(1.5);trackerCallback=function(a,c){a.length=0;c.data.forEach(function(b){a.push({x:b.x/_CAN.width,y:b.y/_CAN.height,w:b.width/_CAN.width,h:b.height/_CAN.height})});log("Recognition done","[Tracking.js]");process.apply(DOM.imageLoader)};faceTracker.on("track",function(a){return trackerCallback.apply(this, -[faceZones,a])});featureTracker.on("track",function(a){return trackerCallback.apply(this,[featureZones,a])});process=function(){if(initialized&&this instanceof HTMLImageElement){console.time("PROCESS");this.src!=last&&(faceZones=[],featureZones=[],exec=!1,last=this.src);exec||(this.defaultWidth=this.width,this.defaultHeight=this.height,log("Image copied","[Canvas]"),exec=!0);_CON.clearRect(0,0,_CAN.width,_CAN.height);filterManager.get("resolution").apply();filterManager.get("contrast").apply();filterManager.get("sobel").apply(); -filterManager.get("gaussian").apply();filterManager.get("canny").apply();var a,c,b,d,e;for(a in featureZones)c=featureZones[a].x*_CAN.width,b=featureZones[a].y*_CAN.height,d=featureZones[a].w*_CAN.width,e=featureZones[a].h*_CAN.height,_CON.beginPath(),_CON.moveTo(c,b),_CON.lineTo(c,b+e),_CON.lineTo(c+d,b+e),_CON.lineTo(c+d,b),_CON.lineTo(c,b),_CON.lineWidth=5,_CON.strokeStyle="#f00",_CON.stroke();for(a in faceZones)c=faceZones[a].x*_CAN.width,b=faceZones[a].y*_CAN.height,d=faceZones[a].w*_CAN.width, -e=faceZones[a].h*_CAN.height,_CON.beginPath(),_CON.moveTo(c,b),_CON.lineTo(c,b+e),_CON.lineTo(c+d,b+e),_CON.lineTo(c+d,b),_CON.lineTo(c,b),_CON.lineWidth=5,_CON.strokeStyle="#ff0",_CON.stroke();console.timeEnd("PROCESS")}};filterManager=new ReactiveFilterManager(DOM.imageLoader,_CAN,process);filterManager.add("resolution",reactiveResolution);filterManager.add("contrast",reactiveContrast);filterManager.add("sobel",reactiveSobel);filterManager.add("gaussian",reactiveGaussianBlur);filterManager.add("canny", -reactiveCanny);Controller.remember(filterManager.get("resolution"));Controller.remember(filterManager.get("contrast"));Controller.remember(filterManager.get("sobel"));Controller.remember(filterManager.get("canny"));Controller.remember(filterManager.get("gaussian"));Controller.addFolder("Tracking.js");Controller.add(track,"trackFace");Controller.add(track,"trackFeatures");Controller.addFolder("Image Resolution");Controller.add(filterManager.get("resolution"),"width",0,2).step(.1);Controller.add(filterManager.get("resolution"), -"height",0,2).step(.1);Controller.addFolder("Basic Image Processing");Controller.add(filterManager.get("contrast"),"contrast",0,100);Controller.addFolder("Gaussian Blur");Controller.add(filterManager.get("gaussian"),"sigma",0,10).step(.5);Controller.add(filterManager.get("gaussian"),"radius",1,11).step(1);Controller.addFolder("Convolution Filters");Controller.add(filterManager.get("sobel"),"sobelActive");Controller.add(filterManager.get("canny"),"canny_radius");Controller.__save_row.children[2].addEventListener("click", +var DOM={body:$("body"),canvas:$("canvas"),imageLoader:$("#image-loader")},_CAN=DOM.canvas;_CAN.width=_CAN.height=1E3;var _CON=_CAN.getContext("2d"),iL,filterManager,process=function(){},exec=!1,last,featureTrackerTask,faceTrackerTask,trackerCallback,Controller,init,zones={feature:[],face:[]},track,featureTracker,faceTracker,initialized=!1,ControllerRememberer=new PermanentStorage; +ControllerRememberer.fetch(function(f){log("Preset loaded.","[PermanentStorage]");Controller=new dat.GUI({load:JSON.parse(f),preset:"default"});init=function(){last=this.src="front/male/1.jpg";initialized=!0;Controller.addFolder("Source Picture");Controller.remember(this);Controller.add(this,"src",this._images).listen()};zones.feature=[];zones.face=[];track={trackFeatures:function(){zones.feature=[];featureTrackerTask=tracking.track(_CAN,featureTracker)},trackFace:function(){zones.face=[];faceTrackerTask= +tracking.track(_CAN,faceTracker)}};faceTracker=new tracking.ObjectTracker(["face"]);featureTracker=new tracking.ObjectTracker(["eye","mouth"]);faceTracker.setInitialScale(1);featureTracker.setInitialScale(1);faceTracker.setStepSize(1.2);featureTracker.setStepSize(1.2);faceTracker.setEdgesDensity(.1);featureTracker.setEdgesDensity(.1);trackerCallback=function(a,c){zones[a].length=0;c.data.forEach(function(b){zones[a].push({x:b.x/_CAN.width,y:b.y/_CAN.height,w:b.width/_CAN.width,h:b.height/_CAN.height})}); +0