User avatar
jbeale
Posts: 3494
Joined: Tue Nov 22, 2011 11:51 pm
Contact: Website

Re: Pi Camera V2 raw bayer data

Tue Jun 28, 2016 2:15 pm

puppy101puppy wrote: What is your command line to capture your raw image, PiCamV2-Raw-Chart2.jpg?

Code: Select all

raspistill --raw -o PiCamV2-Raw-Chart2.jpg
It's a little confusing because that is a valid JPEG file, but it is much larger than normal and also includes the RAW frame data appended at the end of the jpeg data.

6by9
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 7304
Joined: Wed Dec 04, 2013 11:27 am
Location: ZZ9 Plural Z Alpha, aka just outside Cambridge.

Re: Pi Camera V2 raw bayer data

Tue Jun 28, 2016 2:51 pm

jbeale wrote:It's a little confusing because that is a valid JPEG file, but it is much larger than normal and includes the RAW frame appended at the end of the jpeg data.
If you want the history, we needed a way to grab the raw images from within Android where the JPEG is the only product of the capture. JPEG has start and end markers for the encoded data in the bitstream, and most decoders just stop when they've found the image they were looking for, so tacking the raw on the end was very convenient.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

User avatar
puppy101puppy
Posts: 16
Joined: Mon Feb 02, 2015 8:19 am

Re: Pi Camera V2 raw bayer data

Wed Jun 29, 2016 4:37 am

6by9 wrote:
puppy101puppy wrote:Thanks for sharing -- indeed it compiles and works for your image.
However, I tried with my raw images captured with raspberry pi camera v2 and it generates a random noise raw image (so there's something wrong with encoding). I was using the command below to capture raw images:

Code: Select all

 raspistill -n -r -o filename.jpg 
Are you also using camera v2? What is your command line to capture your raw image, PiCamV2-Raw-Chart2.jpg?
Can you post one of your files somewhere for analysis? dcraw was working fine when I made the mods.
puppy101puppy wrote:Also, "dcraw" function it generates a warning, although it creates the raw image fine for your PiCamV2-Raw-Chart2.jpg (not the raw image captured with my above command):

Code: Select all

 ./dcraw filename.jpg filename.ppm 
cannot decode file filename.ppm 
dcraw automatically creates the output filename from the source one, so your command is trying to process the two files filename.jpg and filename.ppm as two raw files.
Hi,

Thanks for your reply!!!

I tried on my another raspberry pi and the "extract raw" worked fine -- perhaps it was some mis-configuration on one of my raspberry pi. Here's the pi/image that doesn't work:
https://drive.google.com/a/g.ucla.edu/f ... sp=sharing
and the random ppm image generated: https://drive.google.com/a/g.ucla.edu/f ... sp=sharing

Also, do you have any suggestions on how to output the 2464 x 3280 "very raw" Bayer pattern, instead of the 2464 x 3280 x 3 demosaiced ppm file?

Thanks.

User avatar
puppy101puppy
Posts: 16
Joined: Mon Feb 02, 2015 8:19 am

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 12:08 am

6by9 wrote:Happy Christmas - I've just hacked dcraw around to process the IMX219 raw images.
Source at https://github.com/6by9/RPiTest/blob/ma ... aw/dcraw.c. I have NOT rebuilt the binary on there as I was hacking on my x86 machine rather than the Pi.
There's no colour conversion matrix supplied, so the colours are well off at the moment. Feel free to take and modify, and once tidied up it's probably worth pushing back upstream to the main dcraw app.

Ideally I'd like to make it support searching for the @BRCM tag so that it can process raws from any of the Pi sensor modes, but one step at a time. (I wanted this to be able to analyse the lens issues without any ISP processing getting in the way).
Came across some problem when changing the exposure time / shutter speed and extract the raw image. By increasing the shutter-speed (-ss), the image should become brighter. However, the extracted raw .ppm image actually became darker for some reason. See the following example:

The .jpg captured at shutter speed = 50 ms (left) and the extracted .ppm (right).
ImageImage

The .jpg captured at shutter speed = 500 ms (left) and the extracted .ppm (right).
Image
Image

Not sure what I did wrong in capturing images. My command line for capturing images is:

Code: Select all

>> raspistill -r -awb off -awbg 1,1 -ss 50000 -ISO 100 -t 30 expo50000.jpg
And for extracting raw image is:

Code: Select all

>> ./dcraw expo50000.jpg 
Any suggestions on why the extracted .ppm raw image is brighter, when the exposure time is actually shorter and when the captured jpg is actually darker?

The original raw large .jpg file I captured can be downloaded at:
50ms exposure: https://drive.google.com/file/d/0B4I8D7 ... sp=sharing
500ms exposure: https://drive.google.com/file/d/0B4I8D7 ... sp=sharing

Your help is appreciated :)

User avatar
puppy101puppy
Posts: 16
Joined: Mon Feb 02, 2015 8:19 am

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 12:18 am

jbeale wrote:
puppy101puppy wrote: What is your command line to capture your raw image, PiCamV2-Raw-Chart2.jpg?

Code: Select all

raspistill --raw -o PiCamV2-Raw-Chart2.jpg
It's a little confusing because that is a valid JPEG file, but it is much larger than normal and also includes the RAW frame data appended at the end of the jpeg data.
Hi jbeale,

Thanks for your reply. The current output .ppm file seems to be demosaiced already. Do you know how to output the raw Bayer pattern (like the one you introduced for camera v1 years ago)?

User avatar
puppy101puppy
Posts: 16
Joined: Mon Feb 02, 2015 8:19 am

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 2:37 am

puppy101puppy wrote:
jbeale wrote:
puppy101puppy wrote: What is your command line to capture your raw image, PiCamV2-Raw-Chart2.jpg?

Code: Select all

raspistill --raw -o PiCamV2-Raw-Chart2.jpg
It's a little confusing because that is a valid JPEG file, but it is much larger than normal and also includes the RAW frame data appended at the end of the jpeg data.
Hi jbeale,

Thanks for your reply. The current output .ppm file seems to be demosaiced already. Do you know how to output the raw Bayer pattern (like the one you introduced for camera v1 years ago)?

Just figured this out: argument -D will do this.

User avatar
puppy101puppy
Posts: 16
Joined: Mon Feb 02, 2015 8:19 am

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 2:39 am

puppy101puppy wrote:
6by9 wrote:Happy Christmas - I've just hacked dcraw around to process the IMX219 raw images.
Source at https://github.com/6by9/RPiTest/blob/ma ... aw/dcraw.c. I have NOT rebuilt the binary on there as I was hacking on my x86 machine rather than the Pi.
There's no colour conversion matrix supplied, so the colours are well off at the moment. Feel free to take and modify, and once tidied up it's probably worth pushing back upstream to the main dcraw app.

Ideally I'd like to make it support searching for the @BRCM tag so that it can process raws from any of the Pi sensor modes, but one step at a time. (I wanted this to be able to analyse the lens issues without any ISP processing getting in the way).
Came across some problem when changing the exposure time / shutter speed and extract the raw image. By increasing the shutter-speed (-ss), the image should become brighter. However, the extracted raw .ppm image actually became darker for some reason. See the following example:

The .jpg captured at shutter speed = 50 ms (left) and the extracted .ppm (right).
ImageImage

The .jpg captured at shutter speed = 500 ms (left) and the extracted .ppm (right).
Image
Image

Not sure what I did wrong in capturing images. My command line for capturing images is:

Code: Select all

>> raspistill -r -awb off -awbg 1,1 -ss 50000 -ISO 100 -t 30 expo50000.jpg
And for extracting raw image is:

Code: Select all

>> ./dcraw expo50000.jpg 
Any suggestions on why the extracted .ppm raw image is brighter, when the exposure time is actually shorter and when the captured jpg is actually darker?

The original raw large .jpg file I captured can be downloaded at:
50ms exposure: https://drive.google.com/file/d/0B4I8D7 ... sp=sharing
500ms exposure: https://drive.google.com/file/d/0B4I8D7 ... sp=sharing

Your help is appreciated :)
Just figured this out: there's a automatic whitening in the dcraw and can be disabled using optional arguments.

6by9
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 7304
Joined: Wed Dec 04, 2013 11:27 am
Location: ZZ9 Plural Z Alpha, aka just outside Cambridge.

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 10:04 am

puppy101puppy wrote:Hi jbeale,

Thanks for your reply. The current output .ppm file seems to be demosaiced already. Do you know how to output the raw Bayer pattern (like the one you introduced for camera v1 years ago)?
Just figured this out: argument -D will do this.[/quote]
That has done some processing on the raw already, although mainly just an unpack and shift down to 8bit. The original data is 10bpp Bayer, so you've lost 2 bits of detail :(
If you want the really raw data, then http://picamera.readthedocs.io/en/relea ... a-captures details how it is stored in the JPEG file, and includes some Python code to extract it to unpacked 10bpp for manipulation.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

User avatar
puppy101puppy
Posts: 16
Joined: Mon Feb 02, 2015 8:19 am

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 9:21 pm

