web-dev-qa-db-ja.com

2Dポイントを3Dに逆投影するにはどうすればよいですか?

スクリーンスペースに4つの2Dポイントがあり、それらを3Dスペースに逆投影する必要があります。 4つのポイントのそれぞれが3Dで回転された剛体の四角形の角であり、四角形のサイズがわかっています。これから3D座標を取得するにはどうすればよいですか?

特定のAPIを使用しておらず、既存の投影行列もありません。これを行うための基本的な数学を探しています。もちろん、単一の2Dポイントを他の参照なしで3Dに変換するのに十分なデータはありませんが、4つのポイントがある場合、それらはすべて同じ平面上で互いに直角になっていることがわかります。そして、あなたはそれらの間の距離を知っていれば、そこからそれを把握できるはずです。残念ながら、どのように解決できるかはわかりません。

これは写真測量の傘下にあるかもしれませんが、それをグーグルで検索しても有用な情報は得られませんでした。

56
Joshua Carmody

申し分なく、私は答えを探してここに来て、簡単でわかりやすいものを見つけられなかったので、先に進んで、愚かではあるが効果的な(そして比較的簡単な)こと、モンテカルロ最適化を行いました。

非常に簡単に言えば、アルゴリズムは次のとおりです。既知の3D座標を既知の2D座標に投影するまで、投影行列をランダムに摂動します。

これがきかんしゃトーマスの静止写真です。

Thomas the Tank Engine

GIMPを使用して、グランドプレーン上の正方形であると思われるものの2D座標を見つけるとします(実際に正方形であるかどうかは、深度の判断に依存します)。

With an outline of the square

2D画像に4つのポイントがあります:_(318, 247)_、_(326, 312)_、_(418, 241)_、および_(452, 303)_。

慣例により、これらのポイントは3Dポイントに対応する必要があると言います:_(0, 0, 0)_、_(0, 0, 1)_、_(1, 0, 0)_、および_(1, 0, 1)_。つまり、y = 0平面の単位正方形。

これらの各3D座標を2Dに投影するには、4Dベクトル_[x, y, z, 1]_に4x4投影行列を乗算し、xおよびy成分をzで除算して実際に遠近感補正を取得します。これは多かれ少なかれ gluProject() が行うことですが、gluProject()も現在のビューポートを考慮し、個別のモデルビュー行列を考慮します(modelview行列は単位行列)。 OpenGLで動作するソリューションが実際に必要なので、gluProject()のドキュメントを見るのは非常に便利ですが、ドキュメントには式のzによる除算がないことに注意してください。

アルゴリズムは、ある投影行列から開始し、希望する投影が得られるまでランダムに摂動することを忘れないでください。そのため、4つの3Dポイントをそれぞれ投影し、必要な2Dポイントにどれだけ近づけるかを確認します。ランダムな摂動により、投影された2Dポイントが上でマークしたポイントに近づいた場合、そのマトリックスを初期(または以前の)推測に対する改善として保持します。

ポイントを定義しましょう:

_# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)
_

行列から始める必要があります。恒等行列は自然な選択のようです。

_mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]
_

実際に射影を実装する必要があります(これは基本的に行列乗算です)。

_def project(p, mat):
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)
_

これは基本的にgluProject()が行うことです。720と576はそれぞれ画像の幅と高さ(つまりビューポート)であり、576から減算して、上からy座標をカウントしたという事実をカウントしますOpenGLは通常、下からカウントします。 zを計算していないことに気付くでしょう。これは、ここでは本当に必要ないからです(ただし、OpenGLが深度バッファーに使用する範囲内に収まるようにするのは便利です)。

ここで、正しい解にどれだけ近いかを評価するための関数が必要です。この関数によって返される値は、あるマトリックスが別のマトリックスより優れているかどうかを確認するために使用するものです。私は距離の二乗の合計で行くことを選択しました、すなわち:

_# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)
_

マトリックスを摂動するには、ある範囲内でランダムな量だけ摂動する要素を選択します。

_def perturb(amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)
_

