web-dev-qa-db-ja.com

球面座標を正距円筒投影座標に変換する方法は?

簡略化された質問

球面座標 (θ、φ)を 正方投影法 (「地理的投影法」とも呼ばれる)上の位置(x、y)に変換するにはどうすればよいですか?

その中で:

  • xは経度で、水平位置は-180〜180度です。
  • yは緯度、つまり垂直位置で、-90〜90度です。
  • θはtheta、水平角度(度)、(0,0,0)から球の表面上の点までのベクトルです。
  • φはphi、垂直角度(度)、(0,0,0)から球の表面上の点へのベクトルです。

元の質問を見つける前に、問題をよく理解していなかった頃に戻りましたが、このソリューションの実用的なアプリケーションを示すのにはまだ良いと思います。

環境

編集:元の質問のタイトルは次のとおりでした:写真を特定の角度で変形してパノラマ写真の一部にする方法

結果の(歪んだ/変換された)画像を正距円筒図法の立方体マップの対応する特定の場所に配置できるように、任意の角度で撮った写真を変換したい場合、どの手順を実行すればよいですか? 、またはパノラマ写真の投影ですか?

異なる投影法の間で変換する方法に関するリソースがたくさんあるので、どちらの投影法が最も簡単でも十分です。実際の写真からそのようなプロジェクションへのステップをどうやって実行したらよいのか分かりません。

カメラは固定位置に留まり、そこから任意の方向に回転できると想定しても安全です。これを行うために必要と思われるデータは、おそらく次のようなものです。

  • 物理カメラの水平角[-180, +180](例:+ 140deg)。
  • 物理カメラの垂直角[-90, +90](例:-30deg)。
  • 写真の解像度w x h(例:1280x720ピクセル)。
  • 写真の水平角度(例:70度)。
  • 写真の垂直角度(例:40度)。
  • レンズ補正a、b、cパラメータ(下記参照)。

私にはこのデータがあります。最初のステップは、レンズ補正を実行して、直線であるはずのすべての線が実際に直線になるようにすることです。そして、これはimagemagickの-​​ Barrel Distortion を使用して行うことができます。この場合、必要なのは、a、b、cの3つのパラメーターのみです。これを修正するために画像に適用される変換は簡単です。

次のステップに行き詰まっています。完全に理解していないか、検索エンジンが役に立たないかのどちらかです。ほとんどの結果は、既に与えられた投影の間の変換に関するものか、高度なアプリケーションを使用して写真をインテリジェントにつなぎ合わせるためのものです。これらの結果は、私の質問への回答に役立ちませんでした。

編集:私はおそらく図がそれをよりよく説明するのに役立つと考えました:)

問題は、与えられた写真Redを変換せずに正距円筒図法に配置できないことです。次の図は、問題を示しています。

Equirectangular projection with a "normal" photo

だから、私はRedを持っていて、それをGreenに変換する必要があります。 Blueは変換の違いを示しますが、これは水平/垂直角度に依存します。

11
Yeti

写真が固定点から撮影された場合、カメラはその点を中心にヨーとピッチのみを回転できます。次に、任意の半径の球を考慮することができます(計算では、半径1を使用することを強くお勧めします)。この場合、写真は長方形になりますsphere(カメラの視点から)。

ホライゾンケース

地平線(赤道)を表示している場合、垂直ピクセルは緯度を表し、水平ピクセルは経度を表します。地平線の単純なパノラマ写真の場合、それほど問題はありません。

Looking at the wireframe of the geo-sphere from (0,0,0)

ここでは、私たちの世界の地平線を大まかに調べます。つまり、カメラの角度はva = ~0です。そして、写真が幅70度、高さ40度であることがわかっている場合、経度の範囲は約70度、緯度の範囲は40度であることもわかるので、これはかなり簡単です。

わずかな歪みを気にしない場合、写真から(longitude,latitude)を任意のピクセルから計算する式(x,y)は簡単です。

photo_width_deg = 70
photo_height_deg = 30
photo_width_px = 1280
photo_height_px = 720
ha = 0
va = 0
longitude = photo_width_deg * (x - photo_width_px/2) / photo_width_px + ha
latitude = photo_height_deg * (y - photo_height_px/2) / photo_height_px + va

問題

しかし、カメラをより垂直に動かすと、この近似はまったく機能しません。

Looking at the wireframe of the geo-sphere from (0,0,0)

では、写真が撮られた垂直/水平角度を指定して、ピクセルを(x, y)の画像から(longitude, latitude)座標に変換するにはどうすればよいですか(va,ha)

解決

私にとって問題を解決した重要なアイデアはこれです:あなたは基本的に2つの球体を持っています:

  • photo-sphereカメラを中心にしています。
  • geo-sphere(正距円筒図法の球)と経度/緯度の座標。

Photo Sphere上のポイントの球面座標を知っていて、このポイントが別のカメラアングルで地球圏のどこにあるかを知りたいとします。

