
Chisme del Lince AstroPi09: la velocidad de la ISS con dos fotos
—Veintisiete mil kilómetros por hora —lee Víctor Vílchez, el Lince de la Noche, en la pantalla—. Eso dice internet que va la ISS.
—¿Y te lo crees? —pregunta la Gata Montesa.
—Hombre, lo dice la NASA.
—Lo dice la NASA porque lo mide. Nosotros también podemos. Con dos fotos.
La Gata abre dos imágenes que hizo una Astro Pi desde la estación: nubes, montañas y un trozo de costa. En la segunda, todo está un poco desplazado.
—Entre una y otra pasaron catorce segundos. Si averiguamos cuánto se ha movido el suelo en la foto, y cuánto suelo es cada píxel… ya tienes velocidad = distancia ÷ tiempo.
—¿Y quién busca los mismos puntos en las dos fotos? ¿Yo, con una lupa?
—Tú no. OpenCV. Encuentra mil detalles en cada foto y los empareja en una fracción de segundo.
El Lince se acerca a la pantalla. Una ciencia que cabe en dos fotos y unas pocas líneas de Python.
Introducción
En el AstroPi08 estimamos la velocidad de la ISS con un modelo de su órbita. Hoy vamos a calcular la velocidad de la ISS con Python a partir de datos de verdad: dos fotos reales tomadas por una Astro Pi en la estación.
Es el experimento estrella de la guía oficial de Astro Pi, Calculate the speed of the ISS using photos. Hoy no buscamos el programa perfecto: queremos que entendáis qué hace cada función y qué se calcula en cada paso.
Por qué este chisme
La velocidad de la ISS con Python es un ejemplo perfecto de cómo trabaja la ciencia con imágenes: se convierte una foto en números (píxeles, coordenadas, tiempos), se hacen cálculos con ellos y se llega a una magnitud física, la velocidad.
Además, es un experimento que muchos equipos de Mission Space Lab adaptan para la ISS: usa la cámara (un dato válido de la Astro Pi) y se puede comprobar con un valor conocido, unos 7,66 km/s.
La idea en una frase
velocidad = distancia que se ha movido el suelo ÷ tiempo entre las dos fotos
- El tiempo sale de la hora guardada dentro de cada foto (el EXIF).
- La distancia sale de cuántos píxeles se han movido los mismos detalles de una foto a otra, multiplicados por lo que mide un píxel sobre el suelo (el GSD).
Qué necesitamos
- La Raspberry Pi (o cualquier ordenador con Python y Thonny).
- Las dos fotos de ejemplo de la guía oficial: rpf.io/examplephoto. Usamos
atlas_photo_012.jpgyatlas_photo_013.jpg. - Dos librerías:
exif(para leer la hora) y OpenCV (para trabajar con las imágenes).
El simulador paso a paso
Antes de tocar el código, haced el simulador de la velocidad de la ISS. Usa las dos fotos reales y los datos que calcula OpenCV con ellas. En 6 pasos se lee la hora, se buscan los detalles, se emparejan, se calcula la distancia media, se pasa a kilómetros y se obtiene la velocidad. En cada paso se ve la función de Python que lo hace.