project()関数は実際には_mat[2]_をまったく使用しないことに注意してください。zを計算せず、y座標がすべて0であるため_mat[*][1]_値この事実を利用して、これらの値を混乱させないようにすることもできますが、これにより小さな高速化が得られますが、それは演習として残されます...)

便宜上、これまでに見つかった最良の行列に対してperturb()を何度も呼び出すことで、大部分の近似を行う関数を追加しましょう。

_def approximate(mat, amount, n=100000):
    est = evaluate(mat)

    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est
_

あとは、実行するだけです...:

_for i in xrange(100):
    mat = approximate(mat, 1)
    mat = approximate(mat, .1)
_

私はこれがすでにかなり正確な答えを与えていると思います。しばらく実行した後、私が見つけたマトリックスは次のとおりでした。

_[
    [1.0836000765696232,  0,  0.16272110011060575, -0.44811064935115597],
    [0.09339193527789781, 1, -0.7990570384334473,   0.539087345090207  ],
    [0,                   0,  1,                    0                  ],
    [0.06700844759602216, 0, -0.8333379578853196,   3.875290562060915  ],
]
_

約_2.6e-5_のエラーが発生します。 (計算で使用されなかった要素が実際に初期行列から変更されていないことに注意してください。これは、これらのエントリを変更しても評価の結果が変更されないため、変更が反映されないためです)

glLoadMatrix()を使用して、マトリックスをOpenGLに渡すことができます(ただし、最初にそれを転置することを忘れないでください。また、モデルビューマトリックスに単位マトリックスを読み込むことを忘れないでください)。

_def transpose(m):
    return [
        [m[0][0], m[1][0], m[2][0], m[3][0]],
        [m[0][1], m[1][1], m[2][1], m[3][1]],
        [m[0][2], m[1][2], m[2][2], m[3][2]],
        [m[0][3], m[1][3], m[2][3], m[3][3]],
    ]

glLoadMatrixf(transpose(mat))
_

これで、たとえばz軸に沿って平行移動して、トラックに沿ってさまざまな位置を取得できます。

_glTranslate(0, 0, frame)
frame = frame + 1

glBegin(GL_QUADS)
glVertex3f(0, 0, 0)
glVertex3f(0, 0, 1)
glVertex3f(1, 0, 1)
glVertex3f(1, 0, 0)
glEnd()
_

With 3D translation

確かに、これは数学的な観点からはあまりエレガントではありません。数値を入力するだけで直接(かつ正確な)答えを得ることができる閉形式の方程式は得られません。ただし、方程式の複雑化を心配することなく、追加の制約を追加できます。たとえば、高さも組み込む場合は、家の角を使用して(評価関数で)地面から屋根までの距離をある程度する必要があると言い、アルゴリズムを再度実行します。それで、はい、それはある種の総当たり攻撃ですが、動作し、うまく動作します。

Choo choo!

67
Vegard

これは、マーカーベースの拡張現実の古典的な問題です。

正方形のマーカー(2Dバーコード)があり、マーカーの4つのエッジを見つけた後、そのポーズ(カメラに対する平行移動と回転)を見つけたいとします。 概要図

私はこの分野への最新の貢献については知りませんが、少なくとも(2009年まで)RPPは上記のPOSITをアウトパフォームするはずでした(実際、これに対する古典的なアプローチです)リンクも参照してください。ソースを提供します。

(PS-私はそれが少し古いトピックであることを知っていますが、とにかく、投稿は誰かに役立つかもしれません)

7
dim_tz

D。 DeMenthonは、オブジェクトのモデルを知っているときに、2D画像の特徴点からオブジェクトのpose(空間での位置と向き)を計算するアルゴリズムを考案しました-これはあなたの正確な問題です

単一の画像からオブジェクトのポーズを見つける方法を説明します。画像内でオブジェクトの4つ以上の非同一平面上の特徴点を検出して一致させることができ、オブジェクト上の相対的なジオメトリを知っていると仮定します。

アルゴリズムはPositとして知られており、古典的な記事「25行のコードでのモデルベースのオブジェクトポーズ」で説明されています( そのウェブサイト 、セクション4)。

