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
* /
2021-09-27 19:58:13 +02:00
import type { Result , FaceResult , BodyResult , HandResult , ObjectResult , GestureResult , PersonResult , Box , Point } 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-05-31 16:40:07 +02:00
const bufferedResult : Result = { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , persons : [ ] , performance : { } , timestamp : 0 } ;
2021-10-09 00:39:04 +02:00
export function calc ( newResult : Result , config : Config ) : Result {
const t0 = performance . now ( ) ;
2021-08-14 19:39:26 +02:00
if ( ! newResult ) return { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , persons : [ ] , performance : { } , timestamp : 0 } ;
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 ;
// curve fitted: buffer = 8 - ln(delay)
// interpolation formula: current = ((buffer - 1) * previous + live) / buffer
// - at 50ms delay buffer = ~4.1 => 28% towards live data
// - at 250ms delay buffer = ~2.5 => 40% towards live data
// - at 500ms delay buffer = ~1.8 => 55% towards live data
// - at 750ms delay buffer = ~1.4 => 71% towards live data
// - at 1sec 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-06-04 19:51:01 +02:00
bufferedResult . canvas = newResult . canvas ;
2021-05-31 16:40:07 +02:00
// interpolate body results
if ( ! bufferedResult . body || ( newResult . body . length !== bufferedResult . body . length ) ) {
2021-09-12 05:54:35 +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-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . box [ j ] + b ) / bufferedFactor ) as Box ;
2021-05-31 16:40:07 +02:00
const boxRaw = newResult . body [ i ] . boxRaw // update boxRaw
2021-09-27 15:19:43 +02:00
. map ( ( b , j ) = > ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . boxRaw [ j ] + b ) / bufferedFactor ) as Box ;
2021-06-01 12:55:40 +02:00
const keypoints = ( newResult . body [ i ] . keypoints // update keypoints
2021-05-31 16:40:07 +02:00
. map ( ( keypoint , j ) = > ( {
score : keypoint.score ,
part : keypoint.part ,
2021-06-01 12:55:40 +02:00
position : [
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . keypoints [ j ] . position [ 0 ] + keypoint . position [ 0 ] ) / bufferedFactor : keypoint.position [ 0 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . keypoints [ j ] . position [ 1 ] + keypoint . position [ 1 ] ) / bufferedFactor : keypoint.position [ 1 ] ,
] ,
positionRaw : [
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . keypoints [ j ] . positionRaw [ 0 ] + keypoint . positionRaw [ 0 ] ) / bufferedFactor : keypoint.position [ 0 ] ,
bufferedResult . body [ i ] . keypoints [ j ] ? ( ( bufferedFactor - 1 ) * bufferedResult . body [ i ] . keypoints [ j ] . positionRaw [ 1 ] + keypoint . positionRaw [ 1 ] ) / bufferedFactor : keypoint.position [ 1 ] ,
] ,
} ) ) ) as Array < { score : number , part : string , position : [ number , number , number ? ] , positionRaw : [ number , number , number ? ] } > ;
2021-10-09 00:39:04 +02:00
const annotations : Record < string , Point [ ] [ ] > = { } ;
let coords = { connected : { } } ;
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 ;
for ( const [ name , indexes ] of Object . entries ( coords . connected as Record < string , string [ ] > ) ) {
const pt : Array < Point [ ] > = [ ] ;
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 ] ) ;
if ( pt0 && pt1 && pt0 . score > ( config . body . minConfidence || 0 ) && pt1 . score > ( config . body . minConfidence || 0 ) ) pt . push ( [ pt0 . position , pt1 . position ] ) ;
}
annotations [ name ] = pt ;
}
bufferedResult . body [ i ] = { . . . newResult . body [ i ] , box , boxRaw , keypoints , annotations : annotations as BodyResult [ '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 ) ) {
2021-09-12 05:54:35 +02:00
bufferedResult . hand = JSON . parse ( JSON . stringify ( newResult . hand as HandResult [ ] ) ) ; // 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
annotations [ key ] = newResult . hand [ i ] . annotations [ key ] && newResult . hand [ i ] . annotations [ key ] [ 0 ]
2021-10-09 00:39:04 +02:00
? newResult . hand [ i ] . annotations [ key ]
. map ( ( val , j ) = > val
. map ( ( coord , k ) = > ( ( 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 ) ) {
2021-09-12 05:54:35 +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-06-01 13:37:17 +02:00
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 = {
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-05-31 16:40:07 +02:00
} ;
2021-06-01 13:37:17 +02:00
rotation . gaze = {
2021-05-31 16:40:07 +02:00
// not fully correct due projection on circle, also causes wrap-around draw on jump from negative to positive
2021-06-01 13:37:17 +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-05-31 16:40:07 +02:00
} ;
bufferedResult . face [ i ] = { . . . newResult . face [ i ] , rotation , box , boxRaw } ; // shallow clone plus updated values
}
}
// interpolate object detection results
if ( ! bufferedResult . object || ( newResult . object . length !== bufferedResult . object . length ) ) {
2021-09-12 05:54:35 +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 ) ) {
2021-09-12 05:54:35 +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
2021-09-12 05:54:35 +02:00
if ( newResult . gesture ) bufferedResult . gesture = newResult . gesture as GestureResult [ ] ;
2021-10-09 00:39:04 +02:00
// append interpolation performance data
const t1 = performance . now ( ) ;
if ( newResult . performance ) bufferedResult . performance = { . . . newResult . performance , interpolate : Math.round ( t1 - t0 ) } ;
2021-05-31 16:40:07 +02:00
return bufferedResult ;
}