@@ -4,9 +4,11 @@ import { convLayer } from '../commons/convLayer';
4
4
import { getImageTensor } from '../commons/getImageTensor' ;
5
5
import { ConvParams } from '../commons/types' ;
6
6
import { NetInput } from '../NetInput' ;
7
+ import { padToSquare } from '../padToSquare' ;
7
8
import { Point } from '../Point' ;
8
9
import { toNetInput } from '../toNetInput' ;
9
10
import { Dimensions , TNetInput } from '../types' ;
11
+ import { isEven } from '../utils' ;
10
12
import { extractParams } from './extractParams' ;
11
13
import { FaceLandmarks } from './FaceLandmarks' ;
12
14
import { fullyConnectedLayer } from './fullyConnectedLayer' ;
@@ -41,31 +43,25 @@ export class FaceLandmarkNet {
41
43
this . _params = extractParams ( weights )
42
44
}
43
45
44
- public async detectLandmarks ( input : tf . Tensor | NetInput | TNetInput ) {
45
- if ( ! this . _params ) {
46
+ public forwardTensor ( imgTensor : tf . Tensor4D ) : tf . Tensor2D {
47
+ const params = this . _params
48
+
49
+ if ( ! params ) {
46
50
throw new Error ( 'FaceLandmarkNet - load model before inference' )
47
51
}
48
52
49
- const netInput = input instanceof tf . Tensor
50
- ? input
51
- : await toNetInput ( input )
52
-
53
- let imageDimensions : Dimensions | undefined
54
-
55
- const outTensor = tf . tidy ( ( ) => {
56
- const params = this . _params
57
-
58
- let imgTensor = getImageTensor ( netInput )
59
- const [ height , width ] = imgTensor . shape . slice ( 1 )
60
- imageDimensions = { width, height }
53
+ return tf . tidy ( ( ) => {
54
+ const [ batchSize , height , width ] = imgTensor . shape . slice ( )
61
55
56
+ let x = padToSquare ( imgTensor , true )
57
+ const [ heightAfterPadding , widthAfterPadding ] = x . shape . slice ( 1 )
62
58
63
59
// work with 128 x 128 sized face images
64
- if ( imgTensor . shape [ 1 ] !== 128 || imgTensor . shape [ 2 ] !== 128 ) {
65
- imgTensor = tf . image . resizeBilinear ( imgTensor , [ 128 , 128 ] )
60
+ if ( heightAfterPadding !== 128 || widthAfterPadding !== 128 ) {
61
+ x = tf . image . resizeBilinear ( x , [ 128 , 128 ] )
66
62
}
67
63
68
- let out = conv ( imgTensor , params . conv0_params )
64
+ let out = conv ( x , params . conv0_params )
69
65
out = maxPool ( out )
70
66
out = conv ( out , params . conv1_params )
71
67
out = conv ( out , params . conv2_params )
@@ -80,14 +76,58 @@ export class FaceLandmarkNet {
80
76
const fc0 = tf . relu ( fullyConnectedLayer ( out . as2D ( out . shape [ 0 ] , - 1 ) , params . fc0_params ) )
81
77
const fc1 = fullyConnectedLayer ( fc0 , params . fc1_params )
82
78
83
- return fc1
79
+
80
+ const createInterleavedTensor = ( fillX : number , fillY : number ) =>
81
+ tf . stack ( [
82
+ tf . fill ( [ 68 ] , fillX ) ,
83
+ tf . fill ( [ 68 ] , fillY )
84
+ ] , 1 ) . as2D ( batchSize , 136 )
85
+
86
+
87
+ /* shift coordinates back, to undo centered padding
88
+ ((x * widthAfterPadding) - shiftX) / width
89
+ ((y * heightAfterPadding) - shiftY) / height
90
+ */
91
+ const shiftX = Math . floor ( Math . abs ( widthAfterPadding - width ) / 2 )
92
+ const shiftY = Math . floor ( Math . abs ( heightAfterPadding - height ) / 2 )
93
+ const landmarkTensor = fc1
94
+ . mul ( createInterleavedTensor ( widthAfterPadding , heightAfterPadding ) )
95
+ . sub ( createInterleavedTensor ( shiftX , shiftY ) )
96
+ . div ( createInterleavedTensor ( width , height ) )
97
+
98
+ return landmarkTensor as tf . Tensor2D
99
+ } )
100
+ }
101
+
102
+ public async forward ( input : tf . Tensor | NetInput | TNetInput ) : Promise < tf . Tensor2D > {
103
+ const netInput = input instanceof tf . Tensor
104
+ ? input
105
+ : await toNetInput ( input )
106
+
107
+ return this . forwardTensor ( getImageTensor ( netInput ) )
108
+ }
109
+
110
+ public async detectLandmarks ( input : tf . Tensor | NetInput | TNetInput ) {
111
+ const netInput = input instanceof tf . Tensor
112
+ ? input
113
+ : await toNetInput ( input )
114
+
115
+ let imageDimensions : Dimensions | undefined
116
+
117
+ const outTensor = tf . tidy ( ( ) => {
118
+ const imgTensor = getImageTensor ( netInput )
119
+
120
+ const [ height , width ] = imgTensor . shape . slice ( 1 )
121
+ imageDimensions = { width, height }
122
+
123
+ return this . forwardTensor ( imgTensor )
84
124
} )
85
125
86
126
const faceLandmarksArray = Array . from ( await outTensor . data ( ) )
87
127
outTensor . dispose ( )
88
128
89
- const xCoords = faceLandmarksArray . filter ( ( c , i ) => ( i - 1 ) % 2 )
90
- const yCoords = faceLandmarksArray . filter ( ( c , i ) => i % 2 )
129
+ const xCoords = faceLandmarksArray . filter ( ( _ , i ) => isEven ( i ) )
130
+ const yCoords = faceLandmarksArray . filter ( ( _ , i ) => ! isEven ( i ) )
91
131
92
132
return new FaceLandmarks (
93
133
Array ( 68 ) . fill ( 0 ) . map ( ( _ , i ) => new Point ( xCoords [ i ] , yCoords [ i ] ) ) ,
0 commit comments