MyZED3DObjectVisualizer.cs 17 KB

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