ZED3DObjectVisualizer.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. /// <summary>
  6. /// For the ZED 3D Object Detection sample.
  7. /// Listens for new object detections (via the ZEDManager.OnObjectDetection event) and moves + resizes cube prefabs
  8. /// to represent them.
  9. /// <para>Works by instantiating a pool of prefabs, and each frame going through the DetectedFrame received from the event
  10. /// to make sure each detected object has a representative GameObject. Also disables GameObjects whose objects are no
  11. /// longer visible and returns them to the pool.</para>
  12. /// </summary>
  13. public class ZED3DObjectVisualizer : MonoBehaviour
  14. {
  15. /// <summary>
  16. /// The scene's ZEDManager.
  17. /// If you want to visualize detections from multiple ZEDs at once you will need multiple ZED3DObjectVisualizer commponents in the scene.
  18. /// </summary>
  19. [Tooltip("The scene's ZEDManager.\r\n" +
  20. "If you want to visualize detections from multiple ZEDs at once you will need multiple ZED3DObjectVisualizer commponents in the scene. ")]
  21. public ZEDManager zedManager;
  22. /// <summary>
  23. /// If true, the ZED Object Detection manual will be started as soon as the ZED is initiated.
  24. /// This avoids having to press the Start Object Detection button in ZEDManager's Inspector.
  25. /// </summary>
  26. [Tooltip("If true, the ZED Object Detection manual will be started as soon as the ZED is initiated. " +
  27. "This avoids having to press the Start Object Detection button in ZEDManager's Inspector.")]
  28. public bool startObjectDetectionAutomatically = true;
  29. /// <summary>
  30. /// Prefab object that's instantiated to represent detected objects.
  31. /// This class expects the object to have the default Unity cube as a mesh - otherwise, it may be scaled incorrectly.
  32. /// It also expects a BBox3DHandler component in the root object, but you won't have errors if it lacks one.
  33. /// </summary>
  34. [Space(5)]
  35. [Header("Box Appearance")]
  36. [Tooltip("Prefab object that's instantiated to represent detected objects. " +
  37. "This class expects the object to have the default Unity cube as a mesh - otherwise, it may be scaled incorrectly.\r\n" +
  38. "It also expects a BBox3DHandler component in the root object, but you won't have errors if it lacks one. ")]
  39. public GameObject boundingBoxPrefab;
  40. /// <summary>
  41. /// The colors that will be cycled through when assigning colors to new bounding boxes.
  42. /// </summary>
  43. [Tooltip("The colors that will be cycled through when assigning colors to new bounding boxes. ")]
  44. //[ColorUsage(true, true)] //Uncomment to enable HDR colors in versions of Unity that support it.
  45. public List<Color> boxColors = new List<Color>()
  46. {
  47. new Color(.231f, .909f, .69f, 1),
  48. new Color(.098f, .686f, .816f, 1),
  49. new Color(.412f, .4f, .804f, 1),
  50. new Color(1, .725f, 0f, 1),
  51. new Color(.989f, .388f, .419f, 1)
  52. };
  53. /// <summary>
  54. /// If true, bounding boxes are rotated to face the camera that detected them. This has more parity with the SDK and will generally result in more accurate boxes.
  55. /// If false, the boxes are calculated from known bounds to face Z = 1.
  56. /// </summary>
  57. [Space(5)]
  58. [Header("Box Transform")]
  59. [Tooltip("If true, bounding boxes are rotated to face the camera that detected them. If false, the boxes are calculated from known bounds to face Z = 1. " +
  60. "'False' has more parity with the SDK and will generally result in more accurate boxes.")]
  61. public bool boxesFaceCamera = false;
  62. /// <summary>
  63. /// If true, transforms the localScale of the root bounding box transform to match the detected 3D bounding box.
  64. /// </summary>
  65. [Tooltip("If true, transforms the localScale of the root bounding box transform to match the detected 3D bounding box. ")]
  66. public bool transformBoxScale = true;
  67. /// <summary>
  68. /// If true, and transformBoxScale is also true, modifies the center and Y bounds of each detected bounding box so that its
  69. /// bottom is at floor level (Y = 0) while keeping the other corners at the same place.
  70. /// </summary>
  71. [Tooltip("If true, and transformBoxScale is also true, modifies the center and Y bounds of each detected bounding box so that its " +
  72. "bottom is at floor level (Y = 0) while keeping the other corners at the same place. WARNING: Estimate Initial position must be set to true.")]
  73. public bool transformBoxToTouchFloor = true;
  74. /// <summary>
  75. /// If true, sets the Y value of the center of the bounding box to 0. Use for bounding box prefabs meant to be centered at the user's feet.
  76. /// </summary>
  77. [Tooltip("If true, sets the Y value of the center of the bounding box to 0. Use for bounding box prefabs meant to be centered at the user's feet. ")]
  78. [LabelOverride("Box Center Always On Floor")]
  79. public bool floorBBoxPosition = false;
  80. /// <summary>
  81. /// Display bounding boxes of objects that are actively being tracked by object tracking, where valid positions are known.
  82. /// </summary>
  83. [Space(5)]
  84. [Header("Filters")]
  85. [Tooltip("Display bounding boxes of objects that are actively being tracked by object tracking, where valid positions are known. ")]
  86. public bool showONTracked = true;
  87. /// <summary>
  88. /// Display bounding boxes of objects that were actively being tracked by object tracking, but that were lost very recently.
  89. /// </summary>
  90. [Tooltip("Display bounding boxes of objects that were actively being tracked by object tracking, but that were lost very recently.")]
  91. public bool showSEARCHINGTracked = false;
  92. /// <summary>
  93. /// Display bounding boxes of objects that are visible but not actively being tracked by object tracking (usually because object tracking is disabled in ZEDManager).
  94. /// </summary>
  95. [Tooltip("Display bounding boxes of objects that are visible but not actively being tracked by object tracking (usually because object tracking is disabled in ZEDManager).")]
  96. public bool showOFFTracked = false;
  97. /// <summary>
  98. /// How wide a bounding box has to be in order to be displayed. Use this to remove tiny bounding boxes from partially-occluded objects.
  99. /// (If you have this issue, it can also be helpful to set showSEARCHINGTracked to OFF.)
  100. /// </summary>
  101. [Tooltip("How wide a bounding box has to be in order to be displayed. Use this to remove tiny bounding boxes from partially-occluded objects.\r\n" +
  102. "(If you have this issue, it can also be helpful to set showSEARCHINGTracked to OFF.)")]
  103. public float minimumWidthToDisplay = 0.3f;
  104. /// <summary>
  105. /// When a detected object is first given a box and assigned a color, we store it so that if the object
  106. /// disappears and appears again later, it's assigned the same color.
  107. /// This is also solvable by making the color a function of the ID number itself, but then you can get
  108. /// repeat colors under certain conditions.
  109. /// </summary>
  110. private Dictionary<int, Color> idColorDict = new Dictionary<int, Color>();
  111. /// <summary>
  112. /// Pre-instantiated bbox prefabs currently not in use.
  113. /// </summary>
  114. private Stack<GameObject> bboxPool = new Stack<GameObject>();
  115. /// <summary>
  116. /// All active GameObjects that were instantiated to the prefab and that currently represent a detected object.
  117. /// Key is the object's objectID.
  118. /// </summary>
  119. private Dictionary<int, GameObject> liveBBoxes = new Dictionary<int, GameObject>();
  120. /// <summary>
  121. /// Used to know which of the available colors will be assigned to the next bounding box to be used.
  122. /// </summary>
  123. private int nextColorIndex = 0;
  124. // Use this for initialization
  125. void Start()
  126. {
  127. if (!zedManager)
  128. {
  129. zedManager = FindObjectOfType<ZEDManager>();
  130. }
  131. zedManager.OnObjectDetection += Visualize3DBoundingBoxes;
  132. zedManager.OnZEDReady += OnZEDReady;
  133. if (zedManager.estimateInitialPosition == false && transformBoxToTouchFloor == true)
  134. {
  135. Debug.Log("Estimate initial position is set to false. Then, transformBoxToTouchFloor is disable.");
  136. transformBoxToTouchFloor = false;
  137. }
  138. }
  139. private void OnZEDReady()
  140. {
  141. if (startObjectDetectionAutomatically && !zedManager.IsObjectDetectionRunning)
  142. {
  143. zedManager.StartObjectDetection();
  144. }
  145. }
  146. /// <summary>
  147. /// Given a frame of object detections, positions a GameObject to represent every visible object
  148. /// in that object's actual 3D location within the world.
  149. /// <para>Called from ZEDManager.OnObjectDetection each time there's a new detection frame available.</para>
  150. /// </summary>
  151. private void Visualize3DBoundingBoxes(DetectionFrame dframe)
  152. {
  153. //Get a list of all active IDs from last frame, and we'll remove each box that's visible this frame.
  154. //At the end, we'll clear the remaining boxes, as those are objects no longer visible to the ZED.
  155. List<int> activeids = liveBBoxes.Keys.ToList();
  156. List<DetectedObject> newobjects = dframe.GetFilteredObjectList(showONTracked, showSEARCHINGTracked, showOFFTracked);
  157. if (newobjects.Any())
  158. {
  159. if (newobjects.Count >= 2)
  160. {
  161. var ball = GameObject.Find("Sphere").GetComponent<Ball>();
  162. if (!ball.IsInUse)
  163. {
  164. ball.Kickoff();
  165. }
  166. }
  167. int count = 1;
  168. foreach (var dobj in newobjects.Take(2))
  169. {
  170. GameObject bump = GameObject.Find($"Bump{count}");
  171. Bounds objbounds = dobj.Get3DWorldBounds();
  172. //Make sure the object is big enough to count. We filter out very small boxes.
  173. if (objbounds.size.x < minimumWidthToDisplay || objbounds.size == Vector3.zero) { }
  174. {
  175. //Remove the ID from the list we'll use to clear no-longer-visible boxes.
  176. if (activeids.Contains(dobj.id)) activeids.Remove(dobj.id);
  177. //Get the box and update its distance value.
  178. GameObject bbox = GetBBoxForObject(dobj);
  179. //Move the box into position.
  180. Vector3 obj_position = dobj.Get3DWorldPosition();
  181. Debug.Log($"count: {count}; X: {obj_position.x}; Y: {obj_position.y}; Z: {obj_position.z};");
  182. if (!ZEDSupportFunctions.IsVector3NaN(obj_position))
  183. {
  184. bbox.transform.position = obj_position;
  185. if (obj_position.z > 3 && obj_position.z < 4)
  186. {
  187. var scale = obj_position.z - 3;
  188. var newZ = scale * 8 - 4;
  189. bump.transform.position = new Vector3(bump.transform.position.x, bump.transform.position.y, newZ);
  190. }
  191. if (floorBBoxPosition)
  192. {
  193. bbox.transform.position = new Vector3(bbox.transform.position.x, 0, bbox.transform.position.z);
  194. }
  195. bbox.transform.rotation = dobj.Get3DWorldRotation(boxesFaceCamera); //Rotate them.
  196. }
  197. //Transform the box if desired.
  198. if (transformBoxScale)
  199. {
  200. //We'll scale the object assuming that it's mesh is the default Unity cube, or something sized equally.
  201. if (transformBoxToTouchFloor)
  202. {
  203. Vector3 startscale = objbounds.size;
  204. float distfromfloor = bbox.transform.position.y - (objbounds.size.y / 2f);
  205. bbox.transform.localScale = new Vector3(objbounds.size.x, objbounds.size.y + distfromfloor, objbounds.size.z);
  206. Vector3 newpos = bbox.transform.position;
  207. newpos.y -= (distfromfloor / 2f);
  208. bbox.transform.position = newpos;
  209. }
  210. else
  211. {
  212. bbox.transform.localScale = objbounds.size;
  213. }
  214. }
  215. //Now that we've adjusted position, tell the handler on the prefab to adjust distance display..
  216. BBox3DHandler boxhandler = bbox.GetComponent<BBox3DHandler>();
  217. if (boxhandler)
  218. {
  219. float disttobox = Vector3.Distance(dobj.detectingZEDManager.GetLeftCameraTransform().position, dobj.Get3DWorldPosition());
  220. boxhandler.SetDistance(disttobox);
  221. boxhandler.UpdateBoxUVScales();
  222. boxhandler.UpdateLabelScaleAndPosition();
  223. }
  224. //DrawDebugBox(dobj);
  225. }
  226. //Remove boxes for objects that the ZED can no longer see.
  227. foreach (int id in activeids)
  228. {
  229. ReturnBoxToPool(id, liveBBoxes[id]);
  230. }
  231. count++;
  232. }
  233. }
  234. else
  235. {
  236. var ball = GameObject.Find("Sphere").GetComponent<Ball>();
  237. ball.Recenter();
  238. }
  239. }
  240. /// <summary>
  241. /// Returs the GameObject (instantiated from boundingBoxPrefab) that represents the provided DetectedObject.
  242. /// If none exists, it retrieves one from the pool (or instantiates a new one if none is available) and
  243. /// sets it up with the proper ID and colors.
  244. /// </summary>
  245. private GameObject GetBBoxForObject(DetectedObject dobj)
  246. {
  247. if (!liveBBoxes.ContainsKey(dobj.id))
  248. {
  249. GameObject newbox = GetAvailableBBox();
  250. newbox.name = "Object #" + dobj.id;
  251. BBox3DHandler boxhandler = newbox.GetComponent<BBox3DHandler>();
  252. Color col;
  253. if (idColorDict.ContainsKey(dobj.id))
  254. {
  255. col = idColorDict[dobj.id];
  256. }
  257. else
  258. {
  259. col = GetNextColor();
  260. idColorDict.Add(dobj.id, col);
  261. }
  262. if (boxhandler)
  263. {
  264. boxhandler.SetColor(col);
  265. if (zedManager.objectDetectionModel == sl.DETECTION_MODEL.CUSTOM_BOX_OBJECTS)
  266. {
  267. //boxhandler.SetID(dobj.rawObjectData.rawLabel.ToString());
  268. boxhandler.SetID(dobj.id.ToString());
  269. }
  270. else
  271. {
  272. boxhandler.SetID(dobj.id.ToString());
  273. }
  274. }
  275. liveBBoxes[dobj.id] = newbox;
  276. return newbox;
  277. }
  278. else return liveBBoxes[dobj.id];
  279. }
  280. /// <summary>
  281. /// Gets an available GameObject (instantiated from boundingBoxPrefab) from the pool,
  282. /// or instantiates a new one if none are available.
  283. /// </summary>
  284. /// <returns></returns>
  285. private GameObject GetAvailableBBox()
  286. {
  287. if (bboxPool.Count == 0)
  288. {
  289. GameObject newbbox = Instantiate(boundingBoxPrefab);
  290. newbbox.transform.SetParent(transform, false);
  291. bboxPool.Push(newbbox);
  292. }
  293. GameObject bbox = bboxPool.Pop();
  294. bbox.SetActive(true);
  295. return bbox;
  296. }
  297. /// <summary>
  298. /// Disables a GameObject that was being used to represent an object (of the given id) and puts it back
  299. /// into the pool for later use.
  300. /// </summary>
  301. private void ReturnBoxToPool(int id, GameObject bbox)
  302. {
  303. bbox.SetActive(false);
  304. bbox.name = "Unused";
  305. bboxPool.Push(bbox);
  306. if (liveBBoxes.ContainsKey(id))
  307. {
  308. liveBBoxes.Remove(id);
  309. }
  310. else
  311. {
  312. Debug.LogError("Tried to remove object ID " + id + " from active bboxes, but it wasn't in the dictionary.");
  313. }
  314. }
  315. /// <summary>
  316. /// Returns a color from the boxColors list.
  317. /// Colors are returned sequentially in order of their appearance in that list.
  318. /// </summary>
  319. /// <returns></returns>
  320. private Color GetNextColor()
  321. {
  322. if (boxColors.Count == 0)
  323. {
  324. return new Color(.043f, .808f, .435f, 1);
  325. }
  326. if (nextColorIndex >= boxColors.Count)
  327. {
  328. nextColorIndex = 0;
  329. }
  330. Color returncol = boxColors[nextColorIndex];
  331. nextColorIndex++;
  332. return returncol;
  333. }
  334. private void OnDestroy()
  335. {
  336. if (zedManager)
  337. {
  338. zedManager.OnObjectDetection -= Visualize3DBoundingBoxes;
  339. zedManager.OnZEDReady -= OnZEDReady;
  340. }
  341. }
  342. /// <summary>
  343. /// Draws a bounding box in the Scene window. Useful for debugging a 3D bbox's position relative to it.
  344. /// </summary>
  345. private void DrawDebugBox(DetectedObject dobj)
  346. {
  347. //Test bbox orientation.
  348. Transform camtrans = dobj.detectingZEDManager.GetLeftCameraTransform();
  349. Vector3[] corners = dobj.rawObjectData.worldBoundingBox;
  350. Vector3[] rotcorners = new Vector3[8];
  351. //Vector3[] corners3d = new Vector3[8];
  352. Vector3[] corners3d = dobj.Get3DWorldCorners();
  353. for (int i = 0; i < 8; i++)
  354. {
  355. Vector3 fixrot = camtrans.InverseTransformPoint(corners[i]);
  356. rotcorners[i] = fixrot;
  357. }
  358. Debug.DrawLine(corners3d[0], corners3d[1], Color.red);
  359. Debug.DrawLine(corners3d[1], corners3d[2], Color.red);
  360. Debug.DrawLine(corners3d[2], corners3d[3], Color.red);
  361. Debug.DrawLine(corners3d[3], corners3d[0], Color.red);
  362. Debug.DrawLine(corners3d[4], corners3d[5], Color.red);
  363. Debug.DrawLine(corners3d[5], corners3d[6], Color.red);
  364. Debug.DrawLine(corners3d[6], corners3d[7], Color.red);
  365. Debug.DrawLine(corners3d[7], corners3d[4], Color.red);
  366. Debug.DrawLine(corners3d[0], corners3d[4], Color.red);
  367. Debug.DrawLine(corners3d[1], corners3d[5], Color.red);
  368. Debug.DrawLine(corners3d[2], corners3d[6], Color.red);
  369. Debug.DrawLine(corners3d[3], corners3d[7], Color.red);
  370. }
  371. }