Superresolución a la “DLSS 1.0”. Mi propia implementación, con Geralt de Rivia como conejillo.

Hola lectores de Tecnología al Día

En una entrega anteriore les mostré algunos ejemplos de superresolución de imágenes reales , basada en redes neuronales. Luego aplicada en imágenes de juegos:

Superresolución con DNNs.

También les mostré como con una red neuronal relativamente simple, del tipo Autoncencoder Convolucional puede igualmente reconstruirse imágenes, en este caso, de rostros, que fue con la que “entrenamos” la red

Superresolución con DNNs (II)

Y les conté como el DLSS de NVIDIA, no es más que un autoencoder convolucional, con varios parámetros de entrada por cada imagen (para su mejor descripción), acelerada la red en los TensorCore. A esta red le entra una imagen de menor resolución y entrega una de mayor resolución. Su aprendizaje, en DLSS 1.0, se realizaba para cada juego especifico, pero según explica NVIDIA, en la versión 2.0 la red es más generalista por lo que no es específcia a cada juego.

Pues bien, hoy quiero mostrarles como emplear una red de ese mismo tipo para superresolución en juegos.

Antes de continuar, les recomiendo ver los dos artículos anteriores para tener una mejor idea de lo que mostraré. Empecemos

El juego de ejemplo que he tomado para entrenar la red con sus imagenes es el Witcher 3.

Capturé 320 imágenes a 1920×1080, en diversos entornos y condiciones.

Estas imagenes en las primeras pruebas las descomponía en bloques solapados de 16×16 pixels, teniendo en cuenta que no fueran muy similares. Así obtuve casi 3 millones de imagenes. Esto llevó un largo tiempo, a pesar de que escribi el codigo tan paralelo como pude para acelerar todo el proceso. Las imagenes van en carpetas separadas, una por cada origen. No se les ocurra tener 3 millones de archivos en una sola carpeta. No es que no se pueda, pero….. jajajaja.

Con estos bloques realicé el entrenamiento de la red. Tomando un 80% aleatorio de los datos para entrenar y un 20% para validar los resultados.

Como comenté antes, la red que uso es un autoencoder convolucional. Muchos experimentos hice, tratando de aumentar la calidad pero sin hacer crecer mucho la red. Tiene unos 778 307 parámetros y requiere unos 500 MegaFLOPS (para la entrada-salida de 32×32). Este es su esquema.

Para crear y entrenar la red, uso Tensoflow 2.2, mediante el API de mayor nivel Keras, que facilita el codigo. Todo con python (puajj!!) y trabajando en Visual Studio Code. Para acelerar el entrenaminto e inferecnia, uso el backend PlaidML, basado en OpenCL y que me permite usar mi AMD Radeon RX 470 (y casi cualquier otro GPU compatible con OCL), pues el backend para GPU oficial de Tensorflow, el que se entrega compilado, es con CUDA asi que solo funciona con GPUs de NVIDIA.


Entrenando en el GPU y descomponiendo más imagenes en el CPU, simultáneamente

Ajustes de parámetros de las capas, parámetros generales, hiper-parametros, formas de entrenar, aumentación de datos, etc. Mucho era en aras de reducir los tiempos de entrenamiento e inferencia. Gracias a que empleo mi GPU en esta tarea, el tiempo no era grande. Emplee batches de 32 imagenes, lo que ayuda a encontrar resultados mejores, aunque demora más, pues se aprovecha menos la capacidad de cómputo paralelo de la GPU. Por ejemplo, con estos batches de 32 se demora un epoch (iteracion de entrenaminto) casi el doble que con batches de 128.

Sin embargo encontre dos problemas.

Uno era el manejar tal cantidad de archivos, al cargarlos para operar. Asi que, luego de haber leido algunos articulos, vi que el solapamiento era inecesario y mayores bloques ayudaban a la calidad. Regeneré todo en bloques de 32×32 y sin solapamiento lo que redujo muchísimo la cantidad de imagenes (ahora unas 600 mil) y el tiempo general.

El otro problema era que en el juego, el anti-aliasing que emplea no es de alta calidad y además las imagenes las habia capturado con la opción Radeon Imagen Sharpenning activa. Esto provocaba que habia mucha alta frecuencia en la imagen. Al reconstruir la imagen aparecia mucho “ruido” (por calificarlo de alguna manera) fundamentalmente en regiones como la hierba o arbustos.