6by9 wrote:
That has done some processing on the raw already, although mainly just an unpack and shift down to 8bit. The original data is 10bpp Bayer, so you've lost 2 bits of detail :(
Thanks. Do you think the argument "-D -4" in dcraw.c generates the 10-bit stored unprocessed raw image (though stored in 16 bits)? (This seems faster compared to the python code)
6by9 wrote: If you want the really raw data, then http://picamera.readthedocs.io/en/relea ... a-captures details how it is stored in the JPEG file, and includes some Python code to extract it to unpacked 10bpp for manipulation.
how to get the output as image file from this piece of code? (I'm just very newbie to raspberry pi camera)

Also, Is following the suggested change to the original python code for v2? Thanks.
waka wrote:Okay, I figured it out it seems. The info given really helped.

Modifying the code from the docs gives us:

Code: Select all

	data = stream.getvalue()[-(10237440+32768):]
	assert data[:4] == 'BRCM'
	data = data[32768:]
	data = np.fromstring(data, dtype=np.uint8)
	full_sensor = False 
	if full_sensor:
		data = data.reshape((2480,4128))[:2466,:4125]
	else:
		data = data.reshape((2480,4128))[:2464,:4100]
the rest of the code can stay the same as far as I can tell.
Last edited by puppy101puppy on Thu Jun 30, 2016 10:42 pm, edited 1 time in total.

User avatar
jbeale
Posts: 3494
Joined: Tue Nov 22, 2011 11:51 pm
Contact: Website

Re: Pi Camera V2 raw bayer data

Thu Jun 30, 2016 9:43 pm

I agree that the RPi camera has a 10 bit ADC. Just FWIW, I'm not sure I'm convinced that the sensor delivers 10 bits of dynamic range, because the raw, un-filtered image looks noisy to me even in the brightest lighting conditions. In other words using only the high 8 bits may not be throwing away much real information.

If interested, there is a good writeup about bit depth in DSLR cameras here: https://theory.uchicago.edu/~ejm/pix/20 ... l#bitdepth He shows that the DSLR cameras (at that time, some years ago) that were recording at 14 bits could have recorded at 12 bits instead with no loss of information, due to the noise levels.

perrociego
Posts: 14
Joined: Fri Jan 06, 2017 4:21 am

Re: Pi Camera V2 raw bayer data

Sat Jan 28, 2017 11:37 pm

Hi to everyone

I´m quite new with all these but my little grain of sand on decoding raw photos from the Raspberry Pi camera V2.1

I get raw photos using Dave Jones' code:

Code: Select all

import time
import picamera
import picamera.array
import numpy as np
with picamera.PiCamera() as camera:
with picamera.array.PiBayerArray(camera) as stream:
camera.capture(stream, 'jpeg', bayer=True)
# Demosaic data and write to output (just use stream.array if you # want to skip the demosaic step)
output = (stream.demosaic() >> 2).astype(np.uint8)
with open('image.data', 'wb') as f:
            output.tofile(f)
And I open those images with Photoshop CC 2015 (I know, very propietary app)

Just use .raw as extension and use these numbers.

Dimensions: Width 3280, Height 2464
Channels: Count 3, Interleaved, 8 Bits
Header: Size 0

Modyfing the code (output = stream.array) you could make also 16 bits images and open mostly the same way (16 Bits, IBM PC)

Adios from Buenos Aires

6by9
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 7304
Joined: Wed Dec 04, 2013 11:27 am
Location: ZZ9 Plural Z Alpha, aka just outside Cambridge.

Re: Pi Camera V2 raw bayer data

Sun Jan 29, 2017 8:22 am

perrociego wrote:I´m quite new with all these but my little grain of sand on decoding raw photos from the Raspberry Pi camera V2.1
I get raw photos using Dave Jones' code:

Code: Select all

import time
import picamera
import picamera.array
import numpy as np
with picamera.PiCamera() as camera:
with picamera.array.PiBayerArray(camera) as stream:
camera.capture(stream, 'jpeg', bayer=True)
# Demosaic data and write to output (just use stream.array if you # want to skip the demosaic step)
output = (stream.demosaic() >> 2).astype(np.uint8)
with open('image.data', 'wb') as f:
            output.tofile(f)
And I open those images with Photoshop CC 2015 (I know, very propietary app)
Just use .raw as extension and use these numbers.

Dimensions: Width 3280, Height 2464
Channels: Count 3, Interleaved, 8 Bits
Header: Size 0
Modyfing the code (output = stream.array) you could make also 16 bits images and open mostly the same way (16 Bits, IBM PC)
There are multiple image formats that some consider as raw.
Normally in photography it is the Bayer image off the sensor, as is present in stream.array in your example.
You've demosaiced the image to an RGB 8bpp image. RGB and YUV are often considered as raw pixel formats (instead of compressed) and are more frequently accepted by imaging programs, however as you have already demosaiced the image you can't easily adjust things like the white balance on the image as with the Bayer image.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

perrociego
Posts: 14
Joined: Fri Jan 06, 2017 4:21 am

Re: Pi Camera V2 raw bayer data

Sun Jan 29, 2017 3:33 pm

Exactly. Raw usually means before demosaicing. The code above generate an already demosaiced image.
The good for me is that it bypass the internal processing (lens shading correction), the bad is that I still cant adjust it properly. :roll:

jamesh
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 23682
Joined: Sat Jul 30, 2011 7:41 pm

Re: Pi Camera V2 raw bayer data

Sun Jan 29, 2017 5:57 pm

There are many different algorithms for demosaic, make sure you chose a good one!
Principal Software Engineer at Raspberry Pi (Trading) Ltd.
Contrary to popular belief, humorous signatures are allowed. Here's an example...
"My grief counseller just died, luckily, he was so good, I didn't care."

perrociego
Posts: 14
Joined: Fri Jan 06, 2017 4:21 am

Re: Pi Camera V2 raw bayer data

Tue Feb 21, 2017 6:32 am

Hi everyone.

I was using dcraw to decode raw images for my super8 transfer project, but it was too slow (10 s) so I decided to try a reduced but faster version of Dave Jones code. (https://picamera.readthedocs.io/en/rele ... a-captures
Jpg was not an option for me because I remove the lens and the vignette compensation / vignette shading isn't (regrettably) removable from the image processing.

I call it raw2ppm

Only for the imx219

I took some VERY HARD reduction options:

- Only the first 8 bits of data are read, the other 2 bits are not. (Side note: 8 bits .ppm are much more easy to deal than 16 bits ones)
- From the standard Bayer pattern (green1, red, blue, green2) it gets only ONE RGB pixel, instead of the usual four. That's means that de output resolution is 1640 x 1232. Debayering is complex, slow and a bit a lie.
- No exif data is processed.

I code it first on Python, but the execution time was much slower (90 s), so I try it on C (0.46 s).

I´m putting both code here. The Python one is older and just as info because you could do the same in dcraw better in less time.

Sorry, the comments are in Spanish, my language.

The C code, the last and much faster:

Code: Select all

//  Created by Marcelo Ragone on 19 Feb 2017.
//  v 0.2


#include <stdio.h>
#include <stdlib.h>                                         // malloc()
#include <string.h>                                         // strcmp(), strlen(), strncpy(), strcat(), strlen()

// cámara RP_imx219
#define wdata               4128                            // ancho en bytes de la matriz RAW de captura (4 valores 10 bits cada 5 bytes)
#define hdata               2480                            // alto
#define wrgb                1640                            // ancho de la matriz RGB de salida (max resolución horizontal / 2)
#define hrgb                1232                            // alto (idem vertical)

#define offset          10270208                            // lugar de la palabra magica: "BRCM"

int main(int argc, const char * argv[]) {
    if (argc != 2) {
        printf("raw2ppm usage: raw2ppm filename\nWill generate a filename.ppm image from the original RPi_imx219 raw file\n");
        return 1;
    }
    
    // genero un array para la data RAW en memoria
    unsigned char* data = malloc(wdata * hdata);            if (data == NULL) return 1;
    
    // abro un archivo y cargo la data
    FILE* file = fopen(argv[1], "rb");                      if (!file) return 2;
    
    // verifico que sea un archivo compatible
    fseek(file, - offset, SEEK_END);                        // posiciono al principio de la data que interesa desde el final para atras
    char str[] = "1234";
    fread(str, 1, 4, file);                                 // leo 4 letras
    if (strcmp(str, "BRCM") != 0) {
        printf("raw2ppm: Unsupported file\n");
        return 3;
    }
    
    fseek(file, - wdata * hdata, SEEK_END);                 // posiciono al principio de la data que interesa desde el final para atras
    fread(data, 1, wdata * hdata, file);                    // leo el archivo y lo pongo en la memoria
    fclose(file);
    
    // genero un array rgb en memoria y lo lleno
    unsigned char* rgb = malloc(wrgb * hrgb * 3);           if (rgb == NULL) return 4;
    
    for (int x = 0; x < wrgb; x++) {
        int xdata = (x >> 1) * 5 + ( (x << 1) & 0b11 );     // xdata = (xword // 4) * 5 + (xword & 0b11), xword = 2 * xrgb
        
        for (int y = 0; y < hrgb; y++) {
            int ydata = y << 1;                             // ydata = yword = 2 * yrgb
            
            rgb[ (y * wrgb + x) * 3 + 0 ] = data[ (ydata    ) * wdata + (xdata + 1) ];          // R
            rgb[ (y * wrgb + x) * 3 + 1 ] =(data[ (ydata    ) * wdata + (xdata    ) ] >> 1) + (data[ (ydata + 1) * wdata + (xdata + 1) ] >> 1);  // (G1 + G2) / 2
            rgb[ (y * wrgb + x) * 3 + 2 ] = data[ (ydata + 1) * wdata + (xdata    ) ];          // B
        }
    }
    
    // paso el array rgb en memoria a archivo
    char filename[4096];                                    // nombre del archivo destino, PATH_MAX?
    strncpy(filename, argv[1], strlen(argv[1]) - 4);        // copio el nombre menos las ultimas cuatro letras. ¿y si n 0 o negativo????
    strcat(filename, ".ppm");                               // le agrego .ppm
    
    file = fopen(filename, "wb");                           if (!file) return 5;
    
    fprintf(file, "P6\n%u %u\n255\n", wrgb, hrgb);
    fwrite(rgb, 1, wrgb * hrgb * 3, file);
    fclose(file);
    
    return 0;
}
I compile it with:
gcc -Wall -std=gnu99 -o raw2ppm raw2ppm.c

To use write: raw2ppm filename
It will generate a filename.ppm image from the original RPi_imx219 raw file (or overwrite)

Python code, older, but a bit more commented:

Code: Select all

'''
    Tratando de abrir los archivos RAW que entrega la cámara del RPi
    Aca estoy probando de tirar los dos ultimos bits, o sea que sea 8 bits
    Versión en que tomo el promediod e lso dos valores de verde (2G)
    '''

import io
import numpy

from time import perf_counter

# cámara RP_imx219
offset = 10270208
metadata = 32768

tinicio = perf_counter()

# genero un stream. abro una archivo, lo leo y lo paso al stream. recortando lo que no interesa. cierro el archivo
data = io.BytesIO()
with open("image.jpg", mode = "rb") as file:
    data = file.read()[ -offset + metadata : ]          # queda 10 237 440 bytes

# el sensor tiene 3296 x 2480 pixeles efectivos, de esos 3280 x 2464 son activos
# 4100 x 4 / 5 = 3280, en 5 bytes esta la data de cuatro fotositos

# al stream lo paso a numpy
data = numpy.frombuffer(data, dtype=numpy.uint8)        # o fromstring ???
data = data.reshape(2480, 4128)                         # le doy las dos dimensiones (alto x ancho) (no hace falta cropear)


# por fila saco 4 word(w) cada 5 byte(b)

#  byte 0   byte 1   byte 2   byte 3   byte 4
# aaaaaaaa bbbbbbbb cccccccc dddddddd aabbccdd

# word0 = byte0 << 2 + byte4 >> 6                                                             # para 16 bits
# word1 = byte1 << 2 + byte4 >> 4 & 0b11
# word2 = byte2 << 2 + byte4 << 2 & 0b11
# word3 = byte3 << 2 + byte4      & 0b11

# xbyte_base = (xword >> 2) * 5  |   xbyte_elem = (xword & 0b11)                              # xbyte = xbyte_base + xbyte_elem
# xbyte = (xword >> 2) * 5 + (xword & 0b11)

# w(y, xword) = b(y, xbyte) << 2 + b(y, xbyte_base + 4) >> (2 * (3 - xbyte_elem)) & 0b11      # para 16 bits
# w(y, xword) = b(y, xbyte)                                                                   # para 8 bits

# genero un nuevo array mitad de ancho y mitad de alto para agilizar y no inventar en el debayering
dataRGB = numpy.empty([1232, 1640, 3], dtype=numpy.uint8)                  # R, G, B

# GRGRGRGRGRGRGR        G1(x,y) en (2x, 2y  ) | R (x,y) en (2x+1, 2y  )
# BGBGBGBGBGBGBG        B (x,y) en (2x, 2y+1) | G2(x,y) en (2x+1, 2y+1)
# GRGRGRGRGRGRGR
# BGBGBGBGBGBGBG

for xrgb in range(1640):
    xbyte = (xrgb >> 1) * 5 + ( (xrgb << 1) & 0b11 )            # xword = xrgb << 1

    for yrgb in range(1232):
        ybyte = yrgb << 1
        
        dataRGB[yrgb, xrgb, 0] =   data[ybyte  , xbyte+1]                                               # R
        dataRGB[yrgb, xrgb, 1] =  (data[ybyte  , xbyte  ] >> 1) + (data[ybyte+1, xbyte+1] >> 1)         # G
        dataRGB[yrgb, xrgb, 2] =   data[ybyte+1, xbyte  ]                                               # B

# guardo el archivo como ppm
with open("image.ppm", "wb") as file:
    header = "P6\n{} {}\n255\n".format(1640, 1232)              # ppm header: magic number, width, height, max
    for letter in header:
        file.write(bytes([ord(letter)]))                        # muy feo estilo
    dataRGB.tofile(file)

print(perf_counter() - tinicio)                                 # 1G toma en la iMac unos 2s, 2G unos 8s, 16 bits unos 26 s
I also did a 16 bits Python version, available to whoever wants it.

I hope it serves to something.

Marcelo Ragone
Buenos Aires

User avatar
schoolpost
Posts: 16
Joined: Sun Feb 19, 2017 10:47 am
Location: Canada
Contact: Website

Re: Pi Camera V2 raw bayer data

Sat Apr 08, 2017 7:38 am

This thread has been rather quiet from some time, thought I’d share some experiments I’ve been working on.

I’d like to preface this by stating, I really like the JPEG coming out of the camera. My biggest gripe with it however is the less than amazing way it handles highlights and the non-friendly editing codec. My primary use for the camera has been for photography, and less for computer vision / data analysis where this wouldn't be a huge deal.

For me the ultimate end goal would be to perform calibration procedures ( hot pixel / lens shading / color calibration ) on the bayer data, wrap it into a DNG with all appropriate EXIF / TAGs and lossless DNG compression.

This above in theory would offer everything the JPEGs currently do and then some, with gains in bit depth, overall image editing flexibility and not to mention depending on the way it’s packed and compressed it could be very close to the size of the JPEG. Sounds a bit too good to be true and for the time being it, it remains to be seen.. But here are some tests I’ve done in my attempt to get there.

My coding / programming knowledge is limited to say the least, so I’ve been looking exclusively for examples and snippets written in python as I’m not as well versed in languages like C.

Approach 1:

The raspiraw project was the first thing I immediately dove into. https://github.com/bablokb/raspiraw

It was designed for the V1 camera, but with some simple tweaks also works with the V2 camera. My biggest issue with it was the less than ideal 16bit encoding. ( unnecessary when we’re dealing with 10bit source )

With the use of this DNG compression tool http://www.slimraw.com/ you could cut that down. With a Lossless DNG compression you could expect an approximate ~70% reduction in size, which is not bad but in that case still larger than the uncompressed 10bit!
( 15.8MB 16bit DNG ----> 11.1MB Lossless compressed 16bit DNG )

So this paired with the fact that the bayer data is still untouched and therefore missing important calibration and corrections, doesn’t make it a very viable solution. At least not without some revision and additional features, also being C not exactly up my alley.

Approach 2:

This is where my more recent approach comes into play.

Upon some searching I stumbled upon this interesting project:
https://github.com/apertus-open-source- ... DNG_Writer

It’s was designed to take in a 16bit raw source and wrap it into a DNG, except this time using Python!

So with that I had a means to wrap bayer data from the Picamera all within python using the bayer code segment from the Picamera docs: http://picamera.readthedocs.io/en/lates ... a-captures

Being in the familiar python space, I was able to do some basic hot pixel correction to the bayer data from a reference dark image image, as well as some lens shading correction before wrapping it into the DNG. ( lens shading didn't work as well as I had hoped, but I believe that comes down to a user error in my methodology, so I left it out for the demo. )

With some adjustments here are some of the results I was able to obtain:
( left: original jpeg, right: python dng processed with Rawtherapee, compression was done after the fact with adobe dng converter )

Image

All the luxuries of an uncompressed RAW image, at mere 2.4MB delta of the JPEG. not bad!
You can also clearly note just how much highlight information you are able to recover.

Image
( shows the heavy vignetting and color cast )

With a proper Lens Shading Calibration and some color tweaks I'm convinced it could get very close to original JPEG.

Now this route doesn’t come without it’s drawbacks:
- The reference python dng writer is incomplete, not ideally formatted or documented
- The way the python dng writer works is also less than ideal, because it essentially writes the raw image into a stripped “empty.dng” file and makes some changes to the appropriate tags.
- It’s also being stored in 16bit’s, but requires the data be mapped into a 12bit range, otherwise the image would be clip heavily
- Relatively fast at 1.6s per image but still slower than what I could imagine a C implementation would be able to provide.
- Requires the adobe dng converter to do the lossless compression.

So there's some of my findings, hopefully some find this information useful and better yet I hope in the right hands this information could be used to build a better solution.

I can also post some of the tweaked python code for those interested.

Thanks for reading!

User avatar
schoolpost
Posts: 16
Joined: Sun Feb 19, 2017 10:47 am
Location: Canada
Contact: Website

Re: Pi Camera V2 raw bayer data

Sun Apr 09, 2017 4:35 am

Update.
Guess the RAW? ;)
Image

Bryan See
Posts: 11
Joined: Mon Apr 10, 2017 2:33 pm

Re: Pi Camera V2 raw bayer data

Wed Apr 12, 2017 11:58 am

Came by to see this thread, I'll comment around a bit. I know that the Bayer pattern of the OV5647 sensor is BGGR. In other words the first row contains alternating green/blue elements, the second row contains alternating red/green elements, and so on as illustrated below:

GBGBGBGBGBGBGB
RGRGRGRGRGRGRG
GBGBGBGBGBGBGB
RGRGRGRGRGRGRG

Although I'm close of knowing the Bayer pattern of the IMX219 sensor, it's unclear whether it could be RGBG, GBGR, or something else.

I also tried the first example Python script of the Bayer capture, I still cannot open the image file generated at the end of it, as the file is supposed to be readable in most packages (like GIMP and Photoshop). The question is: Could that be in DNG format? Is there anything I can do to solve this?

6by9
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 7304
Joined: Wed Dec 04, 2013 11:27 am
Location: ZZ9 Plural Z Alpha, aka just outside Cambridge.

Re: Pi Camera V2 raw bayer data

Wed Apr 12, 2017 12:52 pm

Bryan See wrote:Came by to see this thread, I'll comment around a bit. I know that the Bayer pattern of the OV5647 sensor is BGGR. In other words the first row contains alternating green/blue elements, the second row contains alternating red/green elements, and so on as illustrated below:

GBGBGBGBGBGBGB
RGRGRGRGRGRGRG
GBGBGBGBGBGBGB
RGRGRGRGRGRGRG

Although I'm close of knowing the Bayer pattern of the IMX219 sensor, it's unclear whether it could be RGBG, GBGR, or something else.
It could be any of the 4 variants depending on how the image is flipped.
I can't remember the default order, but if we assume that it is the same BGGR as OV5647, then applying a horizontal flip will mean it is GBRG. Just a vertical flip would give GRBG. Both horizontal and vertical flips would give RGGB.
RGBG, BGRG, GBGR, and GRGB aren't valid Bayer patterns as then the two green channels are in the same column.
Bryan See wrote:I also tried the first example Python script of the Bayer capture, I still cannot open the image file generated at the end of it, as the file is supposed to be readable in most packages (like GIMP and Photoshop). The question is: Could that be in DNG format? Is there anything I can do to solve this?
No, it's raw RGB image data.
As noted in the example:

Code: Select all

# If you want to view this in most packages (like GIMP) you'll need to
# convert it to 8-bit RGB data. The simplest way to do this is by
# right-shifting everything by 2-bits (yes, this makes all that
# unpacking work at the start rather redundant...)
Although I'd suggest looking at Vooya if you're trying to view them on an x86 platform (it's free for x86 Linux flavours, not Windows or MacOS). It supports viewing 10 bit data packed as 16bit words.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

perrociego
Posts: 14
Joined: Fri Jan 06, 2017 4:21 am

Re: Pi Camera V2 raw bayer data

Wed Apr 12, 2017 2:39 pm

On the IMX219 the Bayer pattern is:

GR
BG

I didnt try on flipped images.

Marcelo

Bryan See
Posts: 11
Joined: Mon Apr 10, 2017 2:33 pm

Re: Pi Camera V2 raw bayer data

Thu Apr 13, 2017 7:40 am

6by9 wrote:
Bryan See wrote:Came by to see this thread, I'll comment around a bit. I know that the Bayer pattern of the OV5647 sensor is BGGR. In other words the first row contains alternating green/blue elements, the second row contains alternating red/green elements, and so on as illustrated below:

GBGBGBGBGBGBGB
RGRGRGRGRGRGRG
GBGBGBGBGBGBGB
RGRGRGRGRGRGRG

Although I'm close of knowing the Bayer pattern of the IMX219 sensor, it's unclear whether it could be RGBG, GBGR, or something else.
It could be any of the 4 variants depending on how the image is flipped.
I can't remember the default order, but if we assume that it is the same BGGR as OV5647, then applying a horizontal flip will mean it is GBRG. Just a vertical flip would give GRBG. Both horizontal and vertical flips would give RGGB.
RGBG, BGRG, GBGR, and GRGB aren't valid Bayer patterns as then the two green channels are in the same column.
Bryan See wrote:I also tried the first example Python script of the Bayer capture, I still cannot open the image file generated at the end of it, as the file is supposed to be readable in most packages (like GIMP and Photoshop). The question is: Could that be in DNG format? Is there anything I can do to solve this?
No, it's raw RGB image data.
As noted in the example:

Code: Select all

# If you want to view this in most packages (like GIMP) you'll need to
# convert it to 8-bit RGB data. The simplest way to do this is by
# right-shifting everything by 2-bits (yes, this makes all that
# unpacking work at the start rather redundant...)
Although I'd suggest looking at Vooya if you're trying to view them on an x86 platform (it's free for x86 Linux flavours, not Windows or MacOS). It supports viewing 10 bit data packed as 16bit words.
Vooya cannot be installed on Raspberry Pi's Raspian OS for some reason (RPI is an ARM platform not x86). Are there any alternatives around?

On the bright side:

Code: Select all

rgb = np.zeros(data.shape + (3,), dtype=data.dtype)

if ver < 2:
    rgb[1::2, 0::2, 0] = data[1::2, 0::2] # Red
    rgb[0::2, 0::2, 1] = data[0::2, 0::2] # Green
    rgb[1::2, 1::2, 1] = data[1::2, 1::2] # Green
    rgb[0::2, 1::2, 2] = data[0::2, 1::2] # Blue
else:
    rgb[1::2, 0::2, 1] = data[0::2, 0::2] # Green
    rgb[0::2, 0::2, 0] = data[0::2, 1::2] # Blue
    rgb[0::2, 1::2, 2] = data[1::2, 0::2] # Red
    rgb[1::2, 1::2, 1] = data[1::2, 1::2] # Green

bayer = np.zeros(rgb.shape, dtype=np.uint8)

if ver < 2:
    bayer[1::2, 0::2, 0] = 1 # Red
    bayer[0::2, 0::2, 1] = 1 # Green
    bayer[1::2, 1::2, 1] = 1 # Green
    bayer[0::2, 1::2, 2] = 1 # Blue
else:
    bayer[1::2, 0::2, 1] = 1 # Green
    bayer[0::2, 0::2, 0] = 1 # Blue
    bayer[1::2, 1::2, 2] = 1 # Red
    bayer[0::2, 1::2, 1] = 1 # Green
Am I going into the right direction? Or are there anything I need to modify in order to get it working so that I could get an error-free IMX219-generated raw RGB image data file that can be opened by most packages?

perrociego
Posts: 14
Joined: Fri Jan 06, 2017 4:21 am

Re: Pi Camera V2 raw bayer data

Thu Apr 13, 2017 3:16 pm

There are a few steps to get a .ppm image file from a RP_imx219 raw image.
You got some examples in this post.
Or better Dave Jones:
http://picamera.readthedocs.io/en/relea ... a-captures

Before begining you may verify the existance of the magic word BRCM (optional)

First you got to offset the data. You want the last 10 237 440 bytes of the raw image file.

Second you have to get working values from the packed 10 bit data.
There are 4 10 bits values in 5 bytes.

Code: Select all

# byte 0   byte 1   byte 2   byte 3   byte 4
# aaaaaaaa bbbbbbbb cccccccc dddddddd aabbccdd
The easy way is to use the first 8 bits and discard the rest, the last byte.

After that you have to order this in RGB
If you want full resolution for every pixel you have only one value (R or G or B) so you have to calculate / create the others.
I prefer half resolution, where I have 4 values GR BG for each pixel.

Thats all the magic.

To save a .ppm (8 bits values) file you need some header:

Code: Select all

header = "P6\n{} {}\n255\n".format(width, height)
and after that the RGB data.

Marcelo

pootle
Posts: 338
Joined: Wed Sep 04, 2013 10:20 am
Location: Staffordshire
Contact: Website

Re: Pi Camera V2 raw bayer data

Thu Apr 13, 2017 8:10 pm

schoolpost wrote:Update.
Guess the RAW? ;)
Image
Ooooh! that's very good, I got part way there myself a few months ago, but couldn't even begin to calculate the correction needed to the raw RGB data that came out.

Can you share your incantation?

I was using the route of raw data extracted from jpg in python, pumped into a numpy array to de-mosaic. I was trying to develop code to automatically produce a correction table, but failed dismally.

User avatar
schoolpost
Posts: 16
Joined: Sun Feb 19, 2017 10:47 am
Location: Canada
Contact: Website

Re: Pi Camera V2 raw bayer data

Thu Apr 13, 2017 8:28 pm

pootle wrote:
schoolpost wrote:Update.
Guess the RAW? ;)
Image
Ooooh! that's very good, I got part way there myself a few months ago, but couldn't even begin to calculate the correction needed to the raw RGB data that came out.

Can you share your incantation?

I was using the route of raw data extracted from jpg in python, pumped into a numpy array to de-mosaic. I was trying to develop code to automatically produce a correction table, but failed dismally.
I suppose you could say I got lucky. I found a chunk of code someone had been working on to wrap a Bayer data from another camera into DNG.

The associated python source they had came with a calibration matrix, that worked really well straight out the box for the raspberry pi camera.

My source is here: https://github.com/schoolpost/pydng

In either of the pydng or pydng10 files there's a camera calibration segment you can take a look at ( dng.ifd.getTag('ColorMatrix1') )
Shifting those 9 values around till you get something you like. ( they work pretty well as is. )

Bryan See
Posts: 11
Joined: Mon Apr 10, 2017 2:33 pm

Re: Pi Camera V2 raw bayer data

Fri Apr 14, 2017 5:33 am

No, I am not wanting to create non-Bayer .ppm files, Marcelo. I want to create a raw Bayer image from a JPEG capture. A DNG file, I mean.

I used the modified Raspiraw from the Illes' one for that purpose, but the original exiftool is used.

Code: Select all

/* Read in jpeg from Raspberry Pi camera captured using 'raspistill --raw'
   and extract raw file with 10-bit values stored left-justified at 16 bpp
   in Adobe DNG (TIFF-EP) format, convert with 'ufraw out.dng' for example

   John Beale  26 May 2013
   and others
   
   Contains code written by Dave Coffin for Berkeley Engineering and Research.

   Free for all uses.


   Requires LibTIFF 3.8.0 plus a patch, see http://www.cybercom.net/~dcoffin/dcraw/

   Compile with:
   gcc -o raspi_dng raspi_dng.c -O4 -Wall -lm -ljpeg -ltiff
   
 */



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <math.h>
#include <tiffio.h>
#include <errno.h>


#define LINELEN 256            // how long a string we need to hold the filename
#define RAWBLOCKSIZE 10270208
#define HEADERSIZE 32768
#define ROWSIZE 3264 // number of bytes per row of pixels, including 24 'other' bytes at end
#define IDSIZE 4    // number of bytes in raw header ID string
#define HPIXELS 3280   // number of horizontal pixels on IMX219 sensor
#define VPIXELS 2464   // number of vertical pixels on IMX219 sensor

int main (int argc, char **argv)
{
	static const short CFARepeatPatternDim[] = { 2,2 };
	// this color matrix is definitely inaccurate, TODO: calibrate
	static const float cam_xyz[] = {
		// R 	G     	B
		1.000,	0.000,	0.000,	// R
		0.000,	1.000,	0.000,	// G
		0.000,	0.000,	1.000	// B
	};
	static const float neutral[] = { 1.0, 1.0, 1.0 }; // TODO calibrate
	long sub_offset=0, white=0xffff;

	int status=1, i, j, row, col;
	unsigned short curve[256];
	struct stat st;
	struct tm tm;
	char datetime[64];
	FILE *ifp;
	TIFF *tif;

	const char* fname = argv[1];
	unsigned long fileLen;  // number of bytes in file
	unsigned long offset;  // offset into file to start reading pixel data
	unsigned char *buffer;
	unsigned short pixel[HPIXELS];  // array holds 16 bits per pixel
	unsigned char split;        // single byte with 4 pairs of low-order bits

	if (argc != 3) {
		fprintf (stderr, "Usage: %s infile outfile\n"
			"Example: %s rpi.jpg output.dng\n", argv[0], argv[0]);
		return 1;
	}

	if (!(ifp = fopen (fname, "rb"))) {
		perror (argv[2]);
		return 1;
	}
	stat (fname, &st);
	gmtime_r (&st.st_mtime, &tm);
	sprintf (datetime, "%04d:%02d:%02d %02d:%02d:%02d",
	tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);

	//Get file length
	fseek(ifp, 0, SEEK_END);
	fileLen=ftell(ifp);
	if (fileLen < RAWBLOCKSIZE) {
		fprintf(stderr, "File %s too short to contain expected 10MB RAW data.\n", fname);
		exit(1);
	}
	offset = (fileLen - RAWBLOCKSIZE) ;  // location in file the raw header starts
	fseek(ifp, offset, SEEK_SET); 
 
	printf("File length = %d bytes.\n",fileLen);
	printf("offset = %d:",offset);

	//Allocate memory for one line of pixel data
	buffer=(unsigned char *)malloc(ROWSIZE+1);
	if (!buffer)
	{
		fprintf(stderr, "Memory error!");
		status = ENOMEM;
		goto fail;
	}
		
	if (!(tif = TIFFOpen (argv[2], "w"))) goto fail;

	//fprintf(stderr, "Writing TIFF header...\n");
	
	TIFFSetField (tif, TIFFTAG_SUBFILETYPE, 1);
	TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, HPIXELS >> 4);
	TIFFSetField (tif, TIFFTAG_IMAGELENGTH, VPIXELS >> 4);
	TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, 8);
	TIFFSetField (tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
	TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
	TIFFSetField (tif, TIFFTAG_MAKE, "Raspberry Pi");
	TIFFSetField (tif, TIFFTAG_MODEL, "Model IMX219");
	TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
	TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, 3);
	TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	TIFFSetField (tif, TIFFTAG_SOFTWARE, "raspi_dng2");
	TIFFSetField (tif, TIFFTAG_DATETIME, datetime);
	TIFFSetField (tif, TIFFTAG_SUBIFD, 1, &sub_offset);
	TIFFSetField (tif, TIFFTAG_DNGVERSION, "\001\001\0\0");
	TIFFSetField (tif, TIFFTAG_DNGBACKWARDVERSION, "\001\0\0\0");
	TIFFSetField (tif, TIFFTAG_UNIQUECAMERAMODEL, "Raspberry Pi - IMX219");
	TIFFSetField (tif, TIFFTAG_COLORMATRIX1, 9, cam_xyz);
	TIFFSetField (tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral);
	TIFFSetField (tif, TIFFTAG_CALIBRATIONILLUMINANT1, 21);
	TIFFSetField (tif, TIFFTAG_ORIGINALRAWFILENAME, fname);

	// fprintf(stderr, "Writing TIFF thumbnail...\n");
	memset (pixel, 0, HPIXELS);	// all-black thumbnail 
	for (row=0; row < VPIXELS >> 4; row++)
		TIFFWriteScanline (tif, pixel, row, 0);
	TIFFWriteDirectory (tif);

	// fprintf(stderr, "Writing TIFF header for main image...\n");

	TIFFSetField (tif, TIFFTAG_SUBFILETYPE, 0);
	TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, HPIXELS);
	TIFFSetField (tif, TIFFTAG_IMAGELENGTH, VPIXELS);
	TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, 16);
	TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);
	TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, 1);
	TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	TIFFSetField (tif, TIFFTAG_CFAREPEATPATTERNDIM, CFARepeatPatternDim);
	TIFFSetField (tif, TIFFTAG_CFAPATTERN, 4, "\002\001\001\0"); // 0 = Red, 1 = Green, 2 = Blue, 3 = Cyan, 4 = Magenta, 5 = Yellow, 6 = White 
	//TIFFSetField (tif, TIFFTAG_LINEARIZATIONTABLE, 256, curve);
	TIFFSetField (tif, TIFFTAG_WHITELEVEL, 1, &white);

	fprintf(stderr, "Processing RAW data...\n");
	// for one file, (TotalFileLength:11112983 - RawBlockSize:10270208) + Header:32768 = 4741655
	// The pixel data is arranged in the file in rows, with 3264 bytes per row.
	// with 3264 bytes per row x 1944 rows we have 6345216 bytes, that is the full 3280x2464 image area.
 
	//Read one line of pixel data into buffer
	fread(buffer, IDSIZE, 1, ifp);

	// now on to the pixel data
	offset = (fileLen - RAWBLOCKSIZE) + HEADERSIZE;  // location in file the raw pixel data starts
	fseek(ifp, offset, SEEK_SET);

	for (row=0; row < VPIXELS; row++) {  // iterate over pixel rows
		fread(buffer, ROWSIZE, 1, ifp);  // read next line of pixel data
		j = 0;  // offset into buffer
		for (col = 0; col < HPIXELS; col+= 4) {  // iterate over pixel columns
			pixel[col+0] = buffer[j++] << 8;
			pixel[col+1] = buffer[j++] << 8;
			pixel[col+2] = buffer[j++] << 8;
			pixel[col+3] = buffer[j++] << 8;
			split = buffer[j++];    // low-order packed bits from previous 4 pixels
			pixel[col+0] += (split & 0b11000000);  // unpack them bits, add to 16-bit values, left-justified
			pixel[col+1] += (split & 0b00110000)<<2;
			pixel[col+2] += (split & 0b00001100)<<4;
			pixel[col+3] += (split & 0b00000011)<<6;
		}
		if (TIFFWriteScanline (tif, pixel, row, 0) != 1) {
			fprintf(stderr, "Error writing TIFF scanline.");
			exit(1);
		}
	} // end for(k..)

	free(buffer); // free up that memory we allocated

	TIFFClose (tif);
	status = 0;
