7
7
YOLO Object detection
8
8
Heavily derived from https://github.com/ModelDepot/tfjs-yolo-tiny (ModelDepot: modeldepot.io)
9
9
*/
10
- import * as tf from " @tensorflow/tfjs" ;
11
- import CLASS_NAMES from " ./../utils/COCO_CLASSES" ;
10
+ import * as tf from ' @tensorflow/tfjs' ;
11
+ import CLASS_NAMES from ' ./../utils/COCO_CLASSES' ;
12
12
13
13
const DEFAULTS = {
14
14
filterBoxesThreshold : 0.01 ,
15
15
IOUThreshold : 0.4 ,
16
16
classProbThreshold : 0.4 ,
17
- URL :
18
- "https://raw.githubusercontent.com/ml5js/ml5-library/master/src/YOLO/model.json"
17
+ URL : 'https://raw.githubusercontent.com/ml5js/ml5-library/master/src/YOLO/model.json'
18
+
19
19
} ;
20
20
21
21
class YOLO {
22
22
constructor ( options ) {
23
23
this . filterBoxesThreshold = options . filterBoxesThreshold || DEFAULTS . filterBoxesThreshold ;
24
24
this . IOUThreshold = options . IOUThreshold || DEFAULTS . IOUThreshold ;
25
- this . classProbThreshold = options . classProbThreshold || DEFAULTS . classProbThreshold ;
25
+ this . classProbThreshold = options . classProbThreshold || DEFAULTS . classProbThreshold ;
26
26
this . modelURL = options . url || DEFAULTS . URL ;
27
27
this . model = null ;
28
28
this . inputWidth = 416 ;
@@ -33,18 +33,18 @@ class YOLO {
33
33
[ 1.87446 , 2.06253 ] ,
34
34
[ 3.33843 , 5.47434 ] ,
35
35
[ 7.88282 , 3.52778 ] ,
36
- [ 9.77052 , 9.16828 ]
36
+ [ 9.77052 , 9.16828 ] ,
37
37
] ;
38
- this . scaleX ;
39
- this . scaleY ;
38
+ // this.scaleX;
39
+ // this.scaleY;
40
40
this . anchorsLength = this . anchors . length ;
41
41
this . classesLength = this . classNames . length ;
42
42
this . init ( ) ;
43
43
}
44
44
45
45
init ( ) {
46
46
// indices tensor to filter the elements later on
47
- this . indicesTensor = tf . range ( 1 , 846 , 1 , " int32" ) ;
47
+ this . indicesTensor = tf . range ( 1 , 846 , 1 , ' int32' ) ;
48
48
49
49
// Grid To Split the raw predictions : Assumes Our Model output is 1 Tensor with 13x13x425
50
50
// gonna hard code all this stuff see if it works
@@ -53,18 +53,18 @@ class YOLO {
53
53
54
54
[ this . ConvIndex , this . ConvDims , this . AnchorsTensor ] = tf . tidy ( ( ) => {
55
55
let ConvIndex = tf . range ( 0 , 13 ) ;
56
- let ConvHeightIndex = tf . tile ( ConvIndex , [ 13 ] ) ;
56
+ const ConvHeightIndex = tf . tile ( ConvIndex , [ 13 ] ) ;
57
57
58
58
let ConvWidthindex = tf . tile ( tf . expandDims ( ConvIndex , 0 ) , [ 13 , 1 ] ) ;
59
59
ConvWidthindex = tf . transpose ( ConvWidthindex ) . flatten ( ) ;
60
60
61
61
ConvIndex = tf . transpose ( tf . stack ( [ ConvHeightIndex , ConvWidthindex ] ) ) ;
62
62
ConvIndex = tf . reshape ( ConvIndex , [ 13 , 13 , 1 , 2 ] ) ;
63
63
64
- let ConvDims = tf . reshape ( tf . tensor1d ( [ 13 , 13 ] ) , [ 1 , 1 , 1 , 2 ] ) ;
65
- //AnchorsTensor
66
- let Aten = tf . tensor2d ( this . anchors ) ;
67
- let AnchorsTensor = tf . reshape ( Aten , [ 1 , 1 , this . anchorsLength , 2 ] ) ;
64
+ const ConvDims = tf . reshape ( tf . tensor1d ( [ 13 , 13 ] ) , [ 1 , 1 , 1 , 2 ] ) ;
65
+ // AnchorsTensor
66
+ const Aten = tf . tensor2d ( this . anchors ) ;
67
+ const AnchorsTensor = tf . reshape ( Aten , [ 1 , 1 , this . anchorsLength , 2 ] ) ;
68
68
69
69
return [ ConvIndex , ConvDims , AnchorsTensor ] ;
70
70
} ) ;
@@ -94,6 +94,7 @@ class YOLO {
94
94
95
95
//does not dispose of the model atm
96
96
dispose ( ) {
97
+ this . model = null ;
97
98
tf . disposeconstiables ( ) ;
98
99
}
99
100
@@ -109,31 +110,31 @@ class YOLO {
109
110
let img = tf . fromPixels ( input ) ;
110
111
this . imgWidth = img . shape [ 1 ] ;
111
112
this . imgHeight = img . shape [ 0 ] ;
112
- img = tf . image . resizeBilinear ( img , [ this . inputHeight , this . inputWidth ] )
113
- . toFloat ( )
114
- . div ( tf . scalar ( 255 ) )
115
- . expandDims ( 0 ) ;
116
- //Scale Stuff
113
+ img = tf . image . resizeBilinear ( img , [ this . inputHeight , this . inputWidth ] ) . toFloat ( ) . div ( tf . scalar ( 255 ) ) . expandDims ( 0 ) ;
114
+
115
+
116
+
117
+ // Scale Stuff
117
118
this . scaleX = this . imgHeight / this . inputHeight ;
118
119
this . scaleY = this . imgWidth / this . inputWidth ;
119
120
return img ;
120
121
}
121
122
122
123
async postProccess ( rawPrediction ) {
123
- let results = {
124
+ const results = {
124
125
totalDetections : 0 ,
125
- detections : [ ]
126
+ detections : [ ] ,
126
127
} ;
128
+ const [ boxes , boxScores , classes , Indices , ] = tf . tidy ( ( ) => {
127
129
128
- const [ boxes , boxScores , classes , Indices ] = tf . tidy ( ( ) => {
129
- rawPrediction = tf . reshape ( rawPrediction , [ 13 , 13 , this . anchorsLength , this . classesLength + 5 ] ) ;
130
+ rawPrediction = tf . reshape ( rawPrediction , [ 13 , 13 , this . anchorsLength , this . classesLength + 5 ] ) ;
130
131
// Box Coords
131
- let BoxXY = tf . sigmoid ( rawPrediction . slice ( [ 0 , 0 , 0 , 0 ] , [ 13 , 13 , this . anchorsLength , 2 ] ) ) ;
132
- let BoxWH = tf . exp ( rawPrediction . slice ( [ 0 , 0 , 0 , 2 ] , [ 13 , 13 , this . anchorsLength , 2 ] ) ) ;
132
+ const boxXY = tf . sigmoid ( rawPrediction . slice ( [ 0 , 0 , 0 , 0 ] , [ 13 , 13 , this . anchorsLength , 2 ] ) ) ;
133
+ const boxWH = tf . exp ( rawPrediction . slice ( [ 0 , 0 , 0 , 2 ] , [ 13 , 13 , this . anchorsLength , 2 ] ) ) ;
133
134
// ObjectnessScore
134
- let BoxConfidence = tf . sigmoid ( rawPrediction . slice ( [ 0 , 0 , 0 , 4 ] , [ 13 , 13 , this . anchorsLength , 1 ] ) ) ;
135
+ const boxConfidence = tf . sigmoid ( rawPrediction . slice ( [ 0 , 0 , 0 , 4 ] , [ 13 , 13 , this . anchorsLength , 1 ] ) ) ;
135
136
// ClassProb
136
- let BoxClassProbs = tf . softmax ( rawPrediction . slice ( [ 0 , 0 , 0 , 5 ] , [ 13 , 13 , this . anchorsLength , this . classesLength ] ) ) ;
137
+ const boxClassProbs = tf . softmax ( rawPrediction . slice ( [ 0 , 0 , 0 , 5 ] , [ 13 , 13 , this . anchorsLength , this . classesLength ] ) ) ;
137
138
138
139
// from boxes with xy wh to x1,y1 x2,y2
139
140
// Mainly for NMS + rescaling
@@ -144,98 +145,78 @@ class YOLO {
144
145
y2 = y + (w/2)
145
146
*/
146
147
// BoxScale
147
- BoxXY = tf . div ( tf . add ( BoxXY , this . ConvIndex ) , this . ConvDims ) ;
148
+ const BoxXY_1 = tf . div ( tf . add ( boxXY , this . ConvIndex ) , this . ConvDims ) ;
148
149
149
- BoxWH = tf . div ( tf . mul ( BoxWH , this . AnchorsTensor ) , this . ConvDims ) ;
150
+ const BoxWH_1 = tf . div ( tf . mul ( boxWH , this . AnchorsTensor ) , this . ConvDims ) ;
150
151
151
- const Div = tf . div ( BoxWH , tf . scalar ( 2 ) ) ;
152
+ const Div = tf . div ( BoxWH_1 , tf . scalar ( 2 ) ) ;
152
153
153
- const BoxMins = tf . sub ( BoxXY , Div ) ;
154
+ const boxMins = tf . sub ( BoxXY_1 , Div ) ;
155
+ const boxMaxes = tf . add ( BoxXY_1 , Div ) ;
154
156
155
- const BoxMaxes = tf . add ( BoxXY , Div ) ;
156
- const Size = [ BoxMins . shape [ 0 ] , BoxMins . shape [ 1 ] , BoxMins . shape [ 2 ] , 1 ] ;
157
+ const size = [ boxMins . shape [ 0 ] , boxMins . shape [ 1 ] , boxMins . shape [ 2 ] , 1 ] ;
157
158
158
159
// main box tensor
159
- const boxes = tf
160
- . concat (
161
- [
162
- BoxMins . slice ( [ 0 , 0 , 0 , 1 ] , Size ) ,
163
- BoxMins . slice ( [ 0 , 0 , 0 , 0 ] , Size ) ,
164
- BoxMaxes . slice ( [ 0 , 0 , 0 , 1 ] , Size ) ,
165
- BoxMaxes . slice ( [ 0 , 0 , 0 , 0 ] , Size )
166
- ] , 3 )
167
- . reshape ( [ 845 , 4 ] ) ;
160
+ const boxes = tf . concat ( [ boxMins . slice ( [ 0 , 0 , 0 , 1 ] , Size ) , boxMins . slice ( [ 0 , 0 , 0 , 0 ] , size ) , boxMaxes . slice ( [ 0 , 0 , 0 , 1 ] , size ) , boxMaxes . slice ( [ 0 , 0 , 0 , 0 ] , size ) ] , 3 ) . reshape ( [ 845 , 4 ] ) ;
168
161
169
162
// Filterboxes by objectness threshold
170
163
// not filtering / getting a mask really
171
164
172
- BoxConfidence = BoxConfidence . squeeze ( [ 3 ] ) ;
173
- const ObjectnessMask = tf . greaterEqual (
174
- BoxConfidence ,
175
- tf . scalar ( this . filterboxesThreshold )
176
- ) ;
165
+ const boxConfidence_1 = boxConfidence . squeeze ( [ 3 ] ) ;
166
+ const objectnessMask = tf . greaterEqual ( boxConfidence_1 , tf . scalar ( this . filterBoxesThreshold ) ) ;
177
167
178
168
// Filterboxes by class probability threshold
179
- const boxScores = tf . mul ( BoxConfidence , tf . max ( BoxClassProbs , 3 ) ) ;
180
- const BoxClassProbMask = tf . greaterEqual (
181
- boxScores ,
182
- tf . scalar ( this . classProbThreshold )
183
- ) ;
169
+ const boxScores = tf . mul ( boxConfidence_1 , tf . max ( boxClassProbs , 3 ) ) ;
170
+ const boxClassProbMask = tf . greaterEqual ( boxScores , tf . scalar ( this . classProbThreshold ) ) ;
184
171
185
172
// getting classes indices
186
- const classes = tf . argMax ( BoxClassProbs , - 1 ) ;
173
+ const classes = tf . argMax ( boxClassProbs , - 1 ) ;
187
174
188
175
// Final Mask each elem that survived both filters (0x0 0x1 1x0 = fail ) 1x1 = survived
189
- const FinalMask = BoxClassProbMask . mul ( ObjectnessMask ) ;
176
+ const finalMask = boxClassProbMask . mul ( objectnessMask ) ;
190
177
191
- const Indices = FinalMask . flatten ( )
192
- . toInt ( )
193
- . mul ( this . IndicesTensor ) ;
194
- return [ boxes , boxScores , classes , Indices ] ;
178
+ const indices = finalMask . flatten ( ) . toInt ( ) . mul ( this . indicesTensor ) ;
179
+ return [ boxes , boxScores , classes , indices ] ;
195
180
} ) ;
196
181
197
182
//we started at one in the range so we remove 1 now
198
183
199
- let indicesArr = Array . from ( await Indices . data ( ) )
200
- . filter ( i => i > 0 )
201
- . map ( i => i - 1 ) ;
184
+ let indicesArr = Array . from ( await Indices . data ( ) ) . filter ( i => i > 0 ) . map ( i => i - 1 ) ;
202
185
203
- if ( indicesArr . length == 0 ) {
186
+ if ( indicesArr . length === 0 ) {
204
187
boxes . dispose ( ) ;
205
188
boxScores . dispose ( ) ;
206
189
classes . dispose ( ) ;
207
190
return results ;
208
191
}
192
+ const [ filteredBoxes , filteredScores , filteredclasses ] = tf . tidy ( ( ) => {
209
193
const indicesTensor = tf . tensor1d ( indicesArr , "int32" ) ;
210
- let filteredBoxes = boxes . gather ( indicesTensor ) ;
211
- let filteredScores = boxScores . flatten ( ) . gather ( indicesTensor ) ;
212
- let filteredclasses = classes . flatten ( ) . gather ( indicesTensor ) ;
213
- boxes . dispose ( ) ;
214
- boxScores . dispose ( ) ;
215
- classes . dispose ( ) ;
216
- indicesTensor . dispose ( ) ;
217
-
218
- //Img Rescale
219
- const Height = tf . scalar ( this . imgHeight ) ;
220
- const Width = tf . scalar ( this . imgWidth ) ;
221
- const ImageDims = tf . stack ( [ Height , Width , Height , Width ] ) . reshape ( [ 1 , 4 ] ) ;
222
- filteredBoxes = filteredBoxes . mul ( ImageDims ) ;
223
-
194
+ const filteredBoxes = boxes . gather ( indicesTensor ) ;
195
+ const filteredScores = boxScores . flatten ( ) . gather ( indicesTensor ) ;
196
+ const filteredclasses = classes . flatten ( ) . gather ( indicesTensor ) ;
197
+
198
+ // Img Rescale
199
+ const Height = tf . scalar ( this . imgHeight ) ;
200
+ const Width = tf . scalar ( this . imgWidth ) ;
201
+ const ImageDims = tf . stack ( [ Height , Width , Height , Width ] ) . reshape ( [ 1 , 4 ] ) ;
202
+ const filteredBoxes_1 = filteredBoxes . mul ( ImageDims ) ;
203
+
204
+ return [ filteredBoxes_1 , filteredScores , filteredclasses ]
205
+ } )
206
+
207
+
224
208
// NonMaxSuppression
225
209
// GreedyNMS
226
- const [ boxArr , scoreArr , classesArr ] = await Promise . all ( [
227
- filteredBoxes . data ( ) ,
228
- filteredScores . data ( ) ,
229
- filteredclasses . data ( )
230
- ] ) ;
210
+ const [ boxArr , scoreArr , classesArr ] = await Promise . all ( [ filteredBoxes . data ( ) , filteredScores . data ( ) , filteredclasses . data ( ) , ] ) ;
211
+
231
212
filteredBoxes . dispose ( ) ;
232
213
filteredScores . dispose ( ) ;
233
214
filteredclasses . dispose ( ) ;
234
215
235
216
let zipped = [ ] ;
236
217
for ( let i = 0 ; i < scoreArr . length ; i ++ ) {
237
218
// [Score,x,y,w,h,classindex]
238
- zipped . push ( [ scoreArr [ i ] , [ boxArr [ 4 * i ] , boxArr [ 4 * i + 1 ] , boxArr [ 4 * i + 2 ] , boxArr [ 4 * i + 3 ] ] , classesArr [ i ] ] ) ;
219
+ zipped . push ( [ scoreArr [ i ] , [ boxArr [ 4 * i ] , boxArr [ 4 * i + 1 ] , boxArr [ 4 * i + 2 ] , boxArr [ 4 * i + 3 ] ] , classesArr [ i ] ] ) ;
239
220
}
240
221
241
222
// Sort by descending order of scores (first index of zipped array)
@@ -247,8 +228,8 @@ class YOLO {
247
228
let Push = true ;
248
229
for ( let i = 0 ; i < selectedBoxes . length ; i ++ ) {
249
230
// Compare IoU of zipped[1], since that is the box coordinates arr
250
- let w = Math . min ( box [ 1 ] [ 3 ] , selectedBoxes [ i ] [ 1 ] [ 3 ] ) - Math . max ( box [ 1 ] [ 1 ] , selectedBoxes [ i ] [ 1 ] [ 1 ] ) ;
251
- let h = Math . min ( box [ 1 ] [ 2 ] , selectedBoxes [ i ] [ 1 ] [ 2 ] ) - Math . max ( box [ 1 ] [ 0 ] , selectedBoxes [ i ] [ 1 ] [ 0 ] ) ;
231
+ let w = Math . min ( box [ 1 ] [ 3 ] , selectedBoxes [ i ] [ 1 ] [ 3 ] ) - Math . max ( box [ 1 ] [ 1 ] , selectedBoxes [ i ] [ 1 ] [ 1 ] ) ;
232
+ let h = Math . min ( box [ 1 ] [ 2 ] , selectedBoxes [ i ] [ 1 ] [ 2 ] ) - Math . max ( box [ 1 ] [ 0 ] , selectedBoxes [ i ] [ 1 ] [ 0 ] ) ;
252
233
let Intersection = w < 0 || h < 0 ? 0 : w * h ;
253
234
let Union = ( box [ 1 ] [ 3 ] - box [ 1 ] [ 1 ] ) * ( box [ 1 ] [ 2 ] - box [ 1 ] [ 0 ] ) + ( selectedBoxes [ i ] [ 1 ] [ 3 ] - selectedBoxes [ i ] [ 1 ] [ 1 ] ) * ( selectedBoxes [ i ] [ 1 ] [ 2 ] - selectedBoxes [ i ] [ 1 ] [ 0 ] ) - Intersection ;
254
235
let Iou = Intersection / Union ;
@@ -271,24 +252,13 @@ class YOLO {
271
252
const [ x1 , y1 , x2 , y2 ] = selectedBoxes [ id ] [ 1 ] ;
272
253
// Need to get this out
273
254
// TODO : add a hsla color for later visualization
274
- const resultObj = {
275
- id,
276
- className,
277
- classIndex,
278
- classProb,
279
- classProbRounded,
280
- x1,
281
- y1,
282
- x2,
283
- y2
284
- } ;
255
+ const resultObj = { id, className, classIndex, classProb, classProbRounded, x1, y1, x2, y2, } ;
285
256
results . detections . push ( resultObj ) ;
286
257
}
287
258
// Misc
288
259
results . totalDetections = results . detections . length ;
289
260
results . scaleX = this . scaleX ;
290
261
results . scaleY = this . scaleY ;
291
-
292
262
return results ;
293
263
}
294
264
}
0 commit comments