🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

1747閲覧

Unity : ウェブカメラで取得した画像の黒い部分だけに当たり判定をつけたい

hideyuki_aka

総合スコア6

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/10/21 17:55

編集2019/10/26 14:46

前提・実現したいこと

Unityで、影を用いたインタラクティブなゲームを制作しております。
そこで、下図のようにウェブカメラで人を撮影し、その撮影された画像の黒い部分だけに当たり判定を与えて、キャラクターが影の上に登ったりなどができることを目標としています。

コライダーを既存のコンポーネントで適用するのではなく、スクリプトで適用すればいいのかと
初心者ながら想像しているのですが、実際どのように組み始めていけば良いか見当がつかず
質問させていただきました。

説明が足らない場合はお手数ですが質問していただけますと助かります。

宜しくお願い致します。

イメージ

補足情報(FW/ツールのバージョンなど)

Unity2019.2.5f1 Personal
macOS Sierra

おわりに

回答をしてくださったお二人ともベストアンサーにしたかったのが正直です。
どちらも実装して試してみましたが、Bongo様の回答は私の技術力を大きく上回る内容でしたが、
むしろそのことによって触れてこなかったシェーダーの世界でもアプローチが可能であることなど
新しい発見が伴うものでした。
ありがとうございました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

映像が更新されるたびにPolygonCollider2Dを付け直すというのも候補になるかもしれません。ですがやはり負荷は大きいでしょうね...

スプライトに付けるスクリプトの例

C#

