En 'Ingeniería inversa de procesado de imagen con redes neuronales' replicamos un procesado de imagen completo usando una red neuronal. La idea era lograr modelar con precisión millones de posibles transiciones de color con un modelo muy compacto, la red neuronal. En este artículo vamos a perseguir el mismo objetivo pero por el otro extremo: fuerza bruta, codificando la misma transformación en una enorme matriz o LUT (Look Up Table). El enfoque es el mismo que en 'Ingeniería inversa de curvas RGB con R', solo que allí con un modelo de tres curvas RGB independientes que proporciona menos grados de libertad.
Existen varios tipos de LUT en procesado de imagen, pero nos centraremos en las más comunes: LUTs 3D. En ellas se codifica para una serie de valores RGB de partida, la salida RGB correspondiente a cada entrada. Las LUT pueden definirse de la longitud (resolución) que deseemos, lo que determinará el número de entradas que tendrán y la precisión conseguida. Pueden almacenarse en dos formatos diferentes:
- Formato .cube: archivo de texto plano en el que, tras una breve cabecera descriptiva, se codifican numéricamente en 3 columnas las ternas RGB de salida a las que corresponderá cada combinación RGB de entrada contemplada, normalizadas entre 0 y 1.
- Formato HaldCLUT: se trata de un archivo de imagen (PNG) en el cual de forma directa podemos ver intuitivamente los colores destino que la LUT va a generar a partir de las posibles combinaciones RGB de entrada.

Si nos fijamos, los valores RGB de entrada a los que corresponden las salidas no aparecen por ningún lado. Esto es debido a que el formato LUT asume que los valores de entrada se muestrean de forma equiespaciada, variando los niveles R, G y B de forma ordenada y conocida. Así bastará acudir a su localización en el fichero .cube o en el PNG para saber a qué valor origen corresponden.
No es posible codificar infinitas combinaciones de colores posibles con un número finito de entradas, por lo que los valores no encontrados directamente en la LUT se deberán interpolar. Esto tiene una consecuencia y es que aplicar una LUT sobre una imagen no tiene un resultado determinista, sino que dependerá de la forma en que el software de procesado realice las interpolaciones, aunque las diferencias deberían ser mínimas.
El formato HaldCLUT, además de tener menos precisión (decimales en coma flotante vs enteros de 16 bits), tiene una importante limitación, y es que debe poderse codificar en una imagen cuadrada, donde cada píxel representará un posible valor RGB de entrada. Eso significa que no todas las resoluciones de LUT son susceptibles de codificarse en HaldCLUT, solo aquellas cuyo número de entradas sea cuadrado perfecto (raíz entera):

Las ventajas del HaldCLUT es que es mucho más compacto que el .cube (unos 10-40KB vs más de 1MB para una LUT 36x36x36), y sobre todo que presenta el procesado de forma muy intuitiva como una imagen en color, sobre la que pueden hacerse directamente procesados de imagen y al guardar de nuevo tendremos una nueva LUT que incorpore las novedades adicionales.
Con ayuda de Gemini construimos tres funciones relativas a LUTs:
build_lut(): función que a partir de dos imágenes (antes/después), almacena en una LUT de resolución parametrizable y en formatos .cube y HaldCLUT el mapeo de ternas RGB que lleva de la imagen origen a la procesada.apply_lut(): función que a partir de una LUT en formato .cube o HaldCLUT, permite aplicarla a cualquier imagen arbitraria, lo que nos servirá para validar la precisión de la LUT.plot_lut(): función que visualiza en 3D la traslación de color RGB que realiza una LUT en formato .cube, dibujando los colores dentro del cubo RGB unidad.
Como primera prueba tomamos el procesado que ya modelamos en su día con una red neuronal. Para obtener el antes/después con que alimentar la función creadora de la LUT, aplicamos el procesado a una imagen sintética que contiene los 17 millones de colores disponibles en 8 bits (a la izquierda la original y a la derecha el procesado):

Hemos usado una LUT de resolución de 36x36x36 (36 valores diferenetes por canal), aunque la más habitual en el mundillo es la LUT 33x33x33, para poder tener la versión también en HaldCLUT:

El resultado es absolutamente impecable, se hace imposible a simple vista ver ninguna diferencia entre el procesado de Photoshop y el producido al aplicar la LUT, teniendo que resaltar mucho las diferencias de niveles para ver el error (hacer clic para ver en alta resolución):
Vamos con un segundo ejemplo, en este caso una conversión a BN en Photoshop acompañado de una curva de contraste. No tenemos ni idea de cómo realiza internamente Photoshop la conversión a BN, pero con la LUT estaremos en disposición de aplicársela a cualquier imagen prescindiendo de Photoshop.
La correspondencia con la imagen sintética y la LUT en formato HaldCLUT:


El resultado de nuevo es indistinguible del procesado en Photoshop (hacer clic para ver en alta resolución):
La función de visualizacion plot_lut() es bastante parametrizable: densidad de la malla, información a presentar, etc... Las 8 esferas de color que podemos ver representan los puntos en el espacio color destino que ocupan los colores puros o vértices del cubo RGB origen: negro, blanco, rojo, verde, azul y las 3 combinaciones binarias.
En forma de línea gruesa se representa los colores destino que la LUT atribuye a una escala de gris. Cuando más se aleje esa línea de la diagonal perfecta más estará coloreando la LUT ciertos tonos de gris.
Por último existe la opción, usada arriba en la versión BN, de dibujar trazos de segmento que unen los colores origen con sus destinos tras aplicar la LUT.
Aquí la visualización para el ejercicio en color (hacer clic para ver en alta resolución):
Repositorio con el código R: GitHub.



No hay comentarios:
Publicar un comentario
Por claridad del blog, por favor trata de utilizar una sintaxis lo más correcta posible y no abusar del uso de emoticonos, mayúsculas y similares.