¿Cómo convertir coordenadas esféricas a coordenadas de proyección equirectangulares?
pregunta simplificada
¿ Cómo se convierte una coordenada esférica (θ, φ) en una posición (x, y) en una proyección equirectangular (también llamada 'proyección geográfica')?
En el cual:
- x es la longitud, la posición horizontal, de -180 a 180 grados.
- y es la latitud, la posición vertical, de -90 a 90 grados.
- θ es theta, el ángulo horizontal en grados, un vector desde (0,0,0) hasta un punto en la superficie de una esfera.
- φ es phi, el ángulo vertical en grados, un vector desde (0,0,0) hasta un punto en la superficie de una esfera.
A continuación encontrará la pregunta original, cuando no entendía bien el problema, pero creo que sigue siendo buena para mostrar cuál es una aplicación práctica de esta solución.
Contexto
Editar: El título original de la pregunta era: ¿Cómo transformar una foto en un ángulo determinado para que forme parte de una foto panorámica?
¿Alguien puede ayudarme con los pasos que debo seguir si quiero transformar una foto tomada en cualquier ángulo determinado de tal manera que pueda colocar la imagen resultante (distorsionada/transformada) en la ubicación específica correspondiente en una proyección equirectangular, mapa cúbico? ¿O alguna proyección fotográfica panorámica?
Cualquier proyección que sea más fácil de hacer es suficiente, porque hay muchos recursos sobre cómo convertir entre diferentes proyecciones. Simplemente no sé cómo dar el paso de una foto real a dicha proyección.
Es seguro asumir que la cámara permanecerá en una ubicación fija y puede girar en cualquier dirección desde allí. Los datos que creo que se requieren para hacer esto probablemente sean algo como esto:
- Ángulo horizontal de la cámara física
[-180, +180]
(por ejemplo, +140 grados). - Ángulo vertical de la cámara física
[-90, +90]
(por ejemplo, -30 grados). - Resolución de la foto
w x h
(por ejemplo, 1280x720 píxeles). - Ángulo horizontal de la foto (por ejemplo, 70 grados).
- Ángulo vertical de la foto (por ejemplo, 40 grados).
- Corrección de lente parámetros a, b, c (ver más abajo).
Tengo estos datos y creo que el primer paso es hacer la corrección de la lente para que todas las líneas que deberían ser rectas lo sean en realidad. Y esto se puede hacer usando imagemagick
Barrel Distortion , en el que solo necesitas completar tres parámetros: a, b y c. La transformación que se aplica a la imagen para corregir esto es sencilla.
Estoy estancado en el siguiente paso. O no lo entiendo del todo, o los motores de búsqueda no me ayudan, porque la mayoría de los resultados tratan de convertir entre proyecciones ya dadas o utilizar aplicaciones avanzadas para unir fotografías de forma inteligente. Estos resultados no me ayudaron a responder mi pregunta.
EDITAR: pensé que tal vez una figura ayudaría a explicarlo mejor :)
El problema es que una determinada fotografía roja no se puede colocar en la proyección equirectangular sin una transformación. La siguiente figura ilustra el problema.
Entonces, tengo Red y necesito transformarlo en Green . El azul muestra la diferencia en la transformación, pero esto depende del ángulo horizontal/vertical.
Si las fotografías se toman desde un punto fijo y la cámara solo puede girar su guiñada y cabeceo alrededor de ese punto. Entonces podemos considerar una esfera de cualquier radio (para las matemáticas, se recomienda utilizar un radio de 1). La foto tendrá una forma rectangular en esta esfera (desde la perspectiva de la cámara).
Caso horizonte
Si está mirando el horizonte (ecuador), los píxeles verticales representan la latitud y los píxeles horizontales representan la longitud. Para una simple fotografía panorámica del horizonte no hay mucho problema:
Aquí contemplamos aproximadamente el horizonte de nuestro mundo. Es decir, la cámara tiene ángulo va = ~0
. Entonces esto es bastante sencillo, porque si sabemos que la foto tiene 70 grados de ancho y 40 grados de alto, entonces también sabemos que el rango de longitud será de aproximadamente 70 grados y el rango de latitud de 40 grados.
Si no nos importa una ligera distorsión, entonces la fórmula para calcular el valor (longitude,latitude)
de cualquier píxel (x,y)
de la foto sería sencilla:
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
Problema
Pero esta aproximación no funciona del todo cuando movemos la cámara mucho más verticalmente:
Entonces, ¿cómo transformamos un píxel de la imagen en (x, y)
una (longitude, latitude)
coordenada dado el ángulo vertical/horizontal en el que se tomó la foto (va,ha)
?
Solución
La idea importante que me resolvió el problema es la siguiente: básicamente tienes dos esferas :
- La fotoesfera con la cámara en el centro.
- La geoesfera (esfera de proyección equirrectangular), con coordenadas de longitud/latitud.
Conoces la coordenada esférica de un punto en la fotoesfera y quieres saber dónde está este punto en la geoesfera con el ángulo de cámara diferente.
El verdadero problema
Tenemos que darnos cuenta de que es difícil hacer cálculos entre las dos esferas utilizando sólo coordenadas esféricas . Las matemáticas para el sistema de coordenadas cartesianas son mucho más simples. En el sistema de coordenadas cartesianas podemos rotar fácilmente alrededor de cualquier eje usando matrices de rotación que se multiplican por el vector de coordenadas [x,y,z]
para recuperar la coordenada rotada.
Advertencia: Aquí es muy importante saber que existen diferentes convenciones con respecto al significado de x
-axis, y
-axis y z
-axis. No se sabe qué eje es el vertical y cuál apunta hacia dónde. Sólo tienes que hacer un dibujo tú mismo y decidirte por este. Si el resultado es incorrecto, probablemente se deba a que están mezclados. Lo mismo ocurre con las coordenadas esféricas theta
y phi
.
La verdadera solución
Entonces, el truco consiste en transformar de fotoesfera a cartesiana, luego aplicar las rotaciones y luego volver a las coordenadas esféricas :
- Tome cualquier píxel de la foto y calcule los grados relativos en que se encuentra alejado del centro de la foto, tanto horizontal como verticalmente.
- Transforma las coordenadas esféricas de la fotoesfera en coordenadas cartesianas (
[x,y,z]
vectores). - Aplique matrices de rotación a las coordenadas tal como se giró la cámara
(ha,va)
. - Transforma las coordenadas cartesianas nuevamente a coordenadas esféricas, y estas serán tu longitud y latitud.
Código de ejemplo
// 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());
}
// ...
}
}
Tenga en cuenta que esto es solo una especie de pseudocódigo. Se recomienda utilizar una biblioteca de matrices que maneje sus multiplicaciones y rotaciones de matrices y vectores.
Mmmm, creo que tal vez deberías dar un paso atrás. Considere el ángulo de su cámara (70 mm aproximadamente). pero su imagen de fondo es de 360 grados en horizontal (pero también en vertical). Considere las distorsiones de perspectiva en ambos tipos de imágenes. Para la imagen de fondo, en sentido vertical sólo el horizonte no está distorsionado verticalmente . Lamentablemente es sólo una línea delgada. A medida que la distorsión aumenta, más llegas a la parte superior o inferior.
No es constante como en la distorsión de barril, pero depende de la distancia vertical del horizonte.
Creo que la mejor manera de darse cuenta de la diferencia es tomar una vista lateral de ambos tipos de cámaras y el objetivo sobre el que se supone que proyectan, desde ahí su trigonometría, matemáticas.
Tenga en cuenta que para la fotografía de 70 mm necesita saber el ángulo en el que se tomó. (o estimarlo)