DetectedObject.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using sl;
  5. using System;
  6. /// <summary>
  7. /// Represents a single object detected by the ZED Object Detection module.
  8. /// Provides various functions for knowing where the object is in the world (position) and how much space it takes up (bounds).
  9. /// <para>It's listed in a DetectionFrame, which is included as the argument in the ZEDManager.OnObjectDetection event.</para>
  10. /// </summary><remarks>
  11. /// This is a higher level version of sl.ObjectData, which comes directly from the ZED SDK and doesn't follow Unity conventions.
  12. /// </remarks>
  13. public class DetectedObject
  14. {
  15. private ObjectDataSDK objectData;
  16. /// <summary>
  17. /// The raw ObjectData object that this instance is an abstraction of - ObjectData's data comes
  18. /// directly from the SDK and doesn't follow Unity conventions.
  19. /// </summary>
  20. public ObjectDataSDK rawObjectData
  21. {
  22. get
  23. {
  24. return objectData;
  25. }
  26. }
  27. /// <summary>
  28. /// Arbitrary ID assigned to the object on first detection. Will persist between frames if the object remains
  29. /// visible and detected.
  30. /// </summary>
  31. public int id
  32. {
  33. get
  34. {
  35. return objectData.id;
  36. }
  37. }
  38. /// <summary>
  39. /// Class of object that was detected (person, vehicle, etc.)
  40. /// </summary>
  41. public OBJECT_CLASS objectClass
  42. {
  43. get
  44. {
  45. return objectData.objectClass;
  46. }
  47. }
  48. /// <summary>
  49. /// SubClass of object that was detected
  50. /// </summary>
  51. public OBJECT_SUBCLASS objectSubClass
  52. {
  53. get
  54. {
  55. return objectData.objectSubClass;
  56. }
  57. }
  58. /// <summary>
  59. /// Current state of the object's tracking. "OK" means it's visible this frame. "Searching" means it was
  60. /// visible recently, but is no longer visible. "OFF" means that object detection has tracking turned off,
  61. /// so 3D data will never be available.
  62. /// </summary>
  63. public OBJECT_TRACK_STATE trackingState
  64. {
  65. get
  66. {
  67. return objectData.objectTrackingState;
  68. }
  69. }
  70. /// <summary>
  71. /// Current action state. "IDLE" means the object is not moving. "MOVING" means the object is moving.
  72. /// </summary>
  73. public OBJECT_ACTION_STATE actionState
  74. {
  75. get
  76. {
  77. return objectData.actionState;
  78. }
  79. }
  80. /// <summary>
  81. /// How confident the ZED SDK is that this object is in fact a valid detection. From 1 to 100.
  82. /// Higher is better, eg. if objectClass is PERSON and confidence is 99, there's a 99% chance it's indeed a person.
  83. /// <para>You can set the minimum confidence threshold in ZEDManager's Inspector.</para>
  84. /// </summary>
  85. public float confidence
  86. {
  87. get
  88. {
  89. return objectData.confidence;
  90. }
  91. }
  92. /// <summary>
  93. /// The manager class responsible for the ZED camera that detected this object.
  94. /// </summary>
  95. public ZEDManager detectingZEDManager;
  96. private Vector3 camPositionAtDetection;
  97. private Quaternion camRotationAtDetection;
  98. /// <summary>
  99. /// sl::Mat material that represents where on the image the detected object exists on a pixel-by-pixel level.
  100. /// For a texture that you can overlay, use GetMaskTexture().
  101. /// </summary>
  102. public sl.ZEDMat maskMat;
  103. /// <summary>
  104. /// Cached texture version of the 2D mask. Retrieved with GetMaskTexture.
  105. /// This is cached after calculating the first time to avoid needless calculation from multiple calls.
  106. /// </summary>
  107. private Texture2D maskTexture = null;
  108. /// <summary>
  109. /// Cached texture version of the 2D mask. Retrieved with GetMaskTexture.
  110. /// Flipped on the Y axis to make it simpler to apply to standard Unity shaders.
  111. /// This is cached after calculating the first time to avoid needless calculation from multiple calls.
  112. /// </summary>
  113. private Texture2D maskTextureFlipped = null;
  114. /// <summary>
  115. /// Constructor that assigns values required for transformations later on.
  116. /// This is necessary because detections are frozen in a particular frame, and results should not change
  117. /// in subsequent frames when the camera moves.
  118. /// </summary>
  119. /// <param name="odata">Raw sl.ObjectData data that this instance represents.</param>
  120. /// <param name="viewingmanager">ZEDManager assigned to the ZED camera that detected the object.</param>
  121. /// <param name="campos">World position of the left ZED camera when the object was detected.</param>
  122. /// <param name="camrot">World rotation of the left ZED camera when the object was detected.</param>
  123. public DetectedObject(ObjectDataSDK odata, ZEDManager viewingmanager, Vector3 campos, Quaternion camrot)
  124. {
  125. objectData = odata;
  126. detectingZEDManager = viewingmanager;
  127. camPositionAtDetection = campos;
  128. camRotationAtDetection = camrot;
  129. maskMat = new ZEDMat(odata.mask);
  130. //maskTexture = ZEDMatToTexture_CPU(maskMat);
  131. }
  132. /// <summary>
  133. /// Returns the pixel positions of the four corners of the object's 2D bounding box on the image.
  134. /// <para>Like most of Unity, the Y values are relative to the bottom of the image, which is unlike the
  135. /// raw imageBoundingBox data from the ObjectData struct.</para>
  136. /// 0 ------- 1
  137. /// | obj |
  138. /// 3-------- 2
  139. /// </summary>
  140. /// <param name="scaleForCanvasUnityError">Adds an optional scaling factor to handle a bug in 2018.3 and greater where the
  141. /// canvas set to Screen Space - Camera has very weird offsets when its projection matrix has certain small changes made to it directly.</param>
  142. public Vector2[] Get2DBoundingBoxCorners_Image(float scaleForCanvasUnityError = 1)
  143. {
  144. //Shorthand.
  145. float zedimagewidth = detectingZEDManager.zedCamera.ImageWidth;
  146. float zedimageheight = detectingZEDManager.zedCamera.ImageHeight;
  147. //Canvas offsets from horizontal and vertical calibration offsets (cx/cy).
  148. CalibrationParameters calib = detectingZEDManager.zedCamera.GetCalibrationParameters();
  149. float cxoffset = zedimagewidth * scaleForCanvasUnityError / 2f - calib.leftCam.cx * scaleForCanvasUnityError;
  150. float cyoffset = 0 - (zedimageheight / 2f - calib.leftCam.cy);
  151. Vector2[] flippedYimagecoords = new Vector2[4];
  152. for (int i = 0; i < 4; i++)
  153. {
  154. Vector2 rawcoord;
  155. rawcoord.x = objectData.imageBoundingBox[i].x * scaleForCanvasUnityError + cxoffset;
  156. rawcoord.y = detectingZEDManager.zedCamera.ImageHeight - objectData.imageBoundingBox[i].y + cyoffset;
  157. #if UNITY_2018_1_OR_NEWER
  158. //Terrible hack to compensate for bug in Unity that scales the Canvas very improperly if you have certain (necessary) values on the projection matrix.
  159. rawcoord.y = (rawcoord.y - (zedimageheight / 2f)) * scaleForCanvasUnityError + (zedimageheight / 2f);
  160. #endif
  161. flippedYimagecoords[i] = rawcoord;
  162. }
  163. return flippedYimagecoords;
  164. }
  165. /// <summary>
  166. /// Returns the viewport positions of the four corners of the object's 2D bounding box on the capturing camera.
  167. /// <para>Like most of Unity, the Y values are relative to the bottom of the image, which is unlike the
  168. /// raw imageBoundingBox data from the ObjectData struct.</para>
  169. /// 0 ------- 1
  170. /// | obj |
  171. /// 3-------- 2
  172. /// </summary>
  173. /// <param name="scaleForCanvasUnityError">Adds an optional scaling factor to handle a bug in 2018.3 and greater where the
  174. /// canvas set to Screen Space - Camera has very weird offsets when its projection matrix has certain small changes made to it directly.</param>
  175. public Vector2[] Get2DBoundingBoxCorners_Viewport(float scaleForCanvasUnityError = 1)
  176. {
  177. Vector2[] imagecoords = Get2DBoundingBoxCorners_Image(scaleForCanvasUnityError);
  178. Vector2[] viewportcorners = new Vector2[4];
  179. for (int i = 0; i < 4; i++)
  180. {
  181. viewportcorners[i] = detectingZEDManager.GetLeftCamera().ScreenToViewportPoint(imagecoords[i]);
  182. }
  183. return viewportcorners;
  184. }
  185. /// <summary>
  186. /// Returns the object's 2D bounding box as a rect. Use this to position a UI element around the object
  187. /// in a Camera-space UI canvas attached to the capturing camera.
  188. /// Values assume Y is relative to the bottom.
  189. /// </summary>
  190. /// <param name="scaleForCanvasUnityError">Adds an optional scaling factor to handle a bug in 2018.3 and greater where the
  191. /// canvas set to Screen Space - Camera has very weird offsets when its projection matrix has certain small changes made to it directly.</param>
  192. public Rect Get2DBoundingBoxRect(float scaleForCanvasUnityError = 1f)
  193. {
  194. Vector2[] imagecoords = Get2DBoundingBoxCorners_Image(scaleForCanvasUnityError);
  195. float width = (imagecoords[1].x - imagecoords[0].x);// * scaleForCanvas;
  196. float height = (imagecoords[0].y - imagecoords[3].y);
  197. Vector2 bottomleftcorner = imagecoords[3];
  198. return new Rect(bottomleftcorner, new Vector2(width, height));
  199. }
  200. /// <summary>
  201. /// Gets the center of the 3D object in world space.
  202. /// </summary>
  203. public Vector3 Get3DWorldPosition()
  204. {
  205. //Get the center of the transformed bounding box.
  206. float ypos = (localToWorld(objectData.worldBoundingBox[0]).y - localToWorld(objectData.worldBoundingBox[4]).y) / 2f + localToWorld(objectData.worldBoundingBox[4]).y;
  207. Vector3 transformedroot = localToWorld(objectData.rootWorldPosition);
  208. return new Vector3(transformedroot.x, ypos, transformedroot.z);
  209. }
  210. /// <summary>
  211. /// Gets the direction that the 3D bounding box is facing, relative to the world.
  212. /// This is simply the opposite of the direction the camera was facing on detection.
  213. /// </summary>
  214. /// <param name="boxesfacecamera">True to artificially rotate the boxes to face the camera.</param>
  215. public Quaternion Get3DWorldRotation(bool boxesfacecamera)
  216. {
  217. Vector3 normal;
  218. if (boxesfacecamera)
  219. {
  220. normal = Get3DWorldPosition() - detectingZEDManager.GetLeftCameraTransform().position; //This makes box face camera.
  221. }
  222. else
  223. {
  224. normal = camRotationAtDetection * Vector3.forward; //This is to face the inverse of the camera's Z direction.
  225. }
  226. normal.y = 0;
  227. return Quaternion.LookRotation(normal, Vector3.up);
  228. }
  229. /// <summary>
  230. /// Gets the size of the bounding box as a Bounds class.
  231. /// Use this to draw lines around an object's bounds, or scale a cube to enclose it.
  232. /// </summary>
  233. public Bounds Get3DWorldBounds()
  234. {
  235. Vector3[] worldcorners = objectData.worldBoundingBox;
  236. Quaternion pitchrot = GetRotationWithoutYaw();
  237. Vector3 leftbottomback = pitchrot * worldcorners[5]; //Shorthand.
  238. Vector3 righttopfront = pitchrot * worldcorners[3]; //Shorthand.
  239. float xsize = Mathf.Abs(righttopfront.x - leftbottomback.x);
  240. float ysize = Mathf.Abs(righttopfront.y - leftbottomback.y);
  241. float zsize = Mathf.Abs(righttopfront.z - leftbottomback.z);
  242. return new Bounds(Vector3.zero, new Vector3(xsize, ysize, zsize));
  243. }
  244. /// <summary>
  245. /// Gets the world positions of the eight corners of the object's 3D bounding box.
  246. /// If facingCamera is set to true, the box will face the ZED that observed it.
  247. /// If false, they will be aligned with the world axes. However, this can result in too large of a box
  248. /// since it has to encompass the camera-aligned version.
  249. /// 1 ---------2
  250. /// /| /|
  251. /// 0 |--------3 |
  252. /// | | | |
  253. /// | 5--------|-6
  254. /// |/ |/
  255. /// 4 ---------7
  256. /// </summary>
  257. /// <param name="facingCamera"></param>
  258. public Vector3[] Get3DWorldCorners()
  259. {
  260. Vector3[] worldspacecorners = new Vector3[8];
  261. for (int i = 0; i < 8; i++)
  262. {
  263. worldspacecorners[i] = localToWorld(objectData.worldBoundingBox[i]);
  264. }
  265. return worldspacecorners;
  266. }
  267. /// <summary>
  268. /// Gets a Texture2D version of the 2D mask that displays which pixels within the bounding box a detected object occupies.
  269. /// Texture is the size of the 2D bounding box and meant to be overlayed on top of it.
  270. /// </summary>
  271. /// <param name="masktex">Texture2D output - set this to the Texture2D object you want to be the mask.</param>
  272. /// <param name="fliponYaxis">True to flip the image on the Y axis, since it comes from the ZED upside-down relative to Unity coords.\r\n
  273. /// Note: It is faster to invert the Y UV value in the shader that displays it, since the bytes must be flipped in a for loop otherwise. </param>
  274. /// <returns>True if texture was successfully retrieved; false otherwise.</returns>
  275. public bool GetMaskTexture(out Texture2D masktex, bool fliponYaxis)
  276. {
  277. if (!fliponYaxis)
  278. {
  279. if (maskTexture == null)
  280. {
  281. IntPtr maskpointer = maskMat.GetPtr(sl.ZEDMat.MEM.MEM_CPU);
  282. if (maskpointer != IntPtr.Zero)
  283. {
  284. maskTexture = ZEDMatToTexture_CPU(maskMat, false);
  285. }
  286. }
  287. }
  288. else
  289. {
  290. if (maskTextureFlipped == null)
  291. {
  292. IntPtr maskpointer = maskMat.GetPtr(sl.ZEDMat.MEM.MEM_CPU);
  293. if (maskpointer != IntPtr.Zero)
  294. {
  295. maskTextureFlipped = ZEDMatToTexture_CPU(maskMat, true);
  296. }
  297. }
  298. }
  299. masktex = fliponYaxis ? maskTextureFlipped : maskTexture;
  300. return masktex != null;
  301. }
  302. /// <summary>
  303. /// Frees the memory from all textures and materials that get cached in this object.
  304. /// Called from ZEDManager when these are part of a frame that are no longer needed.
  305. /// </summary>
  306. public void CleanUpTextures()
  307. {
  308. if (maskTexture != null)
  309. {
  310. GameObject.Destroy(maskTexture);
  311. }
  312. if (maskTextureFlipped != null)
  313. {
  314. GameObject.Destroy(maskTextureFlipped);
  315. }
  316. }
  317. /// <summary>
  318. /// Transforms 3D points provided from the raw ObjectData values to world space.
  319. /// </summary>
  320. /// <param name="localPos">Any 3D position provided from the raw ObjectData object, like world position or the 3D bbox corners.</param>
  321. /// <returns>The given position, but in world space.</returns>
  322. private Vector3 localToWorld(Vector3 localPos)
  323. {
  324. return camRotationAtDetection * localPos + camPositionAtDetection;
  325. }
  326. /// <summary>
  327. /// Helper function to
  328. /// </summary>
  329. /// <returns></returns>
  330. private Quaternion GetRotationWithoutYaw()
  331. {
  332. return Quaternion.Euler(camRotationAtDetection.eulerAngles.x, 0f, camRotationAtDetection.eulerAngles.z);
  333. }
  334. /// <summary>
  335. /// Converts the given zedMat to an Alpha8 (8-bit single channel) texture.
  336. /// Assumes you are giving it the ZEDMat from an Object Detection mask, and is therefore 8-bit single channel.
  337. /// </summary>
  338. private static Texture2D ZEDMatToTexture_CPU(sl.ZEDMat zedmat, bool flipYcoords = false)
  339. {
  340. int width = zedmat.GetWidth(); //Shorthand.
  341. int height = zedmat.GetHeight();
  342. IntPtr maskpointer = zedmat.GetPtr(sl.ZEDMat.MEM.MEM_CPU);
  343. if (maskpointer != IntPtr.Zero && zedmat.IsInit() && width > 0 && height > 0)
  344. {
  345. byte[] texbytes = new byte[zedmat.GetStepBytes() * height];
  346. System.Runtime.InteropServices.Marshal.Copy(maskpointer, texbytes, 0, texbytes.Length);
  347. if (flipYcoords)
  348. {
  349. byte[] flippedbytes = new byte[texbytes.Length];
  350. int steplength = zedmat.GetWidthBytes();
  351. for (int i = 0; i < texbytes.Length; i += steplength)
  352. {
  353. Array.Copy(texbytes, i, flippedbytes, flippedbytes.Length - i - steplength, steplength);
  354. }
  355. texbytes = flippedbytes;
  356. }
  357. Texture2D zedtex = new Texture2D(width, height, TextureFormat.Alpha8, false, false);
  358. zedtex.anisoLevel = 0;
  359. zedtex.LoadRawTextureData(texbytes);
  360. zedtex.Apply(); //Slight bottleneck here - it forces the CPU and GPU to sync.
  361. return zedtex;
  362. }
  363. else
  364. {
  365. Debug.LogError("Pointer to texture was null - returning null.");
  366. return null;
  367. }
  368. }
  369. }