2021-05-31 16:40:07 +02:00
/ * *
2021-09-25 17:51:15 +02:00
* Results interpolation for smoothening of video detection results inbetween detected frames
2021-05-31 16:40:07 +02:00
* /
2022-08-21 21:23:03 +02:00
import type { Result , FaceResult , BodyResult , HandResult , ObjectResult , PersonResult , Box , Point , BodyLandmark , BodyAnnotation } from '../result' ;
2021-10-09 00:39:04 +02:00
import type { Config } from '../config' ;
import * as moveNetCoords from '../body/movenetcoords' ;
import * as blazePoseCoords from '../body/blazeposecoords' ;
import * as efficientPoseCoords from '../body/efficientposecoords' ;
2021-10-19 15:13:14 +02:00
import { now } from './util' ;
2021-10-27 15:45:38 +02:00
import { env } from './env' ;
2021-05-31 16:40:07 +02:00
2021-11-14 17:22:52 +01:00
const bufferedResult : Result = { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , persons : [ ] , performance : { } , timestamp : 0 , error : null } ;
2021-10-27 15:45:38 +02:00
let interpolateTime = 0 ;
2021-05-31 16:40:07 +02:00
2021-10-09 00:39:04 +02:00
export function calc ( newResult : Result , config : Config ) : Result {
2021-10-19 15:13:14 +02:00
const t0 = now ( ) ;
2021-11-14 17:22:52 +01:00
if ( ! newResult ) return { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , persons : [ ] , performance : { } , timestamp : 0 , error : null } ;
2021-05-31 16:40:07 +02:00
// each record is only updated using deep clone when number of detected record changes, otherwise it will converge by itself
// otherwise bufferedResult is a shallow clone of result plus updated local calculated values
// thus mixing by-reference and by-value assignments to minimize memory operations
2021-06-02 22:46:07 +02:00
const elapsed = Date . now ( ) - newResult . timestamp ;
2022-09-21 19:51:49 +02:00
/ * c u r v e f i t t e d : b u f f e r = 8 - l n ( d e l a y )
interpolation formula : current = ( ( buffer - 1 ) * previous + live ) / buffer
- at 50 ms delay buffer = ~ 4.1 = > 28 % towards live data
- at 250 ms delay buffer = ~ 2.5 = > 40 % towards live data
- at 500 ms delay buffer = ~ 1.8 = > 55 % towards live data
- at 750 ms delay buffer = ~ 1.4 = > 71 % towards live data
- at 1 sec delay buffer = 1 which means live data is used
* /
2021-08-18 20:28:31 +02:00
const bufferedFactor = elapsed < 1000 ? 8 - Math . log ( elapsed + 1 ) : 1 ;
2021-05-31 16:40:07 +02:00
2021-11-14 17:22:52 +01:00
if ( newResult . canvas ) bufferedResult . canvas = newResult . canvas ;
if ( newResult . error ) bufferedResult . error = newResult . error ;
2021-06-04 19:51:01 +02:00
2021-05-31 16:40:07 +02:00
// interpolate body results
if ( ! bufferedResult . body || ( newResult . body . length !== bufferedResult . body . length ) ) {
2022-08-21 19:34:51 +02:00
bufferedResult . body = JSON . parse ( JSON . stringify ( newResult . body ) ) as BodyResult [ ] ; // deep clone once
2021-05-31 16:40:07 +02:00
} else {
for ( let i = 0 ; i < newResult . body . length ; i ++ ) {
const box = newResult . body [ i ] . box // update box
2021-11-23 16:40:40 +01:00
. map ( ( newBoxCoord , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . box [ j ] + newBoxCoord ) / bufferedFactor ) as Box ;
2021-05-31 16:40:07 +02:00
const boxRaw = newResult . body [ i ] . boxRaw // update boxRaw
2021-11-23 16:40:40 +01:00
. map ( ( newBoxCoord , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . boxRaw [ j ] + newBoxCoord ) / bufferedFactor ) as Box ;
2021-06-01 12:55:40 +02:00
const keypoints = ( newResult . body [ i ] . keypoints // update keypoints
2021-11-23 16:40:40 +01:00
. map ( ( newKpt , j ) = > ( {
score : newKpt.score ,
2022-08-21 19:34:51 +02:00
part : newKpt.part ,
2021-06-01 12:55:40 +02:00
position : [
2021-11-23 16:40:40 +01:00
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . position [ 0 ] || 0 ) + ( newKpt . position [ 0 ] || 0 ) ) / bufferedFactor : newKpt.position [ 0 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . position [ 1 ] || 0 ) + ( newKpt . position [ 1 ] || 0 ) ) / bufferedFactor : newKpt.position [ 1 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . position [ 2 ] || 0 ) + ( newKpt . position [ 2 ] || 0 ) ) / bufferedFactor : newKpt.position [ 2 ] ,
2021-06-01 12:55:40 +02:00
] ,
positionRaw : [
2021-12-31 19:58:03 +01:00
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . positionRaw [ 0 ] || 0 ) + ( newKpt . positionRaw [ 0 ] || 0 ) ) / bufferedFactor : newKpt.positionRaw [ 0 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . positionRaw [ 1 ] || 0 ) + ( newKpt . positionRaw [ 1 ] || 0 ) ) / bufferedFactor : newKpt.positionRaw [ 1 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . positionRaw [ 2 ] || 0 ) + ( newKpt . positionRaw [ 2 ] || 0 ) ) / bufferedFactor : newKpt.positionRaw [ 2 ] ,
] ,
distance : [
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . distance ? . [ 0 ] || 0 ) + ( newKpt . distance ? . [ 0 ] || 0 ) ) / bufferedFactor : newKpt.distance?. [ 0 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . distance ? . [ 1 ] || 0 ) + ( newKpt . distance ? . [ 1 ] || 0 ) ) / bufferedFactor : newKpt.distance?. [ 1 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * ( bufferedResult . body [ i ] . keypoints [ j ] . distance ? . [ 2 ] || 0 ) + ( newKpt . distance ? . [ 2 ] || 0 ) ) / bufferedFactor : newKpt.distance?. [ 2 ] ,
2021-06-01 12:55:40 +02:00
] ,
2022-08-21 19:34:51 +02:00
} ) ) ) as { score : number , part : BodyLandmark , position : [ number , number , number ? ] , positionRaw : [ number , number , number ? ] } [ ] ;
2021-10-09 00:39:04 +02:00
2021-12-15 15:26:32 +01:00
const annotations : Record < BodyAnnotation , Point [ ] [ ] > = { } as Record < BodyAnnotation , Point [ ] [ ] > ; // recreate annotations
2021-10-09 00:39:04 +02:00
let coords = { connected : { } } ;
2022-08-21 19:34:51 +02:00
if ( config . body . modelPath ? . includes ( 'efficientpose' ) ) coords = efficientPoseCoords ;
else if ( config . body . modelPath ? . includes ( 'blazepose' ) ) coords = blazePoseCoords ;
else if ( config . body . modelPath ? . includes ( 'movenet' ) ) coords = moveNetCoords ;
2021-10-09 00:39:04 +02:00
for ( const [ name , indexes ] of Object . entries ( coords . connected as Record < string , string [ ] > ) ) {
2022-08-21 19:34:51 +02:00
const pt : Point [ ] [ ] = [ ] ;
2021-10-09 00:39:04 +02:00
for ( let j = 0 ; j < indexes . length - 1 ; j ++ ) {
const pt0 = keypoints . find ( ( kp ) = > kp . part === indexes [ j ] ) ;
const pt1 = keypoints . find ( ( kp ) = > kp . part === indexes [ j + 1 ] ) ;
2021-11-23 16:40:40 +01:00
// if (pt0 && pt1 && pt0.score > (config.body.minConfidence || 0) && pt1.score > (config.body.minConfidence || 0)) pt.push([pt0.position, pt1.position]);
if ( pt0 && pt1 ) pt . push ( [ pt0 . position , pt1 . position ] ) ;
2021-10-09 00:39:04 +02:00
}
annotations [ name ] = pt ;
}
2022-08-21 19:34:51 +02:00
bufferedResult . body [ i ] = { . . . newResult . body [ i ] , box , boxRaw , keypoints , annotations } ; // shallow clone plus updated values
2021-05-31 16:40:07 +02:00
}
}
// interpolate hand results
if ( ! bufferedResult . hand || ( newResult . hand . length !== bufferedResult . hand . length ) ) {
2022-08-21 19:34:51 +02:00
bufferedResult . hand = JSON . parse ( JSON . stringify ( newResult . hand ) ) ; // deep clone once
2021-05-31 16:40:07 +02:00
} else {
for ( let i = 0 ; i < newResult . hand . length ; i ++ ) {
const box = ( newResult . hand [ i ] . box // update box
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . hand [ i ] . box [ j ] + b ) / bufferedFactor ) ) as Box ;
2021-05-31 16:40:07 +02:00
const boxRaw = ( newResult . hand [ i ] . boxRaw // update boxRaw
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . hand [ i ] . boxRaw [ j ] + b ) / bufferedFactor ) ) as Box ;
2021-09-21 22:48:16 +02:00
if ( bufferedResult . hand [ i ] . keypoints . length !== newResult . hand [ i ] . keypoints . length ) bufferedResult . hand [ i ] . keypoints = newResult . hand [ i ] . keypoints ; // reset keypoints as previous frame did not have them
const keypoints = newResult . hand [ i ] . keypoints && newResult . hand [ i ] . keypoints . length > 0 ? newResult . hand [ i ] . keypoints // update landmarks
2021-05-31 16:40:07 +02:00
. map ( ( landmark , j ) = > landmark
2021-09-27 15:19:43 +02:00
. map ( ( coord , k ) = > ( ( ( bufferedFactor - 1 ) * ( bufferedResult . hand [ i ] . keypoints [ j ] [ k ] || 1 ) + ( coord || 0 ) ) / bufferedFactor ) ) as Point )
2021-09-02 14:50:16 +02:00
: [ ] ;
2021-10-09 00:39:04 +02:00
let annotations = { } ;
if ( Object . keys ( bufferedResult . hand [ i ] . annotations ) . length !== Object . keys ( newResult . hand [ i ] . annotations ) . length ) {
bufferedResult . hand [ i ] . annotations = newResult . hand [ i ] . annotations ; // reset annotations as previous frame did not have them
annotations = bufferedResult . hand [ i ] . annotations ;
} else if ( newResult . hand [ i ] . annotations ) {
2021-09-21 22:48:16 +02:00
for ( const key of Object . keys ( newResult . hand [ i ] . annotations ) ) { // update annotations
2022-08-21 21:23:03 +02:00
annotations [ key ] = newResult . hand [ i ] ? . annotations ? . [ key ] ? . [ 0 ]
2021-10-09 00:39:04 +02:00
? newResult . hand [ i ] . annotations [ key ]
2021-12-27 16:59:56 +01:00
. map ( ( val , j : number ) = > val
. map ( ( coord : number , k : number ) = > ( ( bufferedFactor - 1 ) * bufferedResult . hand [ i ] . annotations [ key ] [ j ] [ k ] + coord ) / bufferedFactor ) )
2021-09-21 22:48:16 +02:00
: null ;
}
2021-05-31 16:40:07 +02:00
}
2021-09-12 05:54:35 +02:00
bufferedResult . hand [ i ] = { . . . newResult . hand [ i ] , box , boxRaw , keypoints , annotations : annotations as HandResult [ 'annotations' ] } ; // shallow clone plus updated values
2021-05-31 16:40:07 +02:00
}
}
// interpolate face results
if ( ! bufferedResult . face || ( newResult . face . length !== bufferedResult . face . length ) ) {
2022-08-21 19:34:51 +02:00
bufferedResult . face = JSON . parse ( JSON . stringify ( newResult . face ) ) as FaceResult [ ] ; // deep clone once
2021-05-31 16:40:07 +02:00
} else {
for ( let i = 0 ; i < newResult . face . length ; i ++ ) {
const box = ( newResult . face [ i ] . box // update box
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . face [ i ] . box [ j ] + b ) / bufferedFactor ) ) as Box ;
2021-05-31 16:40:07 +02:00
const boxRaw = ( newResult . face [ i ] . boxRaw // update boxRaw
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . face [ i ] . boxRaw [ j ] + b ) / bufferedFactor ) ) as Box ;
2021-11-12 21:07:23 +01:00
if ( newResult . face [ i ] . rotation ) {
const rotation : {
matrix : [ number , number , number , number , number , number , number , number , number ] ,
angle : { roll : number , yaw : number , pitch : number } ,
gaze : { bearing : number , strength : number }
} = { matrix : [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] , angle : { roll : 0 , yaw : 0 , pitch : 0 } , gaze : { bearing : 0 , strength : 0 } } ;
rotation . matrix = newResult . face [ i ] . rotation ? . matrix as [ number , number , number , number , number , number , number , number , number ] ;
rotation . angle = {
2022-09-21 19:51:49 +02:00
roll : ( ( bufferedFactor - 1 ) * ( bufferedResult . face [ i ] . rotation ? . angle ? . roll || 0 ) + ( newResult . face [ i ] . rotation ? . angle ? . roll || 0 ) ) / bufferedFactor ,
yaw : ( ( bufferedFactor - 1 ) * ( bufferedResult . face [ i ] . rotation ? . angle ? . yaw || 0 ) + ( newResult . face [ i ] . rotation ? . angle ? . yaw || 0 ) ) / bufferedFactor ,
pitch : ( ( bufferedFactor - 1 ) * ( bufferedResult . face [ i ] . rotation ? . angle ? . pitch || 0 ) + ( newResult . face [ i ] . rotation ? . angle ? . pitch || 0 ) ) / bufferedFactor ,
2021-11-12 21:07:23 +01:00
} ;
rotation . gaze = {
// not fully correct due projection on circle, also causes wrap-around draw on jump from negative to positive
2022-08-21 19:34:51 +02:00
bearing : ( ( bufferedFactor - 1 ) * ( bufferedResult . face [ i ] . rotation ? . gaze . bearing || 0 ) + ( newResult . face [ i ] . rotation ? . gaze . bearing || 0 ) ) / bufferedFactor ,
strength : ( ( bufferedFactor - 1 ) * ( bufferedResult . face [ i ] . rotation ? . gaze . strength || 0 ) + ( newResult . face [ i ] . rotation ? . gaze . strength || 0 ) ) / bufferedFactor ,
2021-11-12 21:07:23 +01:00
} ;
bufferedResult . face [ i ] = { . . . newResult . face [ i ] , rotation , box , boxRaw } ; // shallow clone plus updated values
2022-09-21 19:51:49 +02:00
} else {
bufferedResult . face [ i ] = { . . . newResult . face [ i ] , box , boxRaw } ; // shallow clone plus updated values
2021-11-12 21:07:23 +01:00
}
2021-05-31 16:40:07 +02:00
}
}
// interpolate object detection results
if ( ! bufferedResult . object || ( newResult . object . length !== bufferedResult . object . length ) ) {
2022-08-21 19:34:51 +02:00
bufferedResult . object = JSON . parse ( JSON . stringify ( newResult . object ) ) as ObjectResult [ ] ; // deep clone once
2021-05-31 16:40:07 +02:00
} else {
for ( let i = 0 ; i < newResult . object . length ; i ++ ) {
2021-06-01 13:07:01 +02:00
const box = ( newResult . object [ i ] . box // update box
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . object [ i ] . box [ j ] + b ) / bufferedFactor ) ) as Box ;
2021-06-01 13:07:01 +02:00
const boxRaw = ( newResult . object [ i ] . boxRaw // update boxRaw
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . object [ i ] . boxRaw [ j ] + b ) / bufferedFactor ) ) as Box ;
2021-05-31 16:40:07 +02:00
bufferedResult . object [ i ] = { . . . newResult . object [ i ] , box , boxRaw } ; // shallow clone plus updated values
}
}
// interpolate person results
2021-06-14 16:23:06 +02:00
if ( newResult . persons ) {
const newPersons = newResult . persons ; // trigger getter function
if ( ! bufferedResult . persons || ( newPersons . length !== bufferedResult . persons . length ) ) {
2022-08-21 19:34:51 +02:00
bufferedResult . persons = JSON . parse ( JSON . stringify ( newPersons ) ) as PersonResult [ ] ;
2021-06-14 16:23:06 +02:00
} else {
for ( let i = 0 ; i < newPersons . length ; i ++ ) { // update person box, we don't update the rest as it's updated as reference anyhow
bufferedResult . persons [ i ] . box = ( newPersons [ i ] . box
2021-09-27 15:19:43 +02:00
. map ( ( box , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . persons [ i ] . box [ j ] + box ) / bufferedFactor ) ) as Box ;
2021-06-14 16:23:06 +02:00
}
2021-05-31 16:40:07 +02:00
}
}
// just copy latest gestures without interpolation
2022-08-21 21:23:03 +02:00
if ( newResult . gesture ) bufferedResult . gesture = newResult . gesture ;
2021-10-09 00:39:04 +02:00
// append interpolation performance data
2021-10-19 17:28:59 +02:00
const t1 = now ( ) ;
2021-10-27 15:45:38 +02:00
interpolateTime = env . perfadd ? interpolateTime + Math . round ( t1 - t0 ) : Math . round ( t1 - t0 ) ;
if ( newResult . performance ) bufferedResult . performance = { . . . newResult . performance , interpolate : interpolateTime } ;
2021-05-31 16:40:07 +02:00
return bufferedResult ;
}