Homography行列の分解 OpenCV Python
OpenCVは便利なんですが不十分な情報や古い情報,ニセの情報がネット上に多すぎます. 基本的に公式のサイトを見ような.公式っぽいけど違うみたいな奴多すぎ. これがまともだったので公式と信じる.
はじめに:Homography 行列の推定とか
findHomographyにぶち込みます. 昔書いたテンプレート抽出のコードを参考にどうぞ.
キャリブレーションはがんばりましょう.decomposeはエラーにめっちゃ敏感です. ROSを使えばかなり簡単にキャリブレーションできます。100枚程度あれば結構いいスコアを出せるので頑張って。
過去にもエピポーラ幾何でなんかやってますね,この時なキャリブレーションが甘かったので結果をやり直してみたい. ossyaritoori.hatenablog.com
cv2.decomposeHomographyMat
公式?によると使い方は以下の通り.
retval, rotations, translations, normals = cv.decomposeHomographyMat( H, K[, rotations[, translations[, normals]]] )
生で吐くとこんな値になります.
(4, [array([[ 0.99069192, -0.0745761 , 0.1138768 ], [ 0.07708256, 0.99686649, -0.01776175], [-0.11219536, 0.02637434, 0.99333609]]), array([[ 0.99069192, -0.0745761 , 0.1138768 ], [ 0.07708256, 0.99686649, -0.01776175], [-0.11219536, 0.02637434, 0.99333609]]), array([[ 0.8447263 , 0.02653746, 0.53454021], [-0.1212945 , 0.98227444, 0.14291458], [-0.52127259, -0.18556049, 0.8329719 ]]), array([[ 0.8447263 , 0.02653746, 0.53454021], [-0.1212945 , 0.98227444, 0.14291458], [-0.52127259, -0.18556049, 0.8329719 ]])], [array([[ 0.30840663], [-0.09586909], [-0.81377852]]), array([[-0.30840663], [ 0.09586909], [ 0.81377852]]), array([[ 0.67210665], [ 0.17138383], [-0.53426701]]), array([[-0.67210665], [-0.17138383], [ 0.53426701]])], [array([[ 0.92347388], [ 0.23136337], [-0.30605063]]), array([[-0.92347388], [-0.23136337], [ 0.30605063]]), array([[ 0.64092667], [-0.04427804], [-0.76632399]]), array([[-0.64092667], [ 0.04427804], [ 0.76632399]])])
そもそもSVDを使ったdecompositionでは4つの解が出ます. したがって4つずつあるこの回転Rと並進T, 法線ベクトルnのペアから一番まともなペアを選びます.
ネコと学ぶ...さんの超わかりやすいサイトより,以下の画像を拝借.
この場合(a)のパターンが正しいです.
これの解き方は一般にわかっている点群を投影して,すべての距離が正になるかで判断します.
全容を理解するのにはInriaの2008年の文献がわかりやすそうです.(ただし長い)
解を絞る条件
要約すると
- カメラ平面から見て同じ側に有る:8点から4点へ(ここまではOpenCVがやる)
- Reference Point Visibility:4点から2点へ
- のこりは...?:normal vectorからなどでやる。
ということみたいで,事前情報なしで絞れるのはせいぜい2つまでで,それ以降は例えばテンプレートのnormalベクトルがどっちを向いているかなどの条件を使います。
例えば,どちらもこちら側にあるかというのは変換前とあとのnormalベクトルがどちらもカメラ側を向いているという条件をつけることで2つに絞れますし, テンプレートが真正面から撮ったものであればnormalベクトルは(0,0,1)になっているはずなのでそれとの内積を計算して大きさを評価するなどが挙げられます。
n_ref = np.float32([0,0,1]).reshape(3,1) for n in normals: print(np.dot(n.T,n_ref))
こんなかんじでやればいいですね。
サンプルコード
Gistにnotebookを上げておきました。多分これで本番環境でも行ける。 キャリブレーションはしっかりしておきましょう。 gist.github.com
解の絞り方,その他の手法などについて
連続的なフレームからの抽出の場合動作に制約をつけて式を組み直したり<例>,その他のセンサとの情報統合からこれを解くなどのアプローチがあります。