Esta entrada es la parte 13 de 13 de la serie Fotogrametría asistida por IA
Reconstrucción fotogramétrica en 3D de una zona residencial. Jardines y hórreos

La Fase 9 dejó el sistema completo sobre el papel: planificación, vuelo autónomo, detección de objetos a dos niveles y procesamiento fotogramétrico en el servidor. Pero entre un sistema completo sobre el papel y uno en el que confías cuando el dron está a 40 metros de altura hay una distancia considerable, y este artículo va exactamente de recorrerla. Lo que viene a continuación es la crónica de una serie de pruebas que incluyen vuelos de producción, una revisión de código que destapó un problema serio de seguridad, el diseño de un sistema de recuperación ante fallos, y dos sesiones de pruebas de campo con catorce vuelos que demostraron algo que ya sospechaba: el firmware de DJI tiene opiniones propias, y no siempre coinciden con la documentación.

Los primeros vuelos de producción: el gimbal que ignoraba las fachadas

El problema de las fachadas transparentes

A principios de junio hice las dos primeras misiones reales con intención de producir resultados, y no de simplemente probar la aplicación: fotogrametría de dos viviendas y sus respectivas parcelas en la zona de Pontevedra. La primera dejó una lección inmediata: para las misiones de modelo 3D, la aplicación calculaba un ángulo de gimbal de -73,5 grados — casi cenital. El resultado en OpenDroneMap fue revelador: los edificios salían planos, como montículos sin fachadas. La cámara nunca llegaba a verlas. La corrección fue hacer el ángulo dependiente de la altitud, interpolando entre -50 grados a baja altura (fachadas cercanas, hace falta mucha inclinación) y -65 a gran altura (cobertura amplia sin desperdiciar imagen en el horizonte).

Una mejora evidente en la representación de las fachadas

Con el gimbal corregido, la siguiente misión fue otra historia: 186 waypoints a 20 metros, 173 fotos, y un resultado de ODM que no esperaba tan pronto — el 100% de las imágenes reconstruidas, una nube densa de 25,9 millones de puntos, GSD de 0,8 cm/píxel y un error 3D relativo de 8 centímetros. Los tejados se distinguen teja a teja.

Un buen nivel de detalle del tejado

Los vuelos reales también sirvieron para calibrar la estimación de batería, que venía siendo optimista. La especificación del Mini 3 Pro promete 34 minutos de vuelo (un consumo teórico del 2,94% por minuto), pero la realidad, medida en tres vuelos, fue un 4% por minuto clavado. El factor de corrección de la fórmula que calcula la cantidad de batería necesaria por misión pasó de 1,15 a 1,45, y desde entonces la estimación acierta.

Una revisión de código con ojos nuevos

Con el sistema ya volando misiones reales, le pedí a Claude una revisión completa del código y la documentación: calidad, bugs, mejoras y deuda pendiente, usando para ello el recién estrenado modelo de IA Claude Fable (del que hablaremos más adelante). El veredicto general fue amable (la arquitectura aguanta bien), pero la lista de hallazgos incluía uno que me quitó la tranquilidad de golpe.

Problemáticas encontradas. Generado con IA

El Mini 3 Pro no soporta misiones onboard, así que toda la navegación va por Virtual Stick: la app envía comandos de velocidad veinte veces por segundo, simulando los controles del mando. El detalle crítico es que, mientras Virtual Stick está activo, la autoridad de control la tiene la app y los sticks físicos del mando no responden. ¿Y qué pasaba si la misión lanzaba una excepción en pleno vuelo, como un timeout de GPS, por ejemplo? Pues que el código capturaba el error, pintaba «ERROR» en la pantalla… y nada más. El dron se quedaba flotando con el control automático activo, los sticks bloqueados, y sin que la app hiciera absolutamente nada por recuperarlo.

Había más: los bucles de navegación no tenían vigilancia de progreso (si el viento superaba la velocidad de crucero, la app insistiría contra el viento para siempre), el monitor de batería arrancaba tarde, una tabla de la base de datos crecía sin purgarse jamás, y ni un solo test en todo el proyecto. Pero el agujero de la recuperación era el que importaba.

Diseñando la recuperación: el matiz que lo cambia todo

