わかりました、私は答えを探してここに来ましたが、シンプルで簡単なものが見つからなかったので、先に進み、ばかげているが効果的な (そして比較的単純な) ことを行いました: モンテカルロ最適化です。
非常に簡単に言えば、アルゴリズムは次のとおりです。既知の 3D 座標が既知の 2D 座標に投影されるまで、投影行列をランダムに摂動します。
これはきかんしゃトーマスの静止画です。
GIMP を使用して、地面上の正方形であると思われるものの 2D 座標を見つけたとします (実際に正方形であるかどうかは、深さの判断に依存します)。
2D 画像(318, 247)
で(326, 312)
、 、(418, 241)
、の 4 つのポイントを取得し(452, 303)
ます。
(0, 0, 0)
慣例により、これらの点は、(0, 0, 1)
、(1, 0, 0)
、およびの 3D 点に対応する必要があると言い(1, 0, 1)
ます。つまり、y=0 平面の単位正方形です。
これらの 3D 座標のそれぞれを 2D に投影するには、4D ベクトルに 4x4 の投影行列を掛けてから[x, y, z, 1]
、x 成分と y 成分を z で割って実際に遠近補正を取得します。これは多かれ少なかれgluProject()が行うことですgluProject()
が、現在のビューポートを考慮し、別のモデルビュー マトリックスを考慮します (モデルビュー マトリックスは恒等マトリックスであると想定できます)。gluProject()
私は実際に OpenGL で機能するソリューションが必要なので、ドキュメントを見ると非常に便利ですが、ドキュメントには式の 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.)
720 と576gluProject()
はそれぞれ画像の幅と高さ (つまり、ビューポート) であり、576 から減算して、y 座標を上から数えたという事実をカウントしますが、OpenGL は通常、それらを上から数えます。下。z を計算していないことに気付くでしょう。これは、ここでは実際には 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()
(z を計算しないため、関数は実際にはまったく使用しないことに注意してくださいmat[2]
。すべての 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
。(計算で使用されなかったと言った要素が実際には最初の行列から変更されていないことに注意してください。これは、これらのエントリを変更しても評価の結果が変わらないため、変更が反映されないためです。)
以下を使用して行列を OpenGL に渡すことができますglLoadMatrix()
(ただし、最初に転置することを忘れないでください。モデルビュー行列を恒等行列と共にロードすることを忘れないでください)。
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()
確かに、これは数学的な観点からはあまりエレガントではありません。数値を差し込むだけで直接的な(そして正確な)答えを得ることができる閉じた形式の方程式は得られません。ただし、方程式を複雑にすることを心配することなく、制約を追加できます。たとえば、高さも含めたい場合は、家のその隅を使用して、(評価関数で) 地面から屋根までの距離はまあまあである必要があると言って、アルゴリズムを再度実行します。そうです、それは一種のブルートフォースですが、機能し、うまく機能します。