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

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

新規登録して質問してみよう
ただいま回答率
85.51%
OpenCV

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

Unity3D

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

Unity

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

Q&A

解決済

1回答

5566閲覧

Unity:レンダリング結果の射影変換について

Kapustin

総合スコア1186

OpenCV

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

Unity3D

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

Unity

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

1グッド

0クリップ

投稿2017/05/23 02:37

編集2017/05/24 08:15

###前提・実現したいこと

Unityのポストエフェクトとして、レンダリングした結果に射影変換を適用させようとしています。
メインカメラにアタッチしたコンポーネントのOnRenderImageにて下記のシェーダを適用させています。

###発生している問題・エラーメッセージ

上記の処理を行っても、変換結果がOpenCvのそれと異なってしまい、悩んでおります。
皆さまのお力をお借りしたく、どうぞよろしくお願いいたします。

###該当のソースコード

Shader "Hidden/MappingShader" { Properties { _MainTex ("Texture", 2D) = "white" {} _translate ("Translate", Float) = 0 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4x4 _mv; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { float4 vec = v.vertex; float x = vec.x; float y = vec.y; float z = vec.z; float w = vec.w; float px = x * _mv[0][0] + y * _mv[0][1] + z * _mv[0][2] + w * _mv[0][3]; float py = x * _mv[1][0] + y * _mv[1][1] + z * _mv[1][2] + w * _mv[1][3]; float pz = x * _mv[2][0] + y * _mv[2][1] + z * _mv[2][2] + w * _mv[2][3]; float pw = x * _mv[3][0] + y * _mv[3][1] + z * _mv[3][2] + w * _mv[3][3]; vec.x = px; vec.y = py; vec.z = pz; vec.w = pw; v2f o; // o.vertex = mul(_mv, v.vertex); o.vertex = vec; o.vertex = UnityObjectToClipPos(o.vertex); o.uv = v.uv; return o; } sampler2D _MainTex; fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
private Material m_Material; private Matrix4x4 matrix; void Awake() { Shader shader = Shader.Find("Hidden/MappingShader"); m_Material = new Material(shader); } void OnRenderImage(RenderTexture source, RenderTexture destination) { Graphics.Blit(source, destination, m_Material); } void Update() { Material.SetMatrix("_mv", matrix); }

###試したこと
行列の転置を試したり計算式を一から書いてみましたが、もっと別なところに原因があるのではないかと考えています。
他に思い当たる原因などありましたら、ご教授いただければ幸いです。

###補足情報(言語/FW/ツール等のバージョンなど)
Unity 5.6.1
macOS Sierra

# 行列のサンプルです。この変換行列での実行結果の画像を添付します。 0.74035 0.16225 0.00000 0.00069 0.13807 0.89541 0.00000 0.00028 0.00000 0.00000 1.00000 0.00000 149.00000 105.00000 0.00000 1.00000

イメージ説明

###追記
OpenCv版の座標変換は、下記のような処理を行っています。
この行列を4x4に変換したものが、最初のサンプルの行列になっています。
正確に言うと(すこしややこしいですが)openframeworksofxQuadWarpというアドオンを利用し、getMatrix()にて得られる変換行列(4x4)を利用しています。
こちらは内部的には(精度の問題で)cvFindHomographyを利用しているようですが、転置行列になるだけで結果はほぼ変わりません。

// openframework上で動かしています。 ofImage image; image.load("in.jpg"); cv::Mat cvInImage = cv::Mat(image.getHeight(), image.getWidth(), CV_8UC3, image.getPixels().getData()); cv::Mat cvOutImage = cvInImage.clone(); vector<cv::Point2f> src; vector<cv::Point2f> dst; for(int i = 0; i < 4; i ++){ // 変換前の座標配列:入力画像の左上、右上、右下、左下の座標 src.push_back(cv::Point2f(warper.srcPoints[i].x, warper.srcPoints[i].y)); // 変換後の座標配列:最初の画像の左(OpenCvの表記)の白い四角の四隅にある黄色い点の座標(左上、右上、右下、左下の順) dst.push_back(cv::Point2f(warper.dstPoints[i].x, warper.dstPoints[i].y)); } cv::Mat warp_matrix = cv::getPerspectiveTransform(src, dst); cv::warpPerspective(cvInImage, cvOutImage, warp_matrix, cvInImage.size()); ofImage outImage; outImage.allocate(image.getHeight(), image.getWidth(), image.getImageType()); outImage.setFromPixels(cvOutImage.ptr(), cvOutImage.cols, cvOutImage.rows, image.getImageType(), false); outImage.save("out.jpg"); cout << warp_matrix << endl;
// OpenCv版の変換行列の出力結果 [0.7403473533105349, 0.1380676946623043, 148.9999999999996; 0.1622461658588558, 0.895410834059531, 104.9999999999999; 0.0006888663351174518, 0.0002752964241302562, 1]

###追記2
warperに設定している変換前座標と変換後座標です。これで得られる行列は先述と同値です。

// SourceRectの設定値:画面のフルスクリーンサイズを設定しています。 // setSourceRect(ofRectangle(0, 0, ofGetScreenWidth(), ofGetScreenHeight())); srcPoint[0] : 0, 0, 0 srcPoint[1] : 1440, 0, 0 srcPoint[2] : 1440, 900, 0 srcPoint[3] : 0, 900, 0 dstPoint[0] : 149, 105, 0 dstPoint[1] : 610, 170, 0 dstPoint[2] : 598, 511, 0 dstPoint[3] : 219, 730, 0
KineSaku👍を押しています

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

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

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

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

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

Bongo

2017/05/23 04:54 編集

差し支えなければ、OpenCV版の座標変換部分と変換行列も見てみたいのですが、よろしいでしょうか。 またもう一点、Unity版で意図する変換は2次元的なものと3次元的なもののいずれでしょうか(元々の形状が長方形として、変換後の形状がZ座標が変化しないままひしゃげた形になるものと、長方形は長方形の形のまま3次元空間内で回転して奥行きのある形状になるものの、いずれが質問者さんのお考えに近いでしょうか)。
Kapustin

2017/05/23 08:14

追記修正のご依頼を頂きありがとうございます。処理内容を追記いたしました。意図する変換は後者のもの(OpenCvで得られる結果と同じもの)を求めております。よろしくお願いいたします。
Bongo

2017/05/23 21:23 編集

すみませんが、二点お伺いします。Unity側のカメラはOrthographicになっているでしょうか。これがPerspectiveだと、UnityObjectToClipPosが変なことをするかもしれません(もしPerspectiveならUnityObjectToClipPosの行を削除してみて、描画がどうなるかお試しください)。もう一つは、描画しようとしているMeshのverticesの内容はどうなっていますでしょうか。これがwarperのsrcPointsに与えた四隅の座標と異なると、うまくいかないかも...と思われました。
Bongo

2017/05/23 23:51 編集

後で考えたら、この質問は少々的外れだった気がしてきました。質問を変えさせてください。 warperにsetSourceRectやsetSourcePointsで変換前座標を設定されているかと思いますが、どのような値に設定されていますでしょうか? また、参考としてwarperが変換したdstPointsの示す四隅の座標も見てみたいのですが、よろしいでしょうか。
Kapustin

2017/05/24 08:18

度々ご質問いただきありがとうございます。各座標の値を追記いたしました。 なお、念のため上記の方法も試してみましたが、やはり思う結果にはなりませんでした。 考察いただき本当にありがとうございます。引き続きよろしくお願いいたします。
guest

回答1

0

ベストアンサー

ここまで情報ご提供いただいたからには何かしらの成果を出したいところです...

ちょっと調べたところ、シェーダーに入力される頂点座標は画像の左下隅原点、右端がX1.0、上端がY1.0のようです。一方、warperの行列は画像左上隅原点、右端が画像幅、下端が画像高さとなる座標系を前提としているわけですから、この差を埋めるため変換を追加してみました。

また、変換後のW成分を残さないとテクスチャがひずむため、UnityObjectToClipPosを削除して、代わりにX・Y座標のみ2倍に拡大し、Wの分だけX・Y座標をずらしてo.vertexに渡しています(ちなみに、o.vertex = float4((vec.xyz / vec.w) * 2.0 - 1.0, 1.0);のようにW成分を潰すとテクスチャがひずむと思います)。

Shader "Hidden/MappingShader" { Properties { _MainTex ("Texture", 2D) = "white" {} _translate("Translate", Float) = 0 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4x4 _mv; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { float4 vec = mul(_mv, v.vertex); // ここで変換されたベクトルのW成分(奥行きに比例)はテクスチャを正しく貼るために残したい v2f o; o.vertex = float4(vec.xy * 2.0 - vec.w, vec.zw); // UnityObjectToClipPos(vec)だとW成分が消されるようなので、自前で2倍に拡大し、消失点が画像隅から画面中心に変わるので、それを加味してX、YからWを引く o.uv = v.uv; return o; } sampler2D _MainTex; fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
private Material m_Material; private float imageWidth; // インスタンス変数追加 private float imageHeight; // インスタンス変数追加 private Matrix4x4 matrix; private Matrix4x4 unityCoordToWarperCoordMatrix; // インスタンス変数追加 private Matrix4x4 warperCoordToUnityCoordMatrix; // インスタンス変数追加 void Awake() { Shader shader = Shader.Find("Hidden/MappingShader"); m_Material = new Material(shader); // warperで変換行列を求める際に使った画像サイズが必要、実際には外部から与える? imageWidth = 1440.0f; imageHeight = 900.0f; // X0.0~1.0を0.0~1440.0に、Y0.0~1.0を0.0~-900.0になるよう反転・引き延ばした上で、Yに900.0足してY900.0~0.0にする unityCoordToWarperCoordMatrix = Matrix4x4.TRS(new Vector3(0.0f, imageHeight, 0.0f), Quaternion.identity, new Vector3(imageWidth, -imageHeight, 1.0f)); // warper座標をUnity座標に戻すには逆行列で変換すればよいはず... warperCoordToUnityCoordMatrix = unityCoordToWarperCoordMatrix.inverse; } void OnRenderImage(RenderTexture source, RenderTexture destination) { Graphics.Blit(source, destination, m_Material); } void Update() { // warperが生成した行列、実際には外部から与える? matrix = new Matrix4x4(); matrix.SetColumn(0, new Vector4(0.74035f, 0.16225f, 0.00000f, 0.00069f)); matrix.SetColumn(1, new Vector4(0.13807f, 0.89541f, 0.00000f, 0.00028f)); matrix.SetColumn(2, new Vector4(0.00000f, 0.00000f, 1.00000f, 0.00000f)); matrix.SetColumn(3, new Vector4(149.00000f, 105.00000f, 0.00000f, 1.00000f)); // warper行列の前後に追加の変換を入れてシェーダーに与える Matrix4x4 compositeMatrix = warperCoordToUnityCoordMatrix * matrix * unityCoordToWarperCoordMatrix; // SetMatrixはインスタンスメソッドなので、Materialをm_Materialに変更 m_Material.SetMatrix("_mv", compositeMatrix); }

これならどうでしょうか?
もう一つの案として、warperで行列を作る時点でUnity座標系を使ってしまうというのも可能かもしれません。

投稿2017/05/24 14:05

編集2017/05/24 22:05
Bongo

総合スコア10807

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

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

Kapustin

2017/05/25 00:57

ありがとうございます!上記の方法で希望する結果になりました!幸せです。 実際には、シェーダーに渡す行列を逆行列にする必要がありましたが、他のパラメータでも試してみたところ、期待する動作になりました。 ご教授いただいたシェーダの計算式や変換行列の加工部分については理解するまでもう少し時間がかかりそうですが、、頑張ってみます。お力添え頂き、本当にありがとうございました!
Bongo

2017/05/25 01:07

いえいえ、こちらもUnityシェーダーの勉強になりたいへん有意義でした。 おそらく、ご提示のUnity側の結果が、いかにもアフィン変換しただけのような平行四辺形になってしまったのも、UnityObjectToClipPosがW座標を無視してしまうことが原因のように思われます。
Kapustin

2017/05/25 02:30

UnityObjectToClipPosではfloat3が引数でWを1.0に固定してしまっているのですね。それでこのような結果になってしまうというのは大変勉強になりました。異なる座標系で処理しようとしていた事も、それに気づけなかった要因だったように思います。鋭いご指摘頂きありがとうございます。 また、上記で「シェーダに渡す行列を逆行列にする必要が〜」と書きましたが間違いでした。ご提示頂いたコードが正解でした。(何を勘違いしていたのか、すみません) また機会があれば、お力添え頂ければ幸いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問