1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(SpriteRenderer), typeof(PolygonCollider2D))] 5public class Silhouette : MonoBehaviour 6{ 7 private static readonly int Threshold = Shader.PropertyToID("_Threshold"); 8 9 [SerializeField] private int width = 320; 10 [SerializeField] private int height = 240; 11 [SerializeField] private int floorHeight = 16; 12 [SerializeField] private int resolutionMultiplier = 1; 13 [SerializeField] private Color color = Color.black; 14 [SerializeField][Range(0.0f, 1.0f)] private float threshold = 0.5f; 15 [SerializeField] private Shader silhouetteShader; 16 17 private new SpriteRenderer renderer; 18 private new PolygonCollider2D collider; 19 private Sprite sprite; 20 private Texture2D spriteTexture; 21 private Material silhouetteMaterial; 22 private WebCamTexture webCamTexture; 23 24 private IEnumerator Start() 25 { 26 // WebCamTextureをセットアップ 27 if (WebCamTexture.devices.Length <= 0) 28 { 29 Destroy(this); 30 yield break; 31 } 32 this.width = Mathf.Max(this.width, 100); 33 this.height = Mathf.Max(this.height, 100); 34 this.webCamTexture = new WebCamTexture(WebCamTexture.devices[0].name, this.width, this.height); 35 this.webCamTexture.Play(); 36 while (this.webCamTexture.width < 100) 37 { 38 yield return null; 39 } 40 41 // この後の処理で使うテクスチャ幅・高さを求める 42 // resolutionMultiplierを大きくするとコライダーが精密になるが、負荷は大きくなる 43 this.width = this.webCamTexture.width * this.resolutionMultiplier; 44 this.height = this.webCamTexture.height * this.resolutionMultiplier; 45 var pixelsPerUnit = (float)this.height; 46 var mainCamera = Camera.main; 47 if (mainCamera.orthographic) 48 { 49 pixelsPerUnit = (0.5f * Screen.height * this.resolutionMultiplier) / mainCamera.orthographicSize; 50 } 51 52 // スプライトをセットアップ 53 this.renderer = this.GetComponent<SpriteRenderer>(); 54 this.collider = this.GetComponent<PolygonCollider2D>(); 55 this.spriteTexture = new Texture2D(this.width, this.height, TextureFormat.ARGB32, false); 56 this.sprite = Sprite.Create( 57 this.spriteTexture, 58 new Rect(0, 0, this.width, this.height), 59 Vector2.zero, 60 pixelsPerUnit); 61 this.renderer.sprite = this.sprite; 62 63 this.silhouetteMaterial = new Material(this.silhouetteShader); 64 while (true) 65 { 66 // WebCamTextureの内容を2値化 67 this.silhouetteMaterial.color = this.color; 68 this.silhouetteMaterial.SetFloat(Threshold, this.threshold); 69 var renderTexture = RenderTexture.GetTemporary(this.width, this.height, 0, RenderTextureFormat.ARGB32); 70 Graphics.Blit(this.webCamTexture, renderTexture, this.silhouetteMaterial, 0); 71 var activeTexture = RenderTexture.active; 72 RenderTexture.active = renderTexture; 73 74 // 床を描く 75 // WebCamTextureのBlitで何も映っていない場合、後のコライダー生成時に 76 // デフォルトの形状で生成されてしまうため、それを防止するのが主目的 77 var floorY = ((float)(this.floorHeight * this.resolutionMultiplier) / this.height); 78 GL.PushMatrix(); 79 this.silhouetteMaterial.SetPass(1); 80 GL.Begin(GL.QUADS); 81 GL.LoadOrtho(); 82 GL.Color(this.color); 83 GL.Vertex(Vector3.zero); 84 GL.Vertex3(0.0f, floorY, 0.0f); 85 GL.Vertex3(1.0f, floorY, 0.0f); 86 GL.Vertex(Vector3.right); 87 GL.End(); 88 GL.PopMatrix(); 89 90 // 結果をスプライト用テクスチャに読み取る 91 this.spriteTexture.ReadPixels(new Rect(0, 0, this.width, this.height), 0, 0); 92 RenderTexture.active = activeTexture; 93 RenderTexture.ReleaseTemporary(renderTexture); 94 this.spriteTexture.Apply(); 95 96 // PolygonCollider2Dを再作成し、不透明部分に沿った形状を作らせる 97 Destroy(this.collider); 98 this.collider = this.gameObject.AddComponent<PolygonCollider2D>(); 99 yield return null; 100 } 101 } 102}

映像処理用シェーダーの例

ShaderLab

1Shader "Hidden/Silhouette" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Color ("Color", Color) = (0, 0, 0, 1) 7 _Threshold ("Threshold", Range(0.0, 1.0)) = 0.5 8 } 9 SubShader 10 { 11 Cull Off ZWrite Off ZTest Always 12 13 Pass 14 { 15 CGPROGRAM 16 #pragma vertex vert 17 #pragma fragment frag 18 19 #include "UnityCG.cginc" 20 21 struct appdata 22 { 23 float4 vertex : POSITION; 24 float2 uv : TEXCOORD0; 25 }; 26 27 struct v2f 28 { 29 float2 uv : TEXCOORD0; 30 float4 vertex : SV_POSITION; 31 }; 32 33 v2f vert (appdata v) 34 { 35 v2f o; 36 o.vertex = UnityObjectToClipPos(v.vertex); 37 o.uv = v.uv; 38 return o; 39 } 40 41 sampler2D _MainTex; 42 float4 _Color; 43 float _Threshold; 44 45 fixed4 frag (v2f i) : SV_Target 46 { 47 float y = dot(tex2D(_MainTex, i.uv).rgb, float3(0.298912, 0.586611, 0.114478)); 48 return fixed4(_Color.rgb, max(sign(y - _Threshold), 0.0)); 49 } 50 ENDCG 51 } 52 53 Pass 54 { 55 CGPROGRAM 56 #pragma vertex vert 57 #pragma fragment frag 58 59 #include "UnityCG.cginc" 60 61 struct appdata 62 { 63 float4 vertex : POSITION; 64 }; 65 66 struct v2f 67 { 68 float4 vertex : SV_POSITION; 69 }; 70 71 v2f vert (appdata v) 72 { 73 v2f o; 74 o.vertex = UnityObjectToClipPos(v.vertex); 75 return o; 76 } 77 78 float4 _Color; 79 80 fixed4 frag (v2f i) : SV_Target 81 { 82 return fixed4(_Color.rgb, 1.0); 83 } 84 ENDCG 85 } 86 } 87}

部屋の明かりを消し、ディスプレイの光で手を照らして撮影してみた様子
図

大きな解像度でもなめらかに動かすには、Unity標準の物理シミュレーションに頼らない独自の衝突判定メカニズムを考えるべきなのかもしれません。
高速化する余地としてはBurstを使ってみるとか、まだプレビュー段階ですがEntity Component SystemUnity Physicsを使ってみるとかですかね(私はまだ使用経験がありませんが...)?
こういったDOTSのパラダイムは従来のUnityのスタイルとは異なる部分が多くとっつきにくいかもしれませんが、効果はかなり大きいようですから、どうしても高速化したいとお考えでしたら検討してみる価値はあるかと思います。

投稿2019/10/22 22:22

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hideyuki_aka

2019/10/26 14:42

Bongo様 回答ありがとうございます! コピペで実装はできましたが、まだまだ知識が浅くどんなやり取りをしているか かみ砕けていません。少しずつ読み解いていこうと思います!
hideyuki_aka

2019/10/28 12:11

質問よろしいでしょうか。 現在は2値化した映像のうち、明るい部分を黒塗りでコライダーを生成していますが、 暗い部分を黒塗りにしてコライダーを充てるには、プログラムのどの部分を ? なるべく自力でと思いc#スクリプトをいじりましたが変更できませんでした。 この問題はシェーダーに依存しているのでしょうか?
Bongo

2019/10/28 12:48

はい、今回の実験の場合は画像処理を回答に提示しましたシェーダーに任せました。 コードの中程に return fixed4(_Color.rgb, max(sign(y - _Threshold), 0.0)); という部分がありますが、これの max(sign(y - _Threshold), 0.0) がそのピクセルのアルファ...不透明度を表しております。 yは元の色をグレースケール化した値で、それから2値化しきい値である_Thresholdを引くことで、yが_Thresholdよりも大きければプラスの、小さければマイナスの値が得られるはずです。さらにそれをsign関数に通すとプラスの値は1.0に、マイナスの値は-1.0になります。そして最後にmax関数で0.0以下の値を0.0にしてしまえば、最終的なアルファは_Thresholdより明るければ1.0(不透明)、暗ければ0.0(透明)になる...というわけです。 ですので、逆に暗い部分のアルファを1.0にしたいのでしたら正負を逆転させて... return fixed4(_Color.rgb, max(-sign(y - _Threshold), 0.0)); とすればどうでしょうか?
hideyuki_aka

2019/11/11 13:47

できました! ありがとうございました。
hideyuki_aka

2019/11/11 14:31

丁寧な回答ありがとうございました! 非常に助かりました
guest

0

本当ならばPolygonColliderの頂点を自由に移動させるのがベストだとは思いますが、質問のような形を自由に変えたり、飛び地ができる(Colliderが2つに分かれる)ような環境では
・ポリゴンの頂点がどの位置から移動してきたのかを知る必要がある。
・ポリゴンの頂点同士の間隔が離れすぎたときに補完。近すぎる時に削除しなければならない
・飛び地ができていないか確認する必要がある。

このように、製作が難しいと思うので、正方形のColliderの2次元配列を作り、黒い場所だけActiveにするのが単純明快かと思います。

ただ、colliderをpixel毎に作成していると数十万は下らない数のコライダーが必要になると思うので
・コライダーを大きくし、チェック頻度を下げる
・黒と白の境界面にのみコライダーを作成する
など工夫は必要になりますが。

一応あらすじだけ載せておきます。

C#

1Texture2D capture;//カメラのキャプチャ画像 2 3BoxCollider2D[,] colliderMap = new BoxCollider2D[capture.Widge, capture.Height]; 4 5const byte THRESHOLD = 0x80;//閾値。RGBをHSLに変換する方法を知らなかったので(汗) 6 7void Start() 8{ 9 for(int i = 0; i < colliderMap.widge; i++) for(int j = 0; j < colliderMap.height; j++) 10 { 11 colliderMap[i, j] = AddComponent<BoxCollider2D>(); 12 colliderMap[i, j].size = new Vector2(0.1f, 0.1f);//任意 13 } 14} 15 16void Update() 17{ 18 for(int i = 0; i < capture.widge; i++) for(int j = 0; j < capture.height; j++) 19 { 20 Color pixel = capture.GetPixel(i, j); 21 colloderMap[i, j].transform.SetActive( 22 pixel.r < THRESHOLD || 23 pixel.g < THRESHOLD || 24 pixel.b < THRESHOLD);//もしRGBともに閾値より明るければ辺り判定を消す 25 } 26}

注意:コピペで実行しないでください。フリーズまたは動作が遅くなる可能性があります

投稿2019/10/22 06:22

KanazawaKureha

総合スコア368

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hideyuki_aka

2019/10/24 09:29

kureha413さま 回答ありがとうございます。 現在試しているのですが、このコードはどのオブジェクトにアタッチすれば正しく動くのでしょうか?
hideyuki_aka

2019/10/24 11:42

webカメラを読み込むスクリプトに追記してなんとかビルドができました。 しかしおっしゃる通り重すぎるようです。。。 毎フレーム数万のコライダーを作成するというのはかなり負荷があるのですね
KanazawaKureha

2019/10/24 12:13

解決策(例): >>ウェブカメラの画像を白黒化→白か黒かをbool型の二次元配列に格納→入れ子構造のfor文(for(...;...;...)for(...;...;...){})を用いてbool配列を探査し、白と黒の境界を見つける→求めた境界の場所と対応する場所にBoxCollider2DをInstantiate()で作成する これで軽量化が図れると思います。
hideyuki_aka

2019/10/26 14:40

ありがとうございます。挑戦してみます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問