Mi primera intuición fue implementar que, si algo falla, el dron ha de quedar en modo sobrevuelo con el Virtual Stick activo para que yo pueda hacer maniobras de emergencia con el mando. Claude me corrigió el modelo mental, y menos mal: es exactamente al revés. Virtual Stick activo significa mandos bloqueados. Si quieres devolverle el control al piloto, lo que hay que hacer es desactivar Virtual Stick. De esa conversación salió el diseño definitivo:

  • Error en vuelo con GPS válido: El dron queda en hover activo y arranca una cuenta atrás de 10 segundos hacia un RTH automático. Si no toco nada, vuelve solo a casa. Si prefiero decidir yo, la cancelo y elijo entre RTH, control manual o salir.
  • Error sin GPS: El RTH de firmware no puede navegar sin GPS, así que no hay cuenta atrás que valga. La aplicación libera el control inmediatamente y me avisa con vibración para que aterrice a mano.
  • Watchdogs: Si el ascenso no progresa en 15 segundos o el dron no se acerca al waypoint en 30, la misión aborta sola y entra en el flujo anterior. Se acabaron los bucles infinitos contra el viento.
Imagen figurativa del proceso de recuperación. Generada por IA

Todo esto se implementó y se probó en el teléfono el mismo día, incluyendo una prueba preliminar que no necesitó dron: pulsar «Volar» sin nada conectado hace que Virtual Stick falle tras tres intentos, y la recuperación entró limpia por el camino de «error en tierra». Pero el resto solo podía validarse volando. Así que preparé un guion de pruebas de campo: quince pruebas numeradas, con sus precondiciones, sus resultados esperados y su presupuesto de baterías.

Catorce vuelos y un descubrimiento incómodo

En la primera sesión de validación realicé seis vuelos. El núcleo funcionó a la primera: parada de emergencia con hover estable y RTH limpio, control manual con los sticks respondiendo al instante, y el watchdog aguantando sin falsos positivos una pausa deliberada de 46 segundos en mitad de una línea de vuelo. Pero la prueba T3 (pulsar el botón Pausa del mando durante la navegación autónoma) falló de la manera más inquietante posible: el dron la ignoró por completo. Tres pulsaciones y la misión siguió como si nada.

Representación conceptual de algunos de los hallazgos. Generada por IA

Esto invalidaba una suposición de seguridad que yo daba por sólida (y que la documentación del SDK sugiere): que los botones físicos del mando siempre interrumpen el control automático. En el Mini 3 Pro con el mando RC-N1, no. Ni el botón Pausa ni los mandos. Si la aplicación pierde el control y tú no tienes la app a mano, no hay escape… salvo el botón RTH dedicado, que afortunadamente sí funciona, algo que confirmé de nuevo en la segunda sesión.

La solución vino de un detalle del SDK: aunque las acciones del controlador son ignoradas durante el vuelo, sus activaciones sí llegan a la aplicación como telemetría del mando. Así que la solución era directa: hacer que la aplicación escuche dichas actuaciones durante la misión, de tal manera que si se mueve un control con decisión (más del 30% del recorrido, para evitar activaciones accidentales), la aplicación cancela la misión, libera Virtual Stick y me devuelve el control en el acto. El botón de pausa que el firmware se niega a respetar, reconstruido por software.

La misión que se completó en quince milisegundos

La prueba T11 era de regresión: agotar la batería hasta el umbral de RTH automático, cambiarla y reanudar la misión. El RTH saltó exactamente al 30%, en el waypoint 26 de 27. Hasta ahí, perfecto. Pero al reanudar, el dron subió a 40 metros (yo había volado a 20, con un cambio de configuración que no guardé), llegó a altitud… y ejecutó un RTH inmediato dando la misión por terminada.

Cambio del recorrido de la misión ante el cambio de batería. Generado por IA

El análisis de los registros de vuelo con Claude lo dejó cristalino, y con una precisión asombrosa: entre «Iniciando navegación» y «Misión completada» pasaron quince milisegundos. La causa era un fallo de diseño que ningún test de laboratorio habría encontrado: el progreso de reanudación se guarda como un índice («ibas por el waypoint 26»), pero la rejilla de waypoints se recalcula al cargar la misión a partir del polígono y la configuración. Mi vuelo a 20 metros generó 27 waypoints; la configuración guardada, a 40 metros, generaba solo 7. Índice 26 sobre una lista de 7: el bucle se saltó todos los waypoints y finalizó la misión.

