Share on Google+Share on FacebookTweet about this on Twitter

 

Los Objetivos de Hoy:

  • Poder realizar capturas RAW con la gran mayoría de terminales móviles
  • Entender como son realmente las capturas RAW
  • Ser capaces de “revelar” dichas capturas RAW sea cuales sea su formato
  • Crear nuestras propias herramientas cuando lo que tenemos fuera no nos soluciona el problema
  • Versión Dcraw Modificada para dar soporte específico al Xiaomi Mi6 (Ver apuntes finales)

Como pequeña introducción, los amantes de la fotografía que usen Android, sabrán que este tiene dos APIs fundamentales a día de hoy: Camera1 y Camera2/HAL3.x. En teoría la segunda viene a sustituir la primera desde hace mucho tiempo ya, pero la vagancia de algunos fabricantes y el uso para variar de APIs propias de otros, hace que como todo se tomen su tiempo. La ventaja de usar Camera2/HAL 3.x es enorme, ya que de forma nativa Android permite la captura RAW, controlar controles ISO, tiempo de exposición, focus… y de ahí existe la extraña creencia que todo ello no puede hacerse en Camera1. Sí se puede, ojo, la diferencia es que es más complicado y no tan eficiente. Y ahí es donde todo esto empieza, por mi cabezonería. El Xiaomi Mi6, por ejemplo, es totalmente compatible con Camera2, pero no está habilitado por defecto. Así que me decidí a lograr capturas RAW sin usar Camera2 (que es trivial). Lo que no tenía ni idea es que iba a dar para tanto.

Siempre he dicho que yo no soy un genio, genio son aquellos que tienen las capacidades y habilidades de crear los códigos tan ingeniosos que crean. Que yo sepa programar y pueda en un momento dado crearme mis herramientas, utilidades y otros es una cosa, pero la capacidad de algunos compañeros para plasmar tanto problemas matemáticos, algoritmos… en código, siempre la he envidiado. Con esto quiero mandar un saludo especial a los creadores de las mismas herramientas que he usado/uso, y algunas que he modificado.

La primera, pienso que es la mejor aplicación de cámara en Android en cuanto a versatilidad y grado técnico. Por desgracia recientemente su creador, troop, eliminó no solo el repositorio de github, sino que terminó borrando casi todo rastro de sí mismo. Llego a estar en el PlayStore, y cada actualización era mejor que la anterior. No se saben bien los motivos, unos dicen que se cansó, otros que realmente la aplicación tenia malware… sea como sea, está oficialmente retirada, pero tenemos forks en github. La última versión que lanzó fue la 4.1 Alpha 5, de este mismo Julio. Por cierto, hablo de FreedCam. Y tengo que decir que fue la que empezó todo esto. Esta app tiene una peculiaridad (bueno tiene muchas) importante, y es que te permite cambiar de Camera1 a Camera2 (si el terminal es compatible), y usar en cada caso lo mejor que tu cámara pueda darte. Y es mucho, porque posiblemente no vas a encontrar otra cámara que en Camera1 sea capaz de hacer una captura RAW. Y sí, la hace… bueno, siempre y cuando claro el terminal exponga en su Driver que admite al menos la captura en algún formato que no sea JPG, y ya os digo que la mayoría puede!! Y sin usar Camera2. Pero todo esto lo veremos más adelante.

La segunda, ha sido la que después de mucho quebrarme el coco, me ha dado todo el código que necesitaba para que con unas simples modificaciones (realmente añadidos) me haya permitido “revelar” mis RAW. ¿Por qué? Bueno, porque aunque con Camera1 es posible hacer capturas RAW, estas capturas nada tienen que ver con las clásicas capturas RAW de Camera2 (DNG generalmente) o de otras cámaras, estos RAW suelen ser RAW pero RAW, sin cabecera, sin nada. En este caso ha sido el software de Dave Coffin el que lo ha hecho posible. Ahí tenemos la gran importancia del código abierto, por sí mismo su software no pudo ayudarme, pero su simplicidad y su buena estructura me hizo que fuese relativamente sencillo modificarlo. Y en este caso me refiero a su utilidad DCRaw, cuyo código fuente tenemos en su propia Web y podemos compilar/reescribir o hacer lo que queramos.

 

¿Por qué RAW?

Tengo que confesar que, entre otros muchos problemas o defectos, llevo muy mal eso de no entender algo. Por supuesto que nadie tiene conocimiento universal, me refiero a estar con algo entre manos y tener más preguntas que respuestas. Soy bueno el algunos campos, pero es obvio que en muchos otros soy un principiante o un completo negado. Y es lo que me ha pasado siempre con la fotografía, es un campo que me apasiona desde un punto de vista técnico y físico, no estético. Si me hubiesen preguntado hace una semana que era un Sensor CFA Bayer sin ponerlo en ningún contexto, sinceramente habría respondido con un: “No tengo ni puñetera idea”. Y eso me encanta, porque si es algo que me interesa tengo asegurados unos cuantos días de estrujamientos de sesos, y en el peor de los casos habré aprendido unas cuantas cosas. Así que si algún aficionado a la fotografía encuentra algún error en mis palabras o algo que no sea así, por favor, que me corrija, soy “nuevo” en esto.

Como me suele pasar siempre, suelo tener algo entre manos, de eso salto a lo otro, de lo otro a otra cosa… y al final estoy hasta arriba. Todo lo que voy a contar a continuación está basado en un Xiaomi Mi6 (Mi terminal actual), pero es aplicable de un modo u otro a cualquier (o casi) dispositivo Android e incluso en muchos aspectos a cualquier cámara de fotos en general.  Hace ya tiempo escribí un artículo principalmente versado sobre software fotográfico, hablando de características principales de las cámaras, HDR, RAW: Luces, Cámaras y… Software!!. La idea no es hacer una segunda parte, sino centrarme en puntos muy específicos dentro de la fotografía digital: La captura RAW.

La captura RAW ha ido aumentando en popularidad desde hace ya mucho tiempo, imprescindible para los profesionales, querida por los aficionados, y generalmente ignorada o desconocida para el resto. Llamamos fotos en RAW de forma genérica a cualquier foto digital que haya sido tomada por una cámara (DSLR, móvil, compacta…) sin que haya sido procesada por el propio dispositivo, o mínimamente procesada. Ya digo que para el aficionado o profesional esto es de primero, pero para la mayoría lo único que saben es que disparan y se genera un archivo digital, generalmente una imagen JPG. Pero esa imagen final que obtienen es el resultado de una serie de procesados internos que transforman/convierten las señales eléctricas recibidas por el sensor, la organización de estos datos, la interpretación, correcciones, interpolaciones… Digamos que el JPG es la foto revelada, mientras que la foto en RAW es el negativo, por tanto. ¿Y esto es importante? Bueno, para el usuario casual es indiferente, las cámaras hacen un procesado y post-procesado bastante decente y no necesitan más. Pero para quien busca ese digamos… “punto extra” o toque artístico añadido, o un resultado mucho más limpio y certero… entonces necesitas trabajar tú personalmente ese negativo. Hay una lógica aplastante en esto: Si un móvil o cualquier cámara de fotos a día de hoy es capaz de realizar todas esas conversiones, adaptaciones, interpretaciones… para obtener al final la imagen JPG, y lo hacen en tiempo real (de forma más o menos aceptable), imaginad el resultado que podría obtener un equipo de sobremesa que tiene infinitamente más potencial, sin necesidad de hacerlo en tiempo real y pudiendo usar filtros/algoritmos mucho más exactos, o que nos gusten más.

