最近有一个需求是换背景拍照,用到了kinect,但是却发现利用kinect抠人物图像出来时头发不见了,可能是因为头发黑色容易吸收红外线吧,下面这张是kinect原始抠图效果:
可以看到,头发部分丢失了。
其实我的解决方案就是找到头发所在的纹理区域,然后将黑色的头发显示出来,以下慢慢道来。
1,先利用kinect把头部和脖子定位出来,先创建2个小球,用来定位头和脖子:
于是,运行程序就是如下图所示:
2,将头部和脖子的空间坐标转为屏幕显示的纹理坐标,将头部距离脖子的距离大小作为整个头的大小,以此容纳头发区域,然后传入最后需要的shader中,shader放在后面讲:
Vector3 HeadInPlanePos = m_Plane.worldToLocalMatrix.MultiplyPoint(m_HeadNode.position);
Vector3 NeckInPlanePos = m_Plane.worldToLocalMatrix.MultiplyPoint(m_NeckNode.position);
Debug.Log(HeadInPlanePos + " " + NeckInPlanePos);
//坐标从-5 到 5;
//头部对应的纹理坐标
Vector2 HeadInTexPos = new Vector2((HeadInPlanePos.x + 5.0f) * 0.1f, (HeadInPlanePos.z + 5.0f) * 0.1f);
m_Mat.SetFloat("_HeadX", 1.0f - HeadInTexPos.x);
m_Mat.SetFloat("_HeadY", 1.0f - HeadInTexPos.y);
//脖子对应的纹理坐标
Vector2 NeckInTexPos = new Vector2((NeckInPlanePos.x + 5.0f) * 0.1f, (NeckInPlanePos.z + 5.0f) * 0.1f);
m_CurRadius = Mathf.Lerp(m_CurRadius, Vector2.Distance(NeckInTexPos, HeadInTexPos), 0.5f);
if (m_CurRadius > 0.05f)
{
m_CurRadius = 0.05f;
}
m_Mat.SetFloat("_Radius", m_CurRadius);
3,以上步骤完成,来看看我的shader是如何实现的,为了不影响效果,我这里只是修改了kinect原本使用的shader,在此基础上做了细微的修改,也就是显示丢失的头发:
上图就是在头部以头部到脖子距离的两倍为半径扩展出来的抠图部分,完整shader如下:
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
// Unlit alpha-blended shader.
// - no lighting
// - no lightmap support
// - no per-material color
Shader "Jx/Bg" {
Properties {
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
LOD 100
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
float4 _MainTex_ST;
uniform half _HeadX;
uniform half _HeadY;
uniform half _Radius;
uniform half _Head2X;
uniform half _Head2Y;
uniform half _Radius2;
v2f vert (appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
UNITY_APPLY_FOG(i.fogCoord, col);
if(distance(i.texcoord.xy, half2(_HeadX, _HeadY)) < _Radius*2.0 /*&& (col.r + col.g + col.b) < 0.8 && abs(col.r - col.g) < 0.6 && abs(col.r - col.b) < 0.6 && abs(col.b - col.g) < 0.6*/)
{
col.a = 1;
}
if(distance(i.texcoord.xy, half2(_Head2X, _Head2Y)) < _Radius2*2.0 /*&& (col.r + col.g + col.b) < 0.8 && abs(col.r - col.g) < 0.6 && abs(col.r - col.b) < 0.6 && abs(col.b - col.g) < 0.6*/)
{
col.a = 1;
}
return col;
}
ENDCG
}
}
}
因为我这里是想最多支持两个玩家的抠图,所以可以看到
uniform half _Head2X;
uniform half _Head2Y;
uniform half _Radius2;
这些就是为第二个玩家的头发确实做修改的。
4,来看看最终效果和最终shader代码:
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
// Unlit alpha-blended shader.
// - no lighting
// - no lightmap support
// - no per-material color
Shader "Jx/Bg" {
Properties {
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
LOD 100
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
float4 _MainTex_ST;
uniform half _HeadX;
uniform half _HeadY;
uniform half _Radius;
uniform half _Head2X;
uniform half _Head2Y;
uniform half _Radius2;
v2f vert (appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
UNITY_APPLY_FOG(i.fogCoord, col);
if(distance(i.texcoord.xy, half2(_HeadX, _HeadY)) < _Radius*2.0 && (col.r + col.g + col.b) < 0.8 && abs(col.r - col.g) < 0.6 && abs(col.r - col.b) < 0.6 && abs(col.b - col.g) < 0.6)
{
col.a = 1;
}
if(distance(i.texcoord.xy, half2(_Head2X, _Head2Y)) < _Radius2*2.0 && (col.r + col.g + col.b) < 0.8 && abs(col.r - col.g) < 0.6 && abs(col.r - col.b) < 0.6 && abs(col.b - col.g) < 0.6)
{
col.a = 1;
}
return col;
}
ENDCG
}
}
}
(col.r + col.g + col.b) < 0.8 && abs(col.r - col.g) < 0.6 && abs(col.r - col.b) < 0.6 && abs(col.b - col.g) < 0.6这部分代码片段就是判定黑色部分为头发,然后将图中透明度为0的对应该处的像素透明设置为1,就把头发显示出来了。