fail:
	fclose (ifp);
	return status;
}

However, it turns out that the resulting raw image is not the same as the JPEG + RAW file. I tested this with pictures taken with the OV5647 sensor and it looks perfectly fine.

What about the bayer example? I haven't seen anyone testing this for IMX219 sensors and opening the resulting file created from this.

Code: Select all

from __future__ import (
    unicode_literals,
    absolute_import,
    print_function,
    division,
    )


import io
import time
import picamera
import numpy as np
from numpy.lib.stride_tricks import as_strided

stream = io.BytesIO()
with picamera.PiCamera() as camera:
    # Let the camera warm up for a couple of seconds
    time.sleep(2)
    # Capture the image, including the Bayer data
    camera.capture(stream, format='jpeg', bayer=True)

# Extract the raw Bayer data from the end of the stream, check the
# header and strip if off before converting the data into a numpy array

data = stream.getvalue()[-6404096:]
assert data[:4] == 'BRCM'
data = data[32768:]
data = np.fromstring(data, dtype=np.uint8)

# The data consists of 1952 rows of 3264 bytes of data. The last 8 rows
# of data are unused (they only exist because the actual resolution of
# 1944 rows is rounded up to the nearest 16). Likewise, the last 24
# bytes of each row are unused (why?). Here we reshape the data and
# strip off the unused bytes

