質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.31%
C#

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

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

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

Q&A

解決済

1回答

829閲覧

【Unity】深度を線形化して深度画像を保存する方法

taiki_inoue

総合スコア5

C#

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

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

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

0グッド

0クリップ

投稿2024/01/25 07:21

実現したいこと

点群データを集めるためのシミュレーション環境を作る目的で最近Unityを勉強し始めた者です.今やりたいことは深度を線形化して深度画像として保存することです.

前提

以前質問させていただいたときの回答(https://teratail.com/questions/6wf3dt1lovwv1g)
を参考にし,深度バッファをRenderTextureに読み込み保存することができました.

得られたカラー画像と深度画像をPythonのopen3d(https://www.open3d.org/docs/release/tutorial/geometry/rgbd_image.html)
で点群に変換したところ,思い通りの点群を得ることができませんでした.

調べるとUnityのデプスバッファは非線形な値ということが分かり,これが思い通りの点群取得を防いでいると考えています.深度を線形にする方法としてシェーダ上で_CameraDepthTextureを用いて深度を取得した後にLinear01Depth,LinearEyeDepthなどの関数で線形化する方法があると分かり,次のサイト(https://light11.hatenadiary.com/entry/2018/05/08/012149)
に載っているコードで試したところ深度画像が得られず真っ白になってしまいました.

深度を線形化して深度画像として保存する方法・ノウハウなど知っている方がいましたら教えていただきたいです.よろしくお願いします!

深度画像とカラー画像を保存するために使ったコード

c#

1using UnityEngine; 2using System.Collections; 3using System.IO; 4 5public class PreserveRenderTexture: MonoBehaviour 6{ 7 [SerializeField] 8 public RenderTexture RenderTextureRef; 9 [SerializeField] 10 public RenderTexture RenderTextureCol; 11 12 // Use this for initialization 13 void Start() 14 { 15 16 } 17 18 // Update is called once per frame 19 void Update() 20 { 21 if (Input.GetKeyDown(KeyCode.Space)) 22 { 23    // 深度画像を保存 24 savePng(); 25 Debug.Log("Start savePng()"); 26    // カラー画像を保存 27 SaveRenderTextureToPNG(RenderTextureCol, "screenshot.png"); 28 Debug.Log("Start SaveRenderTextureToPNG"); 29 30 } 31 } 32 33 void savePng() 34 { 35 Texture2D tex = new Texture2D(RenderTextureRef.width, RenderTextureRef.height, TextureFormat.RGB24, false); 36 37 // 現在のレンダーターゲットを覚えておく 38 RenderTexture currentTarget = RenderTexture.active; 39 40 // texと同サイズのRenderTextureを用意する 41 RenderTexture depthTexture = RenderTexture.GetTemporary(RenderTextureRef.width, RenderTextureRef.height); 42 43 // 深度をカラーバッファ上にレンダリングするためのマテリアルを作る 44 Material blitMaterial = new Material(Shader.Find("Unlit/MyUnlit")); 45 46 // RenderTextureRefをdepthTexture上にレンダリングする 47 Graphics.Blit(RenderTextureRef, depthTexture, blitMaterial); 48 49 // Blitによって現在のレンダーターゲットはdepthTextureに切り替わっているので、その内容をtexに読み取る 50 tex.ReadPixels(new Rect(0, 0, RenderTextureRef.width, RenderTextureRef.height), 0, 0); 51 tex.Apply(); 52 53 // レンダーターゲットを元に戻す 54 RenderTexture.active = currentTarget; 55 56 // depthTexture、blitMaterialはもう不要なので削除する 57 RenderTexture.ReleaseTemporary(depthTexture); 58 Object.Destroy(blitMaterial); 59 60 // Encode texture into PNG 61 byte[] bytes = tex.EncodeToPNG(); 62 Object.Destroy(tex); 63 64 //Write to a file in the project folder 65 File.WriteAllBytes(Application.dataPath + "/../DepthRenderTexture_0125.png", bytes); 66 67 } 68 69 void SaveRenderTextureToPNG(RenderTexture rt, string filePath) 70 { 71 // アクティブなレンダリングテクスチャを設定 72 RenderTexture.active = rt; 73 74 // Texture2Dを作成してデータを読み込む 75 Texture2D screenshot = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); 76 screenshot.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); 77 screenshot.Apply(); 78 79 // 画像をバイトデータに変換 80 byte[] bytes = screenshot.EncodeToPNG(); 81 82 // ファイルに保存 83 System.IO.File.WriteAllBytes(filePath, bytes); 84 85 // アクティブなレンダリングテクスチャをnullに設定 86 RenderTexture.active = null; 87 } 88 89 90}

保存された深度画像とカラー画像
イメージ説明
イメージ説明

open3dで作成したRGBD画像と点群
イメージ説明
イメージ説明
イメージ説明

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

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

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

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

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

guest

回答1

0

ベストアンサー

あいにくOpen3Dについては経験がないため自信がないのですが、ちょっと検索してみたところ「Learning Open3D」に

Redwoodフォーマットは、16ビットのシングルチャンネル画像に深度を格納している。 整数値はミリメートル単位の深度測定値を表す。 Open3Dが奥行き画像を解析するためのデフォルトのフォーマットである。

との解説がありました。また「Question - Can ImageConversion.EncodeToPng() do 16 bits per channel - Unity Forum」のjonaskhalilさんの回答によりますと、EncodeToPngは16ビットグレースケールPNGを出力することもできるそうです。そこで、まず下記のようなシェーダーを用意して...

ShaderLab

1Shader "Unlit/DistanceInMillimeters" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 } 7 SubShader 8 { 9 Cull Off ZWrite Off ZTest Always 10 11 Pass 12 { 13 CGPROGRAM 14 #pragma vertex vert 15 #pragma fragment frag 16 17 #include "UnityCG.cginc" 18 19 struct appdata 20 { 21 float4 vertex : POSITION; 22 float2 uv : TEXCOORD0; 23 }; 24 25 struct v2f 26 { 27 float2 uv : TEXCOORD0; 28 float4 vertex : SV_POSITION; 29 }; 30 31 sampler2D _MainTex; 32 33 v2f vert (appdata v) 34 { 35 v2f o; 36 o.vertex = UnityObjectToClipPos(v.vertex); 37 o.uv = v.uv; 38 39 return o; 40 } 41 42 // このユニフォーム変数でカメラのニア・ファープレーンの逆数を受け取ります 43 float2 _ReciprocalNearFar; 44 45 float4 frag (v2f i) : SV_Target 46 { 47 // 深度テクスチャ上の値を取得して... 48 float depth = tex2D(_MainTex, i.uv); 49 50 // プラットフォームによってカメラに近い側が白か、遠い側が白かが変わるので、遠い側が白になるよう統一して... 51 #ifdef UNITY_REVERSED_Z 52 depth = 1.0 - depth; 53 #endif 54 55 // カメラのZ軸に沿った、メートル単位の奥行きを求めます 56 float linearEyeDepth = 1.0 / ((_ReciprocalNearFar.y - _ReciprocalNearFar.x) * depth + _ReciprocalNearFar.x); 57 58 // ミリメートル単位の深度を16ビット1チャンネル画像で表現するとなると、最大深度は65.535mとなるはずです 59 // つまりlinearEyeDepthを65.535で割れば、0mm~65535mmを表現する線形グレーが得られるかと思います 60 return linearEyeDepth / 65.535; 61 } 62 ENDCG 63 } 64 } 65}

PreserveRenderTextureは下記のように改変してみました。変更した部分には★マーク付きのコメントを入れています。

C#

1using UnityEngine; 2using System.Collections; 3using System.IO; 4 5public class PreserveRenderTexture: MonoBehaviour 6{ 7 [SerializeField] 8 public RenderTexture RenderTextureRef; 9 [SerializeField] 10 public RenderTexture RenderTextureCol; 11 12 // ★PassDepthのcameraと同じカメラを、こちらにもインスペクター上でセットしておいてください 13 [SerializeField] 14 public Camera camera; 15 16 // Use this for initialization 17 void Start() 18 { 19 20 } 21 22 // Update is called once per frame 23 void Update() 24 { 25 if (Input.GetKeyDown(KeyCode.Space)) 26 { 27    // 深度画像を保存 28 savePng(); 29 Debug.Log("Start savePng()"); 30    // カラー画像を保存 31 SaveRenderTextureToPNG(RenderTextureCol, "screenshot.png"); 32 Debug.Log("Start SaveRenderTextureToPNG"); 33 34 } 35 } 36 37 void savePng() 38 { 39 // ★テクスチャのフォーマットをR16に変更しました 40 Texture2D tex = new Texture2D(RenderTextureRef.width, RenderTextureRef.height, TextureFormat.R16, false); 41 42 // 現在のレンダーターゲットを覚えておく 43 RenderTexture currentTarget = RenderTexture.active; 44 45 // texと同サイズのRenderTextureを用意する 46 // ★depthTextureのフォーマットもR16に変更しました 47 RenderTexture depthTexture = RenderTexture.GetTemporary(RenderTextureRef.width, RenderTextureRef.height, 0, RenderTextureFormat.R16); 48 49 // 深度をカラーバッファ上にレンダリングするためのマテリアルを作る 50 // ★マテリアルのシェーダーを「Unlit/DistanceInMillimeters」に変更しました 51 Material blitMaterial = new Material(Shader.Find("Unlit/DistanceInMillimeters")); 52 53 // ★マテリアルにcameraのニア・ファープレーンの逆数を与えます 54 blitMaterial.SetVector("_ReciprocalNearFar", new Vector4(1.0f / camera.nearClipPlane, 1.0f / camera.farClipPlane)); 55 56 // ★以降は元のコードのままです 57 58 // RenderTextureRefをdepthTexture上にレンダリングする 59 Graphics.Blit(RenderTextureRef, depthTexture, blitMaterial); 60 61 // Blitによって現在のレンダーターゲットはdepthTextureに切り替わっているので、その内容をtexに読み取る 62 tex.ReadPixels(new Rect(0, 0, RenderTextureRef.width, RenderTextureRef.height), 0, 0); 63 tex.Apply(); 64 65 // レンダーターゲットを元に戻す 66 RenderTexture.active = currentTarget; 67 68 // depthTexture、blitMaterialはもう不要なので削除する 69 RenderTexture.ReleaseTemporary(depthTexture); 70 Object.Destroy(blitMaterial); 71 72 // Encode texture into PNG 73 byte[] bytes = tex.EncodeToPNG(); 74 Object.Destroy(tex); 75 76 //Write to a file in the project folder 77 File.WriteAllBytes(Application.dataPath + "/../DepthRenderTexture_0125.png", bytes); 78 79 } 80 81 void SaveRenderTextureToPNG(RenderTexture rt, string filePath) 82 { 83 // アクティブなレンダリングテクスチャを設定 84 RenderTexture.active = rt; 85 86 // Texture2Dを作成してデータを読み込む 87 Texture2D screenshot = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); 88 screenshot.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); 89 screenshot.Apply(); 90 91 // 画像をバイトデータに変換 92 byte[] bytes = screenshot.EncodeToPNG(); 93 94 // ファイルに保存 95 System.IO.File.WriteAllBytes(filePath, bytes); 96 97 // アクティブなレンダリングテクスチャをnullに設定 98 RenderTexture.active = null; 99 } 100 101 102}

私の試した限りでは、どうやらそれらしいPNGファイルが出力されたようでした。これでうまくいかなければOpen3Dで点群を作るところまで試して調べてみようかと思うのですが、申し上げましたようにOpen3Dは未経験につき手こずるかもしれません。すみませんがご容赦ください。

投稿2024/01/25 18:38

Bongo

総合スコア10816

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

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

taiki_inoue

2024/01/26 05:46

Bongoさん,前回に引き続き質問に答えてくださりありがとうございます! 試した結果を画像でお伝えしたいのとShaderコードで聞きたい箇所があり追加で質問させていただきました.もしお時間空いてましたらこちら(https://teratail.com/questions/6shab5c2dqup7b) も回答していただければ幸いです...
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問