{"id":11828,"date":"2026-05-10T21:54:41","date_gmt":"2026-05-10T19:54:41","guid":{"rendered":"https:\/\/bitacora.eniac2000.com\/?p=11828"},"modified":"2026-05-10T21:54:45","modified_gmt":"2026-05-10T19:54:45","slug":"aplicacion-android-para-fotogrametria-con-ia-fase-9-deteccion-inteligente-de-objetos-a-dos-niveles-dispositivo-y-servidor","status":"publish","type":"post","link":"https:\/\/bitacora.eniac2000.com\/?p=11828","title":{"rendered":"Aplicaci\u00f3n Android para fotogrametr\u00eda con IA. Fase 9: Detecci\u00f3n inteligente de objetos a dos niveles, dispositivo y servidor"},"content":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 12 de 12 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=fotogrametria-asistida-por-ia\" class=\"series-1957\" title=\"Fotogrametr\u00eda asistida por IA\">Fotogrametr\u00eda asistida por IA<\/a><\/div>\n<p>La <a href=\"https:\/\/bitacora.eniac2000.com\/?p=11773\" target=\"_blank\" rel=\"noreferrer noopener\">Fase 8<\/a> dej\u00f3 cerrado el panel de condiciones de vuelo: meteorolog\u00eda de AEMET, \u00edndice geomagn\u00e9tico Kp de la NOAA y monitorizaci\u00f3n GPS en tiempo real. Con toda esa informaci\u00f3n, el piloto sabe si es buen momento para volar. Pero una vez en el aire, \u00bfqu\u00e9 ve el dron? Hasta ahora, la \u00fanica respuesta llegaba con retraso: el m\u00f3dulo de auditor\u00eda del servidor procesaba el <em>stream<\/em> RTMP con un YOLOv8n gen\u00e9rico y, francamente, los resultados dejaban bastante que desear: en un vuelo de validaci\u00f3n del proceso de cuatro minutos se realizaron 105 detecciones, la mayor\u00eda falsos positivos. La Fase 9 nace para resolver exactamente eso: darle ojos inteligentes al sistema, tanto en el aire como en tierra.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"687\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/fotogrametria-fase9-1024x687.jpg\" alt=\"\" class=\"wp-image-11833\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/fotogrametria-fase9-1024x687.jpg 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/fotogrametria-fase9-300x201.jpg 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/fotogrametria-fase9-768x516.jpg 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/fotogrametria-fase9.jpg 1168w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Ilustraci\u00f3n de lo desarrollado en Fase 9. Generada por IA<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">La idea inicial: Roboflow<\/h2>\n\n\n\n<p>Mi primera intuici\u00f3n fue hacer uso de Roboflow. Pod\u00eda desplegar un MCP integrado en Claude Code para tener este entorno habilitado en el flujo de desarrollo asistido por IA, y la plataforma promete un pipeline completo: anotaci\u00f3n, entrenamiento y despliegue. Pero creo que es conveniente dar un peque\u00f1o paso atr\u00e1s, y explicar lo que es un MCP.<\/p>\n\n\n\n<p>Un MCP, o Model Context Protocol, es un modelo de integraci\u00f3n que permite que un agente de inteligencia artificial, como Claude Code, se conecte con herramientas externas para interactuar directamente con ellas, dentro de su propio flujo de trabajo, sin necesitar interacciones manuales entre distintos sistemas por parte del usuario, lo que incrementa de manera dram\u00e1tica las capacidades del agente de IA, ya que no s\u00f3lo puede <em>pedir<\/em> al usuario que haga tal o cual tarea en el sistema externo, sino que puede usarlo directamente, e incluso evaluar el resultado de esa interacci\u00f3n. Sencillamente soberbio. Sus principales ventajas son las siguientes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Interoperabilidad Total<\/strong>: Las integraciones eran silos cerrados para pasar a ser esquemas interoperables. Un servidor MCP escrito para una base de datos de Postgres funciona igual de bien en <strong>Claude Desktop<\/strong>, en <strong>Claude Code<\/strong> o en cualquier otro IDE que adopte el est\u00e1ndar. No hay que reinventar la rueda.<\/li>\n\n\n\n<li><strong>Contexto en Tiempo Real<\/strong>: En lugar de copiar y pegar logs o fragmentos de c\u00f3digo, el MCP permite que la IA \u00abpregunte\u00bb a la fuente de verdad en el momento exacto. Si el estado de un servidor cambia, Claude lo sabe porque tiene un canal directo de comunicaci\u00f3n.<\/li>\n\n\n\n<li><strong>Seguridad y Control Local<\/strong>: <\/li>\n\n\n\n<li>A diferencia de otras soluciones que requieren subir todos tus datos a la nube para que la IA los procese, los <strong>servidores MCP<\/strong> pueden ejecutarse localmente en tu m\u00e1quina. T\u00fa decides qu\u00e9 archivos o qu\u00e9 datos espec\u00edficos permites que el modelo consulte, manteniendo la privacidad de la informaci\u00f3n sensible.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"687\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/uso-mcp-1024x687.jpg\" alt=\"\" class=\"wp-image-11834\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/uso-mcp-1024x687.jpg 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/uso-mcp-300x201.jpg 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/uso-mcp-768x516.jpg 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/uso-mcp.jpg 1168w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Uso de servidores MCP para extender las capacidades d Claude Code. Generada por IA<\/figcaption><\/figure>\n\n\n\n<p>Volviendo a la mesa de dibujo, como dec\u00eda, le ped\u00ed a Claude que evaluara la propuesta y, adicionalmente, inclu\u00ed en el proceso de evaluaci\u00f3n de la misma una validaci\u00f3n del dise\u00f1o preliminar contra ChatGPT, que precisamente tengo configurado como m\u00f3dulo de consulta desde Claude Code a trav\u00e9s de su propio MCP. La raz\u00f3n de esto es poderlo utilizar como un \u00abasesor externo\u00bb a Claude Code. Y en este caso, le ped\u00ed que hiciera la funci\u00f3n de <em>abogado del diablo.<\/em><\/p>\n\n\n\n<p>El veredicto fue demoledor. Con el plan gratuito de Roboflow, la exportaci\u00f3n a TFLite est\u00e1 bloqueada, los datos deben ser p\u00fablicos, y las limitaciones de inferencia lo hacen inviable para un proyecto que aspira a funcionar en una combinaci\u00f3n de procesamiento <em>offline<\/em> y en el propio dispositivo Android. ChatGPT, en su rol de adversario de razonamiento, identific\u00f3 adem\u00e1s que el acoplamiento a una plataforma comercial no ten\u00eda sentido para un proyecto personal sin \u00e1nimo de lucro inmediato. Punto para el abogado del diablo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La alternativa: Ultralytics a coste cero<\/h2>\n\n\n\n<p>El plan B fue hacer uso, directamente, de Ultralytics. Las razones para ello eran de peso. En primer lugar, la licencia AGPL-3.0 lo hacen perfectamente v\u00e1lida para uso personal. En segundo lugar, el poder emplear YOLOv8n como arquitectura, en su configuraci\u00f3n nano, de 6 MB, funciona perfectamente en CPU frente a GPU. Tercero, hace posible utilizar VisDrone como <em>dataset<\/em> de entrenamiento (se trata de 10.000 im\u00e1genes a\u00e9reas capturadas con drones DJI, lo que es exactamente mi dominio). Cuarto, la posibilidad de hacer entrenamiento gratuito en Google Colab o Kaggle con GPU T4. Y por \u00faltimo, la posibilidad de realizar exportaci\u00f3n a todos los formatos que necesitaba: PyTorch para el servidor, TFLite INT8 para Android. Y todo ello a coste cero. Una aproximaci\u00f3n muy completa para resolver el escenario que ten\u00eda encima de la mesa.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/ultralytics-1024x559.png\" alt=\"\" class=\"wp-image-11835\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/ultralytics-1024x559.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/ultralytics-300x164.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/ultralytics-768x419.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/ultralytics.png 1408w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">El plan B en toda su gloria. Generada por IA<\/figcaption><\/figure>\n\n\n\n<p>Aun as\u00ed, antes de decidir, le ped\u00ed a Claude que usara el MCP de NotebookLM para hacer una investigaci\u00f3n profunda sobre la viabilidad de usar Ultralytics en mi <em>hardware<\/em> (recordemos, un servidor HPE ProLiant DL360p Gen8 con Xeon E5 y 96 GB de RAM, pero sin GPU). La conclusi\u00f3n fue clara: para realizar inferencia en CPU sobre im\u00e1genes est\u00e1ticas de alta resoluci\u00f3n, el rendimiento era m\u00e1s que suficiente aplicando SAHI (Slicing Aided Hyper Inference) para dividir cada foto en mosaicos de 640&#215;640. Ya s\u00f3lo era cuesti\u00f3n de ponerse a ello, para lo que estructur\u00e9 el proceso en cuatro pasos bien diferenciados.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Primer paso. Entrenamiento del modelo<\/h2>\n\n\n\n<p>La primera tarea fue entrenar un YOLOv8n especializado en vista a\u00e9rea. El servidor ODM ya ten\u00eda un <em>script<\/em> de entrenamiento (<code>train_visdrone.py<\/code>) preparado desde la Fase 5, pero hab\u00eda fallado al intentar ejecutarlo en CPU. Tras varios intentos fallidos hab\u00eda acabado generando un <em>core dump<\/em> de 500 MB que confirmaba que entrenar en el Xeon no era viable.<\/p>\n\n\n\n<p>Instal\u00e9 el MCP oficial de Google Colab (<code>googlecolab\/colab-mcp<\/code>) en el Mac Mini para controlar el <em>notebook<\/em> directamente desde Claude Code. La idea era elegante: crear celdas, ejecutarlas, leer resultados, todo sin salir del terminal. La realidad fue menos elegante.<\/p>\n\n\n\n<p><strong>Google Colab<\/strong> (o Colaboratory) es una plataforma gratuita basada en la nube que permite escribir y ejecutar c\u00f3digo de <strong>Python<\/strong> directamente desde tu navegador, sin necesidad de realizar configuraciones previas en tu ordenador. Funciona bajo un sistema de \u00abnotebooks\u00bb (cuadernos interactivos) inspirados en Jupyter, donde puedes combinar texto enriquecido, ecuaciones matem\u00e1ticas y c\u00f3digo ejecutable, todo almacenado de forma autom\u00e1tica en tu cuenta de <strong>Google Drive<\/strong>.<\/p>\n\n\n\n<p>Lo que realmente lo distingue es el acceso gratuito a recursos de computaci\u00f3n de alto rendimiento, espec\u00edficamente <strong>GPUs y TPUs<\/strong>. Esto lo convierte en la herramienta muy valiosa para estudiantes, investigadores y desarrolladores de <strong>Inteligencia Artificial<\/strong> y Ciencia de Datos, ya que permite entrenar modelos complejos o procesar grandes vol\u00famenes de informaci\u00f3n de manera r\u00e1pida y colaborativa, tal como si estuvieras trabajando en un documento compartido de Google Docs. Sobre el papel era perfecto para esta fase de entrenamiento del modelo.<\/p>\n\n\n\n<p>El primer intento lleg\u00f3 hasta la <em>epoch<\/em> 36 de 100 antes de que un <em>MCP timeout <\/em>cortara la conexi\u00f3n. Los <em>checkpoints<\/em> no se hab\u00edan guardado porque el directorio de salida no estaba bien configurado. Al reintentar, el kernel de Colab se hab\u00eda reiniciado, perdiendo las dependencias instaladas y el dataset descargado. Vuelta a empezar.<\/p>\n\n\n\n<p>El segundo intento a\u00f1ad\u00eda <code>save_period=5<\/code> para guardar <em>checkpoints<\/em> cada 5 <em>epochs<\/em>. Lleg\u00f3 hasta la <em>epoch<\/em> 79 de 100 cuando Colab cort\u00f3 la sesi\u00f3n por l\u00edmite de uso de GPU gratuita. Y como los <em>checkpoints<\/em> estaban en almacenamiento ef\u00edmero de Colab&#8230; se perdieron otra vez. Adem\u00e1s, el MCP de Colab ten\u00eda un problema fundamental: <code>update_cell<\/code> reportaba \u00e9xito pero <code>run_code_cell<\/code> ejecutaba el contenido anterior de la celda. Las actualizaciones no sincronizaban con el runtime. Entre unas cosas y otras, fueron 6 horas de tiempo perdido.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">El salto a Kaggle<\/h3>\n\n\n\n<p>Ya hab\u00eda identificado Kaggle como plataforma alternativa, y que aportaba las siguientes caracter\u00edsticas: 30 horas semanales de GPU (m\u00e1s generoso que Colab), sesiones de hasta 12 horas, y <code>\/kaggle\/working\/<\/code> que persiste autom\u00e1ticamente tras finalizar la sesi\u00f3n. No hay MCP, pero a estas alturas eso era casi una ventaja, as\u00ed que prepar\u00e9 un script completo en 6 celdas que el usuario ejecuta directamente en el navegador.<\/p>\n\n\n\n<p>El primer obst\u00e1culo en Kaggle fue la existencia de un <em>bug<\/em> de la versi\u00f3n preinstalada de PyTorch (<code>THPDtypeType.tp_dict == nullptr<\/code>) que dio la cara al intentar ejecutar la primera de las celdas. Se resolvi\u00f3 instalando Ultralytics antes de importar torch, que trae su propia versi\u00f3n compatible. A partir de ah\u00ed, todo fue fluido: el entrenamiento complet\u00f3 80 epochs en 2 horas y 7 minutos a 5.6 iteraciones por segundo.<\/p>\n\n\n\n<p>Los resultados finales del entrenamiento fueron los siguientes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Clase<\/th><th>mAP@0.5<\/th><\/tr><\/thead><tbody><tr><td>car<\/td><td>0.681<\/td><\/tr><tr><td>bus<\/td><td>0.321<\/td><\/tr><tr><td>van<\/td><td>0.254<\/td><\/tr><tr><td>pedestrian<\/td><td>0.249<\/td><\/tr><tr><td>motor<\/td><td>0.249<\/td><\/tr><tr><td><strong>Global<\/strong><\/td><td><strong>0.236<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>El mAP global queda ligeramente por debajo del umbral de 0.25 que nos hab\u00edamos marcado, pero las clases relevantes para fotogrametr\u00eda (coches, veh\u00edculos, peatones) tienen valores m\u00e1s que aceptables. Las clases que bajan la media \u2014 bicycle (0.029) y awning-tricycle (0.063) \u2014 son objetos muy peque\u00f1os y poco frecuentes que no son prioritarios para nuestro caso de uso.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Despliegue y comparativa con el modelo gen\u00e9rico<\/h3>\n\n\n\n<p>Con los modelos exportados (PyTorch de 5.9 MB y TFLite INT8 de 11.6 MB), el despliegue en el servidor fue directo: copiar el <code>.pt<\/code> a <code>\/odm-data\/models\/<\/code>, actualizar la variable <code>YOLO_MODEL<\/code> en el docker-compose, y reiniciar el ai-processor.<\/p>\n\n\n\n<p>La prueba de fuego fue volver a analizar el v\u00eddeo del vuelo real del 11 de abril con el nuevo modelo. El resultado fue contundente:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Modelo<\/th><th>Detecciones<\/th><th>Observaci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td>YOLOv8n gen\u00e9rico<\/td><td>105<\/td><td>Mayor\u00eda falsos positivos<\/td><\/tr><tr><td>YOLOv8n VisDrone<\/td><td>9<\/td><td>5 peatones, 2 camiones, 2 autobuses<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>De las 9 detecciones, la validaci\u00f3n visual en el visor Leaflet confirm\u00f3 que las 5 detecciones de peat\u00f3n eran correctas (era yo, al inicio del vuelo), aunque aparec\u00edan duplicadas por detecci\u00f3n en frames consecutivos. Un cami\u00f3n era un falso positivo, y dos detecciones de baja confianza (bus\/peat\u00f3n al 41%) correspond\u00edan en realidad a un \u00e1rbol. Con tan s\u00f3lo subir el umbral de confianza de 0.35 a 0.50, los falsos positivos desaparecieron y quedaron solo las detecciones v\u00e1lidas. Nada mal para una primera iteraci\u00f3n, aunque a\u00fan espero mejorar mucho el proceso.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Segundo paso. Detecci\u00f3n en tiempo real en el m\u00f3vil<\/h2>\n\n\n\n<p>La segunda capa de detecci\u00f3n se realiza en el dispositivo de captura: consiste en el mismo modelo YOLOv8n, pero cuantizado a TFLite INT8 y ejecut\u00e1ndose directamente en el tel\u00e9fono durante el vuelo. Se trata de una duramente informativa: no controla el dron, s\u00f3lo muestra al operador lo que el modelo ve.<\/p>\n\n\n\n<p>La implementaci\u00f3n requiri\u00f3 cuatro ficheros nuevos y tres modificados. El <code>ObjectDetector<\/code> carga el modelo desde <em>assets<\/em>, ejecuta inferencia y aplica NMS (Non-Maximum Suppression). El <code>DetectionOverlay<\/code> dibuja <em>bounding boxes<\/em> de colores sobre el v\u00eddeo FPV con etiquetas de clase y confianza. Un bot\u00f3n de ojo en la esquina permite activar o desactivar la detecci\u00f3n, y un <em>badge<\/em> muestra el n\u00famero de objetos detectados y los FPS de inferencia.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/yolo-motorola-1024x559.png\" alt=\"\" class=\"wp-image-11836\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/yolo-motorola-1024x559.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/yolo-motorola-300x164.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/yolo-motorola-768x419.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/yolo-motorola.png 1408w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Implementaci\u00f3n de la captura en tiempo real. Generada por IA<\/figcaption><\/figure>\n\n\n\n<p>El primer intento de integraci\u00f3n provoc\u00f3 un fallo en la aplicaci\u00f3n al entrar en la pantalla de vuelo. El culpable: el GPU Delegate de TensorFlow Lite lanzaba un <code>NoClassDefFoundError<\/code> en el Moto G73, que es un <code>Error<\/code> de la JVM, no una <code>Exception<\/code>. Mi recolector de eventos no lo pillaba. Qued\u00f3 corregido con <code>catch (e: Throwable)<\/code>, con lo que detector cae elegantemente a CPU.<\/p>\n\n\n\n<p>El segundo problema fue m\u00e1s sutil: las cajas de detecci\u00f3n aparec\u00edan en la esquina superior izquierda, diminutas y lejos del objeto. El modelo TFLite INT8 devuelve coordenadas ya normalizadas (rango 0-1), pero mi c\u00f3digo las divid\u00eda otra vez por el tama\u00f1o de entrada (416), aplast\u00e1ndolas a valores microsc\u00f3picos. Una vez eliminada la doble normalizaci\u00f3n, las cajas se posicionaron correctamente sobre los objetos detectados.<\/p>\n\n\n\n<p>La validaci\u00f3n final: con la aplicaci\u00f3n instalada en el Moto G73 y el dron conectado, activ\u00e9 la detecci\u00f3n y me puse delante de la c\u00e1mara. El overlay mostr\u00f3 \u00abpedestrian 61%\u00bb con una caja correctamente posicionada alrededor de mi cara, a 3-4 FPS en CPU. No es espectacular, pero es suficiente para alertar al operador de la presencia de personas u objetos durante el vuelo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tercer paso. An\u00e1lisis batch de fotos en el servidor<\/h2>\n\n\n\n<p>La tercera pieza del puzzle es un contenedor separado, <code>photo-analyzer<\/code>, que analiza las fotos de alta resoluci\u00f3n (12-48 MP) descargadas del dron despu\u00e9s del vuelo. A diferencia del <em>stream<\/em> RTMP a 720p que procesa el ai-processor en tiempo real, aqu\u00ed se trabaja con las fotos completas que se usan para la ortofotograf\u00eda.<\/p>\n\n\n\n<p>La clave t\u00e9cnica es SAHI: cada foto se divide en mosaicos de 640&#215;640 con un 25% de solapamiento, se ejecuta la inferencia sobre cada mosaico, y despu\u00e9s se reensamblan las detecciones eliminando duplicados en las zonas de solapamiento. Esto permite detectar objetos peque\u00f1os que se perder\u00edan al redimensionar una foto de 48 MP a 640&#215;640 directamente.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"853\" height=\"613\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/VisorDeHallazgos-IA-Vuelo-d2ca8b37-9906-4a70-be05-3dcd8fe6c74f.png\" alt=\"Visor de hallazgos con las capas de detecci\u00f3n\" class=\"wp-image-11830\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/VisorDeHallazgos-IA-Vuelo-d2ca8b37-9906-4a70-be05-3dcd8fe6c74f.png 853w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/VisorDeHallazgos-IA-Vuelo-d2ca8b37-9906-4a70-be05-3dcd8fe6c74f-300x216.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/05\/VisorDeHallazgos-IA-Vuelo-d2ca8b37-9906-4a70-be05-3dcd8fe6c74f-768x552.png 768w\" sizes=\"auto, (max-width: 853px) 100vw, 853px\" \/><figcaption class=\"wp-element-caption\">Visor de hallazgos con las capas de detecci\u00f3n<\/figcaption><\/figure>\n\n\n\n<p>El photo-analyzer expone una API FastAPI en el puerto 8003, y audit-api act\u00faa como proxy con un endpoint <code>POST \/audit\/flights\/{id}\/analyze-photos<\/code> que encola el trabajo. Las detecciones se almacenan en una nueva tabla PostGIS (<code>photo_detections<\/code>) con geometr\u00eda georreferenciada extra\u00edda del EXIF GPS de cada foto, y se exponen como GeoJSON en una capa separada del visor Leaflet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Cuarto paso. Preparaci\u00f3n para fine-tuning continuo<\/h2>\n\n\n\n<p>El modelo VisDrone es un buen punto de partida, pero su dominio consiste en escenas urbanas densas. Si alg\u00fan d\u00eda necesito detectar objetos espec\u00edficos de mi caso de uso (tejados da\u00f1ados, paneles solares, infraestructuras agr\u00edcolas) necesitar\u00e9 ajuste fino con datos propios. Esta <em>wave<\/em> prepara la infraestructura de versionado de modelos: una tabla en PostGIS que registra cada versi\u00f3n con su SHA256, fecha de entrenamiento, dataset, n\u00famero de epochs y mAP de validaci\u00f3n.<\/p>\n\n\n\n<p>El flujo iterativo queda documentado: volar, revisar detecciones, anotar 200-500 fotos en Roboflow (aqu\u00ed s\u00ed es \u00fatil como herramienta de anotaci\u00f3n gratuita) o CVAT, fine-tune en Kaggle partiendo del modelo VisDrone como <em>transfer learning<\/em>, validar, exportar a ambos formatos, desplegar y comparar. Todo el <em>pipeline<\/em> a coste cero.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El papel de los asistentes IA en la toma de decisiones<\/h2>\n\n\n\n<p>Un aspecto que merece menci\u00f3n aparte es c\u00f3mo he usado la orquestaci\u00f3n de m\u00faltiples asistentes IA para tomar decisiones informadas, precisamente gracias a la integraci\u00f3n que explicaba al inicio mediante MCPs. Claude Code sigue siendo el agente principal de desarrollo, pero en esta fase se ha apoyado en agentes de IA adicionales, mediante sendos MCPs espec\u00edficos:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>ChatGPT como abogado del diablo<\/strong>: cada decisi\u00f3n arquitect\u00f3nica importante (Roboflow frente a Ultralytics, TFLite frente a ONNX, separaci\u00f3n de contenedores) se valid\u00f3 enviando la propuesta a ChatGPT con instrucciones de buscar debilidades, identificar riesgos y proponer alternativas. El resultado fue que se descart\u00f3 Roboflow antes de invertir una sola hora de implementaci\u00f3n, y se identific\u00f3 la limitaci\u00f3n de GSD del Mini 3 Pro para detecci\u00f3n de grietas (menos de 1 p\u00edxel a 30 metros).<\/li>\n\n\n\n<li><strong>NotebookLM para investigaci\u00f3n t\u00e9cnica<\/strong>: se us\u00f3 para evaluar en profundidad la viabilidad de Ultralytics en hardware sin GPU, investigar formatos de exportaci\u00f3n y sus implicaciones de rendimiento, y recopilar benchmarks de referencia para el Xeon E5.<\/li>\n<\/ul>\n\n\n\n<p>Esta din\u00e1mica de \u00abconsulta experta\u00bb a trav\u00e9s de MCPs es algo que no hab\u00eda explorado hasta ahora en este proyecto, y la experiencia ha sido sorprendentemente productiva. No sustituye el criterio del desarrollador, pero aporta una segunda opini\u00f3n t\u00e9cnica instant\u00e1nea que habr\u00eda requerido horas de investigaci\u00f3n manual.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Resultado final<\/h2>\n\n\n\n<p>La Fase 9 ha transformado el sistema de un <em>pipeline<\/em> de captura pasivo a uno con percepci\u00f3n activa a dos niveles:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>En vuelo<\/strong>: el operador ve detecciones en tiempo real sobre el v\u00eddeo FPV, a 3-4 FPS en CPU, con un selector para activar\/desactivar seg\u00fan necesidad.<\/li>\n\n\n\n<li><strong>Post-vuelo<\/strong>: el servidor analiza cada foto de alta resoluci\u00f3n con SAHI, detectando objetos que ser\u00edan invisibles en el stream de 720p, y los geolocaliza autom\u00e1ticamente usando los metadatos EXIF.<\/li>\n<\/ol>\n\n\n\n<p>El modelo VisDrone entrenado reduce las detecciones del gen\u00e9rico de 105 a 9, eliminando la mayor\u00eda de falsos positivos y produciendo resultados coherentes con la realidad observada. Y todo ello con un <em>stack<\/em> completamente gratuito: Ultralytics + Kaggle + TFLite + SAHI. Sin suscripciones, sin APIs de pago, sin limitaciones artificiales.<\/p>\n\n\n\n<p>Para la siguiente fase del proyecto, la fase 10, tengo algunas ideas en la cabeza, como por ejemplo: mejoras de UX, soporte multi-dron, capas WMS del Catastro y SIGPAC, y la infraestructura de seguridad y monitorizaci\u00f3n que el servidor merece. A su debido tiempo trabajar\u00e9 en esta nueva fase.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 12 de 12 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=fotogrametria-asistida-por-ia\" class=\"series-1957\" title=\"Fotogrametr\u00eda asistida por IA\">Fotogrametr\u00eda asistida por IA<\/a><\/div><p>La Fase 8 dej\u00f3 cerrado el panel de condiciones de<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1845,13],"tags":[106,1984,1959,1989,1990,1991,1988,1987,1985,1964],"series":[1957],"class_list":["post-11828","post","type-post","status-publish","format-standard","hentry","category-generado-con-ia","category-informatica","tag-android","tag-chatgpt","tag-claude-code","tag-google-colab","tag-mcr","tag-notebooklm","tag-sahi","tag-tflite","tag-ultralytics","tag-yolo","series-fotogrametria-asistida-por-ia"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11828","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11828"}],"version-history":[{"count":4,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11828\/revisions"}],"predecessor-version":[{"id":11837,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11828\/revisions\/11837"}],"wp:attachment":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11828"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11828"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11828"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=11828"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}