OpenCV カメラを OpenGL カメラに変換する
0 まえがき
- より転載。
1. 予備知識と概要
この記事は、OpenCV ライブラリの一般的なカメラのカメラ キャリブレーション モデルであるピンホール カメラの概念を読者がすでに理解していることを前提としています。カメラ モデルと投影されたジオメトリの詳細については、Richard Hartley と Andrew Zisserman の著書『Multiple View Geometry in Computer Vision』、特に第 6 章「カメラ モデル」を参照するのが最も適切な説明です (これは私の非常に偏った見解です)。このチュートリアルの残りの部分では、この本を「HZ ブック」と略します。
多くのカメラ パラメータは、glFrustrum() や glOrtho() などの OpenGL 関数呼び出しを使用して設定できます。ただし、このチュートリアルでは、マトリックスを直接設定し、これらのマトリックスをシェーダーに送信します。これらの詳細が初めての場合は、このチュートリアルのコード例を参照すると、より明確になります。マトリックスが直接使用されるため、このチュートリアルは、ピンホール モデルに類似した同様のカメラ モデルの OpenGL 射影マトリックスを導出する手がかりを提供する可能性があります。
2. リソース
この問題を解決するために使用した 2 つのリソースを次に示します。どちらも素晴らしいリソースです。ここにいる方は、おそらく、これら 2 つのリソースでは、このチュートリアルの目的でもある OpenCV キャリブレーション マトリックスを OpenGL マトリックスに変換する方法が詳しく説明されていないことに気づくでしょう。
最新の OpenGL を初めて使用する場合は、次の一連のチュートリアルが良い出発点になります。
3.OpenCVとOpenGLにおける画像座標系
3.1. OpenCV/HZ および OpenGL のスピンドル
まず、2 つの標準画像座標系について詳しく説明します。
HZ 座標系と OpenCV 座標系の両方で、カメラは主軸が正の Z 軸と一致していると想定します。言い換えれば、正の Z 軸はカメラの視野内を指します。一方、OpenGL では、主軸は画像座標系の負の Z 軸と一致します。これらの変更により、2 つの表現間の X 軸も 180 度回転します。
3.2. OpenGL における同次座標と正規化デバイス座標
-
OpenCV/HZ フレームワークには、画像座標系、カメラ座標系、ワールド座標系の 3 つの座標系があります。
-
OpenGL フレームワーク内には、画像座標系、カメラ座標系、世界座標系、および正規化デバイス座標 (NDC) の 4 つの座標系があります。OpenCV/HZ フレームワークに例えて、これらの座標系がどのように機能するのか、操作の順序やその他の必要な基本とともに説明します。
4. OpenCV/HZ フレームワークでのプロジェクション
図 1. OpenCV フレームワークにおける 3 つの座標系 (世界座標系、カメラ座標系、画像座標系) 間の関係の概略図。カメラによって観察されるシーンは、カメラ座標系の Z 軸正方向に位置します。画像座標系では、原点が左下隅にあり、Y 軸が上向き、つまりデータ マトリックス レイアウトの Y の反対方向であることに注意してください。詳細については、図 3 を参照してください。( x 0 , y 0 x_0, y_0バツ0、y0) は画像座標系の主点であり、カメラのキャリブレーション中に検出されるパラメータです。ピクセル座標系もありますが、これについてはあまり説明しません。
設定:
- KCV\mathbf{K}_{CV}KCV _: 上の三角形のサイズは3 × 3 3 \times 3です。3×3の内部カメラ キャリブレーション マトリックス。
- R\mathbf{R}R (回転): 直交のサイズは3 × 3 3 \times 33×3マトリックス。
- \mathbf{t}t (変換): 列ベクトル、サイズ 3。
- X \mathbf{X}X (ワールド ポイント): 列ベクトル、サイズ 4。
- x CV \mathbf{x}_{CV}バツCV _(イメージ ポイント): サイズ 3 の列ベクトル。
それで、
x CV = KCV [ R ∣ t ] X \mathbf{x}_{CV}=\mathbf{K}_{CV}[\mathbf{R}|\mathbf{t}]\mathbf{X}バツCV _=KCV _[ R ∣ t ] X同次座標を扱っているため、x CV \mathbf{x}_{CV}バツCV _正規化を実行します。ベクトルのインデックス付けが 0 ベースであると仮定します (つまり、最初の項目のインデックスは 0 であるため、3 番目の項目のインデックスは (2) になります。
x CV = x CV x CV ( 2 ) \mathbf{x}_{CV}=\frac{\mathbf{x}_{CV}}{\mathbf{x}_{CV}(2)}バツCV _=バツCV _( 2 )バツCV _
この操作の後、
x CV ( 0 ) = xcolx CV ( 1 ) = xrowx CV ( 2 ) = 1 \begin{aligned}\mathbf{x}_{CV}(0)&=x_{col}\\\mathbf{x}_ {CV}(1)&=x_{行}\\\mathbf{x}_{CV}(2)&=1\end{整列}バツCV _( 0 )バツCV _( 1 )バツCV _( 2 )=バツコル_=バツろw=1
if xcol x_{col}バツコル_またはxrow x_{row}バツろw画像空間内にないため、これらの点は画像上に描画されません。たとえば、ゼロ未満の画像座標は、画像サイズより大きい座標と同様に破棄されます。OpenGL でも同様のプロセスが発生します。次元 (z) を追加するだけです。
5.OpenGLフレームワークでのプロジェクション
図2。この図は、OpenCV 規約から OpenGL 規約への変換という非常に特殊なケースを対象としていることに注意することが重要です。通常、カメラ座標系の主軸は負の z です。ご使用のキャリブレーション マトリックスがこれに該当する場合は、中止して他のガイダンスを参照してください。
仮説的なキャリブレーション マトリックスの主軸がカメラ座標系の +z であると仮定します。そのため、OpenCV と同様に、パイプラインの開始点は次のようになります。
- 回転と平行移動によりR ∣ t \mathbf{R}\mid\mathbf{t}R∣t はワールドポイントを変換します。
- 次に、カメラの座標系が少し異なり、OpenGL ではニア プレーンとファー プレーンという概念があり、これらのパラメータはユーザーが定義します。KGL \mathbf{K}_{GL}によるKGL _、カメラ座標系の点を次の空間、つまり私が直方体空間と呼んでいる空間に変換します。これは適切な回転と平行移動ではなく、左手座標系への反映です。
- 次に、NDC \mathbf{NDC}NDC変換は、直方体空間を角度± 1 \pm 1± 1 Cube - 正規化されたデバイス座標系。
これでユーザー指定の変換がすべて完了します。座標が左手の正規化されたデバイス座標系になると、OpenGL はそれらの座標を画像座標に変換します。自分でトラブルシューティングを行って変換を実行するには、変換コーナー 1 に方程式があります。
設定:
- NDC \mathbf{NDC}NDC (正規化されたデバイス座標):4 × 4 4 \times 44×4 つの内部基準カメラ キャリブレーション マトリックス。
- KGL \mathbf{K}_{GL}KGL _:4 × 4 4 \times 44×4 つの内部基準カメラ キャリブレーション マトリックス。
- R\mathbf{R}R(回転):直交3 × 3 3 \times 33×3マトリックス。
- \mathbf{t}t (変換): 列ベクトル、サイズ 3。
- X \mathbf{X}X (世界座標点): 列ベクトル、サイズ 4。
- x GL \mathbf{x}_{GL}バツGL _(イメージ ポイント): 列ベクトル、サイズ 4。
それから、
x GL = NDC KGL [ R t 0 0 0 1 ] X \mathbf{x}_{GL}=\mathbf{NDC}\\\mathbf{K}_{GL}\begin{bmatrix}\&\mathbf{ ; R}&\&\mathbf{t}\\0&0&0&1\end{bmatrix}\mathbf{X}バツGL _=NDC K GL _[ 0R0 0t1】バツ
まだNDC を指定しないでください \mathbf{NDC}代わりに、 NDCマトリックスは、OpenGL のすべての座標系がどのように機能するかを指定します。しかし、心配しないでください。これらの項目を 1 つずつ説明していきます。
剪裁点/对象
まず、OpenGL には、近いプレーンと遠いプレーンの間に位置するプレーンの概念があります。ただし、在 OpenCV 框架中,我们认为位于主平面和正无穷远之间的任何点都是可视点
OpenGL ではこれは当てはまりません。これらの平面を考慮して、OpenCV では画像空間でのクリッピング (前のセクション) が非常に直感的です - 点が画像内にない場合 (∈ [ 0 ,cols ) × [ 0 , rows ) \in[0 ,列数)\倍[0, 行数)∈[ 0 ,コルス)_ _×[ 0 ,row s ) )、それは描画されません - OpenGL は、同様の目的を達成するために4 要素の同種ベクトルを使用します。
OpenGL の NDC 座標をx GL \mathbf{x}_{GL}として表します。バツGL _、これは 4 つの要素を持つ列ベクトルです。これは同次ベクトルでもあり、その最後の要素は通常文字 w で表されます。OpenCV 表記と同様に、 4 番目のエントリを除算することで画像点x GLを除算します (ここでもインデックス付けが 0 から始まると仮定します) \mathbf{x}_{GL}バツGL _正規化を実行します。最後の要素が 1 に等しい場合、4 要素ベクトルは正規化されるとします。
x GL = x GL x GL ( 3 ) \mathbf{x}_{GL}=\frac{\mathbf{x}_{GL}}{\mathbf{x}_{GL}(3)}バツGL _=バツGL _( 3 )バツGL _
以前と同様に、同様の結果が得られます。
x GL ( 0 ) = x NDC \mathbf{x}_{GL}(0)=x_{NDC}バツGL _( 0 )=バツNDC _ _x GL (1) = y NDC \mathbf{x}_{GL}(1)=y_{NDC}バツGL _( 1 )=yNDC _ _x GL (2) = z NDC \mathbf{x}_{GL}(2)=z_{NDC}バツGL _( 2 )=zNDC _ _x GL ( 3 ) = 1 \mathbf{x}_{GL}(3)=1バツGL _( 3 )=1これらの
座標は必ずしも画像座標である必要はありません。OpenGL では、オブジェクトの描画順序を計算するために Z 値が必要です。NDC 空間は各辺の長さが 2 の立方体で、その次元は[ − 1 , 1 ] × [ − 1 , 1 ] × [ − 1 , 1 ] [-1, 1]\times[-1, 1]\ です。倍[-1, 1][ − 1 、1 ]×[ − 1 、1 ]×[ − 1 、1 ]。Song Hoの Web サイトには、NDC スペースに関する素晴らしいイラストがいくつかあります。
if ( x GL ) (\mathbf{x}_{GL})( ×GL _) ∣ a ∣ > 1 \mid a\mid > 1を満たす∣ある∣>1、次にx GL \mathbf{x}_{GL}バツGL _は描画されません (むしろ、その座標を持つエッジはクリップされます)。つまり、座標が -1 未満または 1 より大きい場合、その座標は NDC 空間の外にあります。
OpenGL 操作の出力は、OpenCV で慣れ親しんでいるような実際の画像座標ではないことに気づいたかもしれません。つまり、データ行列内の座標ではありません。あなたが正しいです。OpenGL はこれを画像空間に変換しますが、これらの変換がどのように機能するかを理解することは役に立ちます。したがって、トラブルシューティングの目的で、以下の変換式ボックスを参照してください。
OpenGL NDC 座標を OpenGL 画像座標に変換するには、ximage, GL \mathbf{x}_{image, GL}バツ画像、GL _ _ _は 3 要素ベクトル、x GL \mathbf{x}_{GL}バツGL _すでに正規化されています:
ximage , GL = [cols 2 0 0 0 rows 2 0 0 0 1 ] x GL x_{image, GL} = \begin{bmatrix} \frac{cols}{2} & 0 & 0 \\ 0 & \frac{行}{2} & 0 \\ 0 & 0 & 1 \end{bmatrix} x_{GL}バツ画像、GL _ _ _=
2コルス_ _0002行_ _0001
バツGL _
OpenGL の画像座標系は OpenCV とは異なる方法で定義されているため (図 3 を参照)、これらの座標を OpenCV 座標に変換するにはさらに変換が必要であることに注意してください。
x CV = [ 1 0 0 0 − 1 0 0 rows 1 ] ximage , GL x_{CV} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & rows & 1 \end {bmatrix} x_{画像、GL}バツCV _= 1000− 1行_ _001 バツ画像、GL _ _ _
OpenGL 射影行列
まず、 OpenCV 行列で[ R ∣ t ] [\mathbf{R}\mid\mathbf{t}] を使用します。[ R∣t ]ですが、これに線を追加すると正方行列になります。例えば:
[ R ∣ t ] GL = [ R t 0 0 0 1 ] [\mathbf{R}\mid\mathbf{t}]_{GL} = \begin{bmatrix}\&\mathbf{R}&\&\ mathbf{t}\\0&0&0&1\end{bmatrix}[ R∣t ]GL _=[ 0R0 0t1】
OpenCV コンテキストに内部パラメータ行列KCV \mathbf{K}_{CV}があるとします。KCV _、その形式は次のとおりです。
KCV = [ α 0 x 0 0 β y 0 0 0 1 ] \mathbf{K}_{CV} = \begin{bmatrix}{\alpha}&0&x_0\\0&\beta&y_0\\0& 0 & 1\end{bmatrix }KCV _= ある000b0バツ0y01
次に、この変更された OpenCV 行列KCV \mathbf{K}_{CV}を使用します。KCV _対応する OpenGL 行列KGL \mathbf{K}_{GL}を作成するにはKGL _透視投影行列。注: OpenCV 組み込み行列の 1 行目と 2 列目のオフセット パラメーターは、Kyle Simek のガイドで説明されているのと同様の方法で、KGL \mathbf{K}_{GL}でも利用できる可能性があります。KGL _OpenGL でモデル化するには、表現内のこのパラメーターに負の値を指定します。ただし、私はこれをテストしていません。私はキャリブレーション時にオフセット パラメーターをゼロに設定することが多いので、テストするのはあなたに任せます。
これらの準備を完了し、画像の行と列を次元として使用した後、OpenGL コンテキストで 2 つの新しい変数と新しい内部パラメーター行列を定義します。
A = − (近く + 遠く) A= −(近く + 遠く)あ=− (近い_ _ _+f a r )
B = 近い∗ 遠い B= 近い∗ 遠いB=近い_ _ _∗KGLの場合= [ − α 0 − (cols − x 0 ) 0 0 β − ( rows − y 0 ) 0 0 0 AB 0 0 1 0 ] \mathbf{K}_{GL} = \begin{bmatrix} ;
KGL _=
− _0000b00− (コルス_ _−バツ0)− (行_ _−y0)あ100B0
NDC = [ − 2 列 0 0 1 0 2 行 0 1 0 0 − 2 遠い − 近い − ( 遠い + 近い ) (遠い − 近い ) 0 0 0 1 ] \mathbf{NDC} = \begin{bmatrix} {- ; \frac{2}{cols}}&0&0&1\\0&\frac{2}{rows}&0&1\\0&0&\frac{-2}{遠近}&\ frac{-(遠+近)}{(遠近)} \\ 0 & 0& 0 & 1\end{bmatrix}NDC= −コルス_ _20000行_ _20000近い−近い_ _ _ _ _− 2011( f a r − n e a r )− ( f a r + n e a r )1
さて、図 2 とこれらの行列をよく見てください。「おいおい、なぜ正と負、右手座標系と左手座標系などを行ったり来たりしているのですか。そうじゃないですか」と思われるかもしれません。しかし、注意すべき点がいくつかあります。私は、HZ の本と OpenCV が好きで、ある程度の知識を持つコンピューター ビジョン愛好家の観点から OpenGL パイプラインを取り上げています。手のジェスチャーのこと。実際には、さらに混乱するのは、OpenGL のカメラ座標系が主軸として負の Z 軸を持っていることです。まだ気づいていない場合のためにもう一度言います。負の Z 軸を主軸として想定してキャリブレーションされた行列がある場合は、他のリソースを確認してください。これが機能することを確認するために多くのテストを行いました。
本題に入る前に、カメラのモデルをどのようにテストしますか? Matlab や octave (無料) などのスクリプト言語を使用するのが最も簡単ですが、C++、Eigen、Python、またはその他の使い慣れた言語を使用して実行することもできます。
- ワールド空間から座標を取得します。これはオブジェクト (3D モデル) ファイルから取得できます。これはX \mathbf{X}ですX、同次座標を使用しているため、要素が 4 つあることを思い出してください。
- \mathbf{x}_{CV}の行列を使用して、OpenCV のイメージ プレーンに投影されたx CVを計算します。バツCV _。
- OpenCV マトリックスのすべての値を上記の OpenGL マトリックスに置き換えます。Matlab やオクターブは 1 ベースのインデックス付け言語であり、それに応じて 0 調整するわけではないことに注意してください。
- x CV \mathbf{x}_{CV}の場合バツCV _画像平面上では、x GL \mathbf{x}_{GL}バツGL _イメージ プレーンにも存在する必要があります。正規化後の場合、x GL \mathbf{x}_{GL}バツGL _の座標が -1 より小さいか +1 より大きい場合、クリップされます。何か問題が発生した場合は、ここでトラブルシューティングしてください。
- 変換を使用して、OpenCV 座標が OpenGL 座標と同等かどうかを確認します。
図 3. 上のサブ図は、OpenCV および OpenGL コンテキストの画像座標系を示しています。左下隅は、OpenGL 座標系の行 (r) インデックスと列 (c) インデックスの定義を示しています。OpenGL 座標系は画像座標系 (r=y) と同じです。OpenCV コンテキストの行列の原点は左上隅にあるため、glReadPixels() から取得した OpenGL 画像を垂直方向に反転する必要があります。
最後に、データ マトリックス (ピクセル座標系)のレイアウト、つまりデータ ピクセルを含む行と列のインデックス、および画像座標系に依存しないレイアウトに関する学術的なポイントで終わります。OpenGL のレイアウトは OpenCV とは異なります。これについては「変換角度」で説明し、図 3 の説明で詳しく説明します。
私のコード (ここ) は現在 OpenGL を使用して、正しい方向でシーンをレンダリングします。次に、glReadPixels() を使用してバッファを取得し、OpenCV Mat イメージ構造にそれを逆さまに書き込み、順方向になります。コードの詳細については、元のブログ投稿を参照してください。