La corrección tiene tres capas: la configuración con la que realmente vuelas se persiste al despegar, el total de waypoints del plan se guarda junto al progreso, y al reanudar se valida que ambas rejillas coincidan y, en caso de no hacerlo, la aplicación desactiva la reanudación y te explica por qué, en lugar de despegar para nada.

El límite de altura fantasma

Para probar el watchdog de ascenso de forma segura y reproducible se me ocurrió un truco: limitar la altura máxima del dron a 20 metros en la aplicación oficial de DJI Fly y configurar la misión a 40. El dron se clavaría en el límite, el watchdog saltaría a los 15 segundos, y podría ver la cuenta atrás de RTH en condiciones controladas. Pues bien: configuré el límite en DJI Fly, lo verifiqué, volé… y el dron subió a 40 metros sin inmutarse. El límite configurado en DJI Fly nunca llegó a persistirse en el dron.

El problema de la altura fantasma. Generado por IA

La alternativa fue gestionar el límite desde la propia aplicación, escribiéndolo directamente al mando del dron por el SDK y releyéndolo para confirmar que está aplicado. Con eso, la prueba salió de libro: el dron se clavó en 20 metros, a los 15 segundos saltó «Ascenso estancado», vibró el teléfono, apareció la cuenta atrás… y exactamente 10 segundos después (los logs lo certifican: 16:16:10 → 16:16:20) el dron inició el retorno automático. Repetí el escenario cancelando la cuenta atrás, tomando el control manual, y pulsando RTH a mitad de cuenta, todo según lo diseñado.

Pero el firmware guardaba una última sorpresa: el límite de altura vuelve solo a 120 metros después de cada misión. Y también al reiniciar el dron. El valor que escribe el SDK es, simplemente, volátil. Como no puedo arreglar el firmware, la aplicación lo compensa: guarda el límite que tú quieres, lo relee antes de cada despegue, lo reaplica si el mando lo ha reseteado, y si la misión planificada supera el límite efectivo, aborta en tierra con un mensaje claro en lugar de descubrirlo a 20 metros de altura.

1.573 puntos de telemetría en el limbo

El último hallazgo no fue del firmware sino de mi propio diseño. El módulo de auditoría envía la posición del dron al servidor cada segundo durante el vuelo y, cuando no hay red, guarda un registro temporal de los puntos en el teléfono para enviarlos después. Suena bien hasta que haces la pregunta correcta: ¿cuándo es «después»? El envío sólo corría durante la misión, y el servidor sólo es accesible desde la red de casa, ya que hay que suponer que en el campo puede no haber conexión. Resultado: 1.573 puntos de telemetría acumulados desde el primer vuelo de pruebas de la serie, sin que nadie los fuera a enviar jamás.

Cuando la telemetría se queda en el limbo. Generado por IA

Claude propuso la solución obvia: un worker que lo envíe todo automáticamente al detectar red, y aquí fui yo quien corrigió el diseño: no quiero que todo se suba solo. El procesamiento fotogramétrico ya es deliberadamente manual (yo decido qué misiones pasan por ODM), y la telemetría debe seguir el mismo criterio. El modelo final tiene dos vías: un botón en Opciones que muestra cuánto hay pendiente («1.573 puntos de 14 vuelos») y lo envía cuando yo quiera, y un envío oportunista que adjunta la telemetría de una misión concreta cuando decido procesar sus fotos: si esa misión va al servidor, su trayectoria viaja con ella.

El primer envío real, por cierto, destapó un bug más: el servidor rechazaba la telemetría de vuelos que no constaban en su base de datos, y los vuelos de campo (sin red) nunca habían llegado a registrarse. El flujo ahora registra cada vuelo antes de enviar sus puntos (la operación es idempotente, así que repetirla es inocua). Resultado verificado: buffer a cero y las trayectorias de los 14 vuelos de campo visibles en el visor del servidor.

Claude Fable 5: El Cerebro de la Bestia

