web-dev-qa-db-fra.com

Comment convertir des coordonnées sphériques en coordonnées de projection équirectangulaires?

Question simplifiée

Comment convertir une coordonnée sphérique (θ, φ) en une position (x, y) sur une projection équirectangulaire (également appelée "projection géographique")?

Dans lequel:

  • x est la longitude, la position horizontale, de -180 à 180 degrés.
  • y est la latitude, la position verticale, de -90 à 90 degrés.
  • θ est thêta, l'angle horizontal en degrés, un vecteur de (0,0,0) à un point à la surface d'une sphère.
  • φ est phi, l'angle vertical en degrés, un vecteur de (0,0,0) à un point à la surface d'une sphère.

Ci-dessous, vous trouverez la question d'origine, à l'époque où je ne comprenais pas bien le problème, mais je pense qu'elle est toujours bonne pour montrer ce qu'est une application pratique de cette solution.

Le contexte

Edit: Le titre de la question d'origine était: Comment transformer une photo à un angle donné pour devenir une partie d'une photo panoramique?

Quelqu'un peut-il m'aider avec les étapes à suivre si je souhaite transformer une photo prise à un angle donné de telle manière que je puisse placer l'image résultante (déformée/transformée) à l'emplacement spécifique correspondant sur une projection équirectangulaire, carte de cube , ou toute projection photo panoramique?

Quelle que soit la projection la plus facile à faire, cela suffit, car il existe de nombreuses ressources sur la façon de convertir entre différentes projections. Je ne sais tout simplement pas comment passer d'une photo réelle à une telle projection.

Il est prudent de supposer que la caméra restera à un emplacement fixe et peut tourner dans n'importe quelle direction à partir de là. Je pense que les données nécessaires pour ce faire sont probablement quelque chose comme ceci:

  • Angle horizontal de la caméra physique [-180, +180] (par exemple + 140deg).
  • Angle vertical de la caméra physique [-90, +90] (par exemple -30deg).
  • Résolution de la photo w x h (par exemple 1280x720 pixels).
  • Angle horizontal de la photo (par exemple 70 degrés).
  • Angle vertical de la photo (par exemple 40 degrés).
  • Paramètres de correction d'objectif a, b, c (voir ci-dessous).

J'ai ces données, et je suppose que la première étape consiste à corriger l'objectif afin que toutes les lignes qui devraient être droites soient en fait droites. Et cela peut être fait à l'aide de imagemagickBarrel Distortion , dans lequel vous n'avez qu'à remplir trois paramètres: a, b et c. La transformation qui est appliquée à l'image pour corriger cela est simple.

Je suis bloqué sur la prochaine étape. Soit je ne le comprends pas complètement, soit les moteurs de recherche ne m'aident pas, car la plupart des résultats concernent la conversion entre des projections déjà données ou utilisent des applications avancées pour assembler intelligemment des photos. Ces résultats ne m'ont pas aidé à répondre à ma question.

EDIT: J'ai pensé que peut-être un chiffre aiderait à mieux l'expliquer :)

Le problème est qu'une photo donnée Rouge ne peut pas être placée dans la projection équirectangulaire sans transformation. La figure ci-dessous illustre le problème.

Equirectangular projection with a "normal" photo

Donc, j'ai Rouge , et je dois le transformer en Vert . Bleu montre la différence de transformation, mais cela dépend de l'angle horizontal/vertical.

11
Yeti