data = data.reshape((1952, 3264))[:1944, :3240]

# Horizontally, each row consists of 2592 10-bit values. Every four
# bytes are the high 8-bits of four values, and the 5th byte contains
# the packed low 2-bits of the preceding four values. In other words,
# the bits of the values A, B, C, D and arranged like so:
#
#  byte 1   byte 2   byte 3   byte 4   byte 5
# AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD AABBCCDD
#
# Here, we convert our data into a 16-bit array, shift all values left
# by 2-bits and unpack the low-order bits from every 5th byte in each
# row, then remove the columns containing the packed bits

data = data.astype(np.uint16) << 2
for byte in range(4):
    data[:, byte::5] |= ((data[:, 4::5] >> ((4 - byte) * 2)) & 0b11)
data = np.delete(data, np.s_[4::5], 1)

# Now to split the data up into its red, green, and blue components. The
# Bayer pattern of the OV5647 sensor is BGGR. In other words the first
# row contains alternating green/blue elements, the second row contains
# alternating red/green elements, and so on as illustrated below:
#
# GBGBGBGBGBGBGB
# RGRGRGRGRGRGRG
# GBGBGBGBGBGBGB
# RGRGRGRGRGRGRG
#
# Please note that if you use vflip or hflip to change the orientation
# of the capture, you must flip the Bayer pattern accordingly