Como no pensaba recapturar la imágenes, las procese todas en Photoshop, aplicando un desenfoque gaussiano a nivel de subpixel (radio 0.5) y luego un enfoque mejorado tambien subpixel (radio 0.75). Esto funciono como un antialiasing y eliminió de las imágenes la mayor parte de ese “ruido”. En las próximas pruebas ya vi como la calidad era mucho mayor.

L que queremos es que la red aprenda a llevar de esto a esto: 960×540 a 1920×1080 (2x)

incluso podriamos intentar esto: 640×360 a 1920×1080 (3x)

Dificil, eh?

La operación actual que hago para procesar una imagen es:

• Reducir la original a la mitad de la resolución mendiante un filtro del “vecino más cercano” (NN), básicamente, tomar uno de cada dos píxeles. Esta imagen simula como si el juego se estuviera dibujando realmente a 960×540.

• La imagen se escala de regreso a la resolucion original, igualmente con un filtro NN. Esta imagen tendra la misma cantidad de pixeles, pero mantiene la mitad de la resolución.

• Esta imagen se descompone en bloques de 32×32 pixeles, solapados a la mitad. Esto hace que básicamete fuera como si procesara dos imágenes, es decir, el doble de cómputo en la inferencia.

Estos dos pasos anteriores pueden hacerse de otra manera, en lugar de escalar la imagen a lo original, escalar los bloques. SIn embargo su coste computacional es casi el doble.

• Se ejecuta la red que toma por entrada los bloques en un solo paquete de datos

• La red entrega el conjunto de bloques de salida inferidos de alta resolución.

• Los bloques se copian a la imagen vacia de salida . El solapamiento se hace de manera interpolada. de manera que los defectos de borde de los bloques se tapan con los datos centrales (mejor calidad) de los bloques que los cubren.

• La imagen se pasa por un filtro CAS (Contrast Adaptive Sharpening, https://gpuopen.com/wp-content/uploads/2019/07/FidelityFX-CAS.pptx), una implementacion que hice del que incluyen los controladores AMD, para “afinar” los detalles. CAS tiene dos versiones. Una solo “afina” la imagen, la otra hace escalado (aumento de resolución) con afinacion

Para la interpolación de los bloques, puede emplearse una interpolación bilineal, pero yocomencé usando una ventana de Hanning que da mejor resultado (los que estudiaron telecomunicaciones lo conocerán aunque seguro su versión 1D). Sin embargo, construí varios filtros “a mano”, y verificaba la calidad de los resultados con dos métricas, PSNR (Peak Signal-to-Noise Ratio) y SSIM (Strcuctural Similarity). Al final obtuve dos, uno que me daba mejor PSNR y otro que daba mejor SSIM. Que hice entonces? Los combiné y obtuve uno que al verificar mejoraba ambos parámetros. Cool.

El programa (se encarga de varias cosas, como generar los bloques para entrenamiento, descomponer y recomponer las imagenes, calcular la calidad de los resultados, etc) lo hice en C++, empleando la biblioteca OpenCV para el trabajo con la imágenes. El proceso es tomar las imágenes presentes en una carpeta y una a una descomponerla, ejecutar el script python con la inferencia y con los bloques inferidos recomponer una imagen de salida. no he podido todavia aplicar la optimización del modelo ni convertirlo del formato de Keras a un protobuffer de Tensorflow, y así usarlo directo desde el programa vía el motor de ejecucion de redes neuronales de OpenCV, quedando el ejemplo completo en un solo entorno y ejecutable y linea de ejecución. Esto obliga ahora a que los bloques de imagen tenga que guardarlos como archivos para que el script los carge y sus resultados los guarde a su vez, para luego cargarlos y reconstruir la imagen final.

Si todo el proceso lo hiciera en un solo pase seria mucho más rapido. La red no se ha optimizado para inferencia, como comenté antes. En mi GPU la inferencia demora unos 4-5 segundos.

Para evaluar la calidad empleo 21 imágenes, que proceso y el resultado lo comparo con las originales con las métricas PSNR y SSIM. Al final obtengo el promedio de estas dos metricas. El resultado es de 30.908 (PSNR) y 0.901042 (SSIM).

Fue interesante, y exaustivo, como no lograba subir de estos limites, lo que tambien se veia en el Loss y el Accuracy durante los entrenamientos. Me parece que es un limite que imponen los datos (tengo que calcular todo con el EDSR a ver hasta donde llega)

En fin les muestro los resultados

Primero, veamos un ejemplo de varios bloques de 32×32 procesados. Estan acomodados por columnas. Cada imagen, de arriba hacia abajo es, fuente a 1920×1080 (destino), fuente a 960×540 (origen), resultado con la red de SR, resultado con interpolación de Lanczos4 de alta calidad

Veamos ahora los resultados completos con algunas imágenes.

En la parte superior de cada imagen se ve la escena genral. Debajo, cada fila es una region donde se notan más los cambios o se destacan detalles que permitan comparar, pues en muchas partes cuesta ver la diferencia entre imágenes.

Cada columna de imagen representa:

• baja resolución 960×540 (origen)

• alta resolución 1920×1080. imagen destino

mirando bien, creo que en al menos dos imagenes estas dos columnas anteriores las puse en orden inverso, fijense bien, la que vean pixelada es la de baja resolucion, ejemplo, al del soldado con el casco

• reconstrucción de superresolucion con la red neuronal.

• interpolación (escalado) y un ligero enfoque “inteligente” con Photoshop (lo mejor que podia hacerse con el)

• en algunas imagenes hay una quinta columna. Es una reconstrucción con el modelo de red neuronal para superresolución EDSR, una del estado del arte (Enhanced Deep Residual Networks for Single Image Super-Resolution, https://arxiv.org/abs/1707.02921). En no todos los ejemplo puse esta, algo cansón hacerlo en cada una, bastante con los demas ejemplos. Inferir una imagen con esta red, aun acelerada en mi GPU demoraba unos 170 segundos. Hay que tener en cuenta que esta red se está entrenada con imágenes REALES, no con imagenes sintéticas o juego o tal.

Se recomienda sacar las imágenes a parte, ya sea en una pestaá del navegador, o mejor , guardar los archivos, para poder ampliar y mover facil y comparar.

Una desventaja es que las imágenes, para ahorrar debo subirlas como JPEG, auqnue me encargue de que tuvieran la mejor calidad sin ser muy grandes. Esto provoca que pueden o perderse algunos detalles finos, o aparecer defectos fundamentalmente en las regiones de bordes fuertes

Luego de evaluar los resultados y observarlos bien, se observa como en regiones con detalles (puntos o lineas) del ancho de un pixel estos casi nunca se recuperan, pues empezando que en la imagen de baja resolución desaparecen, así que es más dificil inferir que ahi existen. Esto se nota mas en regiones de bordes de las construcciones como rellanos, marcos de ventanas y puertas, bordes de muros y similares. Los resultados son mejores que con Photoshop y mayormente mejor que con EDSR en regiones de texturas.

EDSR a pesar de su entrenaminto con imagenes reales, entrega buenos resultados, aunque aparecen algunos defectos en regones de texturas periódicas, y en otras regiones de texturas suaves, las suviza algo más. Pero en regiones con gradientes finos largos, los define mejor que nuestra red.

Hay mas imágenes, las 21 que dije antes, pero no las elaboré todas asi para presentarsela, por razones de tiempo y espacio en el blog, asi como consumo de datos para quien vea la pagina por datos moviles (ya consume 9 MB). Si alguien desea las imagenes en todas su resolucion y calidad para comparar mejor, me pone un email y se las mando.

Ahora estoy preparando, para hacerle “cirujia” a la red. Sustituir la entrada para directamente pasarle la imagen de baja resolución (en lugar de hacerlo por bloques) y obtener la de alta resolución en un solo paso. Será mas simple y rápido, aunque me parece que la calidad puede bajar un poco en los bordes de la imagen.

Ahora voy a capturar un video en el juego, a la mejor calidad posible y voy a procesar cada fotograma generando un nuevo video con “DLSS” :-), y ver que tal se ve. Como este ejemplo no tiene en cuenta los cambios temporales, deben haber variaciones pequeñas en cada fotograma. Algo asi como un “ruido” continuo. Para esto debo prepara como minimo una red con la parte de codificacion con capas recurrentes, que permitiría mantener información temporal. Un paso más avanzado seria emplear al menos dos cuadros (actual y anterior) y ajustar el anterior al actual a nivel de pixel mediante un flujo optico denso (DLSS aprovecha los vectores de movimiento para TAA que entregan algunos motroes/juegos, o si no, tal vez lo calcula en el momento de la inferencia), asi se tiene mas informacion temporal y las estructuras de imagen a nivel de subpixel. En otras palabras, más nformación para reconstruir la nueva imagen.

Tambien veré que tal queda emplear la red entrenada en The Witcher 3 con imágenes de juegos diferentes.

Tal vez actualice alguna que otra imagen

Ya les cuento luego.

En el proximo articulo del tema tratare de explicarles más claramente como funciona DLSS.

6 respuestas a «Superresolución a la “DLSS 1.0”. Mi propia implementación, con Geralt de Rivia como conejillo.»

  1. OK pero si puedes prueba al menos con una a ver, no lo hagas con las 3 millones claro y entonces sabras si al reescalar esa densidad sirvió para la FIDELIDAD de la que nos hablaste en el segundo artículo.

    Saludos

  2. Oye Maikel intenta en vez aplicar un filtro Gaussiano prueba con aumentar a las misma resolución de una imagen en Photoshop los pixels por pulgada a ver que pasa por que pienso que el filtro al distorsionar por difuminación la imagen hace que esta cambie datos que si aumentas los pixeles por pulgadas mantendría, es una teoría de como creo que fucionaría uno y otro caso.

    Saludos

    1. no, los “pixeles por pulgada” solo indican densidad en la representacion, para la imagen como tal ese valor no representa nada, no la define. para lograrlo lo unico que puede hacer un software es interpolar, y a menos que sea con algun metodo de superresolucion, no ganas detalle ninguno, solo aumentantes la cantidad de pixeles.

      Ademas, es una medida relativa a la representacion final, por eso la pulgada/centimetro o la medida que sea. es decir, solo tiene sentido con respecto a una representacion real, en que superfice, que tamaño se dibuja, digase impresa o en pantalla

      es decir, si tien una imagen de 1000×1000 pixeles y la imprimes en un tamano de 3×3 centimetros (sorry, usare el sistema internacional de medidas, que no somos ni de eeuu ni de UK y asociados), con una impresora a 300 puntos por centrimetro, tendria una representacin casi equivalente 1 a 1. si haces la imprimes en un pared, 3×3 metros, la impresion seria a “300 ppc” pero realemente la imagen le var a ver los pixeles pues cada uno mediria casi 1×1 centrimetro.

      -o la imagen original la capturas a mayor resolucion de verdad, para que se vean detalles
      -o la interpolas al tamaño necesario para que aun no tengas detalles no veas los pixelones.

      en otras palabras, aumentar los PPP en una imagen no importa para nada a la imagen como tal, no ganas detalles, solo suavizas por la interpolacion en el mejor de los casos (y consumes mas memoria y almacenaminto por gusto) asi al representarla se ve mejor (o menos mala, segun sea el caso).

      el filtro gaussiano subpixel me ayuda a reducir la alta frecuencia “mala” (y sirve un poco como un “antialiasing” general) y los detalles comunes que se difuminan los recupero mayormente luego con el enfoque.
      aumentar la escala interpolando no elimina el ruido, solo lo agranda junto con todo. ademas es necesria la resolucion real de la imagen

      1. Por que lo que si tengo claro es que el filtro al ser filtro quitará ruido pero seguro distorsiona la fidelidad no se si tenga razón o no pero es mera intuición y en ello si no te llevo la contraria es solo por conocer.

        Saludos de nuevo

        1. el gausiano lo hago subpixel para tratar de reducir el problema a nivel de un pixel y que afecte poco a mas de uno.

          luego el enfoque restaura lo mas comun que se haya difuminado

          si se pierden algunos detalles reales, pero el resultado es mejor al final

          es como un antialiasig a pantalla completa

Deja una respuesta

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