Visión por computador a Unreal Engine: Un reto del inLab FIB

Visión por computador a Unreal Engine: Un reto del inLab FIB
Autores:

Imaginemos por un momento que se nos plantea la elaboración de un software muy especial. Se trata de un proyecto en el que la representación debe ser apta para su visualización en público.

Este fue precisamente el reto con el que nos encontramos en inLab FIB: un proyecto que combinaba renderización 3D con visión por computador y unas restricciones de tiempo de cálculo muy exigentes.

Con esta tarea por delante, en inLab FIB nos embarcamos en el desarrollo de este producto con un equipo formado por cuatro personas.

Elección de la Tecnología Adecuada

El primer paso fue seleccionar la tecnología con la que íbamos a trabajar, basándonos en los requisitos establecidos. Por motivos de confidencialidad, no revelaremos los detalles concretos del problema a tratar, pero sí podemos decir que se requería:

  • Un detector de personas capaz de procesar seis imágenes en 10 ms.
  • Un sistema de renderizado con iluminación moderna.
  • La aplicación de varias técnicas tradicionales de visión por computador, también dentro del mismo límite de 10 ms.

Frente a este desafío, evaluamos diversas tecnologías. Uno de los primeros aspectos que tuvimos que considerar fue cómo detectar personas en las imágenes. En este ámbito, decidimos utilizar un modelo de IA llamado YOLO, concretamente la versión 8. Este modelo es independiente de la plataforma, pero habitualmente se implementa a través de una API en Python combinada con un ejecutor de modelos.

Sin embargo, Python presenta problemas de rendimiento, lo que nos llevó a descartarlo casi inmediatamente. La otra opción viable para utilizar YOLO era C++, un lenguaje que, a pesar de los retos en la gestión de memoria, es uno de los más eficientes disponibles. Para ponerlo en contexto, C++ se ha convertido en el lenguaje predominante en la industria del videojuego y en cualquier aplicación con altos requisitos de cálculo.

Una vez resuelto el problema de la IA, necesitábamos una forma de realizar cálculos de visión por computador. Dado que ya habíamos elegido C++, la decisión natural fue utilizar OpenCV, una de las bibliotecas más populares para el procesamiento de imágenes.

Renderización y Representación 3D

Para la visualización del sistema, una de las opciones que consideramos fue el uso de un motor de videojuegos. Aunque nuestro proyecto no tenía una finalidad lúdica, su naturaleza compartía muchas similitudes con la industria del entretenimiento. En este contexto, evaluamos dos alternativas: Unity3D y Unreal Engine 5.

Unity3D cuenta con un sistema de scripting en C# y permite la creación de plugins en varios lenguajes, principalmente en C++. Aunque dispone de un sistema de iluminación muy potente, la integración entre la lógica del plugin y la escena 3D presentaba dificultades, especialmente en la conversión de datos. Uno de los principales inconvenientes es que Unity-C# utiliza un sistema de garbage collector, mientras que en C++ la gestión de la memoria es manual.

Además, hay que tener en cuenta que el entorno 3D y todos los elementos del motor están implementados en C#, por lo que era necesario crear una API de comunicación con el plugin en C++. Estas API suelen tener muchas limitaciones y, en general, se recomienda que sean sencillas, justo lo contrario de lo que nosotros pretendíamos hacer.

Ante estas consideraciones, la mejor opción resultó ser Unreal Engine 5. Aunque pueda parecer una elección por descarte, presentaba varias ventajas clave:

  • Su scripting es en C++, lo que permitía la integración directa con nuestro código.
  • Su sistema de renderización es uno de los mejores disponibles actualmente.
  • Su modelo de licencias se ajustaba perfectamente a las necesidades del cliente.

Garantizando una Arquitectura Modular

Aunque elegimos Unreal Engine, una de las cosas que teníamos claras en inLab FIB era que el diseño de nuestro sistema debía ser independiente del motor. En otras palabras, el código de renderización 3D podía conocer la existencia del dominio de cálculo, pero no al revés.

Para lograrlo, dividimos el sistema en dos capas:

  • La capa «Unreal», responsable de mostrar información al usuario y adquirir datos de la interfaz de usuario.
  • La capa de «lógica», encargada de procesar toda la información y capturar imágenes de las seis cámaras.

Optimización del Rendimiento GPU

Uno de los grandes riesgos era que tanto Unreal Engine como los motores de IA hacen un uso intensivo de la GPU. Trabajábamos con un backend basado en CUDA [1] sobre una tarjeta gráfica RTX 3090. Aunque en teoría este hardware era suficiente, en la práctica no pudo procesar las seis imágenes y renderizar la escena en los 10 ms requeridos.

Afortunadamente, los eventos deportivos del cliente se desarrollaban en pistas pequeñas con superficies lisas e iluminación controlada. La solución, por tanto, fue desactivar Nanite [2] en Unreal Engine y utilizar un modelo de renderización basado en light maps [4].

Implementación de la UI

Además de la representación 3D, el cliente necesitaba una interfaz de usuario (UI) para controlar el evento en tiempo real y gestionar las diversas cámaras. Crear esta UI con UMG [5] de Unreal Engine representó un reto significativo.

Como no había alternativas mejores, decidimos incorporar un desarrollador especializado para esta tarea.

Conclusión

Elegir Unreal Engine para este proyecto fue una decisión acertada, a pesar de los retos que supuso. El hecho de que nuestro algoritmo tuviera que competir con el motor por los recursos de la GPU fue un obstáculo importante. Sin embargo, contar con un motor tan potente nos proporcionó herramientas útiles y una velocidad de desarrollo superior a la esperada.

Referencias

[1] CUDA Toolkit – NVIDIA
[2] Nanite – Geometría Virtualizada en Unreal Engine
[3] ImGui – Biblioteca de Interfaz de Usuario
[4] Comprensión del Lightmapping en Unreal Engine
[5] UMG – Diseñador de Interfaz de Usuario en Unreal Engine