rgb = np.zeros(data.shape + (3,), dtype=data.dtype)
rgb[1::2, 0::2, 0] = data[1::2, 0::2] # Red
rgb[0::2, 0::2, 1] = data[0::2, 0::2] # Green
rgb[1::2, 1::2, 1] = data[1::2, 1::2] # Green
rgb[0::2, 1::2, 2] = data[0::2, 1::2] # Blue

# At this point we now have the raw Bayer data with the correct values
# and colors but the data still requires de-mosaicing and
# post-processing. If you wish to do this yourself, end the script here!
#
# Below we present a fairly naive de-mosaic method that simply
# calculates the weighted average of a pixel based on the pixels
# surrounding it. The weighting is provided by a byte representation of
# the Bayer filter which we construct first:

bayer = np.zeros(rgb.shape, dtype=np.uint8)
bayer[1::2, 0::2, 0] = 1 # Red
bayer[0::2, 0::2, 1] = 1 # Green
bayer[1::2, 1::2, 1] = 1 # Green
bayer[0::2, 1::2, 2] = 1 # Blue

# Allocate an array to hold our output with the same shape as the input
# data. After this we define the size of window that will be used to
# calculate each weighted average (3x3). Then we pad out the rgb and
# bayer arrays, adding blank pixels at their edges to compensate for the
# size of the window when calculating averages for edge pixels.

