質問するログイン新規登録
C#

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

Unity3D

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

Unity

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

Q&A

解決済

1回答

995閲覧

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

taiki_inoue

総合スコア5

C#

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

Unity3D

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

Unity

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

0グッド

0クリップ

投稿2024/01/26 05:41

0

0

実現したいこと

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

前提

こちらの質問(https://teratail.com/questions/4z92hds0whox04)
の回答で提示していただいたコードを試したところUnityのScene画面,Game画面で「Unlit/DistanceInMillimeters」シェーダを採用したdepthRenderTextureのMaterialをアタッチしたQuadは添付画像のようになりましたが,「PreserveRenderTexture.cs」で保存された深度画像を確認するとdepthRenderTextureの内容を保存できているようでした.

Unity画面
イメージ説明
イメージ説明

保存した深度画像
イメージ説明

そこでopen3dで点群変換を試したところ,次のようになり点が平面を構成しており深度の線形化に成功したと思います.

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

ただUnityの画面ではCube, カメラの位置関係は次の画像となっており,変換した点群は2つの平面が成す角度が90度になって欲しいです.そこで点群カメラの位置・姿勢・ニアクリップ面・ファークリップ面を変更せず,Cubeの位置を変更して深度画像保存・点群変換を試みました.

先ほどのシーンを横から見た場合
イメージ説明

カメラからの距離が離れすぎると,オブジェクトがカメラのビュー空間内であっても,Unityで保存した深度画像をopen3dのo3d.io.read_imageで読み込んだときに画素値が全て0になり点群が作成されない場合がありました.

Cubeをカメラから遠ざけた場合に保存された深度画像とopen3dで作成したRGBD画像
イメージ説明
イメージ説明
イメージ説明

また,Cubeの向きを変更して再度試したところ,やはりカメラから遠い点の深度が適切に読み込めず,求めている点群が作成されないこともありました.

Cubeの向きを変えた場合に保存された深度画像とopen3dで作成したRGBD画像・点群
イメージ説明
イメージ説明
イメージ説明
イメージ説明

点群変換の際の挙動についてはopen3dの問題の可能性が高いので,これから自分で原因を探してみます.

聞きたいこと

最後に自分のUnityと3DCGに関する理解を深めるために提案していただいたコード内で分からなかった点を質問させていただきます.

// このユニフォーム変数でカメラのニア・ファープレーンの逆数を受け取ります float2 _ReciprocalNearFar;

_ReciprocalNearFarで検索したところヒットするものがなく,なぜこの変数がニア・ファープレーンの逆数を受け取るのか分からないので解説していただきたいです.

// カメラのZ軸に沿った、メートル単位の奥行きを求めます float linearEyeDepth = 1.0 / ((_ReciprocalNearFar.y - _ReciprocalNearFar.x) * depth + _ReciprocalNearFar.x);

この式が意味することと導出過程を教えていただきたいです.

画像・文章ともに長くなり申し訳ありません.最後の2箇所教えていただければ幸いです.よろしくお願いします!

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

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

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

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

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

guest

回答1

0

ベストアンサー

_ReciprocalNearFarについては、あれは私が勝手に名付けたものです。あの名前に必然性はありませんので、お好きな名前にしていただいてかまいません。
Cg/HLSL でシェーダープロパティを参照する - Unity マニュアル」にありますように、通常はPropertiesブロックにプロパティを定義して、シェーダーコード本体にそれと対応するユニフォーム変数を宣言するスタイルになるかと思いますが(このようにするとマテリアルのインスペクター上に設定項目が現れ、そこから値を設定することもできるでしょう)、今回はPreserveRenderTexture

C#

1 // ★マテリアルにcameraのニア・ファープレーンの逆数を与えます 2 blitMaterial.SetVector("_ReciprocalNearFar", new Vector4(1.0f / camera.nearClipPlane, 1.0f / camera.farClipPlane));

の部分でマテリアルにデータを送り込む方式にしており、マテリアル自体にプロパティを持たせる必要はないだろうと思いまして、プロパティは定義せずにユニフォーム変数の宣言のみを行う形にしました。

linearEyeDepthの算出方法ですが、あの式はUnityが用意しているLinearEyeDepthの式を拝借したものです。組み込みのシェーダーはダウンロードアーカイブから入手でき、それに収録されているUnityCG.cgincによると

ShaderLab

1// Z buffer to linear depth 2inline float LinearEyeDepth( float z ) 3{ 4 return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w); 5}

となっていました。式の中で使われている_ZBufferParamsはUnity組み込みの変数で、「ビルトインのシェーダー変数 - Unity マニュアル」によると

Z バッファ値をリニア化するために使用します。 x は (1-far/near)、 y は (far/near)、 z は (x/far)、 w は (y/far) です。

となっているそうです。LinearEyeDepthを使わずにわざわざ自前で計算したのは、今回の深度画像出力のためのレンダリングは通常のシーン内オブジェクトのレンダリングとはタイミングが異なるためです。シーン内オブジェクト用のシェーダーであれば、レンダリングを行っているカメラのニア・ファープレーン情報をもとにUnityが自動的に_ZBufferParamsをセットしてくれるため気兼ねなくLinearEyeDepthを使えるはずですが、今回は深度画像撮影用のカメラのニア・ファープレーンを使って計算しないと結果が狂ってしまうためあのようにしました。

ついでに、点群変換時に結果が狂ってしまう件について私もちょっと調べてみましたので報告いたします。
まず実験材料として、Unity側では下図のようなカラー画像・深度画像を撮影しました。床の赤青チェッカーパターンは、各タイルが1m四方のサイズになるようにしています。

図1

図2

このときのカメラ設定はField Of Viewが60、Clipping PlanesのNearが0.512、Farが8.192です。
ご質問者さんのスクリーンショットを拝見しますに、点群が妙に引き延ばされて配置されてしまったようですが、私の予想ですとUnity上のカメラ設定(特にField Of View)とOpen3D上での点群生成時のカメラ内部パラメーターがマッチしていないんじゃないかと思います。
また、カメラから遠い部分が消えてしまう現象についてですが、open3d.geometry.RGBDImage.create_from_color_and_depthによるとどうやらデフォルトではdepth_truncが3.0に設定されており、3mより遠くのデータは捨てられてしまうようでした。もしあそこがデフォルトのままでしたら、もっと大きくしてやると遠くの点も出力されるかもしれません。

私の場合は下記のようなコードで点群を生成してみたのですが、

Python

1import open3d as o3d 2import matplotlib.pyplot as plt 3import math 4 5color_raw = o3d.io.read_image("../../screenshot.png") 6depth_raw = o3d.io.read_image("../../DepthRenderTexture_0125.png") 7 8# 深度切り捨て距離を延長、ついでにグレースケール変換をオフ 9rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth( 10 color_raw, 11 depth_raw, 12 depth_trunc=65.535, 13 convert_rgb_to_intensity=False) 14print(rgbd_image) 15 16plt.subplot(1, 2, 1) 17plt.title('Unitychan color image') 18plt.imshow(rgbd_image.color) 19plt.subplot(1, 2, 2) 20plt.title('Unitychan depth image') 21plt.imshow(rgbd_image.depth) 22plt.show() 23 24# Unity側のカメラの画角が60°で、撮影した画像の解像度が256ピクセル四方なら... 25fieldOfView = 60.0 26resolution = 256 27 28# 画像解像度の半分(128ピクセル)を画角の半分(30°)のタンジェントで割れば、ピクセル単位の焦点距離が算出できるはず 29halfExtent = resolution * 0.5 30focalLength = halfExtent / math.tan(math.radians(fieldOfView * 0.5)) 31 32pcd = o3d.geometry.PointCloud.create_from_rgbd_image( 33 rgbd_image, 34 o3d.camera.PinholeCameraIntrinsic( 35 resolution, resolution, 36 focalLength, focalLength, 37 halfExtent, halfExtent)) 38pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) 39 40o3d.visualization.draw_geometries([pcd])

下図のような結果が出力されました。見た感じではUnity上でオブジェクトを配置した時と比べて変に伸縮したりはしていない様子でした。

図3

図4

投稿2024/01/27 05:45

Bongo

総合スコア10816

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

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

taiki_inoue

2024/01/29 04:30

解説ありがとうございます!C#スクリプトでマテリアルにデータを送ることができると初めて分かりました. また,open3dは触られてないと言っていたにも関わらず手を伸ばしていただきありがとうございます!ご指摘の通りdepth_truncをデフォルトで行っていました.私もカメラの内部パラメータをUnityと合わせる必要性までは考えましたが,まだ実装が不得手なもので色々と悩んでいたところです... 私の質問にここまで丁寧に答えてくださり感謝しております!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問