Instalar las librerías
En el terminal de la Raspberry Pi:
sudo apt install -y python3-opencv
pip3 install --break-system-packages exif
Después, descargad las fotos de rpf.io/examplephoto, descomprimid el zip y copiad atlas_photo_012.jpg y atlas_photo_013.jpg a ~/astropi/astropi09. No abráis las fotos con otro programa y las volváis a guardar: se puede perder el EXIF y, con él, la hora.
El código
Guardad este iss_speed.py en ~/astropi/astropi09, junto a las dos fotos. Es el código de la guía oficial, ordenado y comentado.
"""
Chisme del Lince AstroPi09 - The speed of the ISS from two photos
Based on "Calculate the speed of the ISS using photos" (Raspberry Pi Foundation,
CC BY-SA 4.0): https://projects.raspberrypi.org/en/projects/astropi-iss-speed
"""
from exif import Image
from datetime import datetime
import cv2
import math
def get_time(image):
"""Read the time the photo was taken from its EXIF data."""
with open(image, 'rb') as image_file:
img = Image(image_file)
time_str = img.get("datetime_original")
return datetime.strptime(time_str, '%Y:%m:%d %H:%M:%S')
def get_time_difference(image_1, image_2):
"""Seconds between the two photos."""
time_difference = get_time(image_2) - get_time(image_1)
return time_difference.seconds
def convert_to_cv(image_1, image_2):
"""Open both photos in black and white for OpenCV."""
return cv2.imread(image_1, 0), cv2.imread(image_2, 0)
def calculate_features(image_1, image_2, feature_number):
"""Find keypoints (corners, edges...) and their descriptors with ORB."""
orb = cv2.ORB_create(nfeatures=feature_number)
keypoints_1, descriptors_1 = orb.detectAndCompute(image_1, None)
keypoints_2, descriptors_2 = orb.detectAndCompute(image_2, None)
return keypoints_1, keypoints_2, descriptors_1, descriptors_2
def calculate_matches(descriptors_1, descriptors_2):
"""Match the same keypoints in both photos by brute force."""
brute_force = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = brute_force.match(descriptors_1, descriptors_2)
return sorted(matches, key=lambda x: x.distance)
def display_matches(image_1_cv, keypoints_1, image_2_cv, keypoints_2, matches):
"""Show the two photos side by side with lines between matches."""
match_img = cv2.drawMatches(image_1_cv, keypoints_1, image_2_cv,
keypoints_2, matches[:100], None)
resize = cv2.resize(match_img, (1600, 600), interpolation=cv2.INTER_AREA)
cv2.imshow('matches', resize)
cv2.waitKey(0)
cv2.destroyWindow('matches')
def find_matching_coordinates(keypoints_1, keypoints_2, matches):
"""(x, y) of every matched keypoint in each photo."""
coordinates_1 = []
coordinates_2 = []
for match in matches:
(x1, y1) = keypoints_1[match.queryIdx].pt
(x2, y2) = keypoints_2[match.trainIdx].pt
coordinates_1.append((x1, y1))
coordinates_2.append((x2, y2))
return coordinates_1, coordinates_2
def calculate_mean_distance(coordinates_1, coordinates_2):
"""Average distance in pixels that the keypoints have moved."""
all_distances = 0
merged_coordinates = list(zip(coordinates_1, coordinates_2))
for coordinate in merged_coordinates:
x_difference = coordinate[0][0] - coordinate[1][0]
y_difference = coordinate[0][1] - coordinate[1][1]
all_distances = all_distances + math.hypot(x_difference, y_difference)
return all_distances / len(merged_coordinates)
def calculate_speed_in_kmps(feature_distance, GSD, time_difference):
"""Pixels -> cm (GSD) -> km, then divided by the time."""
distance = feature_distance * GSD / 100000
return distance / time_difference
# ---------------------------------------------------------------
# THE IMPORTANT PART: run every step in order
# ---------------------------------------------------------------
image_1 = 'atlas_photo_012.jpg'
image_2 = 'atlas_photo_013.jpg'
time_difference = get_time_difference(image_1, image_2)
image_1_cv, image_2_cv = convert_to_cv(image_1, image_2)
keypoints_1, keypoints_2, descriptors_1, descriptors_2 = calculate_features(image_1_cv, image_2_cv, 1000)
matches = calculate_matches(descriptors_1, descriptors_2)
display_matches(image_1_cv, keypoints_1, image_2_cv, keypoints_2, matches)
coordinates_1, coordinates_2 = find_matching_coordinates(keypoints_1, keypoints_2, matches)
average_feature_distance = calculate_mean_distance(coordinates_1, coordinates_2)
speed = calculate_speed_in_kmps(average_feature_distance, 12648, time_difference)
print(f"Time between photos: {time_difference} s")
print(f"Matches: {len(matches)}")
print(f"Average distance: {average_feature_distance:.1f} pixels")
print(f"Speed of the ISS: {speed:.2f} km/s")
El código, función a función
1. El tiempo: get_time y get_time_difference
Cada foto guarda «datos ocultos» en su EXIF: la cámara, la posición… y la hora exacta, en datetime_original. get_time la lee y la convierte en un objeto datetime con strptime. get_time_difference resta las dos horas y devuelve los segundos.
En nuestras fotos: de las 23:31:57 a las 23:32:11 del 21 de abril de 2025. 14 segundos.
2. Las fotos para OpenCV: convert_to_cv
cv2.imread(foto, 0) abre cada foto en blanco y negro. Para buscar formas no hace falta el color, y así el cálculo va más rápido.
3. Los detalles: calculate_features
ORB (Oriented FAST and Rotated BRIEF) busca en cada foto puntos fáciles de reconocer: esquinas de nubes, bordes de montañas, manchas. Son los keypoints. Le pedimos como máximo 1000 por foto.
Para cada keypoint, ORB guarda un descriptor: una especie de huella dactilar de cómo es la imagen a su alrededor. Con esa huella se puede reconocer el mismo punto en la otra foto, aunque se haya movido.
4. Las parejas: calculate_matches
Ahora hay que saber qué punto de la foto 1 es el mismo que cuál de la foto 2. BFMatcher lo hace por fuerza bruta: compara cada huella de la foto 1 con todas las de la foto 2 y se queda con la más parecida. Con crossCheck=True solo acepta la pareja si también es la mejor en sentido contrario.
Con nuestras fotos salieron 368 parejas. display_matches las dibuja con líneas, y esta es la ventana que nos abrió en la Raspberry Pi:

5. Dónde está cada punto: find_matching_coordinates
Para cada pareja guarda las coordenadas (x, y) del punto en la foto 1 y en la foto 2, en dos listas.
6. Cuánto se ha movido: calculate_mean_distance
Para cada pareja calcula cuánto se ha desplazado el punto con Pitágoras: math.hypot(diferencia_x, diferencia_y). Luego hace la media de todas las distancias.
En nuestras fotos: 857,1 píxeles.
7. La velocidad: calculate_speed_in_kmps
El GSD (Ground Sample Distance) es cuánto suelo cubre un píxel. Para la cámara HQ de la ISS y fotos de 4056 × 3040 píxeles, la guía da 12648 centímetros por píxel: unos 126 metros.
distancia = 857,1 px × 12648 cm/px ÷ 100 000 = 108,4 km
velocidad = 108,4 km ÷ 14 s = 7,74 km/s
Se divide entre 100 000 porque un kilómetro tiene 100 000 centímetros.
Cómo probarlo
Ejecutad iss_speed.py. Se abrirá una ventana con las dos fotos y las parejas: haced clic dentro de la ventana y pulsad una tecla para cerrarla. Este fue nuestro resultado en la Raspberry Pi:
Time between photos: 14 s
Matches: 368
Average distance: 857.1 pixels
Speed of the ISS: 7.74 km/s
7,74 km/s, unos 27 900 km/h, frente a los unos 7,66 km/s que se suelen dar para la ISS. Con dos fotos y unas pocas líneas de Python.
Qué medimos realmente con la velocidad ISS Python
No todas las parejas son buenas
Si miráis cuánto se movió cada pareja, la mayoría se desplazó unos 700-800 píxeles. Pero unas cuantas están mal emparejadas: OpenCV confundió dos puntos parecidos y esas parejas «se movieron» mucho más o mucho menos. Esas parejas suben la media.

Es un buen ejemplo de medir no significa medir bien: el resultado se parece al valor de referencia, pero conviene saber de dónde salen los números. Mejorarlo es uno de los retos que propone la propia guía.
El GSD tiene letra pequeña
El valor 12648 solo vale para fotos de esa cámara y a resolución completa. Si reducís la foto, cada píxel cubre más suelo y el GSD cambia. Por eso, en la ISS, las fotos para este experimento deben guardarse a resolución completa.
Para el aula
- Predice: antes de ejecutar, ¿cuánto tiempo crees que pasó entre las dos fotos? ¿Cuántos kilómetros recorre la ISS en ese tiempo si va a 7,66 km/s?
- Cuentas: comprueba a mano el cálculo: 857,1 × 12 648 ÷ 100 000 ÷ 14.
- Unidades: ¿por qué se divide entre 100 000? Pasa 7,74 km/s a km/h.
- Comparar: ¿cuánto se aleja 7,74 km/s de 7,66 km/s, en tanto por ciento?
Retos
Reto 1
Haz que funcione: instala las librerías, descarga las fotos y obtén tus 7,74 km/s.
Reto 2
Cambia el 1000 de calculate_features por 100 y por 2000. ¿Cambia la velocidad? ¿Por qué?
Reto 3
Usa otras dos fotos de la carpeta de ejemplo. ¿Sale lo mismo?
Reto 4
En vez de la media, usa la mediana de las distancias (statistics.median). ¿Qué velocidad sale? ¿Por qué cambia?
Reto 5
Investiga por qué la ISS va a 7,66 km/s pero su «sombra» sobre el suelo va algo más despacio. Pista: está a unos 400 km de altura.
Qué hemos aprendido
- La velocidad de la ISS con Python se calcula con dos fotos: distancia ÷ tiempo.
- El tiempo sale del EXIF de cada foto, y la distancia, de cuántos píxeles se han movido los detalles.
- ORB busca detalles, la fuerza bruta los empareja y Pitágoras mide cuánto se han movido.
- El GSD convierte píxeles en kilómetros, pero solo para una cámara y una resolución concretas.
Próximamente: AstroPi10, NDVI: la salud de las plantas desde el espacio.
Para seguir investigando
- AstroPi08: ¿dónde está la ISS?
- AstroPi04: la cámara que ve lo invisible
- Guía oficial: Calculate the speed of the ISS using photos
- Documentación de OpenCV sobre detección de características
Código y fotos de ejemplo basados en el proyecto «Calculate the speed of the ISS using photos» de la Raspberry Pi Foundation, con licencia CC BY-SA 4.0.
Etiqueta:AIDARAC, Astro Pi, EXIF, ies monterroso, ISS, Mission Space Lab, montesteam, OpenCV, python, Raspberry Pi



