miércoles, 18 de julio de 2018

Fusión HDR de imágenes con R

En ocasiones los fotógrafos se enfrentan a escenas cuya diferencia de luminosidad entre las partes más oscuras y las más claras supera el rango dinámico que el sensor de la cámara es capaz de captar. Esto significa que con una sola fotografía es imposible capturar toda la información: si se salvan las altas luces las sombras tendrán un ruido excesivo que arruinará el detalle, y si por el contrario las sombras se fotografían correctamente las luces aparecerán quemadas.

Una de las técnicas usadas para resolver este problema es la fusión HDR (High Dynamic Range o Alto Rango Dinámico), que probablemente puedas configurar en tu móvil, y que consiste en hacer varias fotografías de diferente nivel de exposición (luminosidad) y combinarlas en una imagen final: las capturas menos expuestas suministrarán la información de las altas luces sin saturar, mientras que las más expuestas proporcionarán las sombras libres de ruido.

Vamos a estudiar una escena de rango dinámico superior al de la cámara usada para fotografiarla, una Canon 350D. Para abordarla se tomaron dos fotografías con una diferencia en exposición de cuatro pasos, es decir, niveles separados por un factor lineal 24 = 16.


Fuente: Seu Universitària, La Nucía (Alicante). Estudio crystalzoo


Las imágenes se ven planas porque se han revelado a partir de los archivos RAW sin aplicar ningún procesado. Pero lo importante en este punto es darnos cuenta de que ninguna de ellas por sí sola basta para obtener un resultado de calidad: la primera presenta excesivo ruido en las sombras y la segunda tiene las ventanas fuertemente quemadas.



Necesitamos conocer la diferencia de exposición entre las dos tomas porque habremos de igualar la exposición de las imágenes previamente a su fusión. Aunque se ajustó una diferencia nominal de cuatro pasos, las cámaras presentan tolerancias así que calcularemos numéricamente un valor más exacto.

Amparándonos en la alta linealidad del sensor, comparamos los niveles RGB lineales (obtenidos con DCRAW) de cada par de píxeles homónimos de una imagen y la otra haciendo un promedio de los ratios. Pero no todos los emparejamientos serán válidos, tomaremos solo aquellos niveles RGB que se encuentren en ambas imágenes entre unos valores mínimo (para evitar un ruido excesivo) y máximo (descartando posibles alinealidades del sensor). Representamos gráficamente qué píxeles participaron finalmente en el cálculo:



Esta imagen tan psicodélica es interesante de interpretar. Las zonas en negro corresponden a píxeles que no se emplearon para calcular la exposición relativa entre las fotografías, porque sus niveles en alguna de las dos imágenes no respetaban los umbrales. El resto sí participaron, indicando su color qué canales RGB entraron en el cálculo: en blanco si contribuyeron los tres canales, en amarillo los canales R y G,...



Del total de niveles RGB disponibles entró en el cómputo el 20%, lo que en una imagen de 8Mpx es una cantidad de información más que suficiente para tener una estimación muy precisa. Como era de esperar los píxeles escogidos resultaron ser los correspondientes a las luminosidades intermedias de la escena.

La exposición relativa calculada fue de 15,69 en escala lineal, lo que en este caso supone una diferencia muy sutil respecto al valor 16 ajustado en la cámara. La siguiente imagen muestra el histograma de exposiciones relativas, indicando en rojo el valor calculado por mediana:



La distribución debe su forma a la definición como cociente entre dos señales afectadas por ruido fotónico, dominante en los niveles de exposición participantes en el cálculo. El ruido fotónico se modela como un proceso de Poisson con alta tasa de llegadas (fotones) y es por tanto aproximable por una normal, así que lo que tenemos es un cociente de distribuciones normales.

Gracias al cálculo preciso de exposición relativa nos permitiremos hacer una fusión "dura", que consiste en tomar de la captura más expuesta todos los niveles RGB que cumplan una condición de no saturación y el resto de niveles de la otra fotografía, sin transición progresiva en las regiones frontera. Los valores RGB de la captura más expuesta se corregirán previamente a la baja por el factor calculado con el fin de igualar la exposición.