output = np.empty(rgb.shape, dtype=rgb.dtype)
window = (3, 3)
borders = (window[0] - 1, window[1] - 1)
border = (borders[0] // 2, borders[1] // 2)

rgb_pad = np.zeros((
    rgb.shape[0] + borders[0],
    rgb.shape[1] + borders[1],
    rgb.shape[2]), dtype=rgb.dtype)
rgb_pad[
    border[0]:rgb_pad.shape[0] - border[0],
    border[1]:rgb_pad.shape[1] - border[1],
    :] = rgb
rgb = rgb_pad

bayer_pad = np.zeros((
    bayer.shape[0] + borders[0],
    bayer.shape[1] + borders[1],
    bayer.shape[2]), dtype=bayer.dtype)
bayer_pad[
    border[0]:bayer_pad.shape[0] - border[0],
    border[1]:bayer_pad.shape[1] - border[1],
    :] = bayer
bayer = bayer_pad

# In numpy >=1.7.0 just use np.pad (version in Raspbian is 1.6.2 at the
# time of writing...)
#
#rgb = np.pad(rgb, [
#    (border[0], border[0]),
#    (border[1], border[1]),
#    (0, 0),
#    ], 'constant')
#bayer = np.pad(bayer, [
#    (border[0], border[0]),
#    (border[1], border[1]),
#    (0, 0),
#    ], 'constant')

# For each plane in the RGB data, we use a nifty numpy trick
# (as_strided) to construct a view over the plane of 3x3 matrices. We do
# the same for the bayer array, then use Einstein summation on each
# (np.sum is simpler, but copies the data so it's slower), and divide
# the results to get our weighted average:

for plane in range(3):
    p = rgb[..., plane]
    b = bayer[..., plane]
    pview = as_strided(p, shape=(
        p.shape[0] - borders[0],
        p.shape[1] - borders[1]) + window, strides=p.strides * 2)
    bview = as_strided(b, shape=(
        b.shape[0] - borders[0],
        b.shape[1] - borders[1]) + window, strides=b.strides * 2)
    psum = np.einsum('ijkl->ij', pview)
    bsum = np.einsum('ijkl->ij', bview)
    output[..., plane] = psum // bsum

# At this point output should contain a reasonably "normal" looking
# image, although it still won't look as good as the camera's normal
# output (as it lacks vignette compensation, AWB, etc).
#
# If you want to view this in most packages (like GIMP) you'll need to
# convert it to 8-bit RGB data. The simplest way to do this is by
# right-shifting everything by 2-bits (yes, this makes all that
# unpacking work at the start rather redundant...)

output = (output >> 2).astype(np.uint8)
with open('image.data', 'wb') as f:
    output.tofile(f)
I also used this from dword1511 as well. I don't know that this will get the same result as the first iteration or not.

Code: Select all

/* Read in JPEG from Raspberry Pi camera captured using 'raspistill --raw'
   and extract RAW file with 10-bit values stored left-justified at 16 bpp
   in Adobe DNG (TIFF-EP) format, convert with 'ufraw out.dng' for example

   John Beale  26 May 2013
   and others

   Contains code written by Dave Coffin for Berkeley Engineering and Research.

   Free for all uses.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <tiffio.h>
#include <errno.h>
#include <libexif/exif-data.h>
#include <unistd.h>
#include <endian.h>

/* Raspberry Pi IMX219 8MP JPEG RAW image format */
#define RPI_IMX219_RAW_HPIXELS 3280    /* number of horizontal pixels on IMX219 sensor */
#define RPI_IMX219_RAW_VPIXELS 2464    /* number of vertical pixels on IMX219 sensor   */
#define RPI_IMX219_RAW_ID_LEN  4       /* number of bytes in raw header ID string      */
#define RPI_IMX219_RAW_HDR_LEN 32768
#define RPI_IMX219_RAW_ROW_LEN 3952    /* number of bytes per row of pixels, including 24 'other' bytes at end */
#define RPI_IMX219_RAW_RAW_LEN 10270208
#define RPI_IMX219_BIT_DEPTH   10
#define RPI_IMX219_TAG_OLD     "imx219"

/* CFA arrangement for different flip configuration (0 = Red, 1 = Green, 2 = Blue) */
#define CFA_PATTERN_NORMAL     "\001\002\0\001"
#define CFA_PATTERN_FLIP_HORIZ "\002\001\001\0"
#define CFA_PATTERN_FLIP_VERT  "\0\001\001\002"
#define CFA_PATTERN_FLIP_BOTH  "\001\0\002\001"
#define CFA_FLIP_NONE          0x00
#define CFA_FLIP_HORIZ         0x01
#define CFA_FLIP_VERT          0x02
#define CFA_FLIP_BOTH          (CFA_FLIP_HORIZ | CFA_FLIP_VERT)

static void usage(const char* self) {
  fprintf (stderr, "Usage: %s [options] infile1.jpg [infile2.jpg ...]\n\n"
    "Options:\n"
      "\t-H          Assume horizontal flip (option -HF of raspistill)\n"
      "\t-V          Assume vertical flip (option -VF of raspistill)\n"
      "\t-o outfile  Create `outfile' instead of infile with dng-extension (unless multiple file supplied)\n"
      "\t-M matrix   Use given color matrix instead of embedded one for conversion\n",
    self);
  exit(EXIT_FAILURE);
}

static void read_matrix(float* matrix, const char* arg) {
  float mmax = 0;
  int   i;

  sscanf(arg, "%f, %f, %f, "
              "%f, %f, %f, "
              "%f, %f, %f, ",
        &matrix[0], &matrix[1], &matrix[2],
        &matrix[3], &matrix[4], &matrix[5],
        &matrix[6], &matrix[7], &matrix[8]);

  /* scale result if input is not normalized */
  for (i = 0; i < 9; i ++) {
    mmax = matrix[i] > mmax ? matrix[i] : mmax;
  }
  if (mmax > 1.0f) {
    for (i = 0; i < 9; i ++) {
      matrix[i] /= mmax;
    }
  }
}

static float rational_to_float(void *p) {
  uint32_t* r;
  r = (uint32_t*)p;
  return be32toh(r[0]) * 1.0f / be32toh(r[1]);
}

static float srational_to_float(void *p) {
  int32_t* r;
  r = (int32_t*)p;
  return be32toh(r[0]) * 1.0f / be32toh(r[1]);
}

static void print_matrix(float* matrix) {
  fprintf(stderr, "Using color matrix:\n"
                    "\t%.4f\t%.4f\t%.4f\n"
                    "\t%.4f\t%.4f\t%.4f\n"
                    "\t%.4f\t%.4f\t%.4f\n",
        matrix[0], matrix[1], matrix[2],
        matrix[3], matrix[4], matrix[5],
        matrix[6], matrix[7], matrix[8]);
}

static int copy_tags(ExifData* edata, TIFF* tif, char* matrix, char* filename, int pattern) {
  const long  white    = (1 << RPI_IMX219_BIT_DEPTH) - 1;
  const short cfadim[] = {2, 2}; /* libtiff5 only supports 2x2 CFA */
  const char* software = "rpi2dng (modified fork, https://github.com/dword1511/raspiraw)";
  const char* uniquem  = "Raspberry Pi - IMX219";
  const char* dngv     = "\001\001\0\0";
  const char* dngbv    = "\001\0\0\0";
  ExifEntry*  eentry   = NULL;
  char*       cfapatt  = NULL;
  struct tm*  tm       = NULL;
  time_t      rawtime;
  char        datetime[64];
  uint16_t    iso;
  float       gain[]   = {1.0, 1.0, 1.0}; /* Default */
  float       neutral[3];
  uint64_t exif_dir_offset = 0;
  //unsigned short curve[256];
  /* Default color matrix from dcraw */
  float cam_xyz[]  = {
    /*  R        G        B         */
     1.2782, -0.4059, -0.0379, /* R */
    -0.0478,  0.9066,  0.1413, /* G */
     0.1340,  0.1513,  0.5176  /* B */
  };

  /* ExifData, TIFF context, Color matrix buffer and Sub-IFD offset buffer are required. */
  /* Color matrix preset and Original file name are optional. */
  if ((NULL == edata) || (NULL == tif)) {
    fprintf(stderr, "Internal error!\n");
    abort();
  }

  /* New and old formats have different CFA arrangements */
  eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_0], EXIF_TAG_MODEL);
  if (NULL == eentry) {
    fprintf(stderr, "EXIF IFD0 does not contain MODEL tag!");
    return EXIT_FAILURE;
  }
  if (0 == strncmp((const char *)eentry->data, RPI_IMX219_TAG_OLD, strlen(RPI_IMX219_TAG_OLD) + 1)) {
    /* Old version uses current horizontal-flip readout */
    fprintf(stderr, "Image is in old format\n");
    switch (pattern) {
      case CFA_FLIP_NONE: {
        cfapatt = CFA_PATTERN_FLIP_HORIZ;
        break;
      }
      case CFA_FLIP_HORIZ: {
        cfapatt = CFA_PATTERN_NORMAL;
        break;
      }
      case CFA_FLIP_VERT: {
        cfapatt = CFA_PATTERN_FLIP_BOTH;
        break;
      }
      case CFA_FLIP_BOTH: {
        cfapatt = CFA_PATTERN_FLIP_VERT;
        break;
      }
      default: {
        fprintf(stderr, "Internal error!\n");
        abort();
      }
    }
  } else {
    fprintf(stderr, "Image is in new format\n");
    switch (pattern) {
      case CFA_FLIP_NONE: {
        cfapatt = CFA_PATTERN_NORMAL;
        break;
      }
      case CFA_FLIP_HORIZ: {
        cfapatt = CFA_PATTERN_FLIP_HORIZ;
        break;
      }
      case CFA_FLIP_VERT: {
        cfapatt = CFA_PATTERN_FLIP_VERT;
        break;
      }
      case CFA_FLIP_BOTH: {
        cfapatt = CFA_PATTERN_FLIP_BOTH;
        break;
      }
      default: {
        fprintf(stderr, "Internal error!\n");
        abort();
      }
    }
  }

  /* Load color matrix and white balance */
  if (NULL != matrix) {
    read_matrix(cam_xyz, matrix);
  } else {
    if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_MAKER_NOTE))) {
      read_matrix(cam_xyz, strstr((const char *)eentry->data, "ccm=") + 4);
      sscanf(strstr((const char *)eentry->data, "gain_r=") + 7, "%f", &gain[0]);
      sscanf(strstr((const char *)eentry->data, "gain_b=") + 7, "%f", &gain[2]);
      neutral[0] = (1 / gain[0]) / ((1 / gain[0]) + (1 / gain[1]) + (1 / gain[2]));
      neutral[1] = (1 / gain[1]) / ((1 / gain[0]) + (1 / gain[1]) + (1 / gain[2]));
      neutral[2] = (1 / gain[2]) / ((1 / gain[0]) + (1 / gain[1]) + (1 / gain[2]));
    } else {
      fprintf(stderr, "JPEG does not contain MakerNotes! Will use default color matrix.");
    }
  }
  print_matrix(cam_xyz);

  /* Write TIFF tags for DNG */
  /* IFD0 */
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_0], EXIF_TAG_MAKE))) {
    TIFFSetField(tif, TIFFTAG_MAKE, eentry->data);
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_0], EXIF_TAG_MODEL))) {
    TIFFSetField(tif, TIFFTAG_MODEL, eentry->data);
  }
  /* Skipped: XResolution (72 = Unkown) */
  /* Skipped: YResolution (72 = Unkown) */
  /* Skipped: ResolutionUnit */
  /* Skipped: Modify date */
  /* Skipped: YCbCrPositioning (for JPEG only) */
  /* Skipped: ExifOffset */
  /* Addons for DNG */
  TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
  TIFFSetField(tif, TIFFTAG_SOFTWARE, software);
  TIFFSetField(tif, TIFFTAG_DNGVERSION, dngv);
  TIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, dngbv);
  TIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, uniquem);
  TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, cam_xyz);
  TIFFSetField(tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral);
  TIFFSetField(tif, TIFFTAG_CALIBRATIONILLUMINANT1, 21); /* D65 light source */
  TIFFSetField(tif, TIFFTAG_MAKERNOTESAFETY, 1); /* Safe to copy MakerNote, see DNG standard */
  TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); /* Not reduced, not multi-page and not a mask */
  TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, RPI_IMX219_RAW_HPIXELS);
  TIFFSetField(tif, TIFFTAG_IMAGELENGTH, RPI_IMX219_RAW_VPIXELS);
  TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 16); /* uint16_t */
  TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);
  TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
  TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
  TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfadim);
  TIFFSetField(tif, TIFFTAG_CFAPATTERN, cfapatt);
  //TIFFSetField(tif, TIFFTAG_LINEARIZATIONTABLE, 256, curve);
  TIFFSetField(tif, TIFFTAG_WHITELEVEL, 1, &white);
  TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
  TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1); /* One row per strip */

  if (NULL != filename) {
    TIFFSetField(tif, TIFFTAG_ORIGINALRAWFILENAME, strlen(filename), filename);
  }
  time(&rawtime);
  tm = localtime(&rawtime);
  snprintf(datetime, 64, "%04d:%02d:%02d %02d:%02d:%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
  TIFFSetField(tif, TIFFTAG_DATETIME, datetime); /* Creation time (for DNG) */

  /* Save IFD0 continue */
  TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_dir_offset);
  TIFFCheckpointDirectory(tif);
  TIFFSetDirectory(tif, 0);

  /* Copy EXIF information */
  /* ExifIFD */
  if (EXIT_SUCCESS != TIFFCreateEXIFDirectory(tif)) {
    fprintf(stderr, "Failed to create EXIF directory!\n");
    return EXIT_FAILURE;
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_EXPOSURE_TIME))) {
    TIFFSetField(tif, EXIFTAG_EXPOSURETIME, rational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_FNUMBER))) {
    TIFFSetField(tif, EXIFTAG_FNUMBER, rational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_EXPOSURE_PROGRAM))) {
    TIFFSetField(tif, EXIFTAG_EXPOSUREPROGRAM, be16toh(*((uint16_t *)eentry->data)));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS))) {
    iso = be16toh(*((uint16_t *)eentry->data));
    TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, &iso);
  }
  /* Skipped: ExifVersion */
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL))) {
    TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, eentry->data);
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED))) {
    TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, eentry->data);
  }
  /* Skipped: ComponentsConfiguration (for JPEG only) */
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE))) {
    TIFFSetField(tif, EXIFTAG_SHUTTERSPEEDVALUE, srational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_APERTURE_VALUE))) {
    /* For original lens only */
    TIFFSetField(tif, EXIFTAG_APERTUREVALUE, rational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_BRIGHTNESS_VALUE))) {
    TIFFSetField(tif, EXIFTAG_BRIGHTNESSVALUE, srational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_MAX_APERTURE_VALUE))) {
    /* For original lens only */
    TIFFSetField(tif, EXIFTAG_MAXAPERTUREVALUE, rational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_METERING_MODE))) {
    TIFFSetField(tif, EXIFTAG_METERINGMODE, be16toh(*((uint16_t *)eentry->data)));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_FLASH))) {
    TIFFSetField(tif, EXIFTAG_FLASH, be16toh(*((uint16_t *)eentry->data)));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_FOCAL_LENGTH))) {
    /* For original lens only */
    TIFFSetField(tif, EXIFTAG_FOCALLENGTH, rational_to_float(eentry->data));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_MAKER_NOTE))) {
    TIFFSetField(tif, EXIFTAG_MAKERNOTE, eentry->size, eentry->data);
    /* Various information can be extracted from the maker note. */
    /* Already handled: exp (ExposureTime), ccm (ColorMatrix) */
    /* ag, gain_r, gain_b, greenness, tg, f ar changing. */
    /* Additional: ISP version */
    //sscanf(strstr((const char *)eentry->data, "ev=") + 3, "%f", &ev);
    //TIFFSetField(tif, EXIFTAG_EXPOSUREBIASVALUE, ev);
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_FLASH_PIX_VERSION))) {
    TIFFSetField(tif, EXIFTAG_FLASHPIXVERSION, eentry->data);
  }
  /* Skipped: ColorSpace (for JPEG only) */
  /* Skipped: ExifImageWidth (for JPEG only) */
  /* Skipped: ExifImageHeight (for JPEG only) */
  /* Skipped: InteropOffset */
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_EXPOSURE_MODE))) {
    TIFFSetField(tif, EXIFTAG_EXPOSUREMODE, be16toh(*((uint16_t *)eentry->data)));
  }
  if (NULL != (eentry = exif_content_get_entry(edata->ifd[EXIF_IFD_EXIF], EXIF_TAG_WHITE_BALANCE))) {
    TIFFSetField(tif, EXIFTAG_WHITEBALANCE, be16toh(*((uint16_t *)eentry->data)));
  }

  /* Patch EXIF IFD in */
  TIFFWriteCustomDirectory(tif, &exif_dir_offset);
  TIFFSetDirectory(tif, 0);
  TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_dir_offset);
  TIFFCheckpointDirectory(tif);

  /* InteropIFD */
  /* Skipped: InteropIndex */
  /* IFD1 (Thumbnail data) */

  return EXIT_SUCCESS;
}

