Guillermo Luijk
04/10/25, 11:58:47
Entre tres chalados estamos diseñando una aplicación para medir el rango dinámico de una cámara digital, es decir la cantidad de pasos de luminosidad que es capaz de capturar su sensor. Y está mal que lo diga, pero es que estoy bajo juramento: no hay ninguna web ni herramienta comercial que en esta tarea llegue al nivel de precisión (alias frikismo inútil pero divertido) que estamos alcanzando.
La aplicación de línea de comandos, rango, se invoca con una serie de opciones parametrizables. En la primera imagen se indican exactamente los parámetros que se han usado en este ejemplo aunque hay bastantes comandos más. Para no aburrir resumo el proceso en cristiano:
Se le suministran al programa dos fotografías, una en negro y otra saturada, para calcular con precisión los niveles de negro y saturación del sensor, valores que son clave en todo el proceso
Además se le suministran fotos hechas sobre una carta con parches de colores de diferentes luminosidades, una para cada valor ISO que se quiera analizar
Sobre estas imágenes se realiza una corrección geométrica para eliminar la distorsión y a continuación se hacen lecturas de Señal y Ruido en cada uno de los parches
Recolectadas las anteriores series de valores de Señal y su correspondiente relación S/N, se obtienen por regresión las curvas de relación S/N que son la forma más potente de caracterizar el rendimiento de un sensor de imagen en lo que respecta al ruido
Finalmente por intersección de estas curvas de relación S/N con los umbrales de relación S/N escogidos como criterio de ruido, se obtiene la cifra de rango dinámico para cada valor ISO
El programa se plantea como código libre y está escrito en C++ multiplataforma para Windows/Linux (los interesados de Mac ya se buscarán la vida). Todo el diseño de los algoritmos y prototipado se hace en R con aceleraciones C++ en partes críticas. La idea es que tenga un CLI (rango) para ser usado por línea de comandos, y dos entornos GUI con menús y manejados vía ratón, uno más ingenieril y espartano (rangoLab) y otro que llegará más tarde y pretende ser más amigable para fotógrafos y usuarios no técnicos, además de ofrecer funcionalidades más allá del puro análisis (DynaRange).
Este sería el comando utilizado para la medición:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBsNcyAOMYzLjwGJz7qI7gegWwniRBpEb4RWy9T_6BKD WLsupsroNrF_sJPu-UVMjYTjd0RM1YacSxYQrcNUZAMcGobOdIe6532eH7-op0TRwucUp_UiTf77I7hBZ4MiuICDlslrQzs-WWP2jkl57Y9ue2EYlvukCUEs3gWpKIHb6lEgrxaLScT1QvvuiF/s1600/_rango0.png
La captura de una carta de luminosidades crecientes sobre el propio monitor. El color magenta es para ayudar a alinear los canales RAW lo que dará muestras más uniformes, y el desenfoque es para impedir que la textura de píxeles del monitor se interprete como ruido, lo que empeoraría las mediciones de rango dinámico:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ4FSVVQfB88MPP_82z5OhBHEqPDBapkLcP2j8ftSGL1 tbwMaVvUgGyiVTh15kHGvQRCHbOoKIJk19vxDq_z79x-LTdLxGtW_DhB84G01drxx_82vZyUX20Ma8rp9Rxs_NvKI07oso 9v7QlVhViutKzDE7Vu7Kb7aZugOJw9tAtKhLQUj2_UrlTM5SGI-1/s1600/_rango1.jpg
La anterior carta corregida geométricamente e indicando los parches leídos que participarán en el cálculo:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD7HH8CLsG_WAR4873AH8wEbmPt_IIZDMUMjXQz6iVOo Dv62Z58CbUjeq9I-TSHzEgV6BYO0U2ehEVDdl0Tu2sPG0CRfoVI5JR6LBACshFEjzd uN1eD2Wt9Ebr6EWYTAvJOSf_PcALwQfqcu2Wjda9e6NEMq9vD4 k-sZBqES9vvMi0OSJWIaf-EW47iljC/s1600/_rango2.png
Leídas las muestras de Señal y Ruido se obtiene la madre del cordero: las curvas que para cada nivel de exposición RAW proporcionan la relación S/N obtenida. Sobre ellas se mide el rango dinámico:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4CgfWQjX51nxJFMmL50tvorDVK9-qOhMuyJqz5e0EgjnHWq4FTkb8E9wWC1AKbnlz-YzjYMbX-897cB2oIVneoQ5-fApbPLnhk2MW3LtKRGTJwxCY6Bbw5i4Yh5ktF_rDCX64rgaNca cg88TaFOjhOSRekSuF7aFY-I3kaIRmoBu_rC2BidGtU58ZsyxN/s1600/_rango3.png
Esta salida gráfica es la más potente que yo haya visto publicada en ningún sitio. Se pueden mostrar los puntos con los que se calcularon las curvas, para cada uno de los 4 canales RAW (por si hubiera diferencias de rendimiento por canal; spoiler: en algunas cámaras los hay), y también el resultado combinado de todos los canales con una cifra de rango dinámico promedio. La resolución de salida puede ser brutal permitiendo formato bitmap de alta calidad con antialiasing (PNG) y también formatos vectoriales que admiten zoom infinito sin perder calidad (PDF, SVG).
Por qué una herramienta así si ya están DxOMark y Photons to Photos? Aparte del reto de algoritmia y programación:
Porque Photons to Photos muestra cifras con una normalización que no explican. Nosotros las vamos a explicar claramente.
Porque vamos un punto más allá en la finura del cálculo (Photons to Photos usa el nivel de negro entero informado en los metadatos, nosotros lo calculamos a partir de un RAW y empleando decimales). El nivel de negro es crítico para estimar correctamente las mediciones de ruido.
Porque estarás midiendo el RD de tu cámara, no solo de tu modelo de cámara (una de las pruebas que nos apetece hacer es comparar el RD de dos unidades de un mismo modelo; o también comparar una unidad consigo misma en frío vs cuando está muy caliente. p.ej. tras grabar vídeo).
El programa no solo va a servir para calcular el RD, sino para generar la propia carta a la que disparar, para calcular con precisión los niveles de negro y saturación del sensor (esto en realidad es más una curiosidad científica para el fotógrafo que un dato útil), y también planeamos que sirva para medir el rendimiento completo del sensor en cualquier circunstancia (sería llevar la curva de relación S/N hasta la saturación).
Listado completo de los comandos que vamos implementando:
C:\>rango
Digital camera Dynamic Range calculation "rango" v1.0
by Juan Manuel Font (coding), Hugo Rodriguez (UI design) and Guillermo Luijk (algorithms)
Usage: rango [OPTION]... [FILE]...
--chart -c <DIMX W H M N> : Create test chart in PNG format ("testchart.png") with a specific resolution, format and number of patches (default DIMX=1920, W=3, H=2, M=4, N=6)
--chart-colour -C <R G B invgamma> : Create test chart in PNG format ("testchart.png") ranging colours from (0,0,0) to (R,G,B) with gamma compression (default R=255, G=101, B=164, invgamma=1.4)
--chart-patches -M <M N> : Read test chart decoding MxN patches over rows (M) and columns (N) (default M=4, N=6)
--chart-coords -x <x1 y1 x2 y2 x3 y3 x4 y4> : Read test chart defined by 4 corners (no specific ordering needed): (x1,y1), (x2,y2), (x3,y3), (x4,y4), being (0,0) the coordinates of the top-left pixel
--black-level -b <float> : Camera RAW black level
--black-file -B <file> : Totally dark RAW file ideally shot at base ISO
--saturation-level -s <float> : Camera RAW saturation level
--saturation-file -S <file> : Totally clipped RAW file ideally shot at base ISO
--input-files -i <files> : Input RAW files shot over the test chart ideally for every ISO
--patch-ratio -r <float> : Relative patch width/height used to compute signal and noise readings (default=0.5)
--snrthreshold-db -d <float> : SNR threshold in dB for DR calculation (default=12dB "Photographic DR")
--drnormalization-mpx -m <float> : Number of Mpx for DR normalization (default=8Mpx, no normalization=per pixel DR=0Mpx)
--raw-channel -w <R G1 G2 B AVG> : Specify with 0/1 boolean values for which RAW channel(s) the calculations (SNR curves, DR) will be carried out (default=0 0 0 0 1)
--poly-fit -f <int 2-3> : Polynomic order to fit the SNR curve (default=3)
--output-file -o <file> : Output CSV text file(s) with all results: black level, sat level, SNR samples, DR values, fitting params (default="results.csv")
--plot -p <int 0-3> <PNG/PDF/SVG> : Export SNR curves plot in PNG/PDF/SVG formats with/without the CLI command that generated them (default=0, don't plot, default format=PNG)
--print-patches -P <file> : Save keystone/ETTR/gamma corrected test chart in PNG format indicating the patches used for all calculations (default="printpatches.png")
Salu2!
La aplicación de línea de comandos, rango, se invoca con una serie de opciones parametrizables. En la primera imagen se indican exactamente los parámetros que se han usado en este ejemplo aunque hay bastantes comandos más. Para no aburrir resumo el proceso en cristiano:
Se le suministran al programa dos fotografías, una en negro y otra saturada, para calcular con precisión los niveles de negro y saturación del sensor, valores que son clave en todo el proceso
Además se le suministran fotos hechas sobre una carta con parches de colores de diferentes luminosidades, una para cada valor ISO que se quiera analizar
Sobre estas imágenes se realiza una corrección geométrica para eliminar la distorsión y a continuación se hacen lecturas de Señal y Ruido en cada uno de los parches
Recolectadas las anteriores series de valores de Señal y su correspondiente relación S/N, se obtienen por regresión las curvas de relación S/N que son la forma más potente de caracterizar el rendimiento de un sensor de imagen en lo que respecta al ruido
Finalmente por intersección de estas curvas de relación S/N con los umbrales de relación S/N escogidos como criterio de ruido, se obtiene la cifra de rango dinámico para cada valor ISO
El programa se plantea como código libre y está escrito en C++ multiplataforma para Windows/Linux (los interesados de Mac ya se buscarán la vida). Todo el diseño de los algoritmos y prototipado se hace en R con aceleraciones C++ en partes críticas. La idea es que tenga un CLI (rango) para ser usado por línea de comandos, y dos entornos GUI con menús y manejados vía ratón, uno más ingenieril y espartano (rangoLab) y otro que llegará más tarde y pretende ser más amigable para fotógrafos y usuarios no técnicos, además de ofrecer funcionalidades más allá del puro análisis (DynaRange).
Este sería el comando utilizado para la medición:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBsNcyAOMYzLjwGJz7qI7gegWwniRBpEb4RWy9T_6BKD WLsupsroNrF_sJPu-UVMjYTjd0RM1YacSxYQrcNUZAMcGobOdIe6532eH7-op0TRwucUp_UiTf77I7hBZ4MiuICDlslrQzs-WWP2jkl57Y9ue2EYlvukCUEs3gWpKIHb6lEgrxaLScT1QvvuiF/s1600/_rango0.png
La captura de una carta de luminosidades crecientes sobre el propio monitor. El color magenta es para ayudar a alinear los canales RAW lo que dará muestras más uniformes, y el desenfoque es para impedir que la textura de píxeles del monitor se interprete como ruido, lo que empeoraría las mediciones de rango dinámico:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ4FSVVQfB88MPP_82z5OhBHEqPDBapkLcP2j8ftSGL1 tbwMaVvUgGyiVTh15kHGvQRCHbOoKIJk19vxDq_z79x-LTdLxGtW_DhB84G01drxx_82vZyUX20Ma8rp9Rxs_NvKI07oso 9v7QlVhViutKzDE7Vu7Kb7aZugOJw9tAtKhLQUj2_UrlTM5SGI-1/s1600/_rango1.jpg
La anterior carta corregida geométricamente e indicando los parches leídos que participarán en el cálculo:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD7HH8CLsG_WAR4873AH8wEbmPt_IIZDMUMjXQz6iVOo Dv62Z58CbUjeq9I-TSHzEgV6BYO0U2ehEVDdl0Tu2sPG0CRfoVI5JR6LBACshFEjzd uN1eD2Wt9Ebr6EWYTAvJOSf_PcALwQfqcu2Wjda9e6NEMq9vD4 k-sZBqES9vvMi0OSJWIaf-EW47iljC/s1600/_rango2.png
Leídas las muestras de Señal y Ruido se obtiene la madre del cordero: las curvas que para cada nivel de exposición RAW proporcionan la relación S/N obtenida. Sobre ellas se mide el rango dinámico:
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4CgfWQjX51nxJFMmL50tvorDVK9-qOhMuyJqz5e0EgjnHWq4FTkb8E9wWC1AKbnlz-YzjYMbX-897cB2oIVneoQ5-fApbPLnhk2MW3LtKRGTJwxCY6Bbw5i4Yh5ktF_rDCX64rgaNca cg88TaFOjhOSRekSuF7aFY-I3kaIRmoBu_rC2BidGtU58ZsyxN/s1600/_rango3.png
Esta salida gráfica es la más potente que yo haya visto publicada en ningún sitio. Se pueden mostrar los puntos con los que se calcularon las curvas, para cada uno de los 4 canales RAW (por si hubiera diferencias de rendimiento por canal; spoiler: en algunas cámaras los hay), y también el resultado combinado de todos los canales con una cifra de rango dinámico promedio. La resolución de salida puede ser brutal permitiendo formato bitmap de alta calidad con antialiasing (PNG) y también formatos vectoriales que admiten zoom infinito sin perder calidad (PDF, SVG).
Por qué una herramienta así si ya están DxOMark y Photons to Photos? Aparte del reto de algoritmia y programación:
Porque Photons to Photos muestra cifras con una normalización que no explican. Nosotros las vamos a explicar claramente.
Porque vamos un punto más allá en la finura del cálculo (Photons to Photos usa el nivel de negro entero informado en los metadatos, nosotros lo calculamos a partir de un RAW y empleando decimales). El nivel de negro es crítico para estimar correctamente las mediciones de ruido.
Porque estarás midiendo el RD de tu cámara, no solo de tu modelo de cámara (una de las pruebas que nos apetece hacer es comparar el RD de dos unidades de un mismo modelo; o también comparar una unidad consigo misma en frío vs cuando está muy caliente. p.ej. tras grabar vídeo).
El programa no solo va a servir para calcular el RD, sino para generar la propia carta a la que disparar, para calcular con precisión los niveles de negro y saturación del sensor (esto en realidad es más una curiosidad científica para el fotógrafo que un dato útil), y también planeamos que sirva para medir el rendimiento completo del sensor en cualquier circunstancia (sería llevar la curva de relación S/N hasta la saturación).
Listado completo de los comandos que vamos implementando:
C:\>rango
Digital camera Dynamic Range calculation "rango" v1.0
by Juan Manuel Font (coding), Hugo Rodriguez (UI design) and Guillermo Luijk (algorithms)
Usage: rango [OPTION]... [FILE]...
--chart -c <DIMX W H M N> : Create test chart in PNG format ("testchart.png") with a specific resolution, format and number of patches (default DIMX=1920, W=3, H=2, M=4, N=6)
--chart-colour -C <R G B invgamma> : Create test chart in PNG format ("testchart.png") ranging colours from (0,0,0) to (R,G,B) with gamma compression (default R=255, G=101, B=164, invgamma=1.4)
--chart-patches -M <M N> : Read test chart decoding MxN patches over rows (M) and columns (N) (default M=4, N=6)
--chart-coords -x <x1 y1 x2 y2 x3 y3 x4 y4> : Read test chart defined by 4 corners (no specific ordering needed): (x1,y1), (x2,y2), (x3,y3), (x4,y4), being (0,0) the coordinates of the top-left pixel
--black-level -b <float> : Camera RAW black level
--black-file -B <file> : Totally dark RAW file ideally shot at base ISO
--saturation-level -s <float> : Camera RAW saturation level
--saturation-file -S <file> : Totally clipped RAW file ideally shot at base ISO
--input-files -i <files> : Input RAW files shot over the test chart ideally for every ISO
--patch-ratio -r <float> : Relative patch width/height used to compute signal and noise readings (default=0.5)
--snrthreshold-db -d <float> : SNR threshold in dB for DR calculation (default=12dB "Photographic DR")
--drnormalization-mpx -m <float> : Number of Mpx for DR normalization (default=8Mpx, no normalization=per pixel DR=0Mpx)
--raw-channel -w <R G1 G2 B AVG> : Specify with 0/1 boolean values for which RAW channel(s) the calculations (SNR curves, DR) will be carried out (default=0 0 0 0 1)
--poly-fit -f <int 2-3> : Polynomic order to fit the SNR curve (default=3)
--output-file -o <file> : Output CSV text file(s) with all results: black level, sat level, SNR samples, DR values, fitting params (default="results.csv")
--plot -p <int 0-3> <PNG/PDF/SVG> : Export SNR curves plot in PNG/PDF/SVG formats with/without the CLI command that generated them (default=0, don't plot, default format=PNG)
--print-patches -P <file> : Save keystone/ETTR/gamma corrected test chart in PNG format indicating the patches used for all calculations (default="printpatches.png")
Salu2!