本当の問題

2つの球の間で計算を行うのは難しい球座標 だけを使用していることを理解する必要があります。 デカルト座標系 の計算ははるかに簡単です。デカルト座標系では、座標ベクトル[x,y,z]を乗算した 回転行列 を使用して任意の軸を中心に簡単に回転でき、回転した座標を取得できます。

警告:ここで知っておくことが非常に重要です異なる規則がありますx- axis、y- axis、およびz-の意味に関して==軸。どの軸が垂直軸で、どの軸がどこを指しているかは不明です。あなたは自分のために絵を描いて、これを決める必要があります。結果が間違っている場合は、おそらくこれらが混同されているためです。球座標のthetaおよびphiについても同様です。

本当の解決策

つまり、トリックは、Photo SphereからCartesianに変換し、次に回転を適用してから、球面座標に戻ることです

  1. 写真の任意のピクセルを取り、それが写真の中心から水平および垂直の両方に離れている相対度を計算します。
  2. Photo Sphereの球面座標をデカルト座標([x,y,z]ベクトル)に変換します。
  3. カメラが回転したように、回転行列を座標に適用します(ha,va)
  4. デカルト座標を球座標に変換すると、これらがあなたの経度と緯度になります。

コード例

// Photo resolution
double img_w_px = 1280;
double img_h_px = 720;
// Camera field-of-view angles
double img_ha_deg = 70;
double img_va_deg = 40;
// Camera rotation angles
double hcam_deg = 230;
double vcam_deg = 60;
// Camera rotation angles in radians
double hcam_rad = hcam_deg/180.0*PI;
double vcam_rad = vcam_rad/180.0*PI;
// Rotation around y-axis for vertical rotation of camera
Matrix rot_y = {
    cos(vcam_rad), 0, sin(vcam_rad),
    0, 1, 0,
    -sin(vcam_rad), 0, cos(vcam_rad)
};
// Rotation around z-axis for horizontal rotation of camera
Matrix rot_z = {
    cos(hcam_rad), -sin(hcam_rad), 0,
    sin(hcam_rad), cos(hcam_rad), 0,
    0, 0, 1
};

Image img = load('something.png');
for(int i=0;i<img_h_px;++i)
{
    for(int j=0;j<img_w_px;++j)
    {
        Pixel p = img.getPixelAt(i, j);

        // Calculate relative position to center in degrees
        double p_theta = (j - img_w_px / 2.0) / img_w_px * img_w_deg / 180.0 * PI;
        double p_phi = -(i - img_h_px / 2.0) / img_h_px * img_h_deg / 180.0 * PI;

        // Transform into cartesian coordinates
        double p_x = cos(p_phi) * cos(p_theta);
        double p_y = cos(p_phi) * sin(p_theta);
        double p_z = sin(p_phi);
        Vector p0 = {p_x, p_y, p_z};

        // Apply rotation matrices (note, z-axis is the vertical one)
        // First vertically
        Vector p1 = rot_y * p0;
        Vector p2 = rot_z * p1;

        // Transform back into spherical coordinates
        double theta = atan2(p2[1], p2[0]);
        double phi = asin(p2[2]);

        // Retrieve longitude,latitude
        double longitude = theta / PI * 180.0;
        double latitude = phi / PI * 180.0;

        // Now we can use longitude,latitude coordinates in many different projections, such as:
        // Polar projection
        {
            int polar_x_px = (0.5*PI + phi)*0.5 * cos(theta) /PI*180.0 * polar_w;
            int polar_y_px = (0.5*PI + phi)*0.5 * sin(theta) /PI*180.0 * polar_h;
            polar.setPixel(polar_x_px, polar_y_px, p.getRGB());
        }
        // Geographical (=equirectangular) projection
        {
            int geo_x_px = (longitude + 180) * geo_w;
            int geo_y_px = (latitude + 90) * geo_h;
            geo.setPixel(geo_x_px, geo_y_px, p.getRGB());
        }
        // ...
    }
}

これは、ある種の疑似コードです。行列とベクトルの乗算と回転を処理する行列ライブラリを使用することをお勧めします。

5
Yeti

うーん、多分あなたは一歩前に戻るべきだと思います。カメラの角度を考えてみてください(70mm程度)。しかし、背景画像は水平方向(ただし垂直方向)で360度です。両方のタイプの画像の遠近歪みを考慮してください。背景の画像については、垂直方向の意味で水平線のみが垂直方向に歪んでいないです。悲しいことに、その細い線だけです。歪みが大きくなるにつれて、上部または下部に到達します。

バレル歪みのように一定ではありませんが、地平線の垂直距離に依存します。

違いを理解するための最良の方法は、両方のタイプのカメラと、そこから投影されるはずのターゲットの側面を、そこから三角法、数学をとることです。

70mmの写真の場合、撮影した角度を知る必要があることに注意してください。 (または見積もり)

1
user3800527