UnityPaul commited on
Commit
505e8d0
1 Parent(s): 9e6cf6b

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +5 -5
  2. RunBlazeFace.cs +70 -57
  3. blazeface.sentis +0 -0
  4. info.json +2 -2
README.md CHANGED
@@ -4,18 +4,18 @@ library_name: unity-sentis
4
  pipeline_tag: object-detection
5
  ---
6
 
7
- # Blaze Face face detector in Unity Sentis (Version 1.3.0-pre.3*)
8
- *Version 1.3.0 Sentis files are not compatible with Sentis 1.4.0 and need to be recreated/downloaded
9
 
10
  This is the [Blaze Face model](https://developers.google.com/mediapipe/solutions/vision/face_detector) formatted to work in Unity Sentis 2023
11
 
12
  ## How to Use
13
  * Create a new scene in Unity 2023
14
- * Install `com.unity.sentis` version `1.3.0-pre.3` from the package manager
15
- * Put the blazeface.sentis file in the Assets/StreamingAssets folder
16
  * Put a video in the Assets/StreamingAssets folder and set _videoName variable to the video name
17
  * Create a RawImage and place it in your scene. Link to this image in the _previewUI field.
18
- * Attach a sprite for the bounding box image to the faceTexture field
19
 
20
  ## Preview
21
  When you get it working you should see something like this:
 
4
  pipeline_tag: object-detection
5
  ---
6
 
7
+ # Blaze Face face detector in Unity Sentis (Version 1.4.0-pre.3*)
8
+ *Version 1.3.0 sentis files are not compatible with 1.4.0 and will need to be recreated/downloaded
9
 
10
  This is the [Blaze Face model](https://developers.google.com/mediapipe/solutions/vision/face_detector) formatted to work in Unity Sentis 2023
11
 
12
  ## How to Use
13
  * Create a new scene in Unity 2023
14
+ * Install `com.unity.sentis` version `1.4.0-pre.3` from the package manager
15
+ * Drag the blazeface.sentis file into the model asset field
16
  * Put a video in the Assets/StreamingAssets folder and set _videoName variable to the video name
17
  * Create a RawImage and place it in your scene. Link to this image in the _previewUI field.
18
+ * Attach a sprite or texture for the bounding box image to the BondingBoxSprite or BorderTexture field
19
 
20
  ## Preview
21
  When you get it working you should see something like this:
RunBlazeFace.cs CHANGED
@@ -3,6 +3,7 @@ using Unity.Sentis;
3
  using UnityEngine.Video;
4
  using UnityEngine.UI;
5
  using Lays = Unity.Sentis.Layers;
 
6
 
7
  /*
8
  * Blaze Face Inference
@@ -11,7 +12,7 @@ using Lays = Unity.Sentis.Layers;
11
  * Basic inference script for blaze face
12
  *
13
  * Put this script on the Main Camera
14
- * Put blazeface.sentis in the Assets/StreamingAssets folder
15
  * Create a RawImage in the scene
16
  * Put a link to that image in previewUI
17
  * Put a video in Assets/StreamingAssets folder and put the name of it int videoName
@@ -22,11 +23,15 @@ using Lays = Unity.Sentis.Layers;
22
 
23
  public class RunBlazeFace : MonoBehaviour
24
  {
 
 
 
25
  //Drag a link to a raw image here:
26
  public RawImage previewUI = null;
27
 
28
  // Put your bounding box sprite image here
29
- public Sprite faceTexture;
 
30
 
31
  // 6 optional sprite images (left eye, right eye, nose, mouth, left ear, right ear)
32
  public Sprite[] markerTextures;
@@ -58,9 +63,6 @@ public class RunBlazeFace : MonoBehaviour
58
  //Holds input image size
59
  int size = 128;
60
 
61
- Ops ops;
62
- ITensorAllocator allocator;
63
-
64
  Model model;
65
 
66
  //webcam device name:
@@ -68,6 +70,8 @@ public class RunBlazeFace : MonoBehaviour
68
 
69
  bool closing = false;
70
 
 
 
71
  public struct BoundingBox
72
  {
73
  public float centerX;
@@ -78,7 +82,6 @@ public class RunBlazeFace : MonoBehaviour
78
 
79
  void Start()
80
  {
81
- allocator = new TensorCachingAllocator();
82
 
83
  //(Note: if using a webcam on mobile get permissions here first)
84
 
@@ -88,7 +91,7 @@ public class RunBlazeFace : MonoBehaviour
88
 
89
  SetupModel();
90
 
91
- SetupEngine();
92
  }
93
 
94
  void SetupInput()
@@ -120,6 +123,16 @@ public class RunBlazeFace : MonoBehaviour
120
  }
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
123
  void Update()
124
  {
125
  if (inputType == InputType.Webcam)
@@ -198,36 +211,43 @@ public class RunBlazeFace : MonoBehaviour
198
  {
199
  float[] offsets = GetGridBoxCoords();
200
 
201
- model = ModelLoader.Load(Application.streamingAssetsPath + "/blazeface.sentis");
202
 
203
  //We need to add extra layers to the model in order to aggregate the box predicions:
204
 
205
  size = model.inputs[0].shape.ToTensorShape()[1]; // Input tensor width
206
 
207
- model.AddConstant(new Lays.Constant("0", new int[] { 0 }));
208
- model.AddConstant(new Lays.Constant("2", new int[] { 2 }));
209
- model.AddConstant(new Lays.Constant("4", new int[] { 4 }));
210
- model.AddConstant(new Lays.Constant("offsets", new TensorFloat(new TensorShape(1, 896, 4), offsets)));
211
-
212
- model.AddLayer(new Lays.Slice("boxes", "regressors", "0", "4", "2"));
213
- model.AddLayer(new Lays.Transpose("scores", "classificators", new int[] { 0, 2, 1 }));
214
- model.AddLayer(new Lays.Add("boxCoords", "boxes", "offsets"));
215
- model.AddOutput("boxCoords");
216
-
217
- model.AddConstant(new Lays.Constant("maxOutputBoxes", new int[] { maxOutputBoxes }));
218
- model.AddConstant(new Lays.Constant("iouThreshold", new float[] { iouThreshold }));
219
- model.AddConstant(new Lays.Constant("scoreThreshold", new float[] { scoreThreshold }));
220
-
221
- model.AddLayer(new Lays.NonMaxSuppression("NMS", "boxCoords", "scores",
222
- "maxOutputBoxes", "iouThreshold", "scoreThreshold",
223
- centerPointBox: Lays.CenterPointBox.Center
224
- ));
225
- model.AddOutput("NMS");
226
- }
227
- public void SetupEngine()
228
- {
229
- worker = WorkerFactory.CreateWorker(backend, model);
230
- ops = WorkerFactory.CreateOps(backend, allocator);
 
 
 
 
 
 
 
231
  }
232
 
233
  void DrawFaces(TensorFloat index3, TensorFloat regressors, int NMAX, Vector2 scale)
@@ -237,12 +257,12 @@ public class RunBlazeFace : MonoBehaviour
237
  //Draw bounding box of face
238
  var box = new BoundingBox
239
  {
240
- centerX = index3[0, n, 0] * scale.x,
241
- centerY = index3[0, n, 1] * scale.y,
242
- width = index3[0, n, 2] * scale.x,
243
- height = index3[0, n, 3] * scale.y
244
  };
245
- DrawBox(box, faceTexture);
246
  if (regressors == null) continue;
247
 
248
  //Draw markers for eyes, ears, nose, mouth:
@@ -250,12 +270,12 @@ public class RunBlazeFace : MonoBehaviour
250
  {
251
  var marker = new BoundingBox
252
  {
253
- centerX = box.centerX + (regressors[0, n, 4 + j * 2] - regressors[0, n, 0]) * scale.x,
254
- centerY = box.centerY + (regressors[0, n, 4 + j * 2 + 1] - regressors[0, n, 1]) * scale.y,
255
  width = 8.0f * scale.x,
256
  height = 8.0f * scale.y,
257
  };
258
- DrawBox(marker, j < markerTextures.Length ? markerTextures[j] : faceTexture);
259
  }
260
  }
261
  }
@@ -265,31 +285,24 @@ public class RunBlazeFace : MonoBehaviour
265
  var transform = new TextureTransform();
266
  transform.SetDimensions(size, size, 3);
267
  transform.SetTensorLayout(0, 3, 1, 2);
268
- using var image0 = TextureConverter.ToTensor(source, transform);
269
-
270
- // Pre-process the image to make input in range (-1..1)
271
- using var image = ops.Mad(image0, 2f, -1f);
272
 
273
  worker.Execute(image);
274
 
275
- using var boxCoords = worker.PeekOutput("boxCoords") as TensorFloat; //face coords
276
- using var regressors = worker.PeekOutput("regressors") as TensorFloat; //contains markers
277
-
278
- var NM1 = worker.PeekOutput("NMS") as TensorInt;
279
-
280
- using var boxCoords2 = ops.Reshape(boxCoords, boxCoords.shape.Unsqueeze(0));
281
- using var output = ops.GatherND(boxCoords2, NM1, 0);
282
 
283
- using var regressors2 = ops.Reshape(regressors,regressors.shape.Unsqueeze(0));
284
- using var markersOutput = ops.GatherND(regressors2, NM1, 0);
285
 
286
- output.MakeReadable();
287
- markersOutput.MakeReadable();
288
 
289
  ClearAnnotations();
290
 
291
  Vector2 markerScale = previewUI.rectTransform.rect.size / size;
292
 
 
293
  DrawFaces(output, markersOutput, output.shape[0], markerScale);
294
 
295
  }
@@ -329,8 +342,8 @@ public class RunBlazeFace : MonoBehaviour
329
  void CleanUp()
330
  {
331
  closing = true;
332
- ops?.Dispose();
333
- allocator?.Dispose();
334
  if (webcam) Destroy(webcam);
335
  if (video) Destroy(video);
336
  RenderTexture.active = null;
 
3
  using UnityEngine.Video;
4
  using UnityEngine.UI;
5
  using Lays = Unity.Sentis.Layers;
6
+ using FF = Unity.Sentis.Functional;
7
 
8
  /*
9
  * Blaze Face Inference
 
12
  * Basic inference script for blaze face
13
  *
14
  * Put this script on the Main Camera
15
+ * Drawg the blazeface.sentis into the modelAsset field
16
  * Create a RawImage in the scene
17
  * Put a link to that image in previewUI
18
  * Put a video in Assets/StreamingAssets folder and put the name of it int videoName
 
23
 
24
  public class RunBlazeFace : MonoBehaviour
25
  {
26
+ //Draw the sentis model asset here:
27
+ public ModelAsset modelAsset;
28
+
29
  //Drag a link to a raw image here:
30
  public RawImage previewUI = null;
31
 
32
  // Put your bounding box sprite image here
33
+ public Sprite boundingboxSprite;
34
+ public Texture2D borderTexture;
35
 
36
  // 6 optional sprite images (left eye, right eye, nose, mouth, left ear, right ear)
37
  public Sprite[] markerTextures;
 
63
  //Holds input image size
64
  int size = 128;
65
 
 
 
 
66
  Model model;
67
 
68
  //webcam device name:
 
70
 
71
  bool closing = false;
72
 
73
+ TensorFloat anchors, centersToCorners;
74
+
75
  public struct BoundingBox
76
  {
77
  public float centerX;
 
82
 
83
  void Start()
84
  {
 
85
 
86
  //(Note: if using a webcam on mobile get permissions here first)
87
 
 
91
 
92
  SetupModel();
93
 
94
+ SetupBoundingBoxSprite();
95
  }
96
 
97
  void SetupInput()
 
123
  }
124
  }
125
 
126
+ void SetupBoundingBoxSprite()
127
+ {
128
+ if (boundingboxSprite == null)
129
+ {
130
+ boundingboxSprite = Sprite.Create(borderTexture,
131
+ new Rect(0, 0, borderTexture.width, borderTexture.height),
132
+ new Vector2(borderTexture.width / 2, borderTexture.height / 2));
133
+ }
134
+ }
135
+
136
  void Update()
137
  {
138
  if (inputType == InputType.Webcam)
 
211
  {
212
  float[] offsets = GetGridBoxCoords();
213
 
214
+ var model = ModelLoader.Load(modelAsset);
215
 
216
  //We need to add extra layers to the model in order to aggregate the box predicions:
217
 
218
  size = model.inputs[0].shape.ToTensorShape()[1]; // Input tensor width
219
 
220
+ anchors = new TensorFloat(new TensorShape(offsets.Length / 4, 4), offsets);
221
+
222
+ centersToCorners = new TensorFloat(new TensorShape(4, 4),
223
+ new float[]
224
+ {
225
+ 1, 0, 1, 0,
226
+ 0, 1, 0, 1,
227
+ -0.5f, 0, 0.5f, 0,
228
+ 0, -0.5f, 0, 0.5f
229
+ });
230
+
231
+ var model2 = Functional.Compile(
232
+ input =>
233
+ {
234
+ var outputs = model.Forward(input);
235
+ var regressors = outputs[0][0]; //shape=(896,16)
236
+ var scores = outputs[1][0].Transpose(0, 1) - scoreThreshold; //shape=(1,896)
237
+ var boxCoords = regressors[.., 0..4] + FunctionalTensor.FromTensor(anchors);//(896,4)
238
+ var boxCorners = FF.MatMul(boxCoords, FunctionalTensor.FromTensor(centersToCorners));
239
+ var indices = FF.NMS(boxCorners, scores, iouThreshold); //shape=(N)
240
+ var indices2 = indices.Unsqueeze(-1).BroadcastTo(new int[] { 4 }); //shape=(N,4)
241
+ var output = FF.Gather(boxCoords, 0, indices2); //shape=(N,4)
242
+ var indices3 = indices.Unsqueeze(-1).BroadcastTo(new int[] { 16 }); //shape=(N,16)
243
+ var markersOutput = FF.Gather(regressors, 0, indices3); //shape=(N,16)
244
+ return (output, markersOutput);
245
+ },
246
+ InputDef.FromModel(model)[0]
247
+ );
248
+
249
+ worker = WorkerFactory.CreateWorker(backend, model2);
250
+
251
  }
252
 
253
  void DrawFaces(TensorFloat index3, TensorFloat regressors, int NMAX, Vector2 scale)
 
257
  //Draw bounding box of face
258
  var box = new BoundingBox
259
  {
260
+ centerX = index3[ n, 0] * scale.x,
261
+ centerY = index3[ n, 1] * scale.y,
262
+ width = index3[ n, 2] * scale.x,
263
+ height = index3[ n, 3] * scale.y
264
  };
265
+ DrawBox(box, boundingboxSprite);
266
  if (regressors == null) continue;
267
 
268
  //Draw markers for eyes, ears, nose, mouth:
 
270
  {
271
  var marker = new BoundingBox
272
  {
273
+ centerX = box.centerX + (regressors[ n, 4 + j * 2] - regressors[ n, 0]) * scale.x,
274
+ centerY = box.centerY + (regressors[ n, 4 + j * 2 + 1] - regressors[ n, 1]) * scale.y,
275
  width = 8.0f * scale.x,
276
  height = 8.0f * scale.y,
277
  };
278
+ DrawBox(marker, j < markerTextures.Length ? markerTextures[j] : boundingboxSprite);
279
  }
280
  }
281
  }
 
285
  var transform = new TextureTransform();
286
  transform.SetDimensions(size, size, 3);
287
  transform.SetTensorLayout(0, 3, 1, 2);
288
+ using var image = TextureConverter.ToTensor(source, transform);
 
 
 
289
 
290
  worker.Execute(image);
291
 
292
+ using var output = worker.PeekOutput("output_0") as TensorFloat; //face coords
293
+ using var markersOutput = worker.PeekOutput("output_1") as TensorFloat; //contains markers
 
 
 
 
 
294
 
295
+ output.CompleteOperationsAndDownload();
296
+ markersOutput.CompleteOperationsAndDownload();
297
 
298
+ // Debug.Log(output.shape + " " + markersOutput.shape);
299
+ //return;
300
 
301
  ClearAnnotations();
302
 
303
  Vector2 markerScale = previewUI.rectTransform.rect.size / size;
304
 
305
+ //Debug.Log(output.shape + " " + markersOutput.shape);
306
  DrawFaces(output, markersOutput, output.shape[0], markerScale);
307
 
308
  }
 
342
  void CleanUp()
343
  {
344
  closing = true;
345
+ anchors?.Dispose();
346
+ centersToCorners?.Dispose();
347
  if (webcam) Destroy(webcam);
348
  if (video) Destroy(video);
349
  RenderTexture.active = null;
blazeface.sentis CHANGED
Binary files a/blazeface.sentis and b/blazeface.sentis differ
 
info.json CHANGED
@@ -1,11 +1,11 @@
1
  {
2
  "code": [
3
- "RunBlazeFace.cs"
4
  ],
5
  "models": [
6
  "blazeface.sentis"
7
  ],
8
  "version": [
9
- "1.3.0-pre.3"
10
  ]
11
  }
 
1
  {
2
  "code": [
3
+ "RunBlazeFace.cs"
4
  ],
5
  "models": [
6
  "blazeface.sentis"
7
  ],
8
  "version": [
9
+ "1.4.0"
10
  ]
11
  }