記事への直接リンク: http://www.cfar.umd.edu/~daniel/daniel_papersfordownload/Pose25Lines.pdf OpenCVの実装: http://opencv.willowgarage.com/ wiki/Posit

考え方は、正確なポーズに収束するまで、スケーリングされた正射投影によって遠近投影を繰り返し近似することです。

5
Julien-L

2次元空間から、構築可能な2つの有効な長方形が作成されます。元の行列投影法がわからなければ、どちらが正しいかわかりません。これは「ボックス」問題と同じです。2つの正方形が表示され、一方が他方の内側にあり、4つの内側の頂点が4つのそれぞれの外側の頂点に接続されています。ボックスをトップダウンまたはボトムアップで見ていますか?

そうは言っても、マトリックス変換Tを探しています...

{{x1、y1、z1}、{x2、y2、z2}、{x3、y3、z3}、{x4、y4、z4}} x T = {{x1、y1}、{x2、y2}、{ x3、y3}、{x4、y4}}

(4 x 3)x T =(4 x 2)

したがって、Tは(3 x 2)行列でなければなりません。したがって、6つの未知数があります。

次に、Tに関する制約のシステムを構築し、シンプレックスで解決します。制約を作成するには、最初の2点を通る線が2番目の2点を通る線と平行でなければならないことを知っています。ポイント1と3を通る線は、ポイント2と4を通る線に平行でなければなりません。1と2を通る線は、ポイント2と3を通る線に直交しなければなりません。 1と2の線の長さは3と4の線の長さと等しくなければなりません。1と3の線の長さは2と4の線の長さと等しくなければなりません。

これをさらに簡単にするために、長方形について知っているので、すべての辺の長さを知っています。

これにより、この問題を解決するための多くの制約が与えられます。

もちろん、戻るために、T-inverseを見つけることができます。

@Rob:はい、無限の数の投影がありますが、ポイントが長方形の要件を満たさなければならない無限の数のプロジェクトはありません。

@nlucaroni:はい。これは、投影に4つのポイントがある場合にのみ解決可能です。長方形が2点だけに投影される場合(つまり、長方形の平面が投影面に直交する場合)、これは解決できません。

うーん...家に帰ってこの小さな宝石を書くべきです。これは楽しそうですね。

アップデート:

  1. いずれかのポイントを修正しない限り、無限の数の投影があります。元の長方形の点を修正すると、2つの元の長方形が考えられます。
4
Jarrett Meyer

私のOpenGLエンジンの場合、次の切り取りはマウス/画面座標を3Dワールド座標に変換します。何が起こっているかの実際の説明については、コメントを読んでください。

/*関数:YCamera :: CalculateWorldCoordinates 
引数:xマウスx座標
 yマウスy座標
 vec座標の保存場所
戻り値:なし
説明:マウス座標を世界座標に変換します
 */

void YCamera :: CalculateWorldCoordinates(float x, float y, YVector3 *vec) { // START GLint viewport[4]; GLdouble mvmatrix[16], projmatrix[16];

GLint real_y;
GLdouble mx, my, mz;

glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);

real_y = viewport[3] - (GLint) y - 1;   // viewport[3] is height of window in pixels
gluUnProject((GLdouble) x, (GLdouble) real_y, 1.0, mvmatrix, projmatrix, viewport, &mx, &my, &mz);

/*  'mouse' is the point where mouse projection reaches FAR_PLANE.
    World coordinates is intersection of line(camera->mouse) with plane(z=0) (see LaMothe 306)

    Equation of line in 3D:
        (x-x0)/a = (y-y0)/b = (z-z0)/c      

    Intersection of line with plane:
        z = 0
        x-x0 = a(z-z0)/c  <=> x = x0+a(0-z0)/c  <=> x = x0 -a*z0/c
        y = y0 - b*z0/c

*/
double lx = fPosition.x - mx;
double ly = fPosition.y - my;
double lz = fPosition.z - mz;
double sum = lx*lx + ly*ly + lz*lz;
double normal = sqrt(sum);
double z0_c = fPosition.z / (lz/normal);

