using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.IO; using com.rfilkov.components; namespace com.rfilkov.kinect { /// /// Background removal manager is the component that filters and renders user body silhouettes. /// public class BackgroundRemovalManager : MonoBehaviour { [Tooltip("Depth sensor index - 0 is the 1st one, 1 - the 2nd one, etc.")] public int sensorIndex = 0; [Tooltip("Index of the player, tracked by this component. -1 means all players, 0 - the 1st player only, 1 - the 2nd player only, etc.")] public int playerIndex = -1; [Tooltip("RawImage used for displaying the foreground image.")] public UnityEngine.UI.RawImage foregroundImage; [Tooltip("Camera used for alignment of bodies to color camera image.")] public Camera foregroundCamera; [Tooltip("Resolution of the generated foreground textures.")] private DepthSensorBase.PointCloudResolution foregroundImageResolution = DepthSensorBase.PointCloudResolution.ColorCameraResolution; [Tooltip("Offset from the lowest body joint to the floor.")] [Range(0f, 0.1f)] public float offsetToFloor = 0.05f; [Tooltip("Whether only the alpha texture is needed.")] public bool computeAlphaMaskOnly = false; [Tooltip("Whether the alpha texture will be inverted or not..")] public bool invertAlphaMask = false; [Tooltip("(Advanced) Whether to apply the median filter before the other filters.")] public bool applyMedianFilter = false; [Tooltip("(Advanced) Number of iterations used by the alpha texture's erode filter 0.")] [Range(0, 9)] public int erodeIterations0 = 0; // 1 [Tooltip("(Advanced) Number of iterations used by the alpha texture's dilate filter 1.")] [Range(0, 9)] public int dilateIterations = 0; // 3; [Tooltip("(Advanced) Whether to apply the gradient filter.")] private bool applyGradientFilter = true; [Tooltip("(Advanced) Number of iterations used by the alpha texture's erode filter 2.")] [Range(0, 9)] public int erodeIterations = 0; // 4; [Tooltip("(Advanced) Whether to apply the blur filter after at the end.")] public bool applyBlurFilter = true; [Tooltip("(Advanced) Color applied to the body contour after the filters.")] public Color bodyContourColor = Color.black; [Tooltip("UI-Text to display the BR-Manager debug messages.")] public UnityEngine.UI.Text debugText; // max number of bodies to track private const int MAX_BODY_COUNT = 10; // primary sensor data structure private KinectInterop.SensorData sensorData = null; private KinectManager kinectManager = null; // sensor interface private DepthSensorBase sensorInt = null; // render texture resolution private Vector2Int textureRes; // Bool to keep track whether Kinect and BR library have been initialized private bool bBackgroundRemovalInited = false; // The single instance of BackgroundRemovalManager //private static BackgroundRemovalManager instance; // last point cloud frame time private ulong lastDepth2SpaceFrameTime = 0; // render textures used by the shaders private RenderTexture colorTexture = null; private RenderTexture vertexTexture = null; private RenderTexture alphaTexture = null; private RenderTexture foregroundTexture = null; // Materials used to apply the shaders private Material medianFilterMat = null; private Material erodeFilterMat = null; private Material dilateFilterMat = null; private Material gradientFilterMat = null; private Material blurFilterMat = null; private Material invertAlphaMat = null; private Material foregroundMat = null; // foreground filter shader private ComputeShader foregroundFilterShader = null; private int foregroundFilterKernel = -1; //private Vector4[] foregroundFilterPos = null; private Vector4[] bodyPosMin = null; private Vector4[] bodyPosMaxX = null; private Vector4[] bodyPosMaxY = null; private Vector4[] bodyPosMaxZ = null; private Vector4[] bodyPosDot = null; // reference to filter-by-distance component private BackgroundRemovalByDist filterByDist = null; ///// ///// Gets the single BackgroundRemovalManager instance. ///// ///// The BackgroundRemovalManager instance. //public static BackgroundRemovalManager Instance //{ // get // { // return instance; // } //} /// /// Determines whether the BackgroundRemovalManager was successfully initialized. /// /// true if the BackgroundRemovalManager was successfully initialized; otherwise, false. public bool IsBackgroundRemovalInited() { return bBackgroundRemovalInited; } /// /// Gets the foreground image texture. /// /// The foreground image texture. public Texture GetForegroundTex() { return foregroundTexture; } /// /// Gets the alpha texture. /// /// The alpha texture. public Texture GetAlphaTex() { return alphaTexture; } /// /// Gets the color texture. /// /// The color texture. public Texture GetColorTex() { return colorTexture; } /// /// Gets the last background removal frame time. /// /// The last background removal time. public ulong GetLastBackgroundRemovalTime() { return lastDepth2SpaceFrameTime; } //----------------------------------- end of public functions --------------------------------------// //void Awake() //{ // instance = this; //} public void Start() { try { // get sensor data kinectManager = KinectManager.Instance; if (kinectManager && kinectManager.IsInitialized()) { sensorData = kinectManager.GetSensorData(sensorIndex); } if (sensorData == null || sensorData.sensorInterface == null) { throw new Exception("Background removal cannot be started, because KinectManager is missing or not initialized."); } if(foregroundImage == null) { // look for a foreground image foregroundImage = GetComponent(); } if (!foregroundCamera) { // by default - the main camera foregroundCamera = Camera.main; } // try to get reference to filter-by-dist component filterByDist = GetComponent(); // Initialize the background removal bool bSuccess = InitBackgroundRemoval(sensorData); if (bSuccess) { if (debugText != null) debugText.text = string.Empty; } else { throw new Exception("Background removal could not be initialized."); } bBackgroundRemovalInited = bSuccess; } catch (DllNotFoundException ex) { Debug.LogError(ex.ToString()); if (debugText != null) debugText.text = "Please check the SDK installations."; } catch (Exception ex) { Debug.LogException(ex); if (debugText != null) debugText.text = ex.Message; } } void OnDestroy() { if (bBackgroundRemovalInited) { // finish background removal FinishBackgroundRemoval(sensorData); } bBackgroundRemovalInited = false; //instance = null; } void Update() { if (bBackgroundRemovalInited) { // update the background removal UpdateBackgroundRemoval(sensorData); // check for valid foreground image texture if(foregroundImage != null && foregroundImage.texture == null) { foregroundImage.texture = foregroundTexture; foregroundImage.rectTransform.localScale = kinectManager.GetColorImageScale(sensorIndex); foregroundImage.color = Color.white; } } } // initializes background removal with shaders private bool InitBackgroundRemoval(KinectInterop.SensorData sensorData) { if (sensorData != null && sensorData.sensorInterface != null && KinectInterop.IsDirectX11Available()) { sensorInt = (DepthSensorBase)sensorData.sensorInterface; if (sensorInt.pointCloudColorTexture != null || sensorInt.pointCloudVertexTexture != null) { Debug.LogError("The sensor-interface's point cloud textures are already in use!"); return false; } // set the texture resolution sensorInt.pointCloudResolution = foregroundImageResolution; textureRes = sensorInt.GetPointCloudTexResolution(sensorData); colorTexture = KinectInterop.CreateRenderTexture(colorTexture, textureRes.x, textureRes.y, RenderTextureFormat.ARGB32); vertexTexture = KinectInterop.CreateRenderTexture(vertexTexture, textureRes.x, textureRes.y, RenderTextureFormat.ARGBHalf); alphaTexture = KinectInterop.CreateRenderTexture(alphaTexture, textureRes.x, textureRes.y, RenderTextureFormat.ARGB32); foregroundTexture = KinectInterop.CreateRenderTexture(foregroundTexture, textureRes.x, textureRes.y, RenderTextureFormat.ARGB32); sensorInt.pointCloudColorTexture = colorTexture; sensorInt.pointCloudVertexTexture = vertexTexture; Shader erodeShader = Shader.Find("Kinect/ErodeShader"); erodeFilterMat = new Material(erodeShader); erodeFilterMat.SetFloat("_TexResX", (float)textureRes.x); erodeFilterMat.SetFloat("_TexResY", (float)textureRes.y); //sensorData.erodeBodyMaterial.SetTexture("_MainTex", sensorData.bodyIndexTexture); Shader dilateShader = Shader.Find("Kinect/DilateShader"); dilateFilterMat = new Material(dilateShader); dilateFilterMat.SetFloat("_TexResX", (float)textureRes.x); dilateFilterMat.SetFloat("_TexResY", (float)textureRes.y); //sensorData.dilateBodyMaterial.SetTexture("_MainTex", sensorData.bodyIndexTexture); Shader gradientShader = Shader.Find("Kinect/GradientShader"); gradientFilterMat = new Material(gradientShader); Shader medianShader = Shader.Find("Kinect/MedianShader"); medianFilterMat = new Material(medianShader); //sensorData.medianBodyMaterial.SetFloat("_Amount", 1.0f); Shader blurShader = Shader.Find("Kinect/BlurShader"); blurFilterMat = new Material(blurShader); Shader invertShader = Shader.Find("Kinect/InvertShader"); invertAlphaMat = new Material(invertShader); Shader foregroundShader = Shader.Find("Kinect/ForegroundShader"); foregroundMat = new Material(foregroundShader); if(filterByDist == null) { foregroundFilterShader = Resources.Load("ForegroundFiltBodyShader") as ComputeShader; foregroundFilterKernel = foregroundFilterShader != null ? foregroundFilterShader.FindKernel("FgFiltBody") : -1; //foregroundFilterPos = new Vector4[KinectInterop.Constants.MaxBodyCount]; bodyPosMin = new Vector4[MAX_BODY_COUNT]; bodyPosMaxX = new Vector4[MAX_BODY_COUNT]; bodyPosMaxY = new Vector4[MAX_BODY_COUNT]; bodyPosMaxZ = new Vector4[MAX_BODY_COUNT]; bodyPosDot = new Vector4[MAX_BODY_COUNT]; } return true; } return false; } // releases background removal shader resources private void FinishBackgroundRemoval(KinectInterop.SensorData sensorData) { if(sensorInt) { sensorInt.pointCloudColorTexture = null; sensorInt.pointCloudVertexTexture = null; } if (colorTexture) { colorTexture.Release(); colorTexture = null; } if (vertexTexture) { vertexTexture.Release(); vertexTexture = null; } if (alphaTexture) { alphaTexture.Release(); alphaTexture = null; } if(foregroundTexture) { foregroundTexture.Release(); foregroundTexture = null; } erodeFilterMat = null; dilateFilterMat = null; medianFilterMat = null; blurFilterMat = null; invertAlphaMat = null; foregroundMat = null; if(foregroundFilterShader != null) { foregroundFilterShader = null; } //foregroundFilterPos = null; bodyPosMin = null; bodyPosMaxX = null; bodyPosMaxY = null; bodyPosMaxZ = null; bodyPosDot = null; } // computes current background removal texture private bool UpdateBackgroundRemoval(KinectInterop.SensorData sensorData) { if (bBackgroundRemovalInited && lastDepth2SpaceFrameTime != sensorData.lastDepth2SpaceFrameTime) { lastDepth2SpaceFrameTime = sensorData.lastDepth2SpaceFrameTime; RenderTexture[] tempTextures = new RenderTexture[2]; tempTextures[0] = RenderTexture.GetTemporary(textureRes.x, textureRes.y, 0); tempTextures[1] = RenderTexture.GetTemporary(textureRes.x, textureRes.y, 0); RenderTexture[] tempGradTextures = null; if (applyGradientFilter) { tempGradTextures = new RenderTexture[2]; tempGradTextures[0] = RenderTexture.GetTemporary(textureRes.x, textureRes.y, 0); tempGradTextures[1] = RenderTexture.GetTemporary(textureRes.x, textureRes.y, 0); } // filter if(filterByDist != null && sensorInt != null) { // filter by distance filterByDist.ApplyVertexFilter(vertexTexture, sensorInt.GetSensorToWorldMatrix()); } else if (foregroundFilterShader != null && sensorInt != null) { // filter by bodies ApplyForegroundFilterByBody(); } Graphics.Blit(vertexTexture, alphaTexture); // median if (applyMedianFilter) { ApplySimpleFilter(vertexTexture, alphaTexture, medianFilterMat, tempTextures); } else { Graphics.Blit(vertexTexture, alphaTexture); } // erode0 ApplyIterableFilter(alphaTexture, alphaTexture, erodeFilterMat, erodeIterations0, tempTextures); if(applyGradientFilter) { Graphics.CopyTexture(alphaTexture, tempGradTextures[0]); } // dilate ApplyIterableFilter(alphaTexture, alphaTexture, dilateFilterMat, dilateIterations, tempTextures); if (applyGradientFilter) { //Graphics.Blit(alphaTexture, tempGradTextures[1]); gradientFilterMat.SetTexture("_ErodeTex", tempGradTextures[0]); ApplySimpleFilter(alphaTexture, tempGradTextures[1], gradientFilterMat, tempTextures); } // erode ApplyIterableFilter(alphaTexture, alphaTexture, erodeFilterMat, erodeIterations, tempTextures); if (tempGradTextures != null) { Graphics.Blit(alphaTexture, tempGradTextures[0]); } // blur if(applyBlurFilter) { ApplySimpleFilter(alphaTexture, alphaTexture, blurFilterMat, tempTextures); } if(invertAlphaMask) { ApplySimpleFilter(alphaTexture, alphaTexture, invertAlphaMat, tempTextures); } if(!computeAlphaMaskOnly) { foregroundMat.SetTexture("_ColorTex", colorTexture); foregroundMat.SetTexture("_GradientTex", tempGradTextures[1]); Color gradientColor = (erodeIterations0 != 0 || dilateIterations != 0 || erodeIterations != 0) ? bodyContourColor : Color.clear; foregroundMat.SetColor("_GradientColor", gradientColor); ApplySimpleFilter(alphaTexture, foregroundTexture, foregroundMat, tempTextures); } else { Graphics.CopyTexture(alphaTexture, foregroundTexture); } // cleanup if (tempGradTextures != null) { RenderTexture.ReleaseTemporary(tempGradTextures[0]); RenderTexture.ReleaseTemporary(tempGradTextures[1]); } RenderTexture.ReleaseTemporary(tempTextures[0]); RenderTexture.ReleaseTemporary(tempTextures[1]); } return true; } // applies foreground filter by body private void ApplyForegroundFilterByBody() { Matrix4x4 matKinectWorld = sensorInt.GetSensorToWorldMatrix(); //foregroundFilterShader.SetMatrix("_Transform", matKinectWorld); //foregroundFilterShader.SetFloat("Distance", 1f); Matrix4x4 matWorldKinect = matKinectWorld.inverse; if (kinectManager != null && kinectManager.userManager != null) { List alUserIds = null; if (playerIndex < 0) { alUserIds = kinectManager.userManager.alUserIds; } else { alUserIds = new List(); ulong userId = kinectManager.GetUserIdByIndex(playerIndex); if (userId != 0) alUserIds.Add(userId); } int uCount = Mathf.Min(alUserIds.Count, MAX_BODY_COUNT); foregroundFilterShader.SetInt("_NumBodies", uCount); // get the background rectangle (use the portrait background, if available) Rect backgroundRect = foregroundCamera.pixelRect; PortraitBackground portraitBack = PortraitBackground.Instance; if (portraitBack && portraitBack.enabled) { backgroundRect = portraitBack.GetBackgroundRect(); } int jCount = kinectManager.GetJointCount(); for (int i = 0; i < uCount; i++) { ulong userId = alUserIds[i]; //foregroundFilterPos[i] = kinectManager.GetUserPosition(userId); //float xMin = float.MaxValue, xMax = float.MinValue; //float yMin = float.MaxValue, yMax = float.MinValue; //float zMin = float.MaxValue, zMax = float.MinValue; //for (int j = 0; j < jCount; j++) //{ // if(kinectManager.IsJointTracked(userId, j)) // { // Vector3 jPos = kinectManager.GetJointPosColorOverlay(userId, j, sensorIndex, foregroundCamera, backgroundRect); // if (jPos.x < xMin) xMin = jPos.x; // if (jPos.y < yMin) yMin = jPos.y; // if (jPos.z < zMin) zMin = jPos.z; // if (jPos.x > xMax) xMax = jPos.x; // if (jPos.y > yMax) yMax = jPos.y; // if (jPos.z > zMax) zMax = jPos.z; // } //} bool bSuccess = kinectManager.GetUserBoundingBox(userId, foregroundCamera, sensorIndex, backgroundRect, out Vector3 pMin, out Vector3 pMax); if (bSuccess) { Vector3 posMin = new Vector3(pMin.x - 0.1f, pMin.y - offsetToFloor, pMin.z - 0.1f); Vector3 posMaxX = new Vector3(pMax.x + 0.1f, posMin.y, posMin.z); Vector3 posMaxY = new Vector3(posMin.x, pMax.y + 0.2f, posMin.z); Vector3 posMaxZ = new Vector3(posMin.x, posMin.y, pMax.z + 0.1f); //foregroundFilterDistXY[i] = new Vector4(xMin - 0.1f, xMax + 0.1f, yMin - offsetToFloor, yMax + 0.1f); //foregroundFilterDistZ[i] = new Vector4(zMin - 0.2f, zMax + 0.0f, 0f, 0f); bodyPosMin[i] = matWorldKinect.MultiplyPoint3x4(posMin); bodyPosMaxX[i] = matWorldKinect.MultiplyPoint3x4(posMaxX) - (Vector3)bodyPosMin[i]; bodyPosMaxY[i] = matWorldKinect.MultiplyPoint3x4(posMaxY) - (Vector3)bodyPosMin[i]; bodyPosMaxZ[i] = matWorldKinect.MultiplyPoint3x4(posMaxZ) - (Vector3)bodyPosMin[i]; bodyPosDot[i] = new Vector3(Vector3.Dot(bodyPosMaxX[i], bodyPosMaxX[i]), Vector3.Dot(bodyPosMaxY[i], bodyPosMaxY[i]), Vector3.Dot(bodyPosMaxZ[i], bodyPosMaxZ[i])); } //string sMessage2 = string.Format("Xmin: {0:F1}; Xmax: {1:F1}", bodyPosMin[i].x, bodyPosMaxX[i].x); //Debug.Log(sMessage2); } } //foregroundFilterShader.SetVectorArray("BodyPos", foregroundFilterPos); foregroundFilterShader.SetVectorArray("_BodyPosMin", bodyPosMin); foregroundFilterShader.SetVectorArray("_BodyPosMaxX", bodyPosMaxX); foregroundFilterShader.SetVectorArray("_BodyPosMaxY", bodyPosMaxY); foregroundFilterShader.SetVectorArray("_BodyPosMaxZ", bodyPosMaxZ); foregroundFilterShader.SetVectorArray("_BodyPosDot", bodyPosDot); foregroundFilterShader.SetTexture(foregroundFilterKernel, "_VertexTex", vertexTexture); foregroundFilterShader.Dispatch(foregroundFilterKernel, textureRes.x / 8, textureRes.y / 8, 1); } // applies iterable filter to the source texture private void ApplyIterableFilter(RenderTexture source, RenderTexture destination, Material filterMat, int numIterations, RenderTexture[] tempTextures) { if (!source || !destination || !filterMat || numIterations == 0) return; Graphics.Blit(source, tempTextures[0]); for (int i = 0; i < numIterations; i++) { Graphics.Blit(tempTextures[i % 2], tempTextures[(i + 1) % 2], filterMat); } if ((numIterations % 2) == 0) { Graphics.Blit(tempTextures[0], destination); } else { Graphics.Blit(tempTextures[1], destination); } } // applies simple filter to the source texture private void ApplySimpleFilter(RenderTexture source, RenderTexture destination, Material filterMat, RenderTexture[] tempTextures) { if (!source || !destination || !filterMat) return; Graphics.Blit(source, tempTextures[0], filterMat); Graphics.Blit(tempTextures[0], destination); } } }