static void process_file(char* inFile, char* outFile, char* matrix, int pattern) {

  int i, j, row, col;
  char*     dngFile = NULL;
  FILE*     ifp     = NULL;
  TIFF*     tif     = NULL;
  ExifData* edata   = NULL;

  unsigned long  fileLen;  // number of bytes in file
  unsigned long  offset;  // offset into file to start reading pixel data
  unsigned char* buffer = NULL;
  uint16_t       pixel[RPI_IMX219_RAW_HPIXELS];  // array holds 16 bits per pixel
  unsigned char  split;        // single byte with 4 pairs of low-order bits

  /* Check file existence */
  if (NULL == (ifp = fopen(inFile, "rb"))) {
    perror(inFile);
    return;
  }

  /* Check file length */
  fseek(ifp, 0, SEEK_END);
  fileLen = ftell(ifp);
  if (fileLen < RPI_IMX219_RAW_RAW_LEN) {
    fprintf(stderr, "File %s too short to contain expected 6MB RAW data.\n", inFile);
    goto fail;
  }

  /* Load and check EXIF-data */
  if (NULL == (edata = exif_data_new_from_file(inFile))) {
    fprintf(stderr, "File %s contains no EXIF-data (and therefore no raw-data)\n", inFile);
    goto fail;
  }

  /* Generate DNG file name */
  if (NULL == outFile) {
    dngFile = strdup(inFile);
    strcpy(dngFile + strlen(dngFile) - 3, "dng"); /* TODO: ad-hoc, fix this */
  } else {
    dngFile = strdup(outFile);
  }

  /* Create output TIFF file */
  if (NULL == (tif = TIFFOpen(dngFile, "w"))) {
    goto fail;
  }
  fprintf(stderr, "Creating %s...\n", dngFile);

  /* Copy metadata */
  if (EXIT_SUCCESS != copy_tags(edata, tif, matrix, inFile, pattern)) {
    goto fail;
  }

  /* Allocate memory for one line of pixel data */
  buffer = (unsigned char *)malloc(RPI_IMX219_RAW_ROW_LEN + 1);
  if (NULL == buffer) {
    fprintf(stderr, "Cannot allocate memory for image data!\n");
    goto fail;
  }

  /* Unpack and copy RAW data */
  /* For one file, (TotalFileLength:11112983 - RawBlockSize:6404096) + Header:32768 = 4741655
   * The pixel data is arranged in the file in rows, with 3264 bytes per row.
   * with 3264 bytes per row x 1944 rows we have 6345216 bytes, that is the full 2592x1944 image area.
   */
  fprintf(stderr, "Extracting RAW data...\n");

  /* Now on to the pixel data */
  /* Location in file the raw pixel data starts */
  offset = (fileLen - RPI_IMX219_RAW_RAW_LEN) + RPI_IMX219_RAW_HDR_LEN;
  fseek(ifp, offset, SEEK_SET);

  /* Iterate over pixel rows */
  for (row = 0; row < RPI_IMX219_RAW_VPIXELS; row ++) {
    fread(buffer, RPI_IMX219_RAW_ROW_LEN, 1, ifp); /* Read next line of pixel data */
    j = 0; /* Offset into buffer */

    /* Iterate over pixel columns (4 pixel per 5 bytes) */
    for (col = 0; col < RPI_IMX219_RAW_HPIXELS; col += 4) {
      pixel[col + 0]  = buffer[j ++] << 8;
      pixel[col + 1]  = buffer[j ++] << 8;
      pixel[col + 2]  = buffer[j ++] << 8;
      pixel[col + 3]  = buffer[j ++] << 8;
      /* Low-order packed bits from previous 4 pixels */
      split           = buffer[j ++];
      /* Unpack the bits, add to 16-bit values, left-justified */
      pixel[col + 0] += (split & 0b11000000);
      pixel[col + 1] += (split & 0b00110000) << 2;
      pixel[col + 2] += (split & 0b00001100) << 4;
      pixel[col + 3] += (split & 0b00000011) << 6;

      /* Right adjust them... TODO: merge with previous steps */
      pixel[col + 0] >>= (16 - RPI_IMX219_BIT_DEPTH);
      pixel[col + 1] >>= (16 - RPI_IMX219_BIT_DEPTH);
      pixel[col + 2] >>= (16 - RPI_IMX219_BIT_DEPTH);
      pixel[col + 3] >>= (16 - RPI_IMX219_BIT_DEPTH);
    }

    if (TIFFWriteEncodedStrip(tif, row, pixel, RPI_IMX219_RAW_HPIXELS * 2) < 0) {
      fprintf(stderr, "Error writing TIFF stripe at row %d.\n", row);
      goto fail;
    }
  }

  TIFFWriteDirectory(tif);

fail:
  if (NULL != tif) {
    TIFFClose(tif);
  }

  if (NULL != edata) {
    exif_data_unref(edata);
  }

  if (NULL != ifp) {
    fclose(ifp);
  }

  if (NULL != buffer) {
    free(buffer);
  }

  if (NULL != dngFile) {
    free(dngFile);
  }

  return;
}