Si les photos sont prises à partir d'un point fixe et que l'appareil photo ne peut que faire pivoter son lacet et s'incliner autour de ce point. Ensuite, nous pouvons considérer une sphère de n'importe quel rayon (pour les mathématiques, il est fortement recommandé d'utiliser un rayon de 1). La photo aura une forme rectangulaire sur cette sphère (du point de vue de la caméra).

Cas Horizon

Si vous regardez l'horizon (équateur), les pixels verticaux représentent la latitude et les pixels horizontaux la longitude. Pour une simple photo panoramique de l'horizon il n'y a pas beaucoup de problème:

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

Ici, nous regardons à peu près l'horizon de notre monde. Autrement dit, la caméra a un angle va = ~0. Ensuite, c'est assez simple, car si nous savons que la photo est large de 70 degrés et haute de 40 degrés, nous savons également que la plage de longitude sera d'environ 70 degrés et la latitude de 40 degrés.

Si nous ne nous soucions pas d'une légère distorsion, alors la formule pour calculer le (longitude,latitude) à partir de n'importe quel pixel (x,y) de la photo serait facile:

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

Problème

Mais cette approximation ne fonctionne pas du tout lorsque nous déplaçons la caméra beaucoup plus verticalement:

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

Alors, comment pouvons-nous transformer un pixel de l'image à (x, y) à un (longitude, latitude) coordonnée en fonction d'un angle vertical/horizontal auquel la photo a été prise (va,ha)?

Solution

L'idée importante qui a résolu le problème pour moi est la suivante: vous avez essentiellement deux sphères :

  • La photo-sphère avec l'appareil photo au centre.
  • La géosphère (sphère de projection équirectangulaire), avec les coordonnées de longitude/latitude.

Vous connaissez la coordonnée sphérique d'un point sur la photo-sphère, et vous voulez savoir où se trouve ce point sur la géosphère avec l'angle de caméra différent.

Le vrai problème

Nous devons réaliser qu'il est difficile de faire des calculs entre les deux sphères en utilisant juste coordonnées sphériques . Le calcul pour le système de coordonnées cartésiennes est beaucoup plus simple. Dans le système de coordonnées cartésiennes, nous pouvons facilement tourner autour de n'importe quel axe en utilisant matrices de rotation qui sont multipliées par le vecteur de coordonnées [x,y,z] pour récupérer les coordonnées pivotées.

Attention: Ici, il est très important de savoir que il existe différentes conventions en ce qui concerne la signification de x- axe, y- axe et z- axe. On ne sait pas quel axe est vertical, et lequel indique où. Il vous suffit de faire un dessin pour vous-même et de décider à ce sujet. Si le résultat est erroné, c'est probablement parce qu'ils sont mélangés. Il en va de même pour theta et phi pour les coordonnées sphériques.

La vraie solution

Donc l'astuce consiste à transformer la photo-sphère en cartésienne, puis à appliquer les rotations, puis à revenir aux coordonnées sphériques :

  1. Prenez n'importe quel pixel sur la photo et calculez les degrés relatifs par rapport au centre de la photo horizontalement et verticalement.
  2. Transformez les coordonnées sphériques de la photo-sphère en coordonnées cartésiennes ([x,y,z] vecteurs).
  3. Appliquez des matrices de rotation aux coordonnées exactement comme la caméra a été tournée (ha,va).
  4. Transformez les coordonnées cartésiennes en coordonnées sphériques, et ce seront votre longitude et votre latitude.

Exemple de code

// 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());
        }
        // ...
    }
}

Remarque, ce n'est qu'une sorte de pseudo-code. Il est conseillé d'utiliser une matrice-bibliothèque qui gère vos multiplications et rotations de matrices et vecteurs.

5
Yeti

hmm, je pense que vous devriez peut-être faire un pas en arrière. Tenez compte de l'angle de votre caméra (environ 70 mm). mais votre image de fond est à 360 degrés à l'horizontale (mais aussi à la verticale). Tenez compte des distorsions de perspective sur les deux types d'images. Pour l'image de fond, dans un sens vertical seul l'horizon n'est pas déformé verticalement. Malheureusement, ce n'est qu'une mince ligne. Plus la distorsion augmente, plus vous atteignez le haut ou le bas.

Ce n'est pas constant comme dans la distorsion en barillet, mais cela dépend de la distance verticale de l'horizon.

Je pense que la meilleure façon de réaliser la différence est de prendre une vue latérale du type d'appareil photo et de la cible sur laquelle ils sont censés se projeter, à partir de là sa trigonométrie, les mathématiques.

Notez que pour l'image 70 mm, vous devez connaître l'angle de prise de vue. (ou estimez-le)

1
user3800527