Si algo define esta etapa del proyecto es la velocidad del ciclo campo–análisis–corrección. Volví de la primera sesión con seis vuelos y un puñado de fallos anotados; conecté el teléfono, y Claude descargó los registros de vuelo, cruzó mis observaciones con las marcas de tiempo y diagnosticó las tres causas raíz, incluyendo un desfase numérico de presentación de los waypoints, que resultó no ser un bug de lógica sino tres vistas usando dos convenciones distintas de numeración. Las correcciones estaban implementadas, probadas en el dispositivo y subidas al repositorio antes de la segunda sesión de vuelo, esa misma tarde. El mismo día: bugs encontrados en el campo por la mañana, corregidos a mediodía, validados en el aire por la tarde.

Claude Fable 5. Imagen oficial

Hay un detalle de contexto que merece mención: toda esta etapa la ha llevado Claude Fable 5, el modelo que Anthropic lanzó el 9 de junio, literalmente la víspera de las pruebas de campo. Fable 5 es el primer modelo de clase Mythos disponible al público: Mythos 5, su hermano sin restricciones en áreas de ciberseguridad, queda reservado a un grupo reducido de especialistas en ciberseguridad, y Fable es esa misma base con salvaguardas para uso general. Es un escalón por encima de Opus, hasta ahora el tope de gama. En la práctica, para este proyecto se notó en dos cosas: el millón de tokens de contexto permitió que la revisión completa del código (12.500 líneas), el análisis de los flight logs y las correcciones convivieran en una sola sesión de trabajo sin perder el hilo, y el razonamiento adaptativo hizo el ciclo de diagnóstico (como cazar los 15 milisegundos de la reanudación fantasma cruzando tres logs) notablemente más fino que en fases anteriores de la serie. No es un anuncio: es la constatación de que la herramienta también ha ido evolucionando debajo del proyecto mientras el proyecto evolucionaba sobre ella.

También hubo una cura de humildad para la IA, que me parece justo contar. Entre los hallazgos de la revisión de código había uno aparentemente de manual: el mapa de MapLibre no recibía los eventos de ciclo de vida de Android, algo que toda la documentación recomienda corregir. Claude lo «corrigió»… y el mapa dejó de renderizar por completo. Hicieron falta cuatro builds comparativos para aislar que MapLibre 11.8 gestiona su render automáticamente y que cualquier integración manual del ciclo de vida (incluso la más mínima) rompe la carga del estilo. La solución correcta era no tocar nada, y dejarlo escrito en el código para que nadie lo reintente. La lección vale para humanos y para modelos: lo que la teoría recomienda no siempre sobrevive al contacto con el dispositivo.

Resultado final

El guion de pruebas terminó con un estupendo resultado de 15 sobre 15. La doctrina de seguridad del sistema quedó validada en vuelo real y dice así: durante la navegación autónoma hay tres vías de escape, que son el botón de emergencia de la app, mover un control del mando (la aplicación lo detecta y libera el control), y el botón RTH físico. El botón Pausa del mando, oficialmente inerte. Si algo falla en el aire, el dron queda en hover y vuelve solo a casa en 10 segundos salvo que yo decida otra cosa; si falla el GPS, me devuelve los controles al instante. Y todos los descubrimientos -el límite volátil, el botón fantasma, la misión de los 15 milisegundos- están documentados en el registro de pruebas del repositorio, con sus registros de vuelo como evidencia.

La aplicación, que empezó esta serie planificando rejillas sobre un mapa, ahora sobrevive a sus propios errores, desconfía del firmware con conocimiento de causa, y ha producido su primer modelo 3D de calidad: 25,9 millones de puntos con 8 centímetros de error. Para lo que viene, hay dos caminos abiertos: la Fase 10 (multi-dron, capas WMS del Catastro y SIGPAC, mejoras de UX) y una Fase 11 de endurecimiento (tests unitarios del motor de cálculo, seguridad del servidor y deuda técnica) que la revisión de código dejó perfectamente inventariada. Pero eso será otro artículo.

Navegación de la serie<< Aplicación Android para fotogrametría con IA. Fase 9: Detección inteligente de objetos a dos niveles, dispositivo y servidor

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.