int main(int argc, char* argv[]) {
  char* matrix = NULL;
  char* fout   = NULL;
  char* fname  = NULL;
  int pattern=0;
  int opt;

  /* Scan options */
  while ((opt = getopt(argc, argv, ":HVM:o:")) != -1) {
    switch (opt) {
    case 'H': {
      pattern |= CFA_FLIP_HORIZ;
      break;
    }
    case 'V': {
      pattern |= CFA_FLIP_VERT;
      break;
    }
    case 'M': {
      matrix   = strdup(optarg);
      break;
    }
    case 'o': {
      fout     = strdup(optarg);
      break;
    }
    default: /* '?' */
      usage(argv[0]);
    }
  }

  /* Expect at least one input-filename */
  if (optind >= argc) {
    usage(argv[0]);
  }

  /* Prevent user from setting output file name when multiple files are supplied */
  if ((optind < argc - 1) && (fout != NULL)) {
    usage(argv[0]);
  }

  /* Scan file names */
  while (optind < argc) {
    fname = argv[optind ++];
    fprintf(stderr, "\n%s:\n", fname);
    process_file(fname, fout, matrix, pattern);
  }

  /* Clean up */
  if (NULL != matrix) {
    free(matrix);
  }
  if (NULL != fout) {
    free(fout);
  }

  return EXIT_SUCCESS;
}
Last edited by Bryan See on Fri Apr 14, 2017 7:13 am, edited 1 time in total.

Return to “Camera board”