前提・実現したいこと
Unityで、影を用いたインタラクティブなゲームを制作しております。
以前にこちらの質問で、ウェブカメラの画像を2値化して黒い部分にのみポリゴンコライダーを当てることをサポートさせていただきました。
しかし、それでは以下の画像のようなピンクや青といった彩度・明度の高い他のオブジェクトも
認識してしまい、2値化処理が行われ続けるほどに画面が真っ黒になります。
今回は、ウェブカメラの画像であらかじめ黒以外を白(0,0,0)に置き換えする前処理がしたく、質問を投稿しました。
宜しくお願い致します!
追記
上画像では顔が白抜きになっていますが、本来は影を読み取るので真っ黒です。
シルエットをウェブカメラで取得します。
試したこと
2値化する際の、閾値を調整しましたがやはり周辺のオブジェクトは残るままで
改善できませんでした。
補足情報(FW/ツールのバージョンなど)
Unity2019.2.5f1 Personal
macOS Sierra
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/11/11 23:42
回答2件
0
ベストアンサー
四隅調整機能追加版
ShaderLab
1Shader "Hidden/Silhouette" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Color ("Color", Color) = (0, 0, 0, 1) 7 _SaturationThreshold ("Saturation Threshold", Range(0.0, 1.0)) = 0.25 8 _ValueThreshold ("Value Threshold", Range(0.0, 1.0)) = 0.25 9 } 10 SubShader 11 { 12 Cull Off ZWrite Off ZTest Always 13 14 Pass 15 { 16 CGPROGRAM 17 #pragma vertex vert 18 #pragma fragment frag 19 20 #include "UnityCG.cginc" 21 22 struct appdata 23 { 24 float4 vertex : POSITION; 25 float2 uv : TEXCOORD0; 26 }; 27 28 struct v2f 29 { 30 // UV座標をfloat4型に変更 31 float4 uv : TEXCOORD0; 32 float4 vertex : SV_POSITION; 33 }; 34 35 // UV変換用行列をセットするための変数を追加 36 float4x4 _HomographyMatrix; 37 38 v2f vert (appdata v) 39 { 40 v2f o; 41 o.vertex = UnityObjectToClipPos(v.vertex); 42 43 // 元のUV座標をUV変換用行列で変換してからセットする 44 o.uv = mul(_HomographyMatrix, float4(v.uv, 0.0, 1.0)); 45 46 return o; 47 } 48 49 sampler2D _MainTex; 50 float4 _Color; 51 float _SaturationThreshold; 52 float _ValueThreshold; 53 54 fixed4 frag (v2f i) : SV_Target 55 { 56 // Webカメラ上の色をtex2Dに代わってtex2Dprojでサンプリングする 57 float3 rawColor = tex2Dproj(_MainTex, i.uv).rgb; 58 59 float value = max(max(rawColor.r, rawColor.g), rawColor.b); 60 float lowestValue = min(min(rawColor.r, rawColor.g), rawColor.b); 61 float difference = value - lowestValue; 62 float saturation = (value > 0.01) ? (difference / value) : 0.0; 63 return fixed4(_Color.rgb, step(value, _ValueThreshold) * step(saturation, _SaturationThreshold)); 64 } 65 ENDCG 66 } 67 68 Pass 69 { 70 CGPROGRAM 71 #pragma vertex vert 72 #pragma fragment frag 73 74 #include "UnityCG.cginc" 75 76 struct appdata 77 { 78 float4 vertex : POSITION; 79 }; 80 81 struct v2f 82 { 83 float4 vertex : SV_POSITION; 84 }; 85 86 v2f vert (appdata v) 87 { 88 v2f o; 89 o.vertex = UnityObjectToClipPos(v.vertex); 90 return o; 91 } 92 93 float4 _Color; 94 95 fixed4 frag (v2f i) : SV_Target 96 { 97 return fixed4(_Color.rgb, 1.0); 98 } 99 ENDCG 100 } 101 } 102}
C#
1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(SpriteRenderer), typeof(PolygonCollider2D))] 5public class Silhouette : MonoBehaviour 6{ 7 private static readonly int SaturationThresholdProperty = Shader.PropertyToID("_SaturationThreshold"); 8 private static readonly int ValueThresholdProperty = Shader.PropertyToID("_ValueThreshold"); 9 private static readonly int HomographyMatrixProperty = Shader.PropertyToID("_HomographyMatrix"); 10 11 [SerializeField] private int width = 320; 12 [SerializeField] private int height = 240; 13 [SerializeField] private int floorHeight = 16; 14 [SerializeField] private int resolutionMultiplier = 1; 15 [SerializeField] private Color color = Color.black; 16 [SerializeField][Range(0.0f, 1.0f)] private float saturationThreshold = 0.25f; 17 [SerializeField][Range(0.0f, 1.0f)] private float valueThreshold = 0.25f; 18 [SerializeField] private Shader silhouetteShader; 19 20 // 四隅の位置を指定するためのフィールドを追加 21 [Header("Corners")] 22 [SerializeField] private Vector2 bottomLeft = new Vector2(0.125f, 0.0625f); 23 [SerializeField] private Vector2 topLeft = new Vector2(0.1875f, 0.875f); 24 [SerializeField] private Vector2 topRight = new Vector2(0.75f, 0.75f); 25 [SerializeField] private Vector2 bottomRight = new Vector2(0.9375f, 0.375f); 26 27 private new SpriteRenderer renderer; 28 private new PolygonCollider2D collider; 29 private Sprite sprite; 30 private Texture2D spriteTexture; 31 private Material silhouetteMaterial; 32 private WebCamTexture webCamTexture; 33 34 // 以前作成したHomographyShaderGUIのものと同様の変換行列作成メソッドを定義 35 private static Matrix4x4 HomographyMatrix(Vector2 bl, Vector2 tl, Vector2 tr, Vector2 br) 36 { 37 var cf = bl; 38 Inverse(br - tr, tl - tr, out var ghMatR0, out var ghMatR1); 39 var v = (bl + tr) - (tl + br); 40 var gh = new Vector2(Vector2.Dot(ghMatR0, v), Vector2.Dot(ghMatR1, v)); 41 var ad = ((gh.x + 1.0f) * br) - bl; 42 var be = ((gh.y + 1.0f) * tl) - bl; 43 var result = Matrix4x4.identity; 44 result.SetColumn(0, new Vector4(ad.x, ad.y, 0.0f, gh.x)); 45 result.SetColumn(1, new Vector4(be.x, be.y, 0.0f, gh.y)); 46 result.SetColumn(2, new Vector4(0.0f, 0.0f, 1.0f, 0.0f)); 47 result.SetColumn(3, new Vector4(cf.x, cf.y, 0.0f, 1.0f)); 48 return result; 49 } 50 51 private static void Inverse(Vector2 column0, Vector2 column1, out Vector2 inverseRow0, out Vector2 inverseRow1) 52 { 53 var determinant = Cross(column0, column1); 54 inverseRow0 = new Vector2(column1.y, -column1.x) / determinant; 55 inverseRow1 = new Vector2(-column0.y, column0.x) / determinant; 56 } 57 58 private static float Cross(Vector2 a, Vector2 b) 59 { 60 return (a.x * b.y) - (b.x * a.y); 61 } 62 63 private IEnumerator Start() 64 { 65 if (WebCamTexture.devices.Length <= 0) 66 { 67 Destroy(this); 68 yield break; 69 } 70 this.width = Mathf.Max(this.width, 100); 71 this.height = Mathf.Max(this.height, 100); 72 this.webCamTexture = new WebCamTexture(WebCamTexture.devices[0].name, this.width, this.height); 73 this.webCamTexture.Play(); 74 while (this.webCamTexture.width < 100) 75 { 76 yield return null; 77 } 78 79 this.width = this.webCamTexture.width * this.resolutionMultiplier; 80 this.height = this.webCamTexture.height * this.resolutionMultiplier; 81 var pixelsPerUnit = (float)this.height; 82 var mainCamera = Camera.main; 83 if (mainCamera.orthographic) 84 { 85 pixelsPerUnit = (0.5f * Screen.height * this.resolutionMultiplier) / mainCamera.orthographicSize; 86 } 87 88 this.renderer = this.GetComponent<SpriteRenderer>(); 89 this.collider = this.GetComponent<PolygonCollider2D>(); 90 this.spriteTexture = new Texture2D(this.width, this.height, TextureFormat.ARGB32, false); 91 this.sprite = Sprite.Create( 92 this.spriteTexture, 93 new Rect(0, 0, this.width, this.height), 94 Vector2.zero, 95 pixelsPerUnit); 96 this.renderer.sprite = this.sprite; 97 98 this.silhouetteMaterial = new Material(this.silhouetteShader); 99 100 // 下のwhileループ中でマテリアルのプロパティをセットする方式ならば、プレイモード中に 101 // インスペクタ上の値を書き換えるとそれが反映されるため調整が容易かと思いますが 102 // (ただしコライダーを常時付け外ししているため、再生状態のままでは書き換えが 103 // 困難かと思います...一時停止ボタンで止めた状態で書き換える必要があるでしょう)、 104 // あらかた調整が済んだら、マテリアルへのプロパティ設定をwhileループの外に出してやれば 105 // ループ内でいちいちプロパティ再設定が行われなくなるため、実行する上での効率が少しだけ 106 // よくなるかと思います。 107 /* 108 this.silhouetteMaterial.color = this.color; 109 this.silhouetteMaterial.SetFloat(SaturationThresholdProperty, this.saturationThreshold); 110 this.silhouetteMaterial.SetFloat(ValueThresholdProperty, this.valueThreshold); 111 112 // 四隅の座標からUV変換行列を作成してマテリアルにセットする 113 this.silhouetteMaterial.SetMatrix(HomographyMatrixProperty, HomographyMatrix( 114 this.bottomLeft, 115 this.topLeft, 116 this.topRight, 117 this.bottomRight)); 118 */ 119 120 while (true) 121 { 122 this.silhouetteMaterial.color = this.color; 123 this.silhouetteMaterial.SetFloat(SaturationThresholdProperty, this.saturationThreshold); 124 this.silhouetteMaterial.SetFloat(ValueThresholdProperty, this.valueThreshold); 125 126 // 四隅の座標からUV変換行列を作成してマテリアルにセットする 127 this.silhouetteMaterial.SetMatrix(HomographyMatrixProperty, HomographyMatrix( 128 this.bottomLeft, 129 this.topLeft, 130 this.topRight, 131 this.bottomRight)); 132 133 var renderTexture = RenderTexture.GetTemporary(this.width, this.height, 0, RenderTextureFormat.ARGB32); 134 Graphics.Blit(this.webCamTexture, renderTexture, this.silhouetteMaterial, 0); 135 var activeTexture = RenderTexture.active; 136 RenderTexture.active = renderTexture; 137 138 var floorY = ((float)(this.floorHeight * this.resolutionMultiplier) / this.height); 139 GL.PushMatrix(); 140 this.silhouetteMaterial.SetPass(1); 141 GL.Begin(GL.QUADS); 142 GL.LoadOrtho(); 143 GL.Color(this.color); 144 GL.Vertex(Vector3.zero); 145 GL.Vertex3(0.0f, floorY, 0.0f); 146 GL.Vertex3(1.0f, floorY, 0.0f); 147 GL.Vertex(Vector3.right); 148 GL.End(); 149 GL.PopMatrix(); 150 151 this.spriteTexture.ReadPixels(new Rect(0, 0, this.width, this.height), 0, 0); 152 RenderTexture.active = activeTexture; 153 RenderTexture.ReleaseTemporary(renderTexture); 154 this.spriteTexture.Apply(); 155 156 Destroy(this.collider); 157 this.collider = this.gameObject.AddComponent<PolygonCollider2D>(); 158 yield return null; 159 } 160 } 161}
投稿2019/11/16 18:02
総合スコア10811
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/11/20 15:26
0
おそらく、周囲のオブジェクトの色を影と誤認識されないような色だけにしてしまうのがいいんじゃないかと思います。
現状だと、特に頭の横に浮かんでいる赤黒い球なんかは厳しいんじゃないでしょうか。
ご提示の画像の状態でもかなり影の色に近いうえに、実際にはこの映像をスクリーンに映してWebカメラで撮影するというプロセスが入ることを考慮するとさらに難しくなるはずです。微妙な色の差を識別するために判定を厳しくすれば、今度は影の部分が撮影条件の微妙な変動などにより判定に落ちてしまうリスクが大きくなるでしょう。
グレースケール化して視覚的明るさだけで二値判定するのではなく、たとえば色相・彩度・明度の3成分を利用すればいくらかは精度を高められるかもしれませんが、それでも劇的な改善は見込めないように思います。
今回の場合はかなり黒に近い色について判定を行いたいという状況ですが、このような領域だと色相・彩度が不確かになって(極端な話だと、完全な真っ黒では色相・彩度がちゃんと定義できなくなってしまうはずです)、色判定の材料としてはあまり役に立たないんじゃないでしょうか。
以前お出ししました二値化スクリプトを彩度・明度を使って判定するよう改造してみました(色相は計算コストが大きめなわりに前述の理由から有用でなさそうだったため省きました)。
試した感じでは、以前の加重平均によるグレースケール化からの二値化と比べると鮮やかな色を除去する効果が上がったように思います(鮮やかな色は赤・緑・青のいずれにしろ高めの成分を持っているはずなので、明度も高くなり除去されやすくなるはずです)。主に調整するべきは明度の閾値で、彩度による判定は甘めでいいかもしれませんね。ですがそれでもご提示の画像のような赤黒い色を影の色と区別するのは困難そうです...
ShaderLab
1Shader "Hidden/Silhouette" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Color ("Color", Color) = (0, 0, 0, 1) 7 _SaturationThreshold ("Saturation Threshold", Range(0.0, 1.0)) = 0.25 8 _ValueThreshold ("Value Threshold", Range(0.0, 1.0)) = 0.25 9 } 10 SubShader 11 { 12 Cull Off ZWrite Off ZTest Always 13 14 Pass 15 { 16 CGPROGRAM 17 #pragma vertex vert 18 #pragma fragment frag 19 20 #include "UnityCG.cginc" 21 22 struct appdata 23 { 24 float4 vertex : POSITION; 25 float2 uv : TEXCOORD0; 26 }; 27 28 struct v2f 29 { 30 float2 uv : TEXCOORD0; 31 float4 vertex : SV_POSITION; 32 }; 33 34 v2f vert (appdata v) 35 { 36 v2f o; 37 o.vertex = UnityObjectToClipPos(v.vertex); 38 o.uv = v.uv; 39 return o; 40 } 41 42 sampler2D _MainTex; 43 float4 _Color; 44 float _SaturationThreshold; 45 float _ValueThreshold; 46 47 float3 RGBToHSV(float3 rgbColor) 48 { 49 float value = max(max(rgbColor.r, rgbColor.g), rgbColor.b); 50 if (value <= 0.0) 51 { 52 return 0.0; 53 } 54 55 float lowestValue = min(min(rgbColor.r, rgbColor.g), rgbColor.b); 56 float difference = value - lowestValue; 57 if (difference <= 0.0) 58 { 59 return float3(0.0, 0.0, value); 60 } 61 62 float saturation = difference / value; 63 float highestIndex = value == rgbColor.r ? 0 : (value == rgbColor.g ? 1 : 2); 64 float hue = ((highestIndex * 2) + (rgbColor[(highestIndex + 1) % 3] - rgbColor[(highestIndex + 2) % 3]) / difference) / 6.0; 65 hue = fmod(hue + 1.0, 1.0); 66 return float3(hue, saturation, value); 67 } 68 69 fixed4 frag (v2f i) : SV_Target 70 { 71 float3 rawColor = tex2D(_MainTex, i.uv).rgb; 72 73 // 彩度、明度を求める 74 float value = max(max(rawColor.r, rawColor.g), rawColor.b); 75 float lowestValue = min(min(rawColor.r, rawColor.g), rawColor.b); 76 float difference = value - lowestValue; 77 float saturation = (value > 0.01) ? (difference / value) : 0.0; 78 79 // 彩度、明度ともにしきい値以下ならアルファ1.0、さもなくば0.0 80 return fixed4(_Color.rgb, step(value, _ValueThreshold) * step(saturation, _SaturationThreshold)); 81 } 82 ENDCG 83 } 84 85 Pass 86 { 87 CGPROGRAM 88 #pragma vertex vert 89 #pragma fragment frag 90 91 #include "UnityCG.cginc" 92 93 struct appdata 94 { 95 float4 vertex : POSITION; 96 }; 97 98 struct v2f 99 { 100 float4 vertex : SV_POSITION; 101 }; 102 103 v2f vert (appdata v) 104 { 105 v2f o; 106 o.vertex = UnityObjectToClipPos(v.vertex); 107 return o; 108 } 109 110 float4 _Color; 111 112 fixed4 frag (v2f i) : SV_Target 113 { 114 return fixed4(_Color.rgb, 1.0); 115 } 116 ENDCG 117 } 118 } 119}
C#
1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(SpriteRenderer), typeof(PolygonCollider2D))] 5public class Silhouette : MonoBehaviour 6{ 7 private static readonly int SaturationThreshold = Shader.PropertyToID("_SaturationThreshold"); 8 private static readonly int ValueThreshold = Shader.PropertyToID("_ValueThreshold"); 9 10 [SerializeField] private int width = 320; 11 [SerializeField] private int height = 240; 12 [SerializeField] private int floorHeight = 16; 13 [SerializeField] private int resolutionMultiplier = 1; 14 [SerializeField] private Color color = Color.black; 15 16 // 彩度のしきい値 17 // これよりも彩度が高ければ除外する 18 [SerializeField][Range(0.0f, 1.0f)] private float saturationThreshold = 0.25f; 19 20 // 明度のしきい値 21 // これよりも明度が高ければ除外する 22 [SerializeField][Range(0.0f, 1.0f)] private float valueThreshold = 0.25f; 23 24 [SerializeField] private Shader silhouetteShader; 25 26 private new SpriteRenderer renderer; 27 private new PolygonCollider2D collider; 28 private Sprite sprite; 29 private Texture2D spriteTexture; 30 private Material silhouetteMaterial; 31 private WebCamTexture webCamTexture; 32 33 private IEnumerator Start() 34 { 35 if (WebCamTexture.devices.Length <= 0) 36 { 37 Destroy(this); 38 yield break; 39 } 40 this.width = Mathf.Max(this.width, 100); 41 this.height = Mathf.Max(this.height, 100); 42 this.webCamTexture = new WebCamTexture(WebCamTexture.devices[0].name, this.width, this.height); 43 this.webCamTexture.Play(); 44 while (this.webCamTexture.width < 100) 45 { 46 yield return null; 47 } 48 49 this.width = this.webCamTexture.width * this.resolutionMultiplier; 50 this.height = this.webCamTexture.height * this.resolutionMultiplier; 51 var pixelsPerUnit = (float)this.height; 52 var mainCamera = Camera.main; 53 if (mainCamera.orthographic) 54 { 55 pixelsPerUnit = (0.5f * Screen.height * this.resolutionMultiplier) / mainCamera.orthographicSize; 56 } 57 58 this.renderer = this.GetComponent<SpriteRenderer>(); 59 this.collider = this.GetComponent<PolygonCollider2D>(); 60 this.spriteTexture = new Texture2D(this.width, this.height, TextureFormat.ARGB32, false); 61 this.sprite = Sprite.Create( 62 this.spriteTexture, 63 new Rect(0, 0, this.width, this.height), 64 Vector2.zero, 65 pixelsPerUnit); 66 this.renderer.sprite = this.sprite; 67 68 this.silhouetteMaterial = new Material(this.silhouetteShader); 69 while (true) 70 { 71 this.silhouetteMaterial.color = this.color; 72 73 // 二値化に使うパラメーターとして、従来の_Thresholdに代わって 74 // 彩度のしきい値(_SaturationThreshold)、明度のしきい値(_ValueThreshold)の2つを渡す 75 this.silhouetteMaterial.SetFloat(SaturationThreshold, this.saturationThreshold); 76 this.silhouetteMaterial.SetFloat(ValueThreshold, this.valueThreshold); 77 78 var renderTexture = RenderTexture.GetTemporary(this.width, this.height, 0, RenderTextureFormat.ARGB32); 79 Graphics.Blit(this.webCamTexture, renderTexture, this.silhouetteMaterial, 0); 80 var activeTexture = RenderTexture.active; 81 RenderTexture.active = renderTexture; 82 83 var floorY = ((float)(this.floorHeight * this.resolutionMultiplier) / this.height); 84 GL.PushMatrix(); 85 this.silhouetteMaterial.SetPass(1); 86 GL.Begin(GL.QUADS); 87 GL.LoadOrtho(); 88 GL.Color(this.color); 89 GL.Vertex(Vector3.zero); 90 GL.Vertex3(0.0f, floorY, 0.0f); 91 GL.Vertex3(1.0f, floorY, 0.0f); 92 GL.Vertex(Vector3.right); 93 GL.End(); 94 GL.PopMatrix(); 95 96 this.spriteTexture.ReadPixels(new Rect(0, 0, this.width, this.height), 0, 0); 97 RenderTexture.active = activeTexture; 98 RenderTexture.ReleaseTemporary(renderTexture); 99 this.spriteTexture.Apply(); 100 101 Destroy(this.collider); 102 this.collider = this.gameObject.AddComponent<PolygonCollider2D>(); 103 yield return null; 104 } 105 } 106}
投稿2019/11/12 05:24
総合スコア10811
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/11/16 05:32
2019/11/16 06:46 編集
2019/11/16 18:03
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。