2021-05-25 14:58:20 +02:00
/ * *
2021-09-25 17:51:15 +02:00
* EfficientPose model implementation
*
* Based on : [ * * BecauseofAI MobileFace * * ] ( https : //github.com/becauseofAI/MobileFace)
*
* Obsolete and replaced by ` faceres ` that performs age / gender / descriptor analysis
2021-05-25 14:58:20 +02:00
* /
2021-09-27 19:58:13 +02:00
import { log , join } from '../util/util' ;
2020-11-18 14:26:28 +01:00
import * as tf from '../../dist/tfjs.esm.js' ;
2021-09-13 19:28:35 +02:00
import type { Tensor , GraphModel } from '../tfjs/types' ;
2021-09-27 19:58:13 +02:00
import { env } from '../util/env' ;
2020-11-13 22:13:35 +01:00
2021-09-17 17:23:00 +02:00
let model : GraphModel | null ;
2020-11-13 22:13:35 +01:00
2021-02-08 17:39:09 +01:00
export async function load ( config ) {
2021-05-23 03:47:59 +02:00
const modelUrl = join ( config . modelBasePath , config . face . embedding . modelPath ) ;
2021-09-17 17:23:00 +02:00
if ( env . initial ) model = null ;
2021-02-08 18:47:38 +01:00
if ( ! model ) {
2021-08-17 14:51:17 +02:00
model = await tf . loadGraphModel ( modelUrl ) as unknown as GraphModel ;
2021-05-23 03:47:59 +02:00
if ( ! model ) log ( 'load model failed:' , config . face . embedding . modelPath ) ;
else if ( config . debug ) log ( 'load model:' , modelUrl ) ;
} else if ( config . debug ) log ( 'cached model:' , modelUrl ) ;
2021-02-08 18:47:38 +01:00
return model ;
2020-11-13 22:13:35 +01:00
}
2021-03-15 17:14:48 +01:00
export function enhance ( input ) : Tensor {
2021-03-12 18:54:08 +01:00
const image = tf . tidy ( ( ) = > {
// input received from detector is already normalized to 0..1
// input is also assumed to be straightened
// const data = tf.image.resizeBilinear(input, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false); // just resize to fit the embedding model
// do a tight crop of image and resize it to fit the model
2021-03-13 00:24:34 +01:00
const box = [ [ 0.05 , 0.15 , 0.85 , 0.85 ] ] ; // empyrical values for top, left, bottom, right
2021-03-12 18:54:08 +01:00
const tensor = input . image || input . tensor ;
2021-03-13 19:47:45 +01:00
if ( ! ( tensor instanceof tf . Tensor ) ) return null ;
2021-08-17 14:51:17 +02:00
if ( ! model || ! model . inputs || ! model . inputs [ 0 ] . shape ) return null ;
2021-03-13 00:24:34 +01:00
const crop = ( tensor . shape . length === 3 )
2021-03-13 19:47:45 +01:00
? tf . image . cropAndResize ( tf . expandDims ( tensor , 0 ) , box , [ 0 ] , [ model . inputs [ 0 ] . shape [ 2 ] , model . inputs [ 0 ] . shape [ 1 ] ] ) // add batch dimension if missing
2021-03-12 18:54:08 +01:00
: tf . image . cropAndResize ( tensor , box , [ 0 ] , [ model . inputs [ 0 ] . shape [ 2 ] , model . inputs [ 0 ] . shape [ 1 ] ] ) ;
// convert to black&white to avoid colorization impact
const rgb = [ 0.2989 , 0.5870 , 0.1140 ] ; // factors for red/green/blue colors when converting to grayscale: https://www.mathworks.com/help/matlab/ref/rgb2gray.html
const [ red , green , blue ] = tf . split ( crop , 3 , 3 ) ;
const redNorm = tf . mul ( red , rgb [ 0 ] ) ;
const greenNorm = tf . mul ( green , rgb [ 1 ] ) ;
const blueNorm = tf . mul ( blue , rgb [ 2 ] ) ;
const grayscale = tf . addN ( [ redNorm , greenNorm , blueNorm ] ) ;
const merge = tf . stack ( [ grayscale , grayscale , grayscale ] , 3 ) . squeeze ( 4 ) ;
2021-03-13 17:26:53 +01:00
/ *
// optional increase image contrast
// or do it per-channel so mean is done on each channel
// or do it based on histogram
const mean = merge . mean ( ) ;
const factor = 5 ;
const contrast = merge . sub ( mean ) . mul ( factor ) . add ( mean ) ;
* /
2021-03-12 18:54:08 +01:00
// normalize brightness from 0..1
2021-07-29 22:06:03 +02:00
const darken = tf . sub ( merge , merge . min ( ) ) ;
const lighten = tf . div ( darken , darken . max ( ) ) ;
2021-03-12 18:54:08 +01:00
return lighten ;
} ) ;
return image ;
}
2021-03-15 17:14:48 +01:00
export async function predict ( input , config ) : Promise < number [ ] > {
if ( ! model ) return [ ] ;
2020-11-13 22:13:35 +01:00
return new Promise ( async ( resolve ) = > {
2021-03-12 18:54:08 +01:00
// let data: Array<[]> = [];
let data : Array < number > = [ ] ;
2020-11-13 22:13:35 +01:00
if ( config . face . embedding . enabled ) {
2021-03-13 17:26:53 +01:00
const image = enhance ( input ) ;
2021-11-02 16:07:11 +01:00
const dataT = model ? . execute ( image ) as Tensor ;
/ *
2021-08-14 17:16:26 +02:00
const dataT = tf . tidy ( ( ) = > {
2021-04-25 19:16:04 +02:00
/ *
// if needed convert from NHWC to NCHW
const nchw = image . transpose ( [ 3 , 0 , 1 , 2 ] ) ;
2021-11-02 16:07:11 +01:00
const res = model . execute ( image ) ;
2021-04-25 19:16:04 +02:00
// optionally do it twice with flipped image and average results
2021-11-02 16:07:11 +01:00
const res1 = model . execute ( image ) ;
2021-04-25 19:16:04 +02:00
const flipped = tf . image . flipLeftRight ( image ) ;
2021-11-02 16:07:11 +01:00
const res2 = model . execute ( flipped ) ;
2021-04-25 19:16:04 +02:00
const merge = tf . stack ( [ res1 , res2 ] , 2 ) . squeeze ( ) ;
const res = reshape . logSumExp ( 1 ) ;
// optional normalize outputs with l2 normalization
const scaled = tf . tidy ( ( ) = > {
const l2 = res . norm ( 'euclidean' ) ;
const scale = res . div ( l2 ) ;
return scale ;
2021-03-11 19:31:36 +01:00
} ) ;
2021-04-25 19:16:04 +02:00
// optional reduce feature vector complexity
2021-05-23 03:47:59 +02:00
const reshape = tf . reshape ( res , [ 128 , 2 ] ) ; // split 256 vectors into 128 x 2
2021-04-25 19:16:04 +02:00
const reduce = reshape . logSumExp ( 1 ) ; // reduce 2nd dimension by calculating logSumExp on it
2021-08-14 17:16:26 +02:00
return reduce ;
2021-04-25 19:16:04 +02:00
} ) ;
2021-11-02 16:07:11 +01:00
* /
const output = await dataT . data ( ) ;
data = Array . from ( output ) ; // convert typed array to simple array
2021-08-14 17:16:26 +02:00
tf . dispose ( dataT ) ;
2021-03-15 17:14:48 +01:00
tf . dispose ( image ) ;
2020-11-13 22:13:35 +01:00
}
resolve ( data ) ;
} ) ;
}