123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using sl;
- using System;
- /// <summary>
- /// Represents a single object detected by the ZED Object Detection module.
- /// Provides various functions for knowing where the object is in the world (position) and how much space it takes up (bounds).
- /// <para>It's listed in a DetectionFrame, which is included as the argument in the ZEDManager.OnObjectDetection event.</para>
- /// </summary><remarks>
- /// This is a higher level version of sl.ObjectData, which comes directly from the ZED SDK and doesn't follow Unity conventions.
- /// </remarks>
- public class DetectedObject
- {
- private ObjectDataSDK objectData;
- /// <summary>
- /// The raw ObjectData object that this instance is an abstraction of - ObjectData's data comes
- /// directly from the SDK and doesn't follow Unity conventions.
- /// </summary>
- public ObjectDataSDK rawObjectData
- {
- get
- {
- return objectData;
- }
- }
- /// <summary>
- /// Arbitrary ID assigned to the object on first detection. Will persist between frames if the object remains
- /// visible and detected.
- /// </summary>
- public int id
- {
- get
- {
- return objectData.id;
- }
- }
- /// <summary>
- /// Class of object that was detected (person, vehicle, etc.)
- /// </summary>
- public OBJECT_CLASS objectClass
- {
- get
- {
- return objectData.objectClass;
- }
- }
- /// <summary>
- /// SubClass of object that was detected
- /// </summary>
- public OBJECT_SUBCLASS objectSubClass
- {
- get
- {
- return objectData.objectSubClass;
- }
- }
- /// <summary>
- /// Current state of the object's tracking. "OK" means it's visible this frame. "Searching" means it was
- /// visible recently, but is no longer visible. "OFF" means that object detection has tracking turned off,
- /// so 3D data will never be available.
- /// </summary>
- public OBJECT_TRACK_STATE trackingState
- {
- get
- {
- return objectData.objectTrackingState;
- }
- }
- /// <summary>
- /// Current action state. "IDLE" means the object is not moving. "MOVING" means the object is moving.
- /// </summary>
- public OBJECT_ACTION_STATE actionState
- {
- get
- {
- return objectData.actionState;
- }
- }
- /// <summary>
- /// How confident the ZED SDK is that this object is in fact a valid detection. From 1 to 100.
- /// Higher is better, eg. if objectClass is PERSON and confidence is 99, there's a 99% chance it's indeed a person.
- /// <para>You can set the minimum confidence threshold in ZEDManager's Inspector.</para>
- /// </summary>
- public float confidence
- {
- get
- {
- return objectData.confidence;
- }
- }
- /// <summary>
- /// The manager class responsible for the ZED camera that detected this object.
- /// </summary>
- public ZEDManager detectingZEDManager;
- private Vector3 camPositionAtDetection;
- private Quaternion camRotationAtDetection;
- /// <summary>
- /// sl::Mat material that represents where on the image the detected object exists on a pixel-by-pixel level.
- /// For a texture that you can overlay, use GetMaskTexture().
- /// </summary>
- public sl.ZEDMat maskMat;
- /// <summary>
- /// Cached texture version of the 2D mask. Retrieved with GetMaskTexture.
- /// This is cached after calculating the first time to avoid needless calculation from multiple calls.
- /// </summary>
- private Texture2D maskTexture = null;
- /// <summary>
- /// Cached texture version of the 2D mask. Retrieved with GetMaskTexture.
- /// Flipped on the Y axis to make it simpler to apply to standard Unity shaders.
- /// This is cached after calculating the first time to avoid needless calculation from multiple calls.
- /// </summary>
- private Texture2D maskTextureFlipped = null;
- /// <summary>
- /// Constructor that assigns values required for transformations later on.
- /// This is necessary because detections are frozen in a particular frame, and results should not change
- /// in subsequent frames when the camera moves.
- /// </summary>
- /// <param name="odata">Raw sl.ObjectData data that this instance represents.</param>
- /// <param name="viewingmanager">ZEDManager assigned to the ZED camera that detected the object.</param>
- /// <param name="campos">World position of the left ZED camera when the object was detected.</param>
- /// <param name="camrot">World rotation of the left ZED camera when the object was detected.</param>
- public DetectedObject(ObjectDataSDK odata, ZEDManager viewingmanager, Vector3 campos, Quaternion camrot)
- {
- objectData = odata;
- detectingZEDManager = viewingmanager;
- camPositionAtDetection = campos;
- camRotationAtDetection = camrot;
- maskMat = new ZEDMat(odata.mask);
- //maskTexture = ZEDMatToTexture_CPU(maskMat);
- }
- /// <summary>
- /// Returns the pixel positions of the four corners of the object's 2D bounding box on the image.
- /// <para>Like most of Unity, the Y values are relative to the bottom of the image, which is unlike the
- /// raw imageBoundingBox data from the ObjectData struct.</para>
- /// 0 ------- 1
- /// | obj |
- /// 3-------- 2
- /// </summary>
- /// <param name="scaleForCanvasUnityError">Adds an optional scaling factor to handle a bug in 2018.3 and greater where the
- /// canvas set to Screen Space - Camera has very weird offsets when its projection matrix has certain small changes made to it directly.</param>
- public Vector2[] Get2DBoundingBoxCorners_Image(float scaleForCanvasUnityError = 1)
- {
- //Shorthand.
- float zedimagewidth = detectingZEDManager.zedCamera.ImageWidth;
- float zedimageheight = detectingZEDManager.zedCamera.ImageHeight;
- //Canvas offsets from horizontal and vertical calibration offsets (cx/cy).
- CalibrationParameters calib = detectingZEDManager.zedCamera.GetCalibrationParameters();
- float cxoffset = zedimagewidth * scaleForCanvasUnityError / 2f - calib.leftCam.cx * scaleForCanvasUnityError;
- float cyoffset = 0 - (zedimageheight / 2f - calib.leftCam.cy);
- Vector2[] flippedYimagecoords = new Vector2[4];
- for (int i = 0; i < 4; i++)
- {
- Vector2 rawcoord;
- rawcoord.x = objectData.imageBoundingBox[i].x * scaleForCanvasUnityError + cxoffset;
- rawcoord.y = detectingZEDManager.zedCamera.ImageHeight - objectData.imageBoundingBox[i].y + cyoffset;
-
- #if UNITY_2018_1_OR_NEWER
- //Terrible hack to compensate for bug in Unity that scales the Canvas very improperly if you have certain (necessary) values on the projection matrix.
- rawcoord.y = (rawcoord.y - (zedimageheight / 2f)) * scaleForCanvasUnityError + (zedimageheight / 2f);
- #endif
- flippedYimagecoords[i] = rawcoord;
- }
- return flippedYimagecoords;
- }
- /// <summary>
- /// Returns the viewport positions of the four corners of the object's 2D bounding box on the capturing camera.
- /// <para>Like most of Unity, the Y values are relative to the bottom of the image, which is unlike the
- /// raw imageBoundingBox data from the ObjectData struct.</para>
- /// 0 ------- 1
- /// | obj |
- /// 3-------- 2
- /// </summary>
- /// <param name="scaleForCanvasUnityError">Adds an optional scaling factor to handle a bug in 2018.3 and greater where the
- /// canvas set to Screen Space - Camera has very weird offsets when its projection matrix has certain small changes made to it directly.</param>
- public Vector2[] Get2DBoundingBoxCorners_Viewport(float scaleForCanvasUnityError = 1)
- {
- Vector2[] imagecoords = Get2DBoundingBoxCorners_Image(scaleForCanvasUnityError);
- Vector2[] viewportcorners = new Vector2[4];
- for (int i = 0; i < 4; i++)
- {
- viewportcorners[i] = detectingZEDManager.GetLeftCamera().ScreenToViewportPoint(imagecoords[i]);
- }
- return viewportcorners;
- }
- /// <summary>
- /// Returns the object's 2D bounding box as a rect. Use this to position a UI element around the object
- /// in a Camera-space UI canvas attached to the capturing camera.
- /// Values assume Y is relative to the bottom.
- /// </summary>
- /// <param name="scaleForCanvasUnityError">Adds an optional scaling factor to handle a bug in 2018.3 and greater where the
- /// canvas set to Screen Space - Camera has very weird offsets when its projection matrix has certain small changes made to it directly.</param>
- public Rect Get2DBoundingBoxRect(float scaleForCanvasUnityError = 1f)
- {
- Vector2[] imagecoords = Get2DBoundingBoxCorners_Image(scaleForCanvasUnityError);
- float width = (imagecoords[1].x - imagecoords[0].x);// * scaleForCanvas;
- float height = (imagecoords[0].y - imagecoords[3].y);
- Vector2 bottomleftcorner = imagecoords[3];
- return new Rect(bottomleftcorner, new Vector2(width, height));
- }
- /// <summary>
- /// Gets the center of the 3D object in world space.
- /// </summary>
- public Vector3 Get3DWorldPosition()
- {
- //Get the center of the transformed bounding box.
- float ypos = (localToWorld(objectData.worldBoundingBox[0]).y - localToWorld(objectData.worldBoundingBox[4]).y) / 2f + localToWorld(objectData.worldBoundingBox[4]).y;
- Vector3 transformedroot = localToWorld(objectData.rootWorldPosition);
- return new Vector3(transformedroot.x, ypos, transformedroot.z);
- }
- /// <summary>
- /// Gets the direction that the 3D bounding box is facing, relative to the world.
- /// This is simply the opposite of the direction the camera was facing on detection.
- /// </summary>
- /// <param name="boxesfacecamera">True to artificially rotate the boxes to face the camera.</param>
- public Quaternion Get3DWorldRotation(bool boxesfacecamera)
- {
- Vector3 normal;
- if (boxesfacecamera)
- {
- normal = Get3DWorldPosition() - detectingZEDManager.GetLeftCameraTransform().position; //This makes box face camera.
- }
- else
- {
- normal = camRotationAtDetection * Vector3.forward; //This is to face the inverse of the camera's Z direction.
- }
- normal.y = 0;
- return Quaternion.LookRotation(normal, Vector3.up);
- }
- /// <summary>
- /// Gets the size of the bounding box as a Bounds class.
- /// Use this to draw lines around an object's bounds, or scale a cube to enclose it.
- /// </summary>
- public Bounds Get3DWorldBounds()
- {
- Vector3[] worldcorners = objectData.worldBoundingBox;
- Quaternion pitchrot = GetRotationWithoutYaw();
- Vector3 leftbottomback = pitchrot * worldcorners[5]; //Shorthand.
- Vector3 righttopfront = pitchrot * worldcorners[3]; //Shorthand.
- float xsize = Mathf.Abs(righttopfront.x - leftbottomback.x);
- float ysize = Mathf.Abs(righttopfront.y - leftbottomback.y);
- float zsize = Mathf.Abs(righttopfront.z - leftbottomback.z);
- return new Bounds(Vector3.zero, new Vector3(xsize, ysize, zsize));
- }
- /// <summary>
- /// Gets the world positions of the eight corners of the object's 3D bounding box.
- /// If facingCamera is set to true, the box will face the ZED that observed it.
- /// If false, they will be aligned with the world axes. However, this can result in too large of a box
- /// since it has to encompass the camera-aligned version.
- /// 1 ---------2
- /// /| /|
- /// 0 |--------3 |
- /// | | | |
- /// | 5--------|-6
- /// |/ |/
- /// 4 ---------7
- /// </summary>
- /// <param name="facingCamera"></param>
- public Vector3[] Get3DWorldCorners()
- {
- Vector3[] worldspacecorners = new Vector3[8];
- for (int i = 0; i < 8; i++)
- {
- worldspacecorners[i] = localToWorld(objectData.worldBoundingBox[i]);
- }
- return worldspacecorners;
- }
- /// <summary>
- /// Gets a Texture2D version of the 2D mask that displays which pixels within the bounding box a detected object occupies.
- /// Texture is the size of the 2D bounding box and meant to be overlayed on top of it.
- /// </summary>
- /// <param name="masktex">Texture2D output - set this to the Texture2D object you want to be the mask.</param>
- /// <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
- /// 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>
- /// <returns>True if texture was successfully retrieved; false otherwise.</returns>
- public bool GetMaskTexture(out Texture2D masktex, bool fliponYaxis)
- {
- if (!fliponYaxis)
- {
- if (maskTexture == null)
- {
- IntPtr maskpointer = maskMat.GetPtr(sl.ZEDMat.MEM.MEM_CPU);
- if (maskpointer != IntPtr.Zero)
- {
- maskTexture = ZEDMatToTexture_CPU(maskMat, false);
- }
- }
- }
- else
- {
- if (maskTextureFlipped == null)
- {
- IntPtr maskpointer = maskMat.GetPtr(sl.ZEDMat.MEM.MEM_CPU);
- if (maskpointer != IntPtr.Zero)
- {
- maskTextureFlipped = ZEDMatToTexture_CPU(maskMat, true);
- }
- }
- }
- masktex = fliponYaxis ? maskTextureFlipped : maskTexture;
- return masktex != null;
- }
- /// <summary>
- /// Frees the memory from all textures and materials that get cached in this object.
- /// Called from ZEDManager when these are part of a frame that are no longer needed.
- /// </summary>
- public void CleanUpTextures()
- {
- if (maskTexture != null)
- {
- GameObject.Destroy(maskTexture);
- }
- if (maskTextureFlipped != null)
- {
- GameObject.Destroy(maskTextureFlipped);
- }
- }
- /// <summary>
- /// Transforms 3D points provided from the raw ObjectData values to world space.
- /// </summary>
- /// <param name="localPos">Any 3D position provided from the raw ObjectData object, like world position or the 3D bbox corners.</param>
- /// <returns>The given position, but in world space.</returns>
- private Vector3 localToWorld(Vector3 localPos)
- {
- return camRotationAtDetection * localPos + camPositionAtDetection;
- }
- /// <summary>
- /// Helper function to
- /// </summary>
- /// <returns></returns>
- private Quaternion GetRotationWithoutYaw()
- {
- return Quaternion.Euler(camRotationAtDetection.eulerAngles.x, 0f, camRotationAtDetection.eulerAngles.z);
- }
- /// <summary>
- /// Converts the given zedMat to an Alpha8 (8-bit single channel) texture.
- /// Assumes you are giving it the ZEDMat from an Object Detection mask, and is therefore 8-bit single channel.
- /// </summary>
- private static Texture2D ZEDMatToTexture_CPU(sl.ZEDMat zedmat, bool flipYcoords = false)
- {
- int width = zedmat.GetWidth(); //Shorthand.
- int height = zedmat.GetHeight();
-
- IntPtr maskpointer = zedmat.GetPtr(sl.ZEDMat.MEM.MEM_CPU);
- if (maskpointer != IntPtr.Zero && zedmat.IsInit() && width > 0 && height > 0)
- {
- byte[] texbytes = new byte[zedmat.GetStepBytes() * height];
- System.Runtime.InteropServices.Marshal.Copy(maskpointer, texbytes, 0, texbytes.Length);
- if (flipYcoords)
- {
- byte[] flippedbytes = new byte[texbytes.Length];
- int steplength = zedmat.GetWidthBytes();
- for (int i = 0; i < texbytes.Length; i += steplength)
- {
- Array.Copy(texbytes, i, flippedbytes, flippedbytes.Length - i - steplength, steplength);
- }
- texbytes = flippedbytes;
- }
- Texture2D zedtex = new Texture2D(width, height, TextureFormat.Alpha8, false, false);
- zedtex.anisoLevel = 0;
- zedtex.LoadRawTextureData(texbytes);
- zedtex.Apply(); //Slight bottleneck here - it forces the CPU and GPU to sync.
- return zedtex;
- }
- else
- {
- Debug.LogError("Pointer to texture was null - returning null.");
- return null;
- }
- }
- }
|