A todo ello hay que añadir un enorme problema adicional que hay en la fotografía digital. Los formatos de imagen como pueda ser JPG (el más usado), es un formato con pérdida, lo que significa que aun cuando el procesamiento de las señales fuese perfecto y todo acorde a nuestros deseos, el simple echo de inscribir dicha información como un archivo jpg, estaría ocasionando una pérdida importante de información. A lo mejor en una imagen de 20MP con una cámara profesional y para una foto de tamaño normal no se aprecia, pero para una foto con resoluciones más bajas o cuando necesitamos sacar un detalle concreto o… no son pocas veces las que vemos claramente los errores de compresión que provoca JPG, como “anillos”, “pixelado” de color y otros artefactos debidos a la cuantificación.

Si ampliamos y nos fijamos bien, se pueden apreciar diferencias en cada uno de todos los pasos, siendo por supuesto los dos últimos muy extremos. Entre Q100 y Q75 pude parecer correcta la imagen en cierto modo, pero si nos fijamos bien con Q75 la imagen tiene mucho más ruido, y la hélice ya empieza a generar un pequeño halo. Para Q50 los colores de la mitad derecha, sobre todo el cuarto superior empiezan a verse incluso bloques, para Q25 el efecto es visible en toda ella y a Q1… bueno, no es que quede mucha imagen. La mayoría de cámaras permiten seleccionar de un modo u otro este grado de cuantificación que por defecto suelen establecer entre 80 y 90. A valor más alto, la imagen como es natural tendrá un mayor tamaño.

RAW es indistinto a todo esto. No es que se trate de un formato de imagen sin pérdidas como pueda ser PNG, puesto aun así lo que se guardaría en el PNG sería la imagen procesada. Una imagen en RAW por lo general contiene casi con exclusividad la información pixel a pixel que se ha tomado. Ya depende de la decena de archivos RAW que usan los fabricantes, el como incluyan la información, en que formato, si añaden metadatos… y esto es un gran dolor de cabeza, porque al no existir un estándar de facto, cada fabricante genera el que cree mejor, y así al final se requieren suites como Adobe CameraRaw o RawTherapee (O por supuesto DCRaw del que ya hablaremos) para poder tratar dichos ficheros. Al contener en la medida de lo posible la información en crudo, por otro lado, son archivos mucho más pesados que sus homólogos comprimidos como JPG.

Pero… ¿Donde empieza todo?

 

Sensores CFA: Bayer

La fotografía digital es totalmente diferente a la tradicional en cuanto a como la luz queda al final plasmada en un formato digital. En la fotografía tradicional es una película fotosensible la que capta la luz externa desde el objetivo. En la fotografía digital, el sensor de imagen es el que es bombardeado por los fotones de la luz, los hace converger, y es capaz de generar pequeñas tensiones en función de la intensidad/variaciones de estos fotones. Como es de suponer su versión más simplista sería un sensor que tan solo fuese capaz de percibir la intensidad de luz, pero a día de hoy sabemos que tenemos sensores de alta definición con una gama de colores enorme. 

El color es un problema, puede que para el ojo humano sea bien sencillo, pero para la electrónica causa dolores de cabeza. Pensar en un sensor en realidad es sencillo, imaginar algo así como una superficie plana rectangular como un colador, que tiene un agujerito por cada pixel de resolución que tiene dicho sensor. Si el sensor es de 4032×3016 pues tendría dicho número de agujeritos. El sensor sería capaz de tomar la luz por cada uno de esos agujeritos, y registraría el estado de cada una de esas celdas fotosensibles, que pasaría luego a un formato digital. ¿Pero que pasa con el color? Esas “células” fotosensibles que habría debajo de cada agujerito no pueden conocer de forma genérica la longitud de onda que les llega… podrían saber si es rojo o azul, azul o verde, verde o… pero no tener una lectura completa de todo. A este problema existen varias soluciones.

La más obvia es usar sensores con diferentes “capas”, las cuales, cada una de ellas es capaz de atrapar la intensidad de uno de los tres colores primarios (Rojo, Verde y Azul, RGB), pero esto es caro. Así que al final, prácticamente la totalidad de cámaras de fotos usan una aproximación realmente al problema, y usan sensores CFA: Color filter array. Los sensores CFA son mucho más baratos, pero hacen trampa. Cada “celda” del sensor no es capaz de capturar la intensidad de más de un color, cada celda sólo sabe capturar el de uno sólo, pero se crea una matriz, un mosaico compuesto de dichas celdas, organizados de un modo muy concreto, y así dar la sensación de que cada celda (pixel) posee color propio. Pero es una ilusión. La efectividad de un sensor CFA empieza por tanto en el patrón o la organización concreta de esas celdas (que pueden ser rojas, verdes o azules) que tendrán en el propio sensor:

(Wikipedia)

 

 

Dentro de los sensores CFA, nosotros nos vamos a centrar a su vez en los sensores Bayer. Bayer fue el inventor del patrón mostrado en la imagen anterior. Bayer se dio cuenta que el ojo humano es mucho más sensible al color Verde que al color Rojo y Azul, tomando pues el Verde como el componente lumínico y el Rojo y Azul como componentes cromáticos. Así que estableció lo que a día de hoy se conoce como Mosaico Bayer o patrón Bayer, que es sencillamente la repetición de un mosaico/bloque de 2×2, que contienen dos componentes verdes siempre enfrentados, y un componente azul y otro rojo. Estos patrones Bayer se nombran en función del orden en el que aparecen dichos componentes, y como es lógico solo hay 4 posibilidades: RGGB, BGGR, GRBG y GBRG. Recordar que el patrón es un bloque de 2×2, no 4 celdas alineadas. En la imagen anterior vemos un patrón BGGR, que se repite indefinidamente. El por qué de tomar el sensor Bayer de ejemplo, es porque a día de hoy es de lejos el tipo de sensor más usado. Pero ojo, no es el único, y no es tampoco nada raro ver sensores CFA de otro tipo, con otros patrones diferentes.

Si nos fijamos en la imagen de arriba, vemos la trampa y en parte el problema. Vale, tenemos distribuidas celdas capaces de captar los tres colores (e intensidad de estos claro), pero si hemos dicho que cada celda representa por así decirlo un pixel… ¿¿como diablos representamos eso?? Que cada celda recogiese en sí misma toda la información RGB tendría sentido, un pixel, un color RGB asignado. Pero en el sensor bayer cada pixel sólo recoge un color, una intensidad. Si tenemos una imagen de 2MB (1920×1080), según el patrón BGGR mostrado, el resultado sería de todo menos fotográfico, veríamos claramente un efecto “mosaico” en la foto, no una imagen colorida y hermosa. Además, tendríamos la peor parte… si usamos bloques de 2×2, podríamos pensar que realmente “1 pixel” real correspondería a cada bloque, lo que implicaría que el sensor tuviese que tener el doble de tamaño. Y ya os digo que esto no es así.

 

Demosaicing (Teoría)

(Nota: Cuidado, si se hace clic pasa a la versión completa, 11MB)

