UnityPaul commited on
Commit
c997bf1
1 Parent(s): ae87d00

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +3 -3
  2. RunYOLO8n.cs +71 -73
  3. info.js +5 -0
  4. yolov8n.sentis +2 -2
README.md CHANGED
@@ -2,8 +2,8 @@
2
  library_name: unity-sentis
3
  pipeline_tag: object-detection
4
  ---
5
- # YOLOv8n validated for Unity Sentis (Version 1.3.0-pre.3 only*)
6
- *Version 1.3.0 Sentis files will not work with version 1.4.0 and above. We are still making some updates to the 1.4.0 files
7
 
8
  [YOLOv8n](https://docs.ultralytics.com/models/yolov8/) is a real-time multi-object recognition model confirmed to run in Unity 2023.
9
 
@@ -12,7 +12,7 @@ First get the package `com.unity.sentis` from the package manager.
12
  You will also need the Unity UI package.
13
 
14
  * Create a new scene in Unity 2023.
15
- * Install `com.unity.sentis` version `1.3.0-pre.3` from the package manager
16
  * Add the c# script to the Main Camera.
17
  * Create a Raw Image in the scene and link it as the `displayImage`
18
  * Put the yolov8n.sentis file in the Assets/StreamingAssets folder
 
2
  library_name: unity-sentis
3
  pipeline_tag: object-detection
4
  ---
5
+ # YOLOv8n validated for Unity Sentis (Version 1.4.0-pre.3*)
6
+ *Version 1.3.0 sentis files are not compatible with 1.4.0 and will need to be recreated/downloaded
7
 
8
  [YOLOv8n](https://docs.ultralytics.com/models/yolov8/) is a real-time multi-object recognition model confirmed to run in Unity 2023.
9
 
 
12
  You will also need the Unity UI package.
13
 
14
  * Create a new scene in Unity 2023.
15
+ * Install `com.unity.sentis` version `1.4.0-pre.3` from the package manager
16
  * Add the c# script to the Main Camera.
17
  * Create a Raw Image in the scene and link it as the `displayImage`
18
  * Put the yolov8n.sentis file in the Assets/StreamingAssets folder
RunYOLO8n.cs CHANGED
@@ -4,6 +4,8 @@ using UnityEngine;
4
  using UnityEngine.UI;
5
  using UnityEngine.Video;
6
  using Lays = Unity.Sentis.Layers;
 
 
7
 
8
  /*
9
  * YOLOv8n Inference Script
@@ -11,7 +13,8 @@ using Lays = Unity.Sentis.Layers;
11
  *
12
  * Place this script on the Main Camera.
13
  *
14
- * Place the yolov8n.sentis file and a *.mp4 video file in the Assets/StreamingAssets folder
 
15
  * Create a RawImage in your scene and set it as the displayImage field
16
  * Drag the classes.txt into the labelsAsset field
17
  * Add a reference to a sprite image for the bounding box and a font for the text
@@ -21,6 +24,8 @@ using Lays = Unity.Sentis.Layers;
21
 
22
  public class RunYOLO8n : MonoBehaviour
23
  {
 
 
24
  const string modelName = "yolov8n.sentis";
25
  // Change this to the name of the video you put in StreamingAssets folder:
26
  const string videoName = "giraffes.mp4";
@@ -28,15 +33,15 @@ public class RunYOLO8n : MonoBehaviour
28
  public TextAsset labelsAsset;
29
  // Create a Raw Image in the scene and link it here:
30
  public RawImage displayImage;
31
- // Link to a bounding box texture here:
32
- public Sprite boxTexture;
 
33
  // Link to the font for the labels:
34
  public Font font;
35
 
36
  const BackendType backend = BackendType.GPUCompute;
37
 
38
  private Transform displayLocation;
39
- private Model model;
40
  private IWorker engine;
41
  private string[] labels;
42
  private RenderTexture targetRT;
@@ -51,15 +56,13 @@ public class RunYOLO8n : MonoBehaviour
51
 
52
  private VideoPlayer video;
53
 
54
- List<GameObject> boxPool = new List<GameObject>();
55
 
56
  [SerializeField, Range(0, 1)] float iouThreshold = 0.5f;
57
  [SerializeField, Range(0, 1)] float scoreThreshold = 0.5f;
58
  int maxOutputBoxes = 64;
59
 
60
- //For using tensor operators:
61
- Ops ops;
62
-
63
  //bounding box data
64
  public struct BoundingBox
65
  {
@@ -70,14 +73,12 @@ public class RunYOLO8n : MonoBehaviour
70
  public string label;
71
  }
72
 
73
-
74
  void Start()
75
  {
76
  Application.targetFrameRate = 60;
77
  Screen.orientation = ScreenOrientation.LandscapeLeft;
78
 
79
- ops = WorkerFactory.CreateOps(backend, null);
80
-
81
  //Parse neural net labels
82
  labels = labelsAsset.text.Split('\n');
83
 
@@ -88,49 +89,50 @@ public class RunYOLO8n : MonoBehaviour
88
  //Create image to display video
89
  displayLocation = displayImage.transform;
90
 
91
- //Create engine to run model
92
- engine = WorkerFactory.CreateWorker(backend, model);
93
-
94
  SetupInput();
95
- }
96
 
 
 
 
 
 
97
  void LoadModel()
98
  {
 
99
  //Load model
100
- model = ModelLoader.Load(Application.streamingAssetsPath + "/" + modelName);
101
-
102
- //The classes are also stored here in JSON format:
103
- Debug.Log($"Class names: \n{model.Metadata["names"]}");
104
-
105
- //We need to add some layers to choose the best boxes with the NMSLayer
106
-
107
- //Set constants
108
- model.AddConstant(new Lays.Constant("0", new int[] { 0 }));
109
- model.AddConstant(new Lays.Constant("1", new int[] { 1 }));
110
- model.AddConstant(new Lays.Constant("4", new int[] { 4 }));
111
-
112
-
113
- model.AddConstant(new Lays.Constant("classes_plus_4", new int[] { numClasses + 4 }));
114
- model.AddConstant(new Lays.Constant("maxOutputBoxes", new int[] { maxOutputBoxes }));
115
- model.AddConstant(new Lays.Constant("iouThreshold", new float[] { iouThreshold }));
116
- model.AddConstant(new Lays.Constant("scoreThreshold", new float[] { scoreThreshold }));
117
-
118
- //Add layers
119
- model.AddLayer(new Lays.Slice("boxCoords0", "output0", "0", "4", "1"));
120
- model.AddLayer(new Lays.Transpose("boxCoords", "boxCoords0", new int[] { 0, 2, 1 }));
121
- model.AddLayer(new Lays.Slice("scores0", "output0", "4", "classes_plus_4", "1"));
122
- model.AddLayer(new Lays.ReduceMax("scores", new[] { "scores0", "1" }));
123
- model.AddLayer(new Lays.ArgMax("classIDs", "scores0", 1));
124
-
125
- model.AddLayer(new Lays.NonMaxSuppression("NMS", "boxCoords", "scores",
126
- "maxOutputBoxes", "iouThreshold", "scoreThreshold",
127
- centerPointBox: Lays.CenterPointBox.Center
128
- ));
129
-
130
- model.outputs.Clear();
131
- model.AddOutput("boxCoords");
132
- model.AddOutput("classIDs");
133
- model.AddOutput("NMS");
134
  }
135
 
136
  void SetupInput()
@@ -138,7 +140,7 @@ public class RunYOLO8n : MonoBehaviour
138
  video = gameObject.AddComponent<VideoPlayer>();
139
  video.renderMode = VideoRenderMode.APIOnly;
140
  video.source = VideoSource.Url;
141
- video.url = Application.streamingAssetsPath + "/" + videoName;
142
  video.isLooping = true;
143
  video.Play();
144
  }
@@ -168,17 +170,11 @@ public class RunYOLO8n : MonoBehaviour
168
  using var input = TextureConverter.ToTensor(targetRT, imageWidth, imageHeight, 3);
169
  engine.Execute(input);
170
 
171
- var boxCoords = engine.PeekOutput("boxCoords") as TensorFloat;
172
- var NMS = engine.PeekOutput("NMS") as TensorInt;
173
- var classIDs = engine.PeekOutput("classIDs") as TensorInt;
174
 
175
- using var boxIDs = ops.Slice(NMS, new int[] { 2 }, new int[] { 3 }, new int[] { 1 }, new int[] { 1 });
176
- using var boxIDsFlat = boxIDs.ShallowReshape(new TensorShape(boxIDs.shape.length)) as TensorInt;
177
- using var output = ops.Gather(boxCoords, boxIDsFlat, 1);
178
- using var labelIDs = ops.Gather(classIDs, boxIDsFlat, 2);
179
-
180
- output.MakeReadable();
181
- labelIDs.MakeReadable();
182
 
183
  float displayWidth = displayImage.rectTransform.rect.width;
184
  float displayHeight = displayImage.rectTransform.rect.height;
@@ -186,22 +182,23 @@ public class RunYOLO8n : MonoBehaviour
186
  float scaleX = displayWidth / imageWidth;
187
  float scaleY = displayHeight / imageHeight;
188
 
 
189
  //Draw the bounding boxes
190
- for (int n = 0; n < output.shape[1]; n++)
191
  {
192
  var box = new BoundingBox
193
  {
194
- centerX = output[0, n, 0] * scaleX - displayWidth / 2,
195
- centerY = output[0, n, 1] * scaleY - displayHeight / 2,
196
- width = output[0, n, 2] * scaleX,
197
- height = output[0, n, 3] * scaleY,
198
- label = labels[labelIDs[0, 0,n]],
199
  };
200
- DrawBox(box, n);
201
  }
202
  }
203
 
204
- public void DrawBox(BoundingBox box , int id)
205
  {
206
  //Create the bounding box graphic or get from pool
207
  GameObject panel;
@@ -220,10 +217,11 @@ public class RunYOLO8n : MonoBehaviour
220
  //Set box size
221
  RectTransform rt = panel.GetComponent<RectTransform>();
222
  rt.sizeDelta = new Vector2(box.width, box.height);
223
-
224
  //Set label text
225
  var label = panel.GetComponentInChildren<Text>();
226
  label.text = box.label;
 
227
  }
228
 
229
  public GameObject CreateNewBox(Color color)
@@ -234,7 +232,7 @@ public class RunYOLO8n : MonoBehaviour
234
  panel.AddComponent<CanvasRenderer>();
235
  Image img = panel.AddComponent<Image>();
236
  img.color = color;
237
- img.sprite = boxTexture;
238
  img.type = Image.Type.Sliced;
239
  panel.transform.SetParent(displayLocation, false);
240
 
@@ -263,7 +261,7 @@ public class RunYOLO8n : MonoBehaviour
263
 
264
  public void ClearAnnotations()
265
  {
266
- foreach(var box in boxPool)
267
  {
268
  box.SetActive(false);
269
  }
@@ -271,7 +269,7 @@ public class RunYOLO8n : MonoBehaviour
271
 
272
  private void OnDestroy()
273
  {
 
274
  engine?.Dispose();
275
- ops?.Dispose();
276
  }
277
- }
 
4
  using UnityEngine.UI;
5
  using UnityEngine.Video;
6
  using Lays = Unity.Sentis.Layers;
7
+ using System.IO;
8
+ using FF = Unity.Sentis.Functional;
9
 
10
  /*
11
  * YOLOv8n Inference Script
 
13
  *
14
  * Place this script on the Main Camera.
15
  *
16
+ * Place the yolob8n.sentis file in the asset folder and drag onto the asset field
17
+ * Place a *.mp4 video file in the Assets/StreamingAssets folder
18
  * Create a RawImage in your scene and set it as the displayImage field
19
  * Drag the classes.txt into the labelsAsset field
20
  * Add a reference to a sprite image for the bounding box and a font for the text
 
24
 
25
  public class RunYOLO8n : MonoBehaviour
26
  {
27
+ // Drag the yolov8n.sentis file here
28
+ public ModelAsset asset;
29
  const string modelName = "yolov8n.sentis";
30
  // Change this to the name of the video you put in StreamingAssets folder:
31
  const string videoName = "giraffes.mp4";
 
33
  public TextAsset labelsAsset;
34
  // Create a Raw Image in the scene and link it here:
35
  public RawImage displayImage;
36
+ // Link to a bounding box sprite or texture here:
37
+ public Sprite borderSprite;
38
+ public Texture2D borderTexture;
39
  // Link to the font for the labels:
40
  public Font font;
41
 
42
  const BackendType backend = BackendType.GPUCompute;
43
 
44
  private Transform displayLocation;
 
45
  private IWorker engine;
46
  private string[] labels;
47
  private RenderTexture targetRT;
 
56
 
57
  private VideoPlayer video;
58
 
59
+ List<GameObject> boxPool = new();
60
 
61
  [SerializeField, Range(0, 1)] float iouThreshold = 0.5f;
62
  [SerializeField, Range(0, 1)] float scoreThreshold = 0.5f;
63
  int maxOutputBoxes = 64;
64
 
65
+ TensorFloat centersToCorners;
 
 
66
  //bounding box data
67
  public struct BoundingBox
68
  {
 
73
  public string label;
74
  }
75
 
76
+
77
  void Start()
78
  {
79
  Application.targetFrameRate = 60;
80
  Screen.orientation = ScreenOrientation.LandscapeLeft;
81
 
 
 
82
  //Parse neural net labels
83
  labels = labelsAsset.text.Split('\n');
84
 
 
89
  //Create image to display video
90
  displayLocation = displayImage.transform;
91
 
 
 
 
92
  SetupInput();
 
93
 
94
+ if (borderSprite == null)
95
+ {
96
+ borderSprite = Sprite.Create(borderTexture, new Rect(0, 0, borderTexture.width, borderTexture.height), new Vector2(borderTexture.width / 2, borderTexture.height / 2));
97
+ }
98
+ }
99
  void LoadModel()
100
  {
101
+
102
  //Load model
103
+ //var model1 = ModelLoader.Load(Path.Join(Application.streamingAssetsPath, modelName));
104
+ var model1 = ModelLoader.Load(asset);
105
+
106
+ centersToCorners = new TensorFloat(new TensorShape(4, 4),
107
+ new float[]
108
+ {
109
+ 1, 0, 1, 0,
110
+ 0, 1, 0, 1,
111
+ -0.5f, 0, 0.5f, 0,
112
+ 0, -0.5f, 0, 0.5f
113
+ });
114
+
115
+ //Here we transform the output of the model1 by feeding it through a Non-Max-Suppression layer.
116
+ var model2 = Functional.Compile(
117
+ input =>
118
+ {
119
+ var modelOutput = model1.Forward(input)[0];
120
+ var boxCoords = modelOutput[0, 0..4, ..].Transpose(0, 1); //shape=(8400,4)
121
+ var allScores = modelOutput[0, 4.., ..]; //shape=(80,8400)
122
+ var scores = FF.ReduceMax(allScores, 0) - scoreThreshold; //shape=(8400)
123
+ var classIDs = FF.ArgMax(allScores, 0); //shape=(8400)
124
+ var boxCorners = FF.MatMul(boxCoords, FunctionalTensor.FromTensor(centersToCorners));
125
+ var indices = FF.NMS(boxCorners, scores, iouThreshold); //shape=(N)
126
+ var indices2 = indices.Unsqueeze(-1).BroadcastTo(new int[] { 4 });//shape=(N,4)
127
+ var coords = FF.Gather(boxCoords, 0, indices2); //shape=(N,4)
128
+ var labelIDs = FF.Gather(classIDs, 0, indices); //shape=(N)
129
+ return (coords, labelIDs);
130
+ },
131
+ InputDef.FromModel(model1)[0]
132
+ );
133
+
134
+ //Create engine to run model
135
+ engine = WorkerFactory.CreateWorker(backend, model2);
 
136
  }
137
 
138
  void SetupInput()
 
140
  video = gameObject.AddComponent<VideoPlayer>();
141
  video.renderMode = VideoRenderMode.APIOnly;
142
  video.source = VideoSource.Url;
143
+ video.url = Path.Join(Application.streamingAssetsPath, videoName);
144
  video.isLooping = true;
145
  video.Play();
146
  }
 
170
  using var input = TextureConverter.ToTensor(targetRT, imageWidth, imageHeight, 3);
171
  engine.Execute(input);
172
 
173
+ var output = engine.PeekOutput("output_0") as TensorFloat;
174
+ var labelIDs = engine.PeekOutput("output_1") as TensorInt;
 
175
 
176
+ output.CompleteOperationsAndDownload();
177
+ labelIDs.CompleteOperationsAndDownload();
 
 
 
 
 
178
 
179
  float displayWidth = displayImage.rectTransform.rect.width;
180
  float displayHeight = displayImage.rectTransform.rect.height;
 
182
  float scaleX = displayWidth / imageWidth;
183
  float scaleY = displayHeight / imageHeight;
184
 
185
+ int boxesFound = output.shape[0];
186
  //Draw the bounding boxes
187
+ for (int n = 0; n < Mathf.Min(boxesFound, 200); n++)
188
  {
189
  var box = new BoundingBox
190
  {
191
+ centerX = output[n, 0] * scaleX - displayWidth / 2,
192
+ centerY = output[n, 1] * scaleY - displayHeight / 2,
193
+ width = output[n, 2] * scaleX,
194
+ height = output[n, 3] * scaleY,
195
+ label = labels[labelIDs[n]],
196
  };
197
+ DrawBox(box, n, displayHeight * 0.05f);
198
  }
199
  }
200
 
201
+ public void DrawBox(BoundingBox box, int id, float fontSize)
202
  {
203
  //Create the bounding box graphic or get from pool
204
  GameObject panel;
 
217
  //Set box size
218
  RectTransform rt = panel.GetComponent<RectTransform>();
219
  rt.sizeDelta = new Vector2(box.width, box.height);
220
+
221
  //Set label text
222
  var label = panel.GetComponentInChildren<Text>();
223
  label.text = box.label;
224
+ label.fontSize = (int)fontSize;
225
  }
226
 
227
  public GameObject CreateNewBox(Color color)
 
232
  panel.AddComponent<CanvasRenderer>();
233
  Image img = panel.AddComponent<Image>();
234
  img.color = color;
235
+ img.sprite = borderSprite;
236
  img.type = Image.Type.Sliced;
237
  panel.transform.SetParent(displayLocation, false);
238
 
 
261
 
262
  public void ClearAnnotations()
263
  {
264
+ foreach (var box in boxPool)
265
  {
266
  box.SetActive(false);
267
  }
 
269
 
270
  private void OnDestroy()
271
  {
272
+ centersToCorners?.Dispose();
273
  engine?.Dispose();
 
274
  }
275
+ }
info.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "version" : [
3
+ "1.4.0-pre.2"
4
+ ]
5
+ }
yolov8n.sentis CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:2a788b23743661e23da08e893fb7efb507e912a017bfe98604b36a9609f9ffb3
3
- size 12897918
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:72c2ccde7dedd160cd8b62907ff2fa06ffe594d4a0fe0d2b13eb270297ca455c
3
+ size 12834028