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);
}
}
}