Paul Bird commited on
Commit
bbd25eb
1 Parent(s): 1cc96c4

Upload 3 files

Browse files
Files changed (3) hide show
  1. RunBlazeFace.cs +355 -0
  2. blazeface.onnx +3 -0
  3. blazeface.sentis +0 -0
RunBlazeFace.cs ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+ using Unity.Sentis;
3
+ using UnityEngine.Video;
4
+ using UnityEngine.UI;
5
+ using Lays = Unity.Sentis.Layers;
6
+
7
+ /*
8
+ * Blaze Face Inference
9
+ * ====================
10
+ *
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 of size 320x320 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
18
+ * Or put a test image in _inputImage
19
+ * Set _inputType to appropriate input
20
+ */
21
+
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;
33
+
34
+ const string videoName = "chatting.mp4";
35
+
36
+ //
37
+ public Texture2D _inputImage;
38
+
39
+ public InputType _inputType = InputType.Video;
40
+
41
+ Vector2Int _resolution = new Vector2Int(640, 640);
42
+ WebCamTexture _webcam;
43
+ VideoPlayer _video;
44
+
45
+ const BackendType backend = BackendType.GPUCompute;
46
+
47
+ RenderTexture _targetTexture;
48
+ public enum InputType { Image, Video, Webcam };
49
+
50
+
51
+ //Some adjustable parameters for the model
52
+ [SerializeField, Range(0, 1)] float _iouThreshold = 0.5f;
53
+ [SerializeField, Range(0, 1)] float _scoreThreshold = 0.5f;
54
+ int _maxOutputBoxes = 64;
55
+
56
+ IWorker _worker;
57
+
58
+ //Holds image size
59
+ int _size;
60
+
61
+ Ops ops;
62
+ ITensorAllocator allocator;
63
+
64
+ Model _model;
65
+
66
+ //webcam device name:
67
+ const string _deviceName = "";
68
+
69
+ bool closing = false;
70
+
71
+ public struct BoundingBox
72
+ {
73
+ public float centerX;
74
+ public float centerY;
75
+ public float width;
76
+ public float height;
77
+ }
78
+
79
+ void Start()
80
+ {
81
+ allocator = new TensorCachingAllocator();
82
+
83
+ //(Note: if using a webcam on mobile get permissions here first)
84
+
85
+ _targetTexture = new RenderTexture(_resolution.x, _resolution.y, 0);
86
+
87
+ SetupInput();
88
+
89
+ SetupModel();
90
+
91
+ SetupEngine();
92
+ }
93
+
94
+ void SetupInput()
95
+ {
96
+ switch (_inputType)
97
+ {
98
+ case InputType.Webcam:
99
+ {
100
+ _webcam = new WebCamTexture(_deviceName, _resolution.x, _resolution.y);
101
+ _webcam.requestedFPS = 30;
102
+ _webcam.Play();
103
+ break;
104
+ }
105
+ case InputType.Video:
106
+ {
107
+ _video = gameObject.AddComponent<VideoPlayer>();//new VideoPlayer();
108
+ _video.renderMode = VideoRenderMode.APIOnly;
109
+ _video.source = VideoSource.Url;
110
+ _video.url = Application.streamingAssetsPath + "/"+videoName;
111
+ _video.isLooping = true;
112
+ _video.Play();
113
+ break;
114
+ }
115
+ default:
116
+ {
117
+ Graphics.Blit(_inputImage, _targetTexture);
118
+ }
119
+ break;
120
+ }
121
+ }
122
+
123
+ void Update()
124
+ {
125
+ if (_inputType == InputType.Webcam)
126
+ {
127
+ // Format video input
128
+ if (!_webcam.didUpdateThisFrame) return;
129
+
130
+ var aspect1 = (float)_webcam.width / _webcam.height;
131
+ var aspect2 = (float)_resolution.x / _resolution.y;
132
+ var gap = aspect2 / aspect1;
133
+
134
+ var vflip = _webcam.videoVerticallyMirrored;
135
+ var scale = new Vector2(gap, vflip ? -1 : 1);
136
+ var offset = new Vector2((1 - gap) / 2, vflip ? 1 : 0);
137
+
138
+ Graphics.Blit(_webcam, _targetTexture, scale, offset);
139
+ }
140
+ if (_inputType == InputType.Video)
141
+ {
142
+ var aspect1 = (float)_video.width / _video.height;
143
+ var aspect2 = (float)_resolution.x / _resolution.y;
144
+ var gap = aspect2 / aspect1;
145
+
146
+ var vflip = false;
147
+ var scale = new Vector2(gap, vflip ? -1 : 1);
148
+ var offset = new Vector2((1 - gap) / 2, vflip ? 1 : 0);
149
+ Graphics.Blit(_video.texture, _targetTexture, scale, offset);
150
+ }
151
+ if (_inputType == InputType.Image)
152
+ {
153
+ Graphics.Blit(_inputImage, _targetTexture);
154
+ }
155
+
156
+ if (Input.GetKeyDown(KeyCode.Escape))
157
+ {
158
+ closing = true;
159
+ Application.Quit();
160
+ }
161
+
162
+ if (Input.GetKeyDown(KeyCode.P))
163
+ {
164
+ _previewUI.enabled = !_previewUI.enabled;
165
+ }
166
+ }
167
+
168
+
169
+ void LateUpdate()
170
+ {
171
+ if (!closing)
172
+ {
173
+ RunInference(_targetTexture);
174
+ }
175
+ }
176
+
177
+ //Calculate the centers of the grid squares for two 16x16 grids and six 8x8 grids
178
+ float[] GetGridBoxCoords()
179
+ {
180
+ var offsets = new float[896 * 4];
181
+ int n = 0;
182
+ for (int j = 0; j < 2 * 16 * 16; j++)
183
+ {
184
+ offsets[n++] = (j / 2) % 16 - 7.5f;
185
+ offsets[n++] = (j / 2 / 16) - 7.5f;
186
+ n += 2;
187
+ }
188
+ for (int j = 0; j < 6 * 8 * 8; j++)
189
+ {
190
+ offsets[n++] = (j / 6) % 8 - 7f;
191
+ offsets[n++] = (j / 6 / 8) - 7f;
192
+ n += 2;
193
+ }
194
+ return offsets;
195
+ }
196
+
197
+ void SetupModel()
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("zero", new int[] { 0 }));
208
+ _model.AddConstant(new Lays.Constant("two", new int[] { 2 }));
209
+ _model.AddConstant(new Lays.Constant("four", new int[] { 4 }));
210
+
211
+ _model.AddLayer(new Lays.Slice("boxes1", "regressors", "zero", "four", "two"));
212
+
213
+ _model.AddLayer(new Lays.Transpose("scores", "classificators", new int[] { 0, 2, 1 }));
214
+
215
+ _model.AddConstant(new Lays.Constant("eighth", new float[] { 1 / 8f }));
216
+ _model.AddConstant(new Lays.Constant("offsets",
217
+ new TensorFloat(new TensorShape(1, 896, 4), offsets)
218
+ ));
219
+ _model.AddLayer(new Lays.Mul("boxes1scaled", "boxes1", "eighth"));
220
+ _model.AddLayer(new Lays.Add("boxCoords", "boxes1scaled", "offsets"));
221
+ _model.AddOutput("boxCoords");
222
+
223
+ _model.AddConstant(new Lays.Constant("maxOutputBoxes", new int[] { _maxOutputBoxes }));
224
+ _model.AddConstant(new Lays.Constant("iouThreshold", new float[] { _iouThreshold }));
225
+ _model.AddConstant(new Lays.Constant("scoreThreshold", new float[] { _scoreThreshold }));
226
+
227
+ _model.AddLayer(new Lays.NonMaxSuppression("NMS", "boxCoords", "scores",
228
+ "maxOutputBoxes", "iouThreshold", "scoreThreshold",
229
+ centerPointBox: Lays.CenterPointBox.Center
230
+ ));
231
+ _model.AddOutput("NMS");
232
+ }
233
+ public void SetupEngine()
234
+ {
235
+ _worker = WorkerFactory.CreateWorker(backend, _model);
236
+ ops = WorkerFactory.CreateOps(backend, allocator);
237
+ }
238
+
239
+ void DrawFaces(TensorFloat index3, TensorFloat regressors, int NMAX, Vector2 scale)
240
+ {
241
+ for (int n = 0; n < NMAX; n++)
242
+ {
243
+ //Draw bounding box of face
244
+ var box = new BoundingBox
245
+ {
246
+ centerX = index3[0, n, 0] * scale.x,
247
+ centerY = index3[0, n, 1] * scale.y,
248
+ width = index3[0, n, 2] * scale.x,
249
+ height = index3[0, n, 3] * scale.y
250
+ };
251
+ DrawBox(box, faceTexture);
252
+ if (regressors == null) continue;
253
+
254
+ //Draw markers for eyes, ears, nose, mouth:
255
+ for (int j = 0; j < 6; j++)
256
+ {
257
+ var marker = new BoundingBox
258
+ {
259
+ centerX = box.centerX + (regressors[0, n, 4 + j * 2] - regressors[0, n, 0]) * scale.x / 8,
260
+ centerY = box.centerY + (regressors[0, n, 4 + j * 2 + 1] - regressors[0, n, 1]) * scale.y / 8,
261
+ width = 1.0f * scale.x,
262
+ height = 1.0f * scale.y,
263
+ };
264
+ DrawBox(marker, j < markerTextures.Length ? markerTextures[j] : faceTexture);
265
+ }
266
+ }
267
+ }
268
+
269
+ void ExecuteML(Texture source)
270
+ {
271
+ var transform = new TextureTransform();
272
+ transform.SetDimensions(_size, _size, 3);
273
+ transform.SetTensorLayout(0, 3, 1, 2);
274
+ using var image0 = TextureConverter.ToTensor(source, transform);
275
+
276
+ // Pre-process the image to make input in range (-1..1)
277
+ using var image = ops.Mad(image0, 2f, -1f);
278
+
279
+ _worker.Execute(image);
280
+
281
+ using var boxCoords = _worker.PeekOutput("boxCoords") as TensorFloat; //face coords
282
+ using var regressors = _worker.PeekOutput("regressors") as TensorFloat; //contains markers
283
+
284
+ var NM1 = _worker.PeekOutput("NMS") as TensorInt;
285
+
286
+ using var boxCoords2 = boxCoords.ShallowReshape(
287
+ new TensorShape(1, boxCoords.shape[0], boxCoords.shape[1], boxCoords.shape[2])) as TensorFloat;
288
+ var output = ops.GatherND(boxCoords2, NM1, 0);
289
+
290
+ using var regressors2 = regressors.ShallowReshape(
291
+ new TensorShape(1, regressors.shape[0], regressors.shape[1], regressors.shape[2])) as TensorFloat;
292
+ var markersOutput = ops.GatherND(regressors2, NM1, 0);
293
+
294
+ output.MakeReadable();
295
+ markersOutput.MakeReadable();
296
+
297
+ ClearAnnotations();
298
+
299
+ Vector2 markerScale = _previewUI.rectTransform.rect.size/ 16;
300
+
301
+ DrawFaces(output, markersOutput, output.shape[0], markerScale);
302
+
303
+ }
304
+
305
+ void RunInference(Texture input)
306
+ {
307
+ // Face detection
308
+ ExecuteML(input);
309
+
310
+ _previewUI.texture = input;
311
+ }
312
+
313
+ public void DrawBox(BoundingBox box, Sprite sprite)
314
+ {
315
+ var panel = new GameObject("ObjectBox");
316
+ panel.AddComponent<CanvasRenderer>();
317
+ panel.AddComponent<Image>();
318
+ panel.transform.SetParent(_previewUI.transform, false);
319
+
320
+ var img = panel.GetComponent<Image>();
321
+ img.color = Color.white;
322
+ img.sprite = sprite;
323
+ img.type = Image.Type.Sliced;
324
+
325
+ panel.transform.localPosition = new Vector3(box.centerX, -box.centerY);
326
+ RectTransform rt = panel.GetComponent<RectTransform>();
327
+ rt.sizeDelta = new Vector2(box.width, box.height);
328
+ }
329
+ public void ClearAnnotations()
330
+ {
331
+ foreach (Transform child in _previewUI.transform)
332
+ {
333
+ Destroy(child.gameObject);
334
+ }
335
+ }
336
+
337
+ void CleanUp()
338
+ {
339
+ ops?.Dispose();
340
+ allocator?.Dispose();
341
+ if (_webcam) Destroy(_webcam);
342
+ if (_video) Destroy(_video);
343
+ RenderTexture.active = null;
344
+ _targetTexture.Release();
345
+ _worker?.Dispose();
346
+ _worker = null;
347
+ }
348
+
349
+ void OnDestroy()
350
+ {
351
+ CleanUp();
352
+ }
353
+
354
+ }
355
+
blazeface.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:add5ce1bcfba21b2bbd73c65c8d344614c692d0e0ba9800eda48c38bed82522f
3
+ size 418480
blazeface.sentis ADDED
Binary file (468 kB). View file