Puede parecer una broma, pero, en la imagen podemos ver realmente la información que capta un sensor Bayer (El Xiaomi Mi6 usa un sensor RGGB). Vista en la previsualización es posible que no lo percibamos, y sólo veamos una imagen completamente verde (poco que ver con la imagen con la que empecé este artículo. Pero si tenéis una conexión mínimamente decente, os invito a hacer clic, ampliar y ver que está pasando. Y se ve, creerme que se ve. Si ampliamos la imagen unas 1600 veces, para quien no tenga ganas de abrirla, esto es lo que va a ver:

 

Ahora no parece tan verde… ahora podemos apreciar perfectamente cuales son los píxeles con las distintas intensidades verdes, y cuales los azules y rojos. El patrón siempre es el mismo, bloques 2×2 RGGB, RGGB…

Pero falta información en la imagen o eso parece… ¿¿donde están realmente los colores?? Es cierto que sabemos que con RGB podemos crear cualquier color, pero aquí tenemos filas y filas del mismo patrón, y dado que hay el doble de píxeles verdes, toda la imagen toma en este caso un color claramente verdoso, El color final se encuentra codificado en la información de la intensidad de los píxeles que les rodea. El patrón Bayer hay que deshacerlo para reconstruir los colores reales, el valor RGB de cada pixel, y no sólo uno de sus componentes. A este proceso es al que se llama demosaicing, que realmente es una interpolación de colores. Como tal, y como ya se dijo, es siempre aproximada, el sensor jamás será capaz de captar los colores “reales” en toda su resolución, pero gracias al software y a la física podemos reconstruir de un modo muy fidedigno la imagen original. Sí, no deja de ser un truco, pero en este caso un gran truco.

Por supuesto el resultado por tanto va a depender en gran medida del modo de interpolación que sea usado. No existe un modo de hacer este proceso de forma única, hay tantos como algoritmos de interpolación existan o queramos crear. Y esto no es exclusivo del sensor Bayer, de echo casi todas las cámaras a día de hoy sino todas, si no usan Bayer usan otro sensor CFA con otro patrón, que también hay que interpolar. Un equipo de sobremesa podría en todo momento aprovecharse de esto para cargar la foto sin “desmosaizar”, y aplicar diferentes algoritmos para ver cual de todos ellos da mejor resultado para dicha toma, o por fines puramente artísticos. Además un equipo puede permitirse el lujo de hacerlo ya no en tiempo real, sino todo tipo de algoritmos que sean complejos y sobrepasen la capacidad de cálculo de cualquier móvil o cámara de fotos.

Cuando empecé a ver todo esto, sinceramente me pareció una locura, sabía como funcionaban más o menos los sensores de imagen, pero creía que cada “celda” del sensor en realidad se subdividía en 2-3 subceldas cada una de ellas captando la intensidad de un color y conformando en sí misma un pixel completo, no tenía ni idea que realmente cada pixel tomado de forma independiente no poseía en sí mismo un valor completo de color RGB. Muchos pueden pensar que puede ser más o menos interesante, pero que a fin de cuenta que importa, si al final es el resultado lo interesante. Por supuesto, pero desde un punto de vista técnico, hay una importancia infinita, y es por culpa del desconocimiento que tenía en estas cosas por el cual no era capaz de “revelar” mi primera captura RAW que realicé con mi terminal con Camera1.

Soy un amante de Photoshop, pero si quieres un control más fino del revelado RAW o tener opciones más flexibles, quizás es mejor probar en esos momentos un poco de RawTherapee, además de ser gratuito. Puede mejorar en millones de cosas, pero a veces nos da ese algo más que no tenemos en PS. Por ejemplo, entre otras cosas, diferentes algoritmos  para “desmosaizar”

 

Parsear archivos RAW

Se desde hace tiempo que se podían hacer capturas RAW con algunas aplicaciones y potencialmente obtener del sensor los datos tal cual los captura, pero de ahí a interpretar dichos datos hay un abismo. Si fuera tan sencillo no existiría Adobe CameraRaw y sus constantes actualizaciones, precisamente para ir soportando poco a poco más formatos. Como he dicho cada fabricante usa sus propios formatos. Al menos incluso en esos casos podemos llamarlos “formato” de archivos. Si lees la información directamente del sensor, poco o nada tiene que ver con esos archivos. Cuando una cámara normal hace una fotografía en RAW o incluso con las APIs “normales” de Android, se genera un archivo RAW generalmente conocido, que además de incluir información a nivel pixel a pixel, incluye la información necesaria que después cualquier “revelador” RAW podría necesitar. Por ejemplo, los parámetros con los que se tomó la foto (exposición, distancia focal, lente, marca, modelo….), el tipo de sensor que usa, si Bayer que patrón Bayer usa, que matrices de color o multiplicación se han aplicado… un largo etc de lo que podemos llamar “metadatos”. Algunos tan importantes, por supuesto, como la resolución de la foto, la profundidad de bits de color… Realmente no componen la imagen en sí mismos, pero si aportan información de todo ello necesaria. Sin ella es poco probable que algún revelador RAW puede interpretarla.

Este es el caso que nos atañe. Sí, después de capturar una imagen RAW con Camera1 con Mi6 obtengo efectivamente un archivo pesado, de unos 15Mb, exactamente (y esto es importante) 15482880Bytes. Hace ya días me preguntaban que sí, que sabían que podía capturarse en RAW con Camera1, pero que les había sido imposible “abrirlas” (revelarlas). A diferencia de otras formas de captura, aquí la información que tenemos es mínima, y solo podemos empezar a teorizar, después de intentar pasarlo sin éxito por diferentes aplicaciones RAW. Cuando las herramientas disponibles no nos valen, hay que hacerse las herramientas, y ver que pasa. Por suerte no es la primera ni la segunda vez que parseo archivos… es decir, analizo archivos en principio de índole binaria, soy capaz de encontrar o identificar cabeceras, datos… y en función de la estructura de estos crear un parser que pueda interpretarlos de algún modo. Con el tiempo aprendes a ver “patrones” en los editores hexadecimales que te hacen pensar sobre que tipo de contenido pueden tener: Comprimido, cifrado, información de color, espacial… y siempre por supuesto no con un archivo, a ser posible al menos con 2 o 3 diferentes, así poder encontrar patrones similares, ideales para cabeceras, tamaños/offsets y otros.

Distribución más o menos uniforme, sin cabecera visible. Si tenemos en cuenta que estamos tratando con archivos de imágenes, y dada la estructura que a simple vista podemos ver, parece razonable pensar que el archivo generado es sencillamente la sucesión de pixeles de la imagen. En principio cada uno de los Bytes podría ser cualquier cosa, pero para empezar podríamos pensar que cada Byte representa la intensidad de color de un Pixel, y aun cuando no conociésemos que es un sensor Bayer, lo primero que pensaría es que los pixeles estarían representados por cada 3 Bytes, pongamos que el primer byte es la intensidad Roja, el segundo Verde y el tercero Azul, y se repite por cada pixel. No parece que exista cabecera y eso es un problema, ya que el archivo en sí mismo no nos da ningún tipo de información extra, que es totalmente necesaria, así que hay que extrapolarla de las cosas que sí sabemos.

Lo que sabemos:

Archivo: 15482880 Bytes
Resolución Foto Tomada: 4032 x 3016

Por lo general un archivo JPG tiene una profundidad de color de 8Bits por canal (24Bits en total), es decir que tanto para el canal Rojo, El Verde y el Azul, se usan 8bits en cada uno que indican su intensidad, y los 3 Bytes conforman el pixel. 1Byte permite 256 valores diferentes, así que una profundidad de 24Bytes podría darnos unos 16.7 Millones de colores posibles. Esto es más que suficiente, pero cada vez exigimos más, y en fotografía profesional, Juegos (ahora de moda), HDR y otras cosillas, se requieren profundidades de color mayores. No es nada raro a día de hoy por tanto ver cámaras que disparan RAW a 10, 12 y 16 Bits por canal, incluso los propios monitores soportan profundidades ya mayores a los 32Bytes de siempre (24Bytes+8 Bytes más del canal Alpha). Sin especificaciones en la cabecera y sin que nadie nos pueda ayudar, sólo nos queda presuponer y hacer cuentas, que no es complicado.

El archivo parece mantener la estructura de principio a fin, aunque si mirásemos al final del archivo veríamos una parte que está totalmente vacía y si miramos otros archivos siempre es el mismo tamaño… así que no es mala idea apuntar ese “extra” de archivo, siempre 282240 Bytes. Bueno, pues si es así sólo hay que multiplicar/dividir para adivinar que pasa. Si suponemos que cada byte es un pixel (un componente RGB), ¿cual debería de ser el tamaño final del archivo?? La resolución la conocemos de antemano

4032 x 3016 = 12160512 pixeles x 3Bytes (1Byte por componente, 8 bits de profundidad) = 36481536 Bytes. Un valor bastante superior al tamaño que realmente tenemos de archivo, con lo que si usásemos una profundidad de color aun mayor, el tamaño sería mucho mayo, y obviamente no vamos a tener menos de 8bits por canal.

Podríamos hacer otra hipótesis, y pensar que realmente no existen 3 componentes por cada pixel, es decir, 24bits por pixel, y tan solo 8bits por pixel, pero entonces tendríamos la percepción de tener una resolución 3 veces inferior. Aun así, vamos a ver como quedaría:

4032 x 3016 = 12160512 pixeles x 1 Bytes= 12160512 Bytes. Un valor mucho más cercano al tamaño original, pero aun no coincide, ni siquiera si sustrajésemos el espacio vacío de 282240 de final de archivo.

Una opción es seguir probando, otra sería hacerlo al revés, partir del tamaño original y ver que pasa:

 

15482880 Bytes x 8 = 123863040 Bits / 4032  = 30720 / 3016 = 10,185 Muy cerca, demasiado… podrían ser 10 Bits por componente, o 10 bits para los tres componentes. El caso es que teniendo en cuenta la resolución y el tamaño, cada pixel está codificado por algo más de 10bits. A ver que pasa si descontamos al tamaño completo el espacio en blanco final de archivo:

15200640 Bytes x 8 = 121605120 Bits / 4032 = 30160 / 3016 = 10 clavados.

Así que una de dos, o cada pixel se codifica con 10 Bits para las tres componentes, o cada “pixel” se representa con una sola componente y 10Bits, que teniendo en cuenta es una imagen RAW, es lógico que se use una profundidad de más de 8bits por canal. Aquí es donde automáticamente recordamos los sensores Bayer y como funcionan, y podemos sacar en conclusión que:

-El archivo no contiene ninguna cabecera
-El contenido del archivo es una sucesión de pixeles consecutivos, cada uno de ellos codificado a 10Bits
-Cada pixel codificado de este modo tan sólo posee una componente, lo cual coincide con lo que cabría esperar en un sensor Bayer.

Todo esto es sólo hipótesis, por ahora no hay una imagen de nada. Lo bueno de Bayer es que como cada pixel se codifica sólo como una componente, podríamos tratar toda la imagen como si no existiese color, y cada pixel sólo tuviese información en la escala de grises. Si fuese así, representar eso en cualquier editor tendría que darnos una imagen en escala de grises de la fotografía original. Eso sería trivial con cualquier aplicación, incluso en PS podemos forzar la apertura de una imagen RAW indicando la resolución, los bits por componente y cuantos canales. El problema que tienen todos esos editores es que la mayoría no contemplan profundidad de 10 Bits. Photoshop por ejemplo te permite forzar la apertura para 8, 16… pero no para 10. Otros si te lo permiten pero entonces no te permiten forzar que lo interpreten sólo como un canal… y ya sin entrar en las diferentes formas de codificar en 10 bits pueden existir.

Aunque sólo fuese para ver si vamos por buen camino, podríamos intentar usar alguna herramienta de conversión para transformar esos 10bits en 16 o en 8. Por ejemplo, cada 10bits leídos escribir 2 Bytes en otro archivo. El archivo final aumentaría el tamaño claro está, por usar 16bits por canal. Otra opción sería el proceso inverso, pasar esos 10bits a 8bits por interpolación, y el archivo final tendría un menor tamaño. Pero de cualquier modo por desgracia no existen herramientas sencillas para esto.

Lo más cercano que tenemos, sin entrar en programar nosotros mismos, quizás sea con ImageMagick, y su utilidad Convert, que precisamente hace cosas similares. Si definimos nuestra imagen de entrada como una sencilla representación gris en 10bits y con la resolución concreta, podemos pedirle que nos lo pase a 16bits:

convert.exe -size 4032×3016 -depth 10 GRAY:MiImagen.raw -depth 16 imagen_16bits.raw

Vemos que efectivamente el tamaño crece para acomodarse a 16bits por componente, y ser mucho más sencillo poder verla con cualquier visor. Tal cual, usando Photoshop o cualquier editor, especificando repito resolución profundidad (16bits ahora) y canales (1 en este caso, gris), tendríamos lo siguiente:

No es una imagen muy definida que digamos, pero sí que podemos ver realmente el motivo. Así que desde luego desencaminados no vamos. Ojo, si no hemos cometido error a este paso la imagen tendría que verse en escala de grises de forma correcta. Pero por ahora pueden ser muchas cosas… podría ser debido a la conversión realizada por ImageMagick y como ha mapeado los niveles de 10 a 16bits, o podría ser que pese a que cada 10bits represente un pixel, podría ser que dichos píxeles no estuviesen organizados, como hemos presupuesto, uno tras otro. Podrían estar organizados de otros modos. Está claro que muy diferente no puede ser, o no tendríamos imagen. De todos modos podemos ver que hace algo mal… podemos apuntar la cadena de Bits y comprobar a mano el valor que tendrían por ejemplo los 10 primeros pixeles en 10bits, y compararlo con el valor que después de la conversión a 16bits estos mantienen… y vemos que el valor no se corresponde, ni siquiera se acerca. Posiblemente porque ImageMagick remapee todo el rango y lo escale, o simplemente tomemos mal los bits.

A este punto, se nos han acabado los amigos que nos solucionen la papeleta. Toca picar código. Para la tarea de crear un pequeño parser según lo que sabemos y a ver que pasa, podríamos hacerlo de cero de forma simple casi en cualquier lenguaje, o podemos hacerlo sobre alguna aplicación ya pensada para estos menesteres. En realidad hacer nosotros el parser por nuestra cuenta tiene la ventaja de no depender de nada mas y el código es simple, pero usar en este caos Dcraw nos da muchas más opciones a la hora de procesar. En principio la tarea es “sencilla”, un script/app que lea el archivo original y cree otro transformando cada 10bits en 16Bits. Mi primera idea, y creo que la lógica, es no mapear ningún tipo de color, sencillamente tomar  de 10 en 10bits, y meterlos cada uno en 16bits, conservando el mismo valor. Así si los primeros 10bits poseen un valor de por ejemplo 958, codificado en 2 Bytes sería 0x03be. El archivo final debería de poseer un tamaño 1.6 Veces mayor, ya que cada pixel pasaría a tener en teoría una profundidad de 16bits, aunque de ellos sólo se usarían 10. Recordamos que buscamos un resultado que podamos abrir y veamos claramente una imagen adecuada en escala de grises. El pseudocódigo de esto sería algo así, pensando en el stream de bytes como si se tratase de una “matriz” de bytes

Ancho_de_matriz = (Resolución_horizontal * 10) / 8
Columna = Fila = 0
Imagen = prueba.raw
Puntero_ArchivoNuevo = ImagenFinal.raw
Buffer, Buffer_Aux

Para (Fila < Resolución_vertical, Fila ++)
  Almacenar en Buffer la cantidad de Ancho_de_Matriz Bytes desde imagen //En cada interacción lee a partir del último valor leído
  Para (Columna < Resolución_horizontal, Columna += 4, Puntero_ArchivoNuevo += 5. Buffer_aux = Buffer)
    ArchivoNuevo [Fila, Columna] = (Buffer_aux [0] << 2) | (Buffer_aux [1] >> 6)
    ArchivoNuevo [Fila, Columna+1] = ((Buffer_aux [1] & 0x3f) << 4) | (Buffer_aux [2] >> 4)
    ArchivoNuevo [Fila, Columna+2] = ((Buffer_aux [2] & 0xf) << 6 )| (Buffer_aux [3] >> 2)
    ArchivoNuevo [Fila, Columna+3] = ((Buffer_aux [3] & 0x3) << 8) | Buffer_aux [4]

Visto así no hay quien lo entienda… pero en realidad es muy sencillo, es lioso porque hay que trabajar a nivel de Bits, y no de Bytes. La premisa es que hay un patrón que se repite, constantemente, y de ahí los dos bucles anidados. El patrón que se repite son las 4 sentencias finales. El bucle externo itera sobre cada fila, el interno por columnas. La única salvedad es que la iteración de columnas no toma columna a columna (byte a Byte) sino de 5 en 5 bytes. La razón es sencilla, si se codifican 10bits por pixel, el valor entero más cercano que a la vez sea múltiplo de 10 y de 8 es el 40. 40 Bits son 4 píxeles (columnas en archivo viejo) y a la vez son 5 Bytes leídos. Así que tratamos directamente con 5 Bytes a la vez, y repetimos hasta terminar.

La conversión lo único que hace es ir almacenando en otro archivo/buffer cada 10bits extraídos en 16bits, y para hacer eso lo más cómodo es desplazando los bits para un lado u otro. Vamos a ilustrar esto, pero de un modo mucho más sencillo, imaginemos que estos son los primeros 5 Bytes, y para facilitar la visualización vamos a obviar que usamos binario y que obviamente no podemos usar otros caracteres que no sean 0/1. pero hacer el seguimiento a los números es más cómodo.

Queremos pasar de esto: 11111111 22222222 33333333 44444444 55555555
A 16 bit, algo como esto: 00000011 11111122 00000022 22223333 00000033 33444444 00000044 55555555

Ejemplo paso a paso de las dos primeras columnas, las otras dos serían similares

Columna 0

Tomamos el primer Byte completo: 11111111, pero ahora tratamos los datos como 16 bits puesto que ArchivoNuevo está definido como tal, luego realmente tenemos: 00000000 11111111.
Lo desplazamos 2 posiciones hacia la izquierda: 00000011 11111100
Tomamos el segundo Byte competo: 22222222, y al igual que antes, tenemos realmente 00000000 22222222
Desplazamos hacia la derecha 6 posiciones: 00000000 000000022
Operamos con OR lógico los dos resultados previos: 00000011 11111100 OR 00000000 00000022 = 00000011 11111122

Columna 2

Tomamos el Segundo Byte completo: 22222222, realmente tenemos: 00000000 22222222.
Aplicamos una máscara AND antes de desplazar para evitar propagar los 2bits superiores, seteándolos a cero: 00000000 22222222 & 00000000 00222222 = 00000000 00222222
Lo desplazamos 4 posiciones hacia la izquierda: 00000022 22220000
Tomamos el Tercer Byte competo: 33333333, realmente 00000000 33333333
Desplazamos hacia la derecha 4 posiciones: 00000000 000003333
Operamos con OR lógico los dos resultados previos: 00000022 22220000 OR 00000000 00003333 = 00000022 22223333

 

Por desgracia, este primer parser nunca me funcionó. Tenía un resultado ligeramente mejor que el anterior expuesto, pero muy muy similar. Quizás ImageMagick hacía un trabajo parecido después de todo. Pero lo bueno de hacerlo uno mismo es que una vez realizado, modificar algunos detalles es trivial, solo hay que modificar, compilar y probar de nuevo. Después de darle unas cuentas vueltas opté por algo muy sencillo… pensemos en compatibilidad… en 5 Bytes tenemos 4 píxeles, ¿¿y si realmente la mayoría de esa información que nos interesa estuviese realmente en esos 4 píxeles?? Es decir, que pasa si eliminamos el quinto Byte y tratamos realmente cada Byte como tal (con una profundiad de 8 bits y no de 10) Contamos 5, usamos y asignamos los 4 primeros y descartamos el quinto. Además es mucho más fácil programarlo, es trivial, sólo hay que asignar directamente sin desplazamientos ni historias.

    ArchivoNuevo [Fila, Columna] = Buffer_aux [0]
    ArchivoNuevo [Fila, Columna+1] = Buffer_aux [1]
    ArchivoNuevo [Fila, Columna+2] = Buffer_aux [2]
    ArchivoNuevo [Fila, Columna+3] = Buffer_aux [3]

Compilamos, convertimos, el quinto no será usado nunca, ya que en el iterador interno contamos de 5 en 5:

No sé como tendréis calibrado los monitores, porque está algo oscura, pero para mi que eso es una imagen perfecta en escala de grises (bastante oscura eso sí), a la original. Pero entonces, ¿para que sirve ese 5 Byte? Bueno, en realidad cuando llegas a este punto piensas que es lógico. El sensor manda los datos en 10Bits, y que mejor forma que hacerlo por cuestiones de compatibilidad que “empaquetados” en Bytes normales, + 1Byte adicional que porta los 2Bits restantes de cada uno de los 4Bytes (píxeles) anteriores. Como en la prueba simplemente fueron eliminados, la conclusión es clara, el quinto Byte contiene los 2 bits LSB de cada uno de los otros, es decir, los Bits de menos peso en esos 10 Bits. La idea original era errónea:

11111111 22222222 33333333 44444444 aabbccdd
No pasa a ser:
00000011 11111122 00000022 22223333 00000033 33444444 00000044 aabbccdd

Pasa a ser:
00000011 111111dd 00000022 222222cc 00000033 333333bb 00000044 444444aa

Lo cual implica una gran diferencia. En realidad podrían asignarse al revés, y suponer que la asignación se hace al contrario, es decir que al primer byte le corresponde los dos MSB del 5º byte, y no los 2 LSB. Por desgracia los resultados son demasiado similares como tener una certeza exacta ahora mismo de cual de las dos asignaciones es la correcta. Sería necesario hacer capturas muy concretas para que las diferencias se diesen en un borde o un cambio suficientemente brusco como para percibirlo. Actualmente he optado por la segunda, por ser más simétrica, los 2 MSB para el primero, los dos siguientes para el segundo… pero da igual una que otra realmente. La implementación, teniendo en cuenta ya expuesta las otras dos, creo que es sencilla, así que la dejo en manos de quien le interese como ejercicio, y en todo caso si le interesa a alguien y no puede, que la pida. El resultado es similar al anterior, aunque una imagen bastante más clara. Si ampliásemos, podríamos observar, aunque en escala de grises, el patrón Bayer, en este caso en monocromo. Se podría multiplicar la imagen anterior para obtener una imagen mucho más clara, Si podemos capturar 10 Bits en vez de en 8 Bits, es precisamente para poder tener una mejor precisión de color y poder representar una paleta mayor, si en la conversión eliminásemos esos 2 bits de más empaquetados en el 5º byte, , pues la verdad es que le quitamos gran parte de la gracia al asunto.

La importancia de la imagen en escala de grises es por la propia naturaleza del sensor Bayer. En realidad cada pixel no representa una tonalidad de gris, sino una intensidad Roja, Verde o Azul, pero mientras que no se deshaga el mosaico bayer, podemos tratarla como si cada pixel fuese ni más ni menos como un pixel gris. Podríamos representarla tal como es, pero al menos que yo sepa no existe ningún visor digamos… Bayer, que represente el color de cada pixel en función de un patron dado, para dar una imagen similar a la expuesta en la apertura de la sección de Demosaicing. No obstante se podría hacer uso de Photoshop para representar el mosaico de un modo bastante fidedigno, creando el patrón RGGB, rellenando una capa sobre la imagen con dicho patrón… pero sería complicar las cosas, cuando lo principal está terminado. Photoshop es cierto que no posee ningún filtro o sistema sencillo para realizar la interpolación de colores (a menos que se haga desde CameraRaw e identificando la imagen), con lo que dcraw nos va a servir para hacer la interpolación. De ahí que, una vez creado el parser, si lo implementamos dentro de dcraw, tendremos en la misma herramienta todo lo que necesitamos. Por supuesto este es el caso específico del Mi6, pero como dije en su momento es extrapolable a cualquier otra imagen RAW capturada por los teléfonos/cámaras… con los correspondientes ajustes, por supuesto.

 

Dcraw

Una gran utilidad, y al ser además de código abierto podemos meternos en sus tripas, editar lo que deseemos, añadir… en realidad nos viene perfecta, ella misma está preparada para gran cantidad de formatos de cámaras, y hace precisamente todo lo que queremos hacer nosotros. Si hemos sido capaces de crear un parser para nuestra cámara, siempre y cuando no esté incluida ya, implementarlo/añadirlo a dcraw es sencillo, además de poder luego procesarla. El código de dcraw está publicado en la propia web del autor ya mencionado, quien quiera una copia fresca la puede obtener de AQUI. Está creada de forma sencilla en C sin dependencias raras y se compila sin problema con gcc:

gcc -o dcraw -O4 dcraw.c -lm -DNODEPS -l ws2_32

En este caso sería compilado para Windows, de ahí la inclusión de ws2_32 por ejemplo, pero se puede compilar para cualquier OS realmente.

Llos cambios necesarios son escasos. Necesitaremos crear el parser por un lado, una metadescripción de nuestro formato y una llamada a nuestro parser cuando se encuentre/identifique nuestra imagen. Como digo el código de dcraw es bastante limpio y con un poquito de tiempo se comprende bastante bien.

Así pues nuestra primera parada es la descripción de nuestro archivo. Sobre la línea 8300 vemos una tabla ya creada con lo que parecen descripciones de diferentes cámaras, así que la idea es incluir la nuestra. Esto podemos hacerlo no sólo con una, sino con las que queremos. Pero recordar que aquí sólo describimos por así decirlo la cámara. Para nuestro caso sería algo así:

{ 15482880,4032,3016, 0, 0, 0, 0, 1,0×94,0,0,”Xiaomi”,”Mi 6″ }

Es necesario buscar en el código el significado de cada campo, en la misma estructura queda más o menos puesto:

fsize: Tamaño del archivo en bytes
rw, rh: Resolución horizontal y vertical respectivamente
lm, tm, rm, bm: Márgenes izquierdo, superior, derecho e inferior
lf; load_flag (usado en varias partes del código para “marcar” ciertos archivos
cf; Filtro a usar, en este caso tendremos que especificar que queremos usar un filtro Bayer RGGB, 0x94.
max: máxima profundidad de bits usada
flags: Diferentes flags, como inversión de imagen y otros.
make: Fabricante
model: Modelo

Hay que tener en este caso concreto algo claro e importante. Recordemos que en el caso del Mi6 el archivo que se genera añade al final del archivo unos miles de Bytes adicionales. Si queremos procesar el archivo directamente sin necesidad de eliminar previamente dichos Bytes, tendremos que especificar el tamaño completo, con dichos bytes incluidos, y en nuestro parser asegurarnos que no se lee más allá. En el pseudocódigo anterior esto no es problema, ya que se lee hasta que la fila leída coincida con la resolución vertical, con lo que el resto se ignorará. Pero hay que tenerlo en cuenta, si el parser lee el archivo completo hasta final de archivo, podría incluir dichos Bytes. Es más cómodo especificar el archivo completo, y no hay que estar metiendo tijera, o en el propio parser tener que cortarlo. La captura RAW lee todo el sensor, así que la resolución siempre será la  máxima por regla general, y el tamaño de archivo también.

Una vez definido nuestro archivo, esta tabla permitirá que el archivo sea reconocido y pueda seguir adelante, aunque no significa que hayamos terminado. Esta tabla es muy útil porque si por ejemplo tenemos otro terminal que captura de un modo muy similar, casi tan sólo tendríamos que añadirlo a la tabla para que directamente funcionase, y usar el mismo parser creado. Sea como sea, lo siguiente es crear el parser tal como quedó dicho anteriormente, cada uno que lo desarrolle como quiera. Podemos colocar el código por ejemplo encima de “minolta_rd175_load_raw” y para seguir con la misma nomenclatura llamar a nuestro parser: “xiaomi_load_raw”. Podemos servirnos de los propios métodos ya creados en dcraw para facilitarnos el parser, pero la cuestión es que al final la “imagen” final resultante quede en raw_image, accedida desde RAW(row,col)

Y para finalizar, sólo queda añadir el salto a xiaomi_load_raw cuando detectemos el archivo en cuestión. Pensar que el parser creado no es específico para xiaomi, en realidad es un parser MIPI de 10bits, y dcraw carecía de él. dcraw sí incluye ya parser parecidos incluso para parsear raw bayer en 10bits, pero tendríamos algo similar al primer resultado que obtuvimos al presuponer que que los bits estaban totalmente serializados, y no empaquetados, como al final fue el caso. Así que podemos hacer uso de esa misma función, sobre la línea 8595 más o menos vemos diferentes case, para 8, 10, 12… bits. Desdoblamos el Case 10, y simplemente llamamos a  nuestra función del mismo modo que hacen otros:

load_raw = &CLASS xiaomi_load_raw; break;

Eso es todo, compilar y listo. Si todo es correcto tan sólo tendremos que invocar dcraw como dice en su propio manual y con los parámetros que queramos. La idea de usar Dcraw es múltiple. Por un lado podemos hacer uso de sus propias funciones para facilitarnos la tarea de crear nuestro parser, pero además nos va a permitir realizar a la imagen diferentes transformaciones/conversiones. Por supuesto, principalmente lo que haremos será la interpolación de colores para convertir nuestro mosaico Bayer en una imagen todo color, pero también es muy útil para ajustar algunos parámetros antes de realizar la interpolación. Recordar que es una imagen RAW, es decir, vemos lo que ha tomado el sensor, sin procesar, sin aplicar matrices correctivas, cambios de espacios de color… nada. Pero precisamente por eso usamos RAW, ¿no? Si queremos precisamente una imagen procesada, nos quedamos con el JPG.

 

Uso de Dcraw

Dcraw es algo así como un revelador RAW, toma una imagen RAW de entrada y la convierte/interpreta a un formato más amigable. Ya sabemos que cuando se lo ordenemos nos va a decodificar la imagen Bayer en una ¿bonita? imagen, pero depende de lo que le digamos, la salida será realmente sin procesar o podrá realizarle algunos ajustes básicos. Cuando una cámara dispara una foto en RAW, por lo general adjunta en ella un perfil DCP que especifica las transformaciones de color, tipo de sensor… y otros ajustes que deberían de ser necesarios (al menos de forma orientativa), para que la imagen sea visualizada de forma correcta. De echo gracias a estos perfiles incrustados la mayoría del software RAW que existe en el mercado (Como Adobe CameraRAW) se basa en estos perfiles para automáticamente aplicar dichos ajustes a la imagen y tener nosotros algo bonito en pantalla.

Muchos podrían pensar que estos ajustes matan el principio del propio RAW, que es tomar una imagen sin procesar. Realmente los perfiles no dictan como se ha procesado la imagen, sino como se podría procesar en función de la información del sensor que el aporta. Así podemos usar el perfil tal como está, o podemos realizar en el editor los ajustes que deseemos, pero desde luego nos suelen servir de base. Aquí de todos modos tenemos un pequeño problema añadido… al archivo RAW nuestro no se le añade ningún tipo de información adicional, ni cabecera. Eso quiere decir que la imagen que vamos a obtener en este caso es totalmente RAW sin perfiles que especifiquen nada… ya no sólo información para que el software de tercero sea capaz de parsearla, sino tampoco ajustes básicos que habría que tener en cuenta del sensor: Matrices de Color, de conversión de espacio de Colores, punto negro… y no es una tontería, porque la no tener una referencia de estos, tendremos que valernos de nuestras habilidades para ajustar correctamente tanto colores como balances de blancos y otros, de nuestra imagen. Sobre la pregunta obvia de si sería posible implementar en Dcraw igualmente matrices de ajuste específicas para cada cámara/dispositivo, la respuesta es que por supuesto, pero esto varía enormemente de sensor en sensor, con lo que no se trata de poder usar una de otro modelo para ajustar la nuestra. Por supuesto si damos con los parámetros que nos gustarían, podríamos hacer que aplicase dichas transformaciones, cosa que hacen muchos otros modelos/dispositivos. Yo no he tocado las matrices, no hay correcciones de nada en principio, quitando eso sí los ajustes al vuelo que permite realizar Dcraw y que ahora veremos. Recordamos que en Dcraw realmente ya especificamos resolución, tamaño de archivos… así que será por este tipo de identificadores por los cuales Dcraw sabrá que imagen estamos metiendo de entrada, no importa la extensión o el nombre.

Algunos ejemplos sencillos y las salidas resultantes:

1. Conversión “estándar”. Imagen resultante no lineal, en 8bits, interpolada, y escalada: dcraw -v -c Camera1_bayer10.bayer > Test1.pgm

Es muy similar a lo que sería la imagen JPG resultante procesada, excluyendo el balance de blancos y un estrechamiento en los niveles. Por razones evidente este tipo de conversiones no son lo que deseamos, para eso menos lío realizando la fotografía normal. La idea de RAW es poder jugar con el máximo de información de la propia imagen para procesarla nosotros de un modo mucho más fino, y en este caso hemos de un plumazo eliminado la linealidad de la imagen, así como la mayor profundidad de colores. Esto se puede solucionar especificando que se realice la conversión en 16bits (manteniendo los 10bits que capturó el sensor), o directamente especificando que se mantenga la linealidad en la imagen, que por defecto también fuerza la salida en 16bits, en vez de 8.

 

2. Conversión manteniendo la linealidad de la imagen y los 10bis de profundidad (convertidos a 16), interpolada y escalada: dcraw -v -c -4 Camera1_bayer10.bayer > Test2.pgm

Pese a lo que pueda parecer, es muy diferente a la anterior. En este caso no sólo existe un mayor rango de colores (no apreciables aun), sino que la imagen es lineal, de ahí a que parezca más oscura (incluso estando escalada). Esto es importante. Prácticamente la totalidad de los sensores de las cámaras tienen un comportamiento lineal, es decir, que el “valor” de cada celda/pixel que es recogido debido a la intensidad de los fotones que llega a ellos, es siempre proporcional. Dicho de otro modo, si por ejemplo para 10 fotones el sensor registrase en dicho pixel un valor de 20, para 20 fotones recogería un valor de 40. Esto puede parecer una tontería, pero la cuestión es que el ojo humano y nuestra percepción es de todo menos lineal. Las imágenes lineales suelen comprimirse sobre todo en las sombras, mientras que las no lineales se expanden hacia los medios tonos e iluminaciones, produciendo un comportamiento, aparente, de más homogeneidad:

Se puede apreciar perfectamente los dos efectos. Los picos o “vacíos” que se encuentran entre las diferentes curvas en la primera imagen se deben a la reducción de la profundidad de color, no presentes en la segunda. Así mismo se puede observar perfectamente la linealidad de la imagen, mientras que el primer histograma se esparce por la mayoría de la escala, la segunda imagen, lineal, se condensa en la parte izquierda, en las sombras. La cuestión es simple, si queremos tratar lo más RAW posible una imagen, es de sentido común que no la transformemos en no-lineal como se hacía en el anterior ejemplo. Aquí no no la convertimos en lineal, sino que la mantenemos lineal, al igual que mantenemos su profundidad de 16bits (1obits). Obtenemos una imagen mucho más fiel a lo que realmente capturó originalmente el sensor.

 

3. Salida RAW pura (sin interpolar), escalada y sin escalar: dcraw -v -c -4 -d/-D Camera1_bayer10.bayer > Test3.pgm

Cuando hablamos que la imagen no está interpolada, significa que no se ha revertido el filtro Bayer. Como ya dijimos no es que la imagen esté en escala de grises, es que realmente cada pixel corresponde a la intensidad de un color concreto. Esta imagen la hemos visto anteriormente. Si se representase en vez de en escala de grises en color, el resultado sería la imagen verdecina que vimos anteriormente. Por lo general, cuando se representan las imágenes sin deshacer el mosaico Bayer, es así como lo hacemos.

Por otro lado, hasta ahora todas las imágenes que se han puesto están escaladas. ¿Que significa esto? Recordar que estamos tratando con imágenes de 16 bits de profundidad y encima lineales. Eso quiere decir que en teoría cada pixel de dicha imagen puede tomar un valor entre 0 y 65535 (debido a los 16bits), y que debido a la linealidad “empezamos” en el cero (negro absoluto, ausencia de fotonos). Pero en cambio el blanco absoluto no existe como tal concepto, dado que el sensor en teoría siempre podría ser más sensible, captar más fotones y aumentar el valor aun más, con lo que hablaríamos del concepto llamado blanco más allá del blanco. Por eso vemos siempre una condensación en el extremo izquierdo en fotos lineales. Bien, ya hemos hablado de la linealidad, ¿pero el escalado? Bueno, es útil/necesario por dos motivos. El primero como se ha dicho debido a la linealidad y la compresión en el lado izquierdo, pero también porque nuestro sensor no es sensible a 16bits, es decir, a los 65536 posibles valores. Para 10bits, nuestro sensor “solo” es capaz de distinguir entre 1024 valores diferentes (recordar que todas las imágenes que vemos en fotos y otros generalmente están a 8bits, 256 valores diferentes), con lo que a efectos prácticos si no escalamos los valores esta sería negra o prácticamente negra entera (de ahí que no vaya a poner una imagen haciendo la prueba). Esto se soluciona de un modo sencillo, y es multiplicando literalmente el valor de cada pixel. En este caso tenemos 10bits = 1024, 65536 / 1024 = 64. Es decir, un factor de escala de x64. Dado que el sensor no es capaz de ir más allá de los 10bits, no vamos a tener pérdida de información, aunque por supuesto no se nos escapa que si el sensor fuese más sensible podríamos captar más detalle en los colores.

Si hacemos, esto, si escalamos la imagen, el resultado pasa de ser negro a ser la imagen que he puesto anteriormente. El parámetro d/D en Dcraw especifica siempre que la salida será sin interpolar, sin deshacer el filtro Bayer, la elección de un parámetro u otro, es que D no escala la imagen, mientras d la escala automáticamente en función del valor que el estima que es el adecuado analizando internamente la imagen. En este caso acierta, y aplica un escalado de x64. Hay que indicar que el mismo resultado lo podemos obtener perfectamente sin escalar la imagen, y haciéndolo manualmente. Por ejemplo en Photoshop, podemos acceder a los ajustes de niveles de la imagen y modificar la salida. Photoshop independientemente de la profundidad de color que se use, usa sliders de 256 valores, pero pese a que sólo muestra 255 saltos mantiene igualmente la información intacta y escala en función a ello. En este ejemplo concreto si no escalásemos la imagen, para tener el mismo resultado tendríamos que desplazar el slider de photoshop al valor 3. Sí, desplazar el medidor derecho que está en 255 al 3. Y magia, la oscuridad desaparece (256/64 = 4, 3 teniendo en cuenta que el cero cuenta.)

 

4. Conversión lineal, interpolada, escalada, con ajuste de balance de blancos, y otros: dcraw -v -c -4 -T -a -k 50 Camera1_bayer10.bayer > Test1.tiff

El parámetro k permite especificar el punto negro. Dado que escalamos la imagen, esta tiene a desplazarse hacia la derecha, haciendo que se produzca un “agujero” en la zona inferior. El cero absoluto originalmente es el cero, pero si cuando se tomó la imagen el valor más pequeño era el 100 (por ejemplo), al multiplicarse por 64 deja un hueco más… “grande” en el negro. Este ajuste permite desplazar de nuevo la imagen hacia la izquierda, restaurando el negro “puro”. Obviamente eso produce un oscurecimiento generalizado debido a que el desplazamiento es a toda la imagen, cosa que luego se podría corregir. Aun en este punto la imagen seguiría siendo lineal.

El otro punto importante es el balance de Blanco. El sensor capta lo que capta, y aquí no está calibrado hacia nada. Es por eso que la imagen se muestra verdosa si se interpola y no se toca nada más, los colores no son los que deberían de ser. El ajustar el balance de blancos pone punto y final al problema. Por desgracia esos ajustes dependen de la fotografía tomada y del sensor. Dado que no tenemos datos más o menos fiables del sensor, sólo podemos probar diferentes ajustes en función claro está de lo que vemos… prueba error. Dcraw incluye el parámetro “a” que permite evaluar la imagen completa y estimar el balance de blancos adecuado, pero como todo es siempre relativo. Por ejemplo para la imagen anterior, dcraw calcula que los valores adecuados para el balance de blancos sería:

1.122304 1.000000 1.947417 1.000437

Dcraw especifica el ajuste de blancos multiplicando cada componente (antes de la interpolación por supuesto) por un valor concreto. En un filtro Bayer tenemos 4 componentes recordemos, en nuestro caso es un sensor RGGB, así que cada multiplicador se aplica a cada uno de ellos, aunque no en ese orden. El orden de aplicación es R G1 B G2. Si nos fijamos, la imagen es muy verdosa, y efectivamente ambos componentes tienen un valor de 1 (o cercano a él). Por el contrario aumenta de forma considerable el componente azul. La imagen resultante es mucho mejor, pero como es natural no está a la altura.

Si creásemos el parser como un complemento en Photoshop para CameraRaw, este tipo de cambios serían totalmente interactivos, al igual que lo usamos para otro tipo de formatos. Por desgracia Photoshop no tiene forma humana de interpretar nuestro archivo, pese a que no es muy complejo. Eso nos “limita” en tanto que si queremos realizar un ajuste de blancos antes de la interpolación, lo tenemos que hacer manualmente. Por supuesto podemos corregir luego todo lo que queramos a la imagen, pero recordemos que ya será sobre la imagen interpolada. De cualquier modo, aun estaríamos con una imagen lineal, y de ahí la apreciación de una imagen “triste”, sin un colorido importante. Ya hemos dicho que el ojo humano no percibe la realidad igual que la tecnología, así que para que la imagen se ajuste a algo más vistoso, el paso final sería retocarla en cualquier editor para romper la linealidad y ajustar correctamente el resto de parámetros.

 

Apuntes finales

Aunque la modificación realizada a Dcraw afecta únicamente en primera instancia al Mi6, es extrapolable a cualquier otro teléfono o dispositivo que sea capaz de capturar imágenes de un modo similar. Gracias a la granularidad de Dcraw, añadir o quitar “perfiles” de cámara es sencillo, y si usan un sensor similar o un sistema que transfiera los datos de igual modo, es suficiente con añadir resolución, tamaño y patrón bayer correspondiente a la lista. No importa que sea un Mi6 o un Mi5 o por supuesto terminales de otros fabricantes, si la cámara reporta que es capaz de capturar RAW en algún formato, podemos añadirlo. Como es lógico, añado sólo el terminal con el que he andado jugando, pero animo a cualquiera que pruebe si le interesa.

No me gusta distribuir binarios, prefiero que el usuario se las apañe y aprenda a compilar por si mismo, pero dado que voy a publicar la herramienta en otro lado también, no creo que haga mal en esta ocasión a nadie distribuirla, y ahorrar el trabajo a algunos. La versión usada es la más actual hasta la fecha de Dave, 1.477, que he cambiado a 1.478. Los únicos cambios realizados son los expuestos: Añadir el parser, desdoblar la rutina de reconocimiento de 10bits para llamar al parser e incluir el Mi6 dentro de la tabla de cámaras. Realmente debido a las pruebas que hice tengo creado 4-5 parser diferentes, pero a efectos del código compilado es indiferente:

Enlace a dcraw compilado

 

Dado el interés que se han tomado algunos, en la medida que tenga tiempo y me manden los archivos necesarios, añadiré soporte a otros dispositivo, incluyendo obviamente el Mi6.

 

Actualizado (15 Sept 2017):

Soporte adicional para los siguientes dispositivos:

-Xiaomi Mi 6
-Xiaomi Mi 5