vec->x = (float) (fPosition.x - (lx/normal)*z0_c);
vec->y = (float) (fPosition.y - (ly/normal)*z0_c);
vec->z = 0.0f;
</ code>

}

4
user14208

Ronsアプローチのフォローアップ:長方形の回転方法を知っている場合、Z値を見つけることができます。

トリックは、射影を行った射影行列を見つけることです。幸いなことに、これは可能であり、さらには安価です。関連する数学は、Paul Heckbertの論文「Projecting Mappings for Image Warping」に記載されています。

http://pages.cs.wisc.edu/~dyer/cs766/readings/heckbert-proj.pdf

これにより、投影中に失われた各頂点の同質部分を回復できます。

これで、ポイントの代わりに4本の線が残ります(Ronが説明したように)。ただし、元の四角形のサイズはわかっているため、何も失われません。これで、Ronのメソッドと2Dアプローチからのデータを線形方程式ソルバーにプラグインして、zを解くことができます。そのようにして、各頂点の正確なZ値を取得します。

注:これは次の理由で機能します。

  1. 元の形状は長方形でした
  2. 3D空間での四角形の正確なサイズがわかっています。

それは本当に特別なケースです。

役に立てば幸いです、ニルス

2

ポイントが実際に長方形の一部であると仮定して、私は一般的なアイデアを与えています:

最大距離で2つのポイントを見つけます:これらは、おそらく対角線を定義します(例外:長方形がYZ平面にほぼ平行で、生徒用に残っている特別な場合)。それらをA、Cと呼びます。BAD、BCDの角度を計算します。これらは、直角と比較して、3D空間で方向を示します。 z距離について調べるには、投影された辺を既知の辺に相関させる必要があります。次に、3d投影法(1/zですか?)に基づいて、距離を知るための正しい軌道に乗っています。

2
tzot

誰も答えなければ、家に帰ったときに線形代数の本を出します。しかし、@ D G、すべての行列が可逆的であるとは限りません。 特異行列は可逆ではない (行列式= 0の場合)。射影行列mustは0と1の固有値を持ち、正方形であるため(これはべき等なので、p ^ 2 = p)。

簡単な例は、行列式= 0であるため[[0 1] [0 1]]であり、それは線x = yへの射影です!

1
nlucaroni

2Dサーフェスへの投影には、同じ2D形状に投影される3D長方形が無限にあります。

このように考えてください。3D四角形を構成する4つの3Dポイントがあります。それらを(x0、y0、z0)、(x1、y1、z1)、(x2、y2、z2)および(x3、y3、z3)と呼びます。これらの点をx-y平面に投影するとき、z座標をドロップします:(x0、y0)、(x1、y1)、(x2、y2)、(x3、y3)。

ここで、3D空間に投影し直したい場合、z0、..、z3をリバースエンジニアリングする必要があります。ただし、a)ポイント間の同じx-y距離を維持し、b)長方形が機能する形状を維持するz座標のセット。したがって、この(無限の)セットのメンバーは次のことを行います。{(z0 + i、z1 + i、z2 + i、z3 + i)| i <-R}。

@Jarrettを編集:これを解決し、3D空間に長方形ができたと想像してください。次に、その長方形をz軸に沿って上下にスライドさせることを想像してください。変換された長方形のこれらの無限の量は、すべて同じx-y投影を持ちます。 「正しい」ものを見つけたことをどうやって知っていますか?

編集#2:申し分なく、これは私がこの質問について行ったコメントからです-これについての推論へのより直感的なアプローチ。

机の上に紙を置くと想像してください。紙の各隅には、机に向かって下向きの無重力レーザーポインターが取り付けられています。紙は3Dオブジェクトであり、机の上のレーザーポインタードットは2D投影です。

さて、justレーザーポインタードットを見ることで、紙が机からどれだけ離れているかをどのように知ることができますか?

できません。用紙をまっすぐ上下に動かします。レーザーポインターは、用紙の高さに関係なく、机の同じ場所に光ります。

逆投影でZ座標を見つけることは、机だけのレーザーポインタードットに基づいて紙の高さを見つけようとするようなものです。

1
Rob Dickerson

形状が平面内の長方形であることがわかっている場合は、問題をさらに制限できます。確かに「どの」平面を把握することはできないので、z = 0で、角の1つがx = y = 0で、エッジがx/y軸に平行な平面にあることを選択できます。

したがって、3dのポイントは{0,0,0}、{w、0,0}、{w、h、0}、および{0、h、0}です。絶対サイズが見つからないことはかなり確信しているので、w/hの比率のみが関連しているため、これは不明です。

この平面に関連して、カメラは空間のある点cx、cy、czにあり、方向nx、ny、nz(これらの1つが冗長になるように長さ1のベクトル)を指し、focus_length/image_widthを持たなければなりません。 wの因子これらの数値は、3x3射影行列に変わります。

これにより、合計7つの未知数が得られます:w/h、cx、cy、cz、nx、ny、およびw。

合計8個の既知の4つのx + yペアがあります。

これは解決できます。

次のステップは、MatlabまたはMathmaticaを使用することです。

1
spitzak

はい、モンテカルロは機能しますが、この問題に対するより良い解決策を見つけました。このコードは完全に機能します(そしてOpenCVを使用します):

Cv2.CalibrateCamera(new List<List<Point3f>>() { points3d }, new List<List<Point2f>>() { points2d }, new Size(height, width), cameraMatrix, distCoefs, out rvecs, out tvecs, CalibrationFlags.ZeroTangentDist | CalibrationFlags.FixK1 | CalibrationFlags.FixK2 | CalibrationFlags.FixK3);

この関数は、既知の3Dおよび2Dポイント、画面のサイズを取得し、回転(rvecs [0])、変換(tvecs [0])、およびカメラの固有値のマトリックスを返します。必要なものはすべて揃っています。

1
Inflight

3Dから2Dに投影すると、情報が失われます。

単一ポイントの単純な場合、逆投影により、3D空間を通る無限の光線が得られます。

通常、ステレオスコピック再構成は2つの2D画像から開始し、両方を3Dに投影し直します。次に、生成された2つの3D光線の交差点を探します。

投影にはさまざまな形式があります。直交または遠近法。私はあなたが正射影を仮定していると推測していますか?

元の行列があると仮定した場合、3D空間に4つの光線があります。その後、3D長方形の寸法によって問題を制約し、解決を試みることができます。

2D投影面に平行ないずれかの軸の周りの回転は方向があいまいになるため、ソリューションは一意ではありません。言い換えると、2d画像がz軸に垂直である場合、3d長方形を時計回りまたは反時計回りにx軸を中心に回転すると、同じ画像が生成されます。 y軸についても同様です。

長方形の平面がz軸に平行である場合、さらに多くのソリューションがあります。

元の投影行列がないため、任意の投影に存在する任意のスケーリング係数によってさらにあいまいさが生じます。投影のスケーリングと3次元のz軸方向の平行移動を区別することはできません。これは、2D投影の平面ではなく、相互に関連している場合に3D空間の4点の相対位置のみに関心がある場合は問題になりません。

透視投影では物事が難しくなります...

1
morechilli

素晴らしい回答をしてくれた@Vegardに感謝します。コードを少し整理しました。

import pandas as pd
import numpy as np

class Point2:
    def __init__(self,x,y):
        self.x = x
        self.y = y

class Point3:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y
        self.z = z

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

def project(p, mat):
    #print mat
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point2(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)    

def perturb(mat, amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)
    return mat2

def approximate(mat, amount, n=1000):
    est = evaluate(mat)
    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

for i in xrange(1000):
    mat,est = approximate(mat, 1)
    print mat
    print est

.1を使用したおおよその呼び出しは機能しなかったため、削除しました。私もしばらくそれを走らせて、最後にそれが

[[0.7576315397559887, 0, 0.11439449272592839, -0.314856490473439], 
[0.06440497208710227, 1, -0.5607502645413118, 0.38338196981556827], 
[0, 0, 1, 0], 
[0.05421620936883742, 0, -0.5673977598434641, 2.693116299312736]]

0.02前後のエラーで。

1
BBDynSys