David Scripps commited on
Commit
a706d11
·
1 Parent(s): 2546a51

adding files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.sentis filter=lfs diff=lfs merge=lfs -text
37
+ *.png filter=lfs diff=lfs merge=lfs -text
38
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
Border Texture.png ADDED

Git LFS Details

  • SHA256: 817b7b67f24ee7ae264ad196237553d0e76d9e41878e3647ff2b9750cf73443d
  • Pointer size: 128 Bytes
  • Size of remote file: 658 Bytes
README.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: unity-sentis
3
+ pipeline_tag: object-detection
4
+ ---
5
+ # LaboroTomato for Unity Sentis (Version 1.4.0-pre.3*)
6
+
7
+ [LaboroTomato](https://github.com/laboroai/LaboroTomato) is is an image dataset of growing tomatoes at different stages of their ripening.
8
+
9
+ This model was trained on the LaboroTomato image dataset using the Ultralytics [YOLOv8n](https://docs.ultralytics.com/models/yolov8/) object detection framework. The sentis example implementation was copied from [sentis-YOLOv8n](https://huggingface.co/unity/sentis-YOLOv8n).
10
+
11
+ ## How to Use
12
+ First get the package `com.unity.sentis` from the package manager.
13
+ You will also need the Unity UI package.
14
+
15
+ * Create a new scene in Unity 6.
16
+ * Install `com.unity.sentis` version `1.4.0-pre.3` from the package manager
17
+ * Add the c# script to the Main Camera.
18
+ * Create a Raw Image in the scene and link it as the `displayImage`
19
+ * Drag the yolov8n.sentis file into the model asset field
20
+ * Drag the classes.txt on to the labelAssets field
21
+ * Put a video file in the Assets/StreamingAssets folder and set the name of videoName to the filename in the script ("tomatoes.mp4")
22
+ * Set the fields for the bounding box texture sprite (you can [create your own one](https://docs.unity3d.com/Manual/9SliceSprites.html) using a transparent texture or use an inbuilt one) and the font
23
+
24
+
25
+ ## Preview
26
+ If working correctly you should see something like this:
27
+
28
+ ![preview](preview.png)
29
+
30
+ ## Information
31
+ The onnx model was designed with the same inputs as [sentis-YOLOv8n](https://huggingface.co/unity/sentis-YOLOv8n). If you are using that implementation, you can simply swap out the model and labels with the ones in this project and it should work.
32
+
33
+ ## References
34
+ For information on how the model was trained and exported to onnx, see the [project github page](https://github.com/DavidAtRedpine/LaboroTomatoYoloV8).
35
+
36
+ ## Unity Sentis
37
+ Unity Sentis is the inference engine that runs in Unity 3D. More information can be found at [here](https://unity.com/products/sentis)
38
+
39
+ ## License
40
+ Ultralytics YOLOv8 uses the GPLv3 license. Details [here](https://github.com/autogyro/yolo-V8?tab=readme-ov-file#license).
41
+
42
+ The LaboroTomato dataset uses the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Details [here](https://github.com/laboroai/LaboroTomato/blob/master/README.md#license).
RunLaboroTomato.cs ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Collections.Generic;
2
+ using Unity.Sentis;
3
+ using UnityEngine;
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
+ * LaboroTomato (made with YoloV8) Inference Script
12
+ * ========================
13
+ *
14
+ * Place this script on the Main Camera.
15
+ *
16
+ * Place the laboro_tomato_yolov8.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
21
+ *
22
+ */
23
+
24
+
25
+ public class RunLaboroTomato : MonoBehaviour
26
+ {
27
+ // Drag the yolov8n.sentis file here
28
+ public ModelAsset asset;
29
+ const string modelName = "laboro_tomato_yolov8.sentis";
30
+ // Change this to the name of the video you put in StreamingAssets folder:
31
+ const string videoName = "tomatoes.mp4";
32
+ // Link the classes.txt here:
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;
48
+
49
+
50
+ //Image size for the model
51
+ private const int imageWidth = 640;
52
+ private const int imageHeight = 640;
53
+
54
+ //The number of classes in the model
55
+ private const int numClasses = 80;
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
+ {
69
+ public float centerX;
70
+ public float centerY;
71
+ public float width;
72
+ public float height;
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
+
85
+ LoadModel();
86
+
87
+ targetRT = new RenderTexture(imageWidth, imageHeight, 0);
88
+
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()
139
+ {
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
+ }
147
+
148
+ private void Update()
149
+ {
150
+ ExecuteML();
151
+
152
+ if (Input.GetKeyDown(KeyCode.Escape))
153
+ {
154
+ Application.Quit();
155
+ }
156
+ }
157
+
158
+ public void ExecuteML()
159
+ {
160
+ ClearAnnotations();
161
+
162
+ if (video && video.texture)
163
+ {
164
+ float aspect = video.width * 1f / video.height;
165
+ Graphics.Blit(video.texture, targetRT, new Vector2(1f / aspect, 1), new Vector2(0, 0));
166
+ displayImage.texture = targetRT;
167
+ }
168
+ else return;
169
+
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;
181
+
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;
205
+ if (id < boxPool.Count)
206
+ {
207
+ panel = boxPool[id];
208
+ panel.SetActive(true);
209
+ }
210
+ else
211
+ {
212
+ panel = CreateNewBox(Color.yellow);
213
+ }
214
+ //Set box position
215
+ panel.transform.localPosition = new Vector3(box.centerX, -box.centerY);
216
+
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)
228
+ {
229
+ //Create the box and set image
230
+
231
+ var panel = new GameObject("ObjectBox");
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
+
239
+ //Create the label
240
+
241
+ var text = new GameObject("ObjectLabel");
242
+ text.AddComponent<CanvasRenderer>();
243
+ text.transform.SetParent(panel.transform, false);
244
+ Text txt = text.AddComponent<Text>();
245
+ txt.font = font;
246
+ txt.color = color;
247
+ txt.fontSize = 40;
248
+ txt.horizontalOverflow = HorizontalWrapMode.Overflow;
249
+
250
+ RectTransform rt2 = text.GetComponent<RectTransform>();
251
+ rt2.offsetMin = new Vector2(20, rt2.offsetMin.y);
252
+ rt2.offsetMax = new Vector2(0, rt2.offsetMax.y);
253
+ rt2.offsetMin = new Vector2(rt2.offsetMin.x, 0);
254
+ rt2.offsetMax = new Vector2(rt2.offsetMax.x, 30);
255
+ rt2.anchorMin = new Vector2(0, 0);
256
+ rt2.anchorMax = new Vector2(1, 1);
257
+
258
+ boxPool.Add(panel);
259
+ return panel;
260
+ }
261
+
262
+ public void ClearAnnotations()
263
+ {
264
+ foreach (var box in boxPool)
265
+ {
266
+ box.SetActive(false);
267
+ }
268
+ }
269
+
270
+ private void OnDestroy()
271
+ {
272
+ centersToCorners?.Dispose();
273
+ engine?.Dispose();
274
+ }
275
+ }
classes.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ b_fully_ripened
2
+ b_half_ripened
3
+ b_green
4
+ l_fully_ripened
5
+ l_half_ripened
6
+ l_green
info.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "version" : [
3
+ "1.4.0-pre.2"
4
+ ]
5
+ }
info.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "code": [ "RunLaboroTomato.cs"],
3
+ "models": [ "laboro_tomato_yolov8.sentis"],
4
+ "data": [ "classes.txt" ]
5
+ }
laboro_tomato_yolov8.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0de233be5f31b89ab1482268ff7e72aca0ebb5917bf92afb500de79c0476a86a
3
+ size 44739449
laboro_tomato_yolov8.sentis ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b24b5e411a644600616be29245edc37876df080c86398fb5368a83fe759217cc
3
+ size 44738340
preview.png ADDED

Git LFS Details

  • SHA256: f2bfbcdc74d8bb1607ae761e809a39fc60cb7590461250d8b53d4641621c6a9f
  • Pointer size: 132 Bytes
  • Size of remote file: 2.63 MB
tomatoes.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:217ecb66a7235e4d3a6eb2557f62b15e69b5c6141ccae8e6ca076680efe0bc7f
3
+ size 3199970