La siguiente imagen es el mapa de fusión, gráfico que muestra en blanco los píxeles que se tomarán de la captura menos expuesta, en negro los de la más expuesta, y en tonos parciales aquellos píxeles con una contribución mixta de ambas capturas. La selección es muy óptima obteniendo el 87% del total de la información de la toma más expuesta, la que proporciona más calidad de imagen por tener mejor relación S/N.



El resultado de la fusión es una imagen con el mismo nivel de exposición que la captura menos expuesta, pero con toda la información de luces y sombras disponible.



Para obtener una imagen final válida falta aplicar sobre la fusión "oscura" y sin ruido un procesado conocido como mapeo de tonos, consistente en un aumento de la luminosidad general pero preservando la información de altas luces, y todo ello a la vez que se logra un aceptable nivel de contraste local para que la imagen no resulte anodina.

En el archivo fusionmapeotonos.tif se tiene tanto la fusión de las dos capturas, como el proceso de mapeo tonal realizado mediante curvas con máscaras de capa en Photoshop. Con mayor resolución haciendo clic sobre la imagen:



Pese a que la fusión se ha hecho sin ninguna progresividad, se hace imposible detectar saltos en las "costuras" del mapa de fusión gracias a la linealidad del sensor y a la precisión con que se ha hecho la corrección de exposición. Esto es especialmente llamativo teniendo en cuenta que existen píxeles finales en los que no todos los canales provienen de una misma imagen origen, como puede comprobarse al ver la contribución de cada captura:



Aunque el proceso pueda parecer complejo su implementación es muy compacta, una buena demostración de la notación matricial de R. No es solo que podamos almacenar de forma nativa imágenes en color en variables de tipo array, sino la potencia que brinda poder acceder y manipular los elementos de dichos arrays con índices lineales.

En concreto la función which() nos ha permitido vectorizar la manipulación de extensos conjuntos de píxeles seleccionados arbitrariamente, con una sintaxis compacta, intuitiva y de ejecución ultrarrápida al ser funciones compiladas que evitan tener que usar bucles. Es funcionalmente equivalente a una máscara de selección en Photoshop.

Una versión más sofisticada, con salida en formato RAW y soportando un número arbitrario de capturas, se desarrolló posteriormente en 'RAW HDR'.

~~~

Repositorio con el código R y archivos auxiliares: GitHub. Archivos RAW originales: raw1.cr2, raw2.cr2.

5 comentarios:

  1. Me alegra ver código para hacer un HDR. Muy ilustrativo, aunque supongo que se podrá complicar mucho más. Por lo menos está bien conocer 1 algoritmo.
    Y muy simple como acceden R y DCRAW al raw, siempre me lo había imaginado con un montón de bucles anidados, y arrays por lo menos de 2 dimensiones.

    Felicidades!

    Un saludo, frankmar98

    ResponderEliminar
  2. Hola Frank, pues ésa es la ventaja de R, que soporta arrays de forma nativa (en eso recuerda a Matlab). Así img1, img2, mapacalc, mapafusion y hdr son todos ellos arrays tridimensionales, de dimensiones las de las imágenes procesadas más la de los tres canales de color: [dimx, dimy, chan].

    Un saludo.

    ResponderEliminar
  3. Elegant and efficient - Nice job Guillermo! Works great on a Mac in RStudio.

    Kirk

    ResponderEliminar
    Respuestas
    1. Thanks Kirk! the code is indeed hyper compact. Did you install RStudio just to give it a try or you coded R before?.

      Eliminar
    2. I just loaded the script into R Studio and, after some experimentation found out how to specify the working directory (I originally modified the script to point to the hard-coded full path to each TIFF image and the corresponding HDR TIFF output). I also found that the first TIFF has to be the darker exposure. Kirk

      Eliminar

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.