@@ -9,12 +9,13 @@ Heavily derived from https://github.com/ModelDepot/tfjs-yolo-tiny (ModelDepot: m
9
9
*/
10
10
import * as tf from '@tensorflow/tfjs' ;
11
11
import CLASS_NAMES from './../utils/COCO_CLASSES' ;
12
+ import { iou } from './utils' ;
12
13
13
14
const DEFAULTS = {
14
15
filterBoxesThreshold : 0.01 ,
15
16
IOUThreshold : 0.4 ,
16
17
classProbThreshold : 0.4 ,
17
- URL : 'https://raw.githubusercontent.com/ml5js/ml5-library/master/src/YOLO/model.json'
18
+ URL : 'https://raw.githubusercontent.com/ml5js/ml5-library/master/src/YOLO/model.json' ,
18
19
19
20
} ;
20
21
@@ -92,7 +93,7 @@ class YOLO {
92
93
}
93
94
}
94
95
95
- //does not dispose of the model atm
96
+ // does not dispose of the model atm
96
97
dispose ( ) {
97
98
this . model = null ;
98
99
tf . disposeconstiables ( ) ;
@@ -102,17 +103,17 @@ class YOLO {
102
103
cache ( ) {
103
104
tf . tidy ( ( ) => {
104
105
const dummy = tf . zeros ( [ 0 , 416 , 416 , 3 ] ) ;
105
- const data = this . model . predict ( dummy ) ;
106
+ this . model . predict ( dummy ) ;
106
107
} ) ;
107
108
}
108
109
109
110
preProccess ( input ) {
110
111
let img = tf . fromPixels ( input ) ;
112
+
111
113
this . imgWidth = img . shape [ 1 ] ;
112
114
this . imgHeight = img . shape [ 0 ] ;
113
- img = tf . image . resizeBilinear ( img , [ this . inputHeight , this . inputWidth ] ) . toFloat ( ) . div ( tf . scalar ( 255 ) ) . expandDims ( 0 ) ;
114
-
115
115
116
+ img = tf . image . resizeBilinear ( img , [ this . inputHeight , this . inputWidth ] ) . toFloat ( ) . div ( tf . scalar ( 255 ) ) . expandDims ( 0 ) ;
116
117
117
118
// Scale Stuff
118
119
this . scaleX = this . imgHeight / this . inputHeight ;
@@ -125,16 +126,15 @@ class YOLO {
125
126
totalDetections : 0 ,
126
127
detections : [ ] ,
127
128
} ;
128
- const [ boxes , boxScores , classes , Indices , ] = tf . tidy ( ( ) => {
129
-
130
- rawPrediction = tf . reshape ( rawPrediction , [ 13 , 13 , this . anchorsLength , this . classesLength + 5 ] ) ;
129
+ const [ boxes , boxScores , classes , Indices ] = tf . tidy ( ( ) => {
130
+ const rawPrediction1 = tf . reshape ( rawPrediction , [ 13 , 13 , this . anchorsLength , this . classesLength + 5 ] ) ;
131
131
// Box Coords
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 ] ) ) ;
132
+ const boxxy = tf . sigmoid ( rawPrediction1 . slice ( [ 0 , 0 , 0 , 0 ] , [ 13 , 13 , this . anchorsLength , 2 ] ) ) ;
133
+ const boxwh = tf . exp ( rawPrediction1 . slice ( [ 0 , 0 , 0 , 2 ] , [ 13 , 13 , this . anchorsLength , 2 ] ) ) ;
134
134
// ObjectnessScore
135
- const boxConfidence = tf . sigmoid ( rawPrediction . slice ( [ 0 , 0 , 0 , 4 ] , [ 13 , 13 , this . anchorsLength , 1 ] ) ) ;
135
+ const boxConfidence = tf . sigmoid ( rawPrediction1 . slice ( [ 0 , 0 , 0 , 4 ] , [ 13 , 13 , this . anchorsLength , 1 ] ) ) ;
136
136
// ClassProb
137
- const boxClassProbs = tf . softmax ( rawPrediction . slice ( [ 0 , 0 , 0 , 5 ] , [ 13 , 13 , this . anchorsLength , this . classesLength ] ) ) ;
137
+ const boxClassProbs = tf . softmax ( rawPrediction1 . slice ( [ 0 , 0 , 0 , 5 ] , [ 13 , 13 , this . anchorsLength , this . classesLength ] ) ) ;
138
138
139
139
// from boxes with xy wh to x1,y1 x2,y2
140
140
// Mainly for NMS + rescaling
@@ -145,94 +145,91 @@ class YOLO {
145
145
y2 = y + (w/2)
146
146
*/
147
147
// BoxScale
148
- const BoxXY_1 = tf . div ( tf . add ( boxXY , this . ConvIndex ) , this . ConvDims ) ;
148
+ const boxXY1 = tf . div ( tf . add ( boxxy , this . ConvIndex ) , this . ConvDims ) ;
149
149
150
- const BoxWH_1 = tf . div ( tf . mul ( boxWH , this . AnchorsTensor ) , this . ConvDims ) ;
150
+ const boxWH1 = tf . div ( tf . mul ( boxwh , this . AnchorsTensor ) , this . ConvDims ) ;
151
151
152
- const Div = tf . div ( BoxWH_1 , tf . scalar ( 2 ) ) ;
152
+ const Div = tf . div ( boxWH1 , tf . scalar ( 2 ) ) ;
153
153
154
- const boxMins = tf . sub ( BoxXY_1 , Div ) ;
155
- const boxMaxes = tf . add ( BoxXY_1 , Div ) ;
154
+ const boxMins = tf . sub ( boxXY1 , Div ) ;
155
+ const boxMaxes = tf . add ( boxXY1 , Div ) ;
156
156
157
157
const size = [ boxMins . shape [ 0 ] , boxMins . shape [ 1 ] , boxMins . shape [ 2 ] , 1 ] ;
158
158
159
159
// main box tensor
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 ] ) ;
160
+ const finalboxes = tf . concat ( [
161
+ boxMins . slice ( [ 0 , 0 , 0 , 1 ] , size ) ,
162
+ boxMins . slice ( [ 0 , 0 , 0 , 0 ] , size ) ,
163
+ boxMaxes . slice ( [ 0 , 0 , 0 , 1 ] , size ) ,
164
+ boxMaxes . slice ( [ 0 , 0 , 0 , 0 ] , size ) ,
165
+ ] , 3 ) . reshape ( [ 845 , 4 ] ) ;
161
166
162
167
// Filterboxes by objectness threshold
163
168
// not filtering / getting a mask really
164
169
165
- const boxConfidence_1 = boxConfidence . squeeze ( [ 3 ] ) ;
166
- const objectnessMask = tf . greaterEqual ( boxConfidence_1 , tf . scalar ( this . filterBoxesThreshold ) ) ;
170
+ const boxConfidence1 = boxConfidence . squeeze ( [ 3 ] ) ;
171
+ const objectnessMask = tf . greaterEqual ( boxConfidence1 , tf . scalar ( this . filterBoxesThreshold ) ) ;
167
172
168
173
// Filterboxes by class probability threshold
169
- const boxScores = tf . mul ( boxConfidence_1 , tf . max ( boxClassProbs , 3 ) ) ;
170
- const boxClassProbMask = tf . greaterEqual ( boxScores , tf . scalar ( this . classProbThreshold ) ) ;
174
+ const boxScores1 = tf . mul ( boxConfidence1 , tf . max ( boxClassProbs , 3 ) ) ;
175
+ const boxClassProbMask = tf . greaterEqual ( boxScores , tf . scalar ( this . classProbThreshold ) ) ;
171
176
172
177
// getting classes indices
173
- const classes = tf . argMax ( boxClassProbs , - 1 ) ;
178
+ const classes1 = tf . argMax ( boxClassProbs , - 1 ) ;
174
179
175
180
// Final Mask each elem that survived both filters (0x0 0x1 1x0 = fail ) 1x1 = survived
176
181
const finalMask = boxClassProbMask . mul ( objectnessMask ) ;
177
182
178
183
const indices = finalMask . flatten ( ) . toInt ( ) . mul ( this . indicesTensor ) ;
179
- return [ boxes , boxScores , classes , indices ] ;
184
+ return [ finalboxes , boxScores1 , classes1 , indices ] ;
180
185
} ) ;
181
186
182
- //we started at one in the range so we remove 1 now
187
+ // we started at one in the range so we remove 1 now
183
188
184
- let indicesArr = Array . from ( await Indices . data ( ) ) . filter ( i => i > 0 ) . map ( i => i - 1 ) ;
189
+ const indicesArr = Array . from ( await Indices . data ( ) ) . filter ( i => i > 0 ) . map ( i => i - 1 ) ;
185
190
186
191
if ( indicesArr . length === 0 ) {
187
192
boxes . dispose ( ) ;
188
193
boxScores . dispose ( ) ;
189
194
classes . dispose ( ) ;
190
195
return results ;
191
196
}
192
- const [ filteredBoxes , filteredScores , filteredclasses ] = tf . tidy ( ( ) => {
193
- const indicesTensor = tf . tensor1d ( indicesArr , "int32" ) ;
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
-
197
+ const [ filteredBoxes , filteredScores , filteredclasses ] = tf . tidy ( ( ) => {
198
+ const indicesTensor = tf . tensor1d ( indicesArr , 'int32' ) ;
199
+ const filteredBoxes1 = boxes . gather ( indicesTensor ) ;
200
+ const filteredScores1 = boxScores . flatten ( ) . gather ( indicesTensor ) ;
201
+ const filteredclasses1 = classes . flatten ( ) . gather ( indicesTensor ) ;
202
+ // Img Rescale
203
+ const Height = tf . scalar ( this . imgHeight ) ;
204
+ const Width = tf . scalar ( this . imgWidth ) ;
205
+ const ImageDims = tf . stack ( [ Height , Width , Height , Width ] ) . reshape ( [ 1 , 4 ] ) ;
206
+ const filteredBoxes2 = filteredBoxes1 . mul ( ImageDims ) ;
207
+ return [ filteredBoxes2 , filteredScores1 , filteredclasses1 ] ;
208
+ } ) ;
209
+
208
210
// NonMaxSuppression
209
211
// GreedyNMS
210
- const [ boxArr , scoreArr , classesArr ] = await Promise . all ( [ filteredBoxes . data ( ) , filteredScores . data ( ) , filteredclasses . data ( ) , ] ) ;
211
-
212
+ const [ boxArr , scoreArr , classesArr ] = await Promise . all ( [ filteredBoxes . data ( ) , filteredScores . data ( ) , filteredclasses . data ( ) ] ) ;
212
213
filteredBoxes . dispose ( ) ;
213
214
filteredScores . dispose ( ) ;
214
215
filteredclasses . dispose ( ) ;
215
216
216
- let zipped = [ ] ;
217
- for ( let i = 0 ; i < scoreArr . length ; i ++ ) {
217
+ const zipped = [ ] ;
218
+ for ( let i = 0 ; i < scoreArr . length ; i += 1 ) {
218
219
// [Score,x,y,w,h,classindex]
219
- zipped . push ( [ scoreArr [ i ] , [ boxArr [ 4 * i ] , boxArr [ 4 * i + 1 ] , boxArr [ 4 * i + 2 ] , boxArr [ 4 * i + 3 ] ] , classesArr [ i ] ] ) ;
220
+ zipped . push ( [ scoreArr [ i ] , [ boxArr [ 4 * i ] , boxArr [ ( 4 * i ) + 1 ] , boxArr [ ( 4 * i ) + 2 ] , boxArr [ ( 4 * i ) + 3 ] ] , classesArr [ i ] ] ) ;
220
221
}
221
222
222
223
// Sort by descending order of scores (first index of zipped array)
223
224
const sorted = zipped . sort ( ( a , b ) => b [ 0 ] - a [ 0 ] ) ;
224
225
const selectedBoxes = [ ] ;
225
226
// Greedily go through boxes in descending score order and only
226
227
// return boxes that are below the IoU threshold.
227
- sorted . forEach ( box => {
228
+ sorted . forEach ( ( box ) => {
228
229
let Push = true ;
229
- for ( let i = 0 ; i < selectedBoxes . length ; i ++ ) {
230
+ for ( let i = 0 ; i < selectedBoxes . length ; i += 1 ) {
230
231
// Compare IoU of zipped[1], since that is the box coordinates arr
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 ] ) ;
233
- let Intersection = w < 0 || h < 0 ? 0 : w * h ;
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 ;
235
- let Iou = Intersection / Union ;
232
+ const Iou = iou ( box [ 1 ] , selectedBoxes [ i ] ) ;
236
233
if ( Iou > this . IOUThreshold ) {
237
234
Push = false ;
238
235
break ;
@@ -244,15 +241,25 @@ class YOLO {
244
241
// final phase
245
242
246
243
// add any output you want
247
- for ( let id = 0 ; id < selectedBoxes . length ; id ++ ) {
244
+ for ( let id = 0 ; id < selectedBoxes . length ; id += 1 ) {
248
245
const classProb = selectedBoxes [ id ] [ 0 ] ;
249
246
const classProbRounded = Math . round ( classProb * 1000 ) / 10 ;
250
247
const className = this . classNames [ selectedBoxes [ id ] [ 2 ] ] ;
251
248
const classIndex = selectedBoxes [ id ] [ 2 ] ;
252
249
const [ x1 , y1 , x2 , y2 ] = selectedBoxes [ id ] [ 1 ] ;
253
250
// Need to get this out
254
251
// TODO : add a hsla color for later visualization
255
- const resultObj = { id, className, classIndex, classProb, classProbRounded, x1, y1, x2, y2, } ;
252
+ const resultObj = {
253
+ id,
254
+ className,
255
+ classIndex,
256
+ classProb,
257
+ classProbRounded,
258
+ x1,
259
+ y1,
260
+ x2,
261
+ y2,
262
+ } ;
256
263
results . detections . push ( resultObj ) ;
257
264
}
258
265
// Misc
0 commit comments