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

Custom lens shading tables

Thu Aug 10, 2017 3:09 pm

Another issue that has been raised over the years is that if you change the lens then you often get colour variation due to pixel vignetting as the default compensation is only valid for the standard lens.
Hopefully we now have a solution.

Since May the camera component has supported a new MMAL (or IL) parameter to pass in an updated table for each of the colour channels.
It follows the same general format as the TI OMAP36xx chips, although with a few restrictions. See http://focus.ti.com/pdfs/wtbu/OMAP36xx_ ... TRM_vQ.zip for the TI docs, page 1197 for an overview of the feature, and then page 1399 for more detail.
It doesn't directly map on to how the Pi pipeline is configured, so we are only supporting a cell/paxel size of 64x64, and it only supports gain format 6 - "Coded as 3-bit integer, 5-bit fraction Range from 0 to 7+31/32". There is some interpolation performed, so don't worry unduly over the apparently coarse grid. (AIUI The OMAP3 applies the same gain to all pixels in the MxN cell, hence going down to a 4x4 cell makes sense).
You get a grid per colour channel, so 4 in the case of Bayer (the 2 green channels are treated independently). The grid is always specified in terms of the full sensor resolution (ie 2592x1944 for OV5647, or 3280x2464 for IMX219), and will be cropped or scaled based on the relationship between the modes as defined in the firmware. The grid must cover the entire image, so will generally overhang the right and bottom edges (ie 41x31 for OV5647, and 52x39 for IMX219).

There are a couple of hoops to jump through in order to get the config data into the pipeline, but if you look at https://github.com/6by9/userland/blob/l ... ll.c#L1077 it gives the code template. (I've got one thing to check on the behaviour here, so there may be an update to the app).

Putting together the table isn't trivial, though not that complex. To make life easier I have written a very basic app to create the ls_table.h header that RaspiStill is taking based on the data from a JPEG+raw or just plain raw capture. See https://github.com/6by9/lens_shading. The source image wants to be of a uniformly illuminated scene of constant colour (white being the ideal).
It's rather simplistic in that it unpacks the raw10 image, takes 3 pixels from the middle of each 64x64 cell, averages them, and then creates a gain based on the difference against the centre 81 pixels (a 9x9 section) as the centre is always assumed to require unity gain. It seems to do a reasonable job of correcting for the lens that I have here, and that was only calibrated with a white sheet of paper and office lighting.

As h&v flips are done before the lens shading compenation, the flips applied when the lens shading grid is generated has to be recorded, passed to the firmware, and the correction manipulated accordingly. I think I've got that lot right, but there is the chance of an issue or two. Let me know if you find odd-balls.

Have fun.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

ddahms
Posts: 64
Joined: Tue Mar 18, 2014 3:38 pm

Re: Custom lens shading tables

Sun Aug 13, 2017 9:32 pm

I gave this a try. It definitely changed the colors of the image but it still looks wrong.

My camera is a v1.3 with a 3.6mm M12 lens (because I screwed up the original lens by trying to clean it). I think I followed your procedure correctly: point camera at white cardboard, run "raspistill -o std.jpg -r", then run "lens_shading_analyze std.jpg" to produce the ls_table.h file. I put it and your modified RaspiStill.c into freshly cloned userland and compiled it. Then I ran the new "raspistill -o test.jpg" and took a photo of the same white cardboard. You can see the result in the (downsampled) attached image--original on the left side, new on the right side. It is different but still doesn't seem right.
shading.jpg
shading.jpg (3.19 KiB) Viewed 3585 times

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

Re: Custom lens shading tables

Mon Aug 14, 2017 7:56 am

Welcome to the wonderful world of camera tuning. You may need to hand tune the shading tables if 6by9s auto system cannot figure it out. It can be quite a lot of work to get these spot on.
Principal Software Engineer at Raspberry Pi (Trading) Ltd.
Please direct all questions to the forum, I do not do support via PM.

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

Re: Custom lens shading tables

Tue Aug 15, 2017 10:05 am

This tuning is very sensitive to the quality of the calibration shot taken. You could take multiple shots and average them to remove some noise artifacts, but I'll leave that as an exercise for others.
Similarly it has to be totally uniformly illuminated and that is surprisingly hard. Your best bet is probably outside on a nice sunny day. That also has the benefit that you can be running with the lowest analogue gain going so removing any non-linearities there.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

drich
Posts: 22
Joined: Tue Jul 28, 2015 7:36 pm

Re: Custom lens shading tables

Wed Aug 16, 2017 12:07 pm

Hello, thank you for adding this feature, you made my life easier :D

Before you made online your 'lens_shading' repo, I already started working on a GUI program to manually generate the table, so I continued it and put it now on my github : https://github.com/dridri/lens_shading_editor

For now I have pretty good results, but need to add more tweaks.

EDIT: just to be sure my understanding is correct, the two green channels are processed separately, but both can be set to the same values right ?

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

Re: Custom lens shading tables

Wed Aug 16, 2017 1:09 pm

drich wrote:
Wed Aug 16, 2017 12:07 pm
EDIT: just to be sure my understanding is correct, the two green channels are processed separately, but both can be set to the same values right?
Normally you'd expect to see very similar values in the two green channels.
In theory there is a very slight offset between them so they could differ, but I'd expect that to be ignored in most situations.

It'd be worth documenting which QT5 libraries are need for your app - it's taken a few minutes of fiddling to make cmake happy. It's then failed the build due to not knowing uint8_t in MainWindow.cpp (stdint.h isn't included). Fix that and it's still not happy:

Code: Select all

/home/pi/lens_shading_editor/MainWindow.cpp: In member function ‘void MainWindow::Export()’:
/home/pi/lens_shading_editor/MainWindow.cpp:201:27: error: ‘class QImage’ has no member named ‘pixelColor’
    uint32_t color = image.pixelColor( x, y ).rgba();
There are always gremlins with the first push :-)
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

drich
Posts: 22
Joined: Tue Jul 28, 2015 7:36 pm

Re: Custom lens shading tables

Wed Aug 16, 2017 1:34 pm

Meh, thank you, should be fixed now :roll:

ddahms
Posts: 64
Joined: Tue Mar 18, 2014 3:38 pm

Re: Custom lens shading tables

Wed Aug 16, 2017 3:04 pm

Everything I tried seemed to have no effect, and I discovered why. The lens_shading_analyse program writes the file "ls_table.h" but RaspiStill.c includes the file "ls_grid.h" and completely ignores "ls_table.h". I had copied ls_grid.h from 6x9's repo along with the modified RaspiStill.c. Renaming my ls_table.h to ls_grid.h made a tremendous difference. It works well now.

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

Re: Custom lens shading tables

Wed Aug 16, 2017 3:41 pm

ddahms wrote:
Wed Aug 16, 2017 3:04 pm
Everything I tried seemed to have no effect, and I discovered why. The lens_shading_analyse program writes the file "ls_table.h" but RaspiStill.c includes the file "ls_grid.h" and completely ignores "ls_table.h". I had copied ls_grid.h from 6x9's repo along with the modified RaspiStill.c. Renaming my ls_table.h to ls_grid.h made a tremendous difference. It works well now.
Doh! I'll correct that now.
I had a command in my bash history that I was using to do the copy from analyse to userland which obviously had a rename in there too.
As I just responded to drich, there are always gremlins with the first push :-)

edit: Done. I've updated userland to look for ls_table.h.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Thu Nov 09, 2017 9:25 pm

Hi all,

I am trying to play around with the lens shading option mentioned above. I noticed two things I do not understand. First, if I am understanding the coding of the ls_table.h correctly, it's a coarse grid of pixel-wise multipliers, coded as format 6 - "Coded as 3-bit integer, 5-bit fraction Range from 0 to 7+31/32".

Now, as far as I understand it, that would mean that 0x00 corresponds to a zero multiplier and 0xFF to a multiplier with strength 7+31/32 = 7.97... . Well, the unit multiplier, 1.0, should than correspond to an entry of 0x20 in the table.

However, analyzing the lens_shading_analyse.c code, it seems that the multipliers are calculated in such a way that 1.0 corresponds to 0x40. Specifically, I am referring to the following line in the code

Code: Select all

uint16_t middle_val = (mid_value_avg / count) << 6;
which multiplies the calculated mid_value by pow(2,6) = 64 = 0x40. Is this correct?

Second, I am not sure that an image capture with the newly compiled raspistill-command really uses the ls_table.h. To test that, I created some specially patterned ls_table.h-tables and captured images of a KODAK grey card. If the multipliers would be applied to the raw image in the way I am thinking they are applied, the pattern present in ls_table.h should show up at least as coarse color variations in the .jpg-image which is captured -- however, that is not the case.

Specifically, I created a ls_table.h with all entries set to zero. So, the .jpg-output image should of course be only a black frame. I do however get the same picture as always. It seems that the entries in ls_table seem have no effect, at least not in the setup I am using, a Pi3 + v2-camera.

Here's what I did on the Pi3 to create a raspistill with custom lens shading:

Code: Select all

# checking out the gits
git clone https://github.com/6by9/userland
cd userland/
git checkout origin/lens_shading

cd ..
git clone https://github.com/6by9/lens_shading
cd lens_shading/

# saving the original raspistill - it gets overwritten by the ./buildme command later on...
cp $(which raspistill) ./raspistill.original

# taking the reference shot (KODAK grey card)
./raspistill.original -r -o test.jpg


 # compiling the lens_shading_analyse prg
make
./lens_shading_analyse test.jpg

# copying the new .h-file into the appropriate place
cp ls_table.h ~/userland/host_applications/linux/apps/raspicam/

# rebuilding raspistill (and other stuff)
cd ../userland
##
## Warning: the following command overrides (among other things) the original raspistill command
## if you want to avoid this, comment out lines 21 - 25 of the buildme-script
##
./buildme

# going back to ~/lens_shading dir...
cd ~/lens_shading

# testing the new binary
##
## !!!! Warning - this command line is wrong - requesting the new image 
##       with the '-r' flag turns off the lens shading compensation!!!!!
##
## correct command line to test the lens shading:
##   ~/userland/build/bin/raspistill  -o ls_Test.jpg
##
~/userland/build/bin/raspistill  -r -o ls_Test.jpg
Well, that does not work. Am I doing something wrong with these steps?
Last edited by cpixip on Wed Nov 15, 2017 9:07 pm, edited 2 times in total.

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Sun Nov 12, 2017 2:56 pm

... some further findings about the issue mentioned above (Pi3 with v2-cam, lens shading not working):
  • I made sure that the code which loads the modified lens shading table during picture taking got compiled in, executed and returned with status 0 during execution.
  • with a raspistill compiled like that, I get a different shading correction as compared to the stock raspistill.
  • however, the shading correction of the newly compiled raspistill stays always the same, regardless of what values I put in the ls_table.h matrix. I checked repeatably that the ls_table.h which got compiled into raspistill actually changed during different compilation runs.
  • if I force the loading of the ls_table.h correction grid to fail, for example by setting at the bottom of ls_table.h

    Code: Select all

    uint32_t ref_transform = 6;
    (instead of the default value "3"), an error is thrown, but the shading is still the shading as observed with any other ls_table.h loaded.

The shading correction of the newly compiled raspistill is actually enhancing the color shift visible in the .jpg-image, compared to the stock raspistill. Thus I am afraid that there is a lens shading done in the newly compiled raspistill, but it is not the one specified in ls_table.h.

Fer
Posts: 35
Joined: Tue Feb 21, 2017 10:27 am

Re: Custom lens shading tables

Tue Nov 14, 2017 1:44 pm

Can anyone confirm this misbehavior? Thanks!

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

Re: Custom lens shading tables

Tue Nov 14, 2017 3:12 pm

I'm in the middle of something at the moment, but do intend to test this as soon as I get a chance.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

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

Re: Custom lens shading tables

Wed Nov 15, 2017 11:55 am

cpixip wrote:
Sun Nov 12, 2017 2:56 pm
[*]with a raspistill compiled like that, I get a different shading correction as compared to the stock raspistill.
<snip>
The shading correction of the newly compiled raspistill is actually enhancing the color shift visible in the .jpg-image, compared to the stock raspistill. Thus I am afraid that there is a lens shading done in the newly compiled raspistill, but it is not the one specified in ls_table.h.
I've just looked at the modified RaspiStill code and will make a bet that you have asked for the raw image.
https://github.com/6by9/userland/blob/l ... ll.c#L1014

Code: Select all

   if(state->wantRAW)
   {
      status = mmal_port_parameter_set_uint32(camera->control, MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE, ~8);
which disables the lens shading block in the ISP deliberately so that the JPEG in calibration image shows how bad the vignetting is on your lens.

I will go back and check that the grids actually being used are the values I'm expecting as you have got me puzzled now as to whether we are using 2p6 or 3p5 fixed point values. I am expecting 3p5, so it's quite possible I've got it wrong in my calibration app, which would have a net result of x2.0 digital gain. Whilst not correct, AE would just compenate for that by reducing the exposure time or dropping analogue gain.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

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

Re: Custom lens shading tables

Wed Nov 15, 2017 4:02 pm

OK, I've convinced myself that it is 3p5, and it's just the analysis tool that is wrong.

The most compelling evidence comes from viewing the steady state stats using -set.

Code: Select all

[email protected]:~/userland $ ./build/bin/raspistill -t 0 -set
<snip>
mmal: Exposure now 12664, analog gain 256/256, digital gain 268/256
mmal: AWB R=335/256, B=408/256
vs

Code: Select all

[email protected]:~/userland $ ./build/bin/raspistill -t 0 -set -r
<snip>
mmal: Exposure now 32352, analog gain 256/256, digital gain 279/256
mmal: AWB R=318/256, B=455/256
As stated earlier, capturing the raw disables the lens shading block. Doing so approximately doubles the exposure time, which confirms that something (lens shading) was boosting values by ~ x2.

I've pushed an update to the analysis tool.
Thanks for the report - when passing so many numbers around the system it is far too easy to slip up like this :)
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Wed Nov 15, 2017 9:00 pm

Hello 6by9 - thanks very much for the feedback! That will keep me going...
6by9 wrote:
Wed Nov 15, 2017 11:55 am
I've just looked at the modified RaspiStill code and will make a bet that you have asked for the raw image [...] which disables the lens shading block in the ISP deliberately so that the JPEG in calibration image shows how bad the vignetting is on your lens.

I can confirm that this is indeed the case. Taking images without the '-r' flag switches on the requested lens shading. Hooray!

If you look at the command history I listed above, you see that I requested in tests always a raw as well as the .jpg. Well, I am keeping the history above for reference, but include some warnings in the comments.

So, to make this explicit for everybody:

If you want to use your own lens shading, never request raw output as well. Do not use the '-r' flag!

With respect to the coding of the table (3p5 vs 2p6) - the normal multiplier values you encounter in lens shading algos would probably always fit into the number range 2p6 provides, and the discretization with 2p6 coding would be finer than the 3p5 coding currently used (=more precise), especially near the center of the image with multipliers close to 1.0.

I am working on a small python script which is able to create the ls_table.h as well. Seems that I got the v2-cam part working correctly, but I have to put some more effort into the v1-cam part. It differs from the lens_shading_analyse-tool in your github in one important aspect - it utilizes a Gaussian pyramid to do the computations, which stabilizes the values somewhat.

I'll give here an example. The output of the lens_shading_analyse tool, "ls_table.h", for a specific image looks like the following visualization:
cprg.jpg
cprg.jpg (30.04 KiB) Viewed 2589 times

Clearly, the computed compensation map is quite noisy - a fact you are probably aware of.

Here's the output - with the same input image - of the Gaussian-based python script I am working on:
pyprg.jpg
pyprg.jpg (23.67 KiB) Viewed 2589 times

Quite an improvement in noise characteristics, I think. The Gaussian pyramid is in a certain sense optimal for this kind of task (it does not create artificial new image structures during resolution reduction). I will post the code of this script in this thread as soon as I am convinced it works with both v1- and v2-cameras.

As a final note, there is an interesting paper published a few months ago by Pagnutti and others, "Laying the foundation to use Raspberry Pi 3 V2 camera module imagery for scientific and engineering purposes", https://www.spiedigitallibrary.org/jour ... full?SSO=1 which lists the lens shading correction of the original v2-camera lens (among a lot of other interesting things!)

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

Re: Custom lens shading tables

Thu Nov 16, 2017 12:08 pm

The main thing to note is that this isn't in the main raspberrypi repos. I'd intended the userland branch and basic calibration app to be used as examples that others would take and use, not as a final solution.

If we want custom lens shading in the main Raspicam apps (and it applies equally to raspivid as raspistill), then we probably want a binary calibration file that can be specified on the command line. Putting it in a C header made it easier to visualise what was going on.
It's all open source code, so I'll leave it for others to do that (although I won't hold my breath as so few people tend to contribute back again).
As for calibration algorithms, everyone and his dog will have their own views on how best to do it which is why I didn't really invest too long in my app. Ideally you'd be averaging over multiple frames as well as some sensible processing of multiple pixels in each image.

2p6 vs 3p5. I'm not convinced that it makes that much difference, particularly as the hardware isn't as crude as the OMAP that applies the same value across the whole paxel. If I get to revisit this then I may add in an option to specify the accuracy.

Yes, interesting paper on the camera sensor, although they would probably be better off taking raspiraw to get closer to the raw data, although that has a greater requirement for understanding the sensor register set.
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Thu Nov 16, 2017 6:40 pm

ok, here, as promised, a python version of the lens shading compensation calculation. I am not sure that I got everything right, so it would be of great help if somebody can check the code. I tried to throw in as much comments as I could think of.

[Note: there's an improved version listed below]

Code: Select all

# -*- coding: utf-8 -*-
""" python script to create ls_table.h
    for lens shading raspistill version. 

    usage: create_ls_table infile [scaler rgb test]

    scaler, if present, defines the unit multiplier of the
    table. Default is currently 64.

    if string 'rgb' is present, the calculated table is
    displayed as rgb data. Otherwise, all 4 color channel
    are simply displayed.

    if string 'test' is present, a simple test pattern is
    overlaid over the original ls_data

    Data is written to "ls_table.h", which has to be copied
    manually into the appropriate dir for raspistill compilation
    
    based on cv2, numpy, pyqtgraph and a recipe in picamera docs
"""

import sys
import cv2
import numpy as np
import pyqtgraph as pg

# defaults if not in command line mode
inFile   = 'test_raw_original.jpg'
rgbMode  = True
testData = False
scaler   = 64

def raw_read(stream):
    """reads a jpg with raw data from either v1 or v2-cam

    - returns 4 color channel matrix with R G1 G2 B planes"""
    
    # first trying to read v1-cam data. If that fails,
    # assume it's a v2-jpg...
    data   = stream.read()[-6404096:]
    if data[:4] == 'BRCM':
        v2_type = False
    else:
        v2_type = True
        stream.seek(0)        
        data   = stream.read()[-10270208:]

    # check for header
    assert data[:4] == 'BRCM'

    # extract raw data
    data = data[32768:]
    data = np.fromstring(data, dtype=np.uint8)

    # reshape the data and strip off the unused bytes
    if v2_type:
        data = data.reshape((2480, 4128))[:2464, :4100]
    else:
        data = data.reshape((1952, 3264))[:1944, :3240]
        
    # promote data array to 16 bits, create space for
    # last two bits
    data = data.astype(np.uint16) << 2

    # now convert to real 10 bit camera signal
    for byte in range(4):
        data[:, byte::5] |= ((data[:, 4::5] >> ((4 - byte) * 2)) & 0b11)
        
    # delete unused pixels  
    data = np.delete(data, np.s_[4::5], 1)

    # we transpose to get correct display for pyqtgraph
    data = data.transpose()

    # using only half of resolution to avoid demosaicing
    # small offsets of color channels will be only visible
    # at pixel sized display scales
    cplane = np.zeros((data.shape[0]/2,data.shape[1]/2,4), dtype=data.dtype)

    # sample into color planes

    # Bayer pattern seems to be different for v1/v2 cams
    if v2_type:
        cplane[:,:, 0] = data[1::2, 1::2] # Red
        cplane[:,:, 1] = data[0::2, 1::2] # Green1
        cplane[:,:, 2] = data[1::2, 0::2] # Green2
        cplane[:,:, 3] = data[0::2, 0::2] # Blue
    else:
        cplane[:,:, 0] = data[0::2, 1::2] # Red
        cplane[:,:, 1] = data[1::2, 1::2] # Green1
        cplane[:,:, 2] = data[0::2, 0::2] # Green2 
        cplane[:,:, 3] = data[1::2, 0::2] # Blue
       
    return cplane,v2_type

def display_raw(cplane,rgbMode):
    """displays 4 channel color planes via pypqtgraph"""
    
    # for convience, split data into separate color channels
    red    = cplane[:,:, 0]
    green1 = cplane[:,:, 1]
    green2 = cplane[:,:, 2]
    blue   = cplane[:,:, 3]
    
    if rgbMode:
        # stack 3 color channels to an approxiate rgb image
        imgA  = np.dstack((red,green1,blue))

        # create grayscale images for the channels used
        # for the rgb image
        imgR  = np.dstack((red,red,red))
        imgG  = np.dstack((green1,green1,green1))
        imgB  = np.dstack((blue,blue,blue))

        # arrange them 
        imgAR = np.vstack((imgA,imgR))
        imgGB = np.vstack((imgG,imgB))
        img   = np.hstack((imgAR,imgGB))        
        
    else:    
        # promote all color channels to grayscale        
        imgR  = np.dstack((red,red,red))
        imgG1 = np.dstack((green1,green1,green1))
        imgG2 = np.dstack((green2,green2,green2))
        imgB  = np.dstack((blue,blue,blue))
        
        # ... and combine them
        imgRG1 = np.vstack((imgR,imgG1))
        imgG2B = np.vstack((imgG2,imgB))
        img   = np.hstack((imgRG1,imgG2B))

    # finally, display the image
    i = pg.image(img)
        
    # ... add some information to the plot
    if rgbMode:        
        rgb = pg.TextItem(html='<b><span style="color:#FFFFFF">R/G1/B</span></b>')
        i.addItem(rgb)

        r   = pg.TextItem(html='<b><span style="color:#FFFFFF">R</span></b>')
        r.setPos(img.shape[0]/2,0)
        i.addItem(r)

        g   = pg.TextItem(html='<b><span style="color:#FFFFFF">G1</span></b>')
        g.setPos(0,img.shape[1]/2)
        i.addItem(g)

        b   = pg.TextItem(html='<b><span style="color:#FFFFFF">B</span></b>')
        b.setPos(img.shape[0]/2,img.shape[1]/2)
        i.addItem(b)
    else:
        r  = pg.TextItem(html='<b><span style="color:#FFFFFF">R</span></b>')
        i.addItem(r)

        g1 = pg.TextItem(html='<b><span style="color:#FFFFFF">G1</span></b>')
        g1.setPos(img.shape[0]/2,0)
        i.addItem(g1)

        g2 = pg.TextItem(html='<b><span style="color:#FFFFFF">G2</span></b>')
        g2.setPos(0,img.shape[1]/2)
        i.addItem(g2)

        b  = pg.TextItem(html='<b><span style="color:#FFFFFF">B</span></b>')
        b.setPos(img.shape[0]/2,img.shape[1]/2)
        i.addItem(b)

# code for creating a gaussian pyramid
def gaussian_pyramid(img, depth):
    G = img.copy() 
    gp = [G]
    for i in range(1,depth):
        G = cv2.pyrDown(G)      
        gp.append(G)
    return gp

#########################
### Start of main prg ###
#########################
arguments = sys.argv[1:]
if len(arguments)>0 :
    stream = open(arguments[0], 'rb')
    if len(arguments)>1 :
        scaler  = int(arguments[1])    
    if len(arguments)>2 :
        rgbMode = (arguments[2]=="rgb")
    if len(arguments)>3 :
        testData = True 
else:
    # open input file
    stream = open(inFile, 'rb')

# using only half of res to avoid demosaicing
cplane,v2_type = raw_read(stream)

# create gaussian pyramid of sufficient size
res = gaussian_pyramid(cplane,6)

# level 5 of the pyramid gives us the correct
# resolution of 
raw = res[5]

# compute center values by averaging the four
# pixels around the center of the appropriate image
# if the image dims are odd, we should probably only
# use the center pixel - not implemented yet
center = ( raw[raw.shape[0]/2-1][raw.shape[1]/2-1]  \
         + raw[raw.shape[0]/2  ][raw.shape[1]/2-1]  \
         + raw[raw.shape[0]/2-1][raw.shape[1]/2  ]  \
         + raw[raw.shape[0]/2-1][raw.shape[1]/2-1] )/4

print "Size of single color plane:",raw.shape[:2]

# calculate the mask, as float to keep precision and range
raw  = raw.astype(float)
mask = float(scaler)*center/raw

# convert back to int, project into safe value range
mask = mask.astype(int).clip(0,0xff)

# modify it if testData is requested
if testData:
    for c in range(0,4):
        for y in range(0,mask.shape[1]):
                for x in range(0,mask.shape[0]):
                    mask[x][y][c] = x
                    
# another test pattern...
#    for c in range(0,4):
#        for y in range(0,mask.shape[1]):
#            if y>mask.shape[1]/2:
#                for x in range(mask.shape[0]/2,mask.shape[0]):
#                    mask[x][y][c] = 0xFE
#            else:
#                for x in range(0,mask.shape[0]/2):
#                    mask[x][y][c] = 0x10

if v2_type:
    print "Raw data from    :","V2 Cam"
else:
    print "Raw data from    :","V1 Cam"    
print "Scaler was set to:",scaler
print "rgbMode          :",rgbMode
print "Minimum value    :",mask.min()," = ",mask.min()/float(scaler)
print "Maximum value    :",mask.max()," = ",mask.max()/float(scaler)

# display calculated mask    
display_raw(mask, rgbMode)

# V1 - rotating mask array to match camera orientation
# not necessary for V2 cam
if not v2_type:
    mask = np.rot90(np.rot90(mask))
                            
# the ls_table.h has the following sequence of channels
cComments = ["R",
             "Gr",
             "Gb",
             "B"]

# now write the table...
with open('ls_table.h','w') as file:

    # initial part of the table
    file.write("uint8_t ls_grid[] = {\n")

    for c in range(0,4):
        # insert channel comment (for readability)
        file.write("//%s - Ch %d\n"%(cComments[c],3-c))
        # scan the mask
        for y in range(0,mask.shape[1]):
            for x in range(0,mask.shape[0]-1):         
                file.write("%d, "%mask[x][y][c])
                
            # finish a single line
            file.write("%d,\n"%mask[mask.shape[0]-1][y][c])
            
    # finish the the ls_grid array
    file.write("};\n");

    # write some additional vars which are expected in ls_table.h
    file.write("uint32_t ref_transform = 3;\n");
    file.write("uint32_t grid_width = %u;\n"%mask.shape[0]);
    file.write("uint32_t grid_height = %u;\n"%mask.shape[1]);
    

## Start the Qt event loop to finally display the pyqtplot
if __name__ == '__main__':
    if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'):
        pg.QtGui.QApplication.exec_()

You need to have installed opencv (needed to reduce the scale of the original data), numpy (for matrix manipulation) and pyqtgraph (for the display of the calculated compensation map). This can be done with

Code: Select all

sudo apt-get install python-opencv
sudo apt-get install python-pyqtgraph
in case these libraries are not already installed.

To use the script, save the above code as "create_ls_table.py". Take a raw test frame and than copy the newly created ls_table.h into the directory where the raspiStill.c-file is hiding. Something along the following lines:

Code: Select all

raspistill -r -o test.jpg
python create_ls_table.py test.jpg
cp ls_table.h ~/userland/host_applications/linux/apps/raspicam/
After that step, you can run the "buildme"-script in the userland main dir to build a new raspistill which uses the new lens shading table. Usually, the newly created binary can be found in ./userland/build/bin/.

The "create_ls_table.py"-script features three additional command line arguments. The second argument, if present, sets the scaler value which is assumed to be the unit multiplier. With 2p6 coding, that would be 64, with 3p5 it would be 32. Of course, you can select other values as well - the autogain will take care of the rest.

The third argument, if it is present and the string "rgb", will show in the upper left corner of the display an approximate rgb-version of the shading map. That should be a negative of the input image, if everything works ok.

Lastly, the third argument, if present, will write out a special test pattern, basically a linearly varying list of values along the x-axis , in order to check the lens shading code. This specific ls_table.h is listed for a v1-cam below.

I am not 100% sure that I got all the the color planes and the image layout of the Bayer pattern correct, but I tested it on both v1- and v2-cameras. If everything works, you should see a display similar to this one:
Capture.JPG
Capture.JPG (40.6 KiB) Viewed 2468 times
where in the upper left corner an approximated RGB-image of the lens shading map is displayed, and three representative color channels (Red/Green1/Blue) in the other quadrants.

To the right a histogram of the table values is displayed. This information will also be output by the script in the form

Code: Select all

Size of single color plane: (41L, 31L)
Raw data from    : V1 Cam
Scaler was set to: 64
rgbMode          : True
Minimum value    : 8  =  0.125
Maximum value    : 177  =  2.765625
As already stated, it would be great if someone could check the code and report some success (or failure) with the lens shading code. Certainly a lot of Raspberry Pi projects would benefit from a functioning lens shading algorithm.

One issue I discovered while testing the lens shading compensation was that values below 32 to not work correctly. The following images shows the problem. To the left is the input image without any lens shading (v1-cam). To the left is the output of raspistill with a special lens shading table in which the scaler linearly increases along the x-axis. One would expect that, correspondingly, brightness should increase from right to left. However, in the range between 0 and 32 table entries, artifacts are clearly visible:
issues.jpg
issues.jpg (40.99 KiB) Viewed 2468 times
Here's the ls_table.h I used:

Code: Select all

uint8_t ls_grid[] = {
//R - Ch 3
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
//Gr - Ch 2
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
//Gb - Ch 1
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
//B - Ch 0
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
};
uint32_t ref_transform = 3;
uint32_t grid_width = 41;
uint32_t grid_height = 31;
I have checked with different experiments that this problem is always present. This issue is kind of severe if the coding is really 3p5, as everything with a multiplier less than 1.0 falls into the problematic range. I have run tests where I changed the uint8_t ls_grid[] definition into a uint16_t ls_grid[] definition, only to discover that in this case table entries somewhat larger than 0xff can be used, and the range of values which give strange output results is doubled, from 0 to 64. Maybe this can give a hint to more capable people to where the problem is hidden?

If I constrain the values in ls_table.h to values in [32;255], everything works perfect and an image like the following can be obtained:
test_not_raw_lens_shading_235_clipped.jpg
test_not_raw_lens_shading_235_clipped.jpg (40.08 KiB) Viewed 2468 times
As I compensated the "calibration image" here with itself, the resulting image is mostly without color and shows mostly an average gray scale value, as is should be. The small halo around the green object is caused by a small movement of the green plastic between the two exposures.
Last edited by cpixip on Fri Nov 17, 2017 6:56 pm, edited 1 time in total.

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

Re: Custom lens shading tables

Fri Nov 17, 2017 12:55 pm

cpixip wrote:
Thu Nov 16, 2017 6:40 pm
I have checked with different experiments that this problem is always present. This issue is kind of severe if the coding is really 3p5, as everything with a multiplier less than 1.0 falls into the problematic range.
Why more problematic if it is 3p5? If you're trying to get more resolution to the coefficients then you'll be artificially using the values as 2p6 and putting up with a x2 digital gain, but then you're less likely to use values <32.

Where is the genuine use case where using gains less than 1 is actually useful? Normally you're increasing the values to compensate for vignetting which results in dark areas in the corners.
cpixip wrote:I have run tests where I changed the uint8_t ls_grid[] definition into a uint16_t ls_grid[] definition, only to discover that in this case table entries somewhat larger than 0xff can be used, and the range of values which give strange output results is doubled, from 0 to 64. Maybe this can give a hint to more capable people to where the problem is hidden?
So you've changed the way that one part of the system is interpreting the table and are surprised by unexpected results?
What you've actually given the firmware is a table with a 0 inserted into every other cell. The interpolation has smoothed that out somewhat. GIGO.

I don't have details of the exact hardware implementation. I would have expected gains of less than 1 to work, but it may be that there was an optimisation employed that saved silicon area by limiting the range. When I get back to this I will do a couple more tests here and may well clip the values to x1.0. (If I'm adding the 2p6 format, then it's possibly worth adding the ones which add 1 to all the values too).
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
Please don't send PMs asking for support - use the forum.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Fri Nov 17, 2017 6:53 pm

Well, thanks for your comments. Let me just state that my intention is not to annoy someone. In another life I have led a team developing a FPGA-based real-time 3D camera with integrated camera calibration. So I know a little bit about some things.

My intention is to make the lens shading algorithm accessible to everyone utilizing the marvelous Raspberry Pi environment for non-standard use - be it in astronomy, microscopy or some other fancy application where the standard lens just won't cut the cake.

I am just trying to understand the requirements and limits of the lens shading implementation.

One standard way to reverse-engineer a system which is basically a black box is to throw unexpected data at it and look at the response. So it's not always garbage in, garbage out. Sometimes you get some clues from the response of the system. And we normal people do not have access to a lot of the things which happen in the image- and video pipeline.
6by9 wrote:
Fri Nov 17, 2017 12:55 pm
Where is the genuine use case where using gains less than 1 is actually useful? Normally you're increasing the values to compensate for vignetting which results in dark areas in the corners.
This is correct - in the normal lens shading application, you do not encounter gains less than 1.0, as the brightest spot will always be at the center of the image and there will be a fall-off toward the edges. I just noted above that the range of usable multiplier values is limited and wanted to document this in the thread entry.

I have updated the script above to take care of this situation by calculating the shading correction differently. Now the maximum response is searched in each color channel and taken as reference. So you end up always with gains >= 1.0. Also, no scaler less than 32 or larger than 255 is allowed, to be in the safe region. Here's the updated script, just in case someone else wants to play around with lens shading:

Code: Select all

# -*- coding: utf-8 -*-
""" python script to create ls_table.h
    for lens shading raspistill version. 

    usage: create_ls_table infile [scaler rgb test]

    scaler, if present, defines the unit multiplier of the
    table. Default is 32. Do not use values less than 32,
    as the interface/pipeline can not realize multipliers
    less than one.

    if string 'rgb' is present, the calculated table is
    displayed as rgb data. Otherwise, all 4 color channel
    are simply displayed.

    if string 'test' is present, a simple test pattern is
    overlaid over the original ls_data

    Data is written to "ls_table.h", which has to be copied
    manually into the appropriate dir for raspistill compilation
    
    based on cv2, numpy, pyqtgraph and a recipe in picamera docs
"""

import sys
import cv2
import numpy as np
import pyqtgraph as pg

# defaults if not in command line mode
inFile   = 'tn.jpg'
rgbMode  = False
testData = False
scaler   = 64

def raw_read(stream):
    """reads a jpg with raw data from either v1 or v2-cam

    - returns 4 color channel matrix with R G1 G2 B planes"""
    
    # first trying to read v1-cam data. If that fails,
    # assume it's a v2-jpg...
    data   = stream.read()[-6404096:]
    if data[:4] == 'BRCM':
        v2_type = False
    else:
        v2_type = True
        stream.seek(0)        
        data   = stream.read()[-10270208:]

    # check for header
    assert data[:4] == 'BRCM'

    # extract raw data
    data = data[32768:]
    data = np.fromstring(data, dtype=np.uint8)

    # reshape the data and strip off the unused bytes
    if v2_type:
        data = data.reshape((2480, 4128))[:2464, :4100]
    else:
        data = data.reshape((1952, 3264))[:1944, :3240]
        
    # promote data array to 16 bits, create space for
    # last two bits
    data = data.astype(np.uint16) << 2

    # now convert to real 10 bit camera signal
    for byte in range(4):
        data[:, byte::5] |= ((data[:, 4::5] >> ((4 - byte) * 2)) & 0b11)
        
    # delete unused pixels  
    data = np.delete(data, np.s_[4::5], 1)

    # we transpose to get correct display for pyqtgraph
    data = data.transpose()

    # using only half of resolution to avoid demosaicing
    # small offsets of color channels will be only visible
    # at pixel sized display scales
    cplane = np.zeros((data.shape[0]/2,data.shape[1]/2,4), dtype=data.dtype)

    # sample into color planes

    # Bayer pattern seems to be different for v1/v2 cams
    if v2_type:
        cplane[:,:, 0] = data[1::2, 1::2] # Red
        cplane[:,:, 1] = data[0::2, 1::2] # Green1
        cplane[:,:, 2] = data[1::2, 0::2] # Green2
        cplane[:,:, 3] = data[0::2, 0::2] # Blue
    else:
        cplane[:,:, 0] = data[0::2, 1::2] # Red
        cplane[:,:, 1] = data[1::2, 1::2] # Green1
        cplane[:,:, 2] = data[0::2, 0::2] # Green2 
        cplane[:,:, 3] = data[1::2, 0::2] # Blue
       
    return cplane,v2_type

def display_raw(cplane,rgbMode):
    """displays 4 channel color planes via pypqtgraph"""
    
    # for convience, split data into separate color channels
    red    = cplane[:,:, 0]
    green1 = cplane[:,:, 1]
    green2 = cplane[:,:, 2]
    blue   = cplane[:,:, 3]
    
    if rgbMode:
        # stack 3 color channels to an approxiate rgb image
        imgA  = np.dstack((red,green1,blue))

        # create grayscale images for the channels used
        # for the rgb image
        imgR  = np.dstack((red,red,red))
        imgG  = np.dstack((green1,green1,green1))
        imgB  = np.dstack((blue,blue,blue))

        # arrange them 
        imgAR = np.vstack((imgA,imgR))
        imgGB = np.vstack((imgG,imgB))
        img   = np.hstack((imgAR,imgGB))        
        
    else:    
        # promote all color channels to grayscale        
        imgR  = np.dstack((red,red,red))
        imgG1 = np.dstack((green1,green1,green1))
        imgG2 = np.dstack((green2,green2,green2))
        imgB  = np.dstack((blue,blue,blue))
        
        # ... and combine them
        imgRG1 = np.vstack((imgR,imgG1))
        imgG2B = np.vstack((imgG2,imgB))
        img   = np.hstack((imgRG1,imgG2B))

    # finally, display the image
    i = pg.image(img)
        
    # ... add some information to the plot
    if rgbMode:        
        rgb = pg.TextItem(html='<b><span style="color:#FFFFFF">R/G1/B</span></b>')
        i.addItem(rgb)

        r   = pg.TextItem(html='<b><span style="color:#FFFFFF">R</span></b>')
        r.setPos(img.shape[0]/2,0)
        i.addItem(r)

        g   = pg.TextItem(html='<b><span style="color:#FFFFFF">G1</span></b>')
        g.setPos(0,img.shape[1]/2)
        i.addItem(g)

        b   = pg.TextItem(html='<b><span style="color:#FFFFFF">B</span></b>')
        b.setPos(img.shape[0]/2,img.shape[1]/2)
        i.addItem(b)
    else:
        r  = pg.TextItem(html='<b><span style="color:#FFFFFF">R</span></b>')
        i.addItem(r)

        g1 = pg.TextItem(html='<b><span style="color:#FFFFFF">G1</span></b>')
        g1.setPos(img.shape[0]/2,0)
        i.addItem(g1)

        g2 = pg.TextItem(html='<b><span style="color:#FFFFFF">G2</span></b>')
        g2.setPos(0,img.shape[1]/2)
        i.addItem(g2)

        b  = pg.TextItem(html='<b><span style="color:#FFFFFF">B</span></b>')
        b.setPos(img.shape[0]/2,img.shape[1]/2)
        i.addItem(b)

# code for creating a gaussian pyramid
def gaussian_pyramid(img, depth):
    G = img.copy() 
    gp = [G]
    for i in range(1,depth):
        G = cv2.pyrDown(G)      
        gp.append(G)
    return gp

#########################
### Start of main prg ###
#########################
arguments = sys.argv[1:]
if len(arguments)>0 :
    stream = open(arguments[0], 'rb')
    if len(arguments)>1 :
        scaler  = int(arguments[1])    
    if len(arguments)>2 :
        rgbMode = (arguments[2]=="rgb")
    if len(arguments)>3 :
        testData = True 
else:
    # open input file
    stream = open(inFile, 'rb')
    
# constraint scaler to the usable range
scaler = max(32,scaler)

# using only half of res to avoid demosaicing
cplane,v2_type = raw_read(stream)

# create gaussian pyramid of sufficient size
res = gaussian_pyramid(cplane,6)

# level 5 of the pyramid gives us the correct
# resolution of 
raw = res[5]

# get the maximum value in each channel in order
# to make sure that gains requested by the table
# are always larger than one
center = np.amax(np.amax(raw, axis=0),axis=0)

print "Size of single color plane:",raw.shape[:2]
print "Unit gain references      :",center

print "0 channel range           :",raw[:,:,0].min(),raw[:,:,0].max()
print "1 channel range           :",raw[:,:,1].min(),raw[:,:,1].max()
print "2 channel range           :",raw[:,:,2].min(),raw[:,:,2].max()
print "3 channel range           :",raw[:,:,3].min(),raw[:,:,3].max()

# calculate the mask, use float here to keep precision and range
raw  = raw.astype(float)

# brute force
#mask = np.zeros( raw.shape, dtype=np.float)
#for c in range(0,4):
#        for y in range(0,mask.shape[1]):
#                for x in range(0,mask.shape[0]):
#                    mask[x,y,c] = scaler*center[c]/raw[x,y,c]

# faster way...
mask = scaler*center/raw

# convert back to int, project into safe value range
mask = mask.astype(int).clip(0,0xff)

# modify it if testData is requested
if testData:
    for c in range(0,4):
        for y in range(0,mask.shape[1]):
                for x in range(0,mask.shape[0]):
                    mask[x][y][c] = x
                    
# another test pattern...
#    for c in range(0,4):
#        for y in range(0,mask.shape[1]):
#            if y>mask.shape[1]/2:
#                for x in range(mask.shape[0]/2,mask.shape[0]):
#                    mask[x][y][c] = 0xFE
#            else:
#                for x in range(0,mask.shape[0]/2):
#                    mask[x][y][c] = 0x10

if v2_type:
    print "Raw data from             :","V2 Cam"
else:
    print "Raw data from             :","V1 Cam"    
print "Scaler was set to         :",scaler
print "rgbMode                   :",rgbMode
print "Minimum value             :",mask.min()," = ",mask.min()/float(scaler)
print "Maximum value             :",mask.max()," = ",mask.max()/float(scaler)

# display calculated mask    
display_raw(mask, rgbMode)

# V1 - rotating mask array to match camera orientation
# not necessary for V2 cam
if not v2_type:
    mask = np.rot90(np.rot90(mask))
                            
# the ls_table.h has the following sequence of channels
cComments = ["R",
             "Gr",
             "Gb",
             "B"]

# now write the table...
with open('ls_table.h','w') as file:

    # initial part of the table
    file.write("uint8_t ls_grid[] = {\n")

    for c in range(0,4):
        # insert channel comment (for readability)
        file.write("//%s - Ch %d\n"%(cComments[c],3-c))
        # scan the mask
        for y in range(0,mask.shape[1]):
            for x in range(0,mask.shape[0]-1):         
                file.write("%d, "%mask[x][y][c])
                
            # finish a single line
            file.write("%d,\n"%mask[mask.shape[0]-1][y][c])
            
    # finish the the ls_grid array
    file.write("};\n");

    # write some additional vars which are expected in ls_table.h
    file.write("uint32_t ref_transform = 3;\n");
    file.write("uint32_t grid_width = %u;\n"%mask.shape[0]);
    file.write("uint32_t grid_height = %u;\n"%mask.shape[1]);
    

## Start the Qt event loop to finally display the pyqtplot
if __name__ == '__main__':
    if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'):
        pg.QtGui.QApplication.exec_()


A further enhancement with this new version of the script is that the lens shading matrix is now computed in float instead with 16bit-ints, allowing a higher dynamic range and somewhat more precision during the calculations.
Last edited by cpixip on Fri Nov 17, 2017 10:43 pm, edited 1 time in total.

Fer
Posts: 35
Joined: Tue Feb 21, 2017 10:27 am

Re: Custom lens shading tables

Fri Nov 17, 2017 8:38 pm

cpixip:
let me say I appreciate your work.

I'm sorry I can't test/contribute because I'm working on headless and python-less boxes (tailored to fit), so I can't try your scripts (I don't even know anything python by the way).
To add insult to the injury, I'm working on a custom version of raspivid.c (because I need very fast interrupt-based frame capture), and trying to sync my modifications to the various improved/experimental versions offered by mighty 6x9 has already proven to be hopeless. :(

So I basically passed by to say "thank you" to both you and 6x9; hoping one day to have a stable raspivid with nice working custom lens shading correction for both V1 and V2 cameras. :D

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Sat Nov 18, 2017 12:39 pm

ok, just want to report on one further experiment. This one was aimed to find out how good the lens shading results are in comparison with the standard.

In order to do so, I did a raw capture of a KODAK gray card with the stock v2-camera. As illumination source I used simply an overcast sky which gave me a very homogeneous incoming light with the appropriate color temperature. With that setup I grabbed a reference image of the gray card and used this to calculate a corresponding ls_table.h. Finally, a new raspistill with lens shading enabled was compiled. I than took two images, one with the raspistill supplied with the distribution, one with the newly compiled lens shading raspistill.

Here's the result, for comparision:
t_v2_5.6_gray_06_comparision_original_lensShade.JPG
t_v2_5.6_gray_06_comparision_original_lensShade.JPG (16.12 KiB) Viewed 2276 times
The left half of the image shows the result with the lens shading raspistill, the right side the result obtained with the original raspistill.

Clearly, the custom lens shading works in principle, but it is clear that there's still some progress possible.

I tested both the lens_shading_analyse.c programm [git cloned today, 18.11.17] and my python-script listed above and both give very similar results, as the following comparison shows (python script left side, c-prg right side):
t_v2_5.6_gray_06_comparision_cprg_pyth.JPG
t_v2_5.6_gray_06_comparision_cprg_pyth.JPG (16.98 KiB) Viewed 2276 times
As noted above, on the right half side the result of the lens_shading_analyse.c is displayed.

On this side, some few very bright pixels appear. They are caused by entries in ls_table.h with values less than 32; specifically, if you look at the table created by lens_shading_analyse.c for this test,

Code: Select all

uint8_t ls_grid[] = {
//R - Ch 3
43, 44, 45, 43, 45, 44, 44, 44, 44, 45, 44, 44, 45, 43, 41, 41, 42, 43, 39, 41, 39, 40, 39, 40, 38, 38, 39, 38, 40, 40, 40, 39, 40, 41, 45, 42, 44, 43, 44, 44, 47, 46, 44, 45, 45, 45, 46, 44, 45, 44, 44, 45,
44, 43, 44, 45, 46, 45, 43, 46, 46, 43, 43, 42, 42, 42, 44, 41, 42, 40, 40, 40, 40, 39, 39, 39, 39, 38, 39, 39, 41, 39, 39, 40, 39, 43, 42, 44, 44, 43, 47, 45, 45, 46, 44, 46, 45, 45, 47, 46, 45, 44, 44, 42,
42, 43, 45, 43, 43, 45, 44, 46, 44, 44, 43, 43, 44, 44, 43, 41, 41, 41, 40, 39, 40, 38, 39, 39, 37, 39, 39, 40, 41, 41, 40, 39, 42, 42, 44, 42, 43, 43, 44, 47, 45, 44, 47, 45, 46, 47, 46, 45, 46, 44, 44, 45,
43, 42, 44, 44, 44, 43, 45, 44, 44, 45, 44, 42, 42, 42, 42, 39, 41, 40, 40, 40, 40, 38, 38, 39, 39, 38, 39, 41, 38, 39, 40, 40, 39, 41, 42, 44, 42, 44, 44, 45, 44, 45, 45, 46, 43, 45, 45, 45, 45, 46, 45, 44,
43, 42, 44, 45, 44, 42, 45, 44, 42, 44, 44, 42, 41, 43, 41, 41, 40, 40, 40, 38, 38, 38, 38, 38, 38, 38, 38, 36, 38, 38, 39, 38, 42, 40, 41, 40, 42, 42, 43, 44, 44, 43, 45, 43, 45, 47, 45, 45, 44, 43, 43, 41,
45, 43, 44, 45, 43, 45, 43, 45, 42, 44, 43, 42, 42, 42, 40, 39, 40, 40, 38, 39, 39, 36, 36, 37, 37, 37, 37, 37, 38, 39, 37, 37, 40, 40, 38, 44, 42, 44, 43, 41, 45, 46, 44, 44, 47, 46, 42, 44, 46, 45, 43, 44,
45, 44, 43, 44, 44, 44, 44, 43, 43, 44, 42, 42, 41, 40, 40, 39, 40, 39, 39, 38, 36, 36, 38, 36, 38, 35, 37, 36, 38, 38, 38, 39, 40, 38, 40, 40, 40, 43, 42, 43, 44, 45, 44, 45, 42, 44, 42, 44, 44, 42, 43, 45,
44, 43, 43, 44, 42, 42, 44, 43, 42, 43, 43, 41, 42, 38, 40, 38, 37, 37, 37, 39, 37, 35, 36, 37, 37, 36, 35, 35, 37, 37, 38, 39, 36, 38, 41, 40, 40, 43, 41, 43, 44, 43, 44, 44, 44, 44, 45, 43, 43, 42, 43, 45,
43, 42, 43, 45, 42, 44, 43, 43, 40, 43, 41, 41, 42, 41, 39, 39, 38, 38, 37, 35, 37, 38, 35, 34, 34, 34, 36, 36, 36, 36, 36, 37, 38, 39, 39, 41, 40, 41, 42, 42, 43, 45, 43, 43, 42, 43, 43, 42, 44, 44, 42, 45,
41, 42, 43, 44, 41, 43, 43, 42, 42, 42, 41, 41, 41, 41, 39, 37, 37, 36, 37, 36, 35, 33, 36, 36, 35, 36, 34, 35, 36, 36, 35, 37, 37, 38, 39, 39, 40, 41, 41, 42, 42, 40, 43, 43, 43, 42, 43, 44, 43, 42, 40, 41,
41, 42, 41, 42, 42, 41, 40, 42, 42, 41, 39, 38, 40, 37, 38, 37, 37, 36, 36, 35, 35, 35, 34, 35, 34, 35, 34, 35, 34, 34, 34, 36, 36, 36, 37, 38, 37, 39, 39, 39, 40, 42, 42, 41, 41, 42, 41, 42, 42, 43, 43, 40,
44, 41, 43, 42, 42, 41, 41, 42, 41, 41, 38, 39, 39, 38, 38, 36, 36, 36, 34, 34, 34, 35, 35, 33, 33, 33, 34, 34, 34, 35, 34, 33, 35, 36, 37, 38, 38, 38, 41, 39, 40, 40, 40, 41, 43, 43, 43, 42, 42, 41, 43, 41,
42, 41, 44, 43, 40, 42, 40, 41, 40, 38, 40, 38, 39, 37, 37, 35, 36, 35, 33, 35, 32, 33, 33, 35, 33, 33, 33, 33, 34, 34, 34, 36, 36, 36, 36, 36, 38, 39, 39, 40, 41, 40, 40, 43, 41, 42, 43, 41, 42, 42, 41, 42,
41, 40, 41, 40, 41, 40, 41, 38, 39, 40, 37, 37, 38, 37, 36, 34, 35, 34, 34, 33, 33, 32, 32, 33, 33, 33, 32, 33, 33, 34, 33, 34, 33, 35, 35, 35, 37, 39, 37, 38, 38, 37, 40, 39, 39, 42, 42, 40, 40, 40, 40, 42,
40, 42, 43, 40, 39, 40, 39, 41, 39, 39, 38, 39, 37, 37, 36, 34, 33, 34, 34, 33, 34, 31, 32, 34, 32, 32, 33, 33, 34, 33, 33, 33, 34, 33, 34, 36, 37, 37, 37, 40, 39, 39, 38, 38, 41, 40, 41, 40, 40, 40, 42, 41,
43, 39, 39, 41, 40, 40, 40, 39, 38, 39, 38, 38, 36, 37, 36, 34, 34, 32, 33, 33, 32, 33, 32, 32, 33, 32, 31, 33, 33, 33, 32, 34, 32, 34, 33, 33, 34, 37, 37, 38, 38, 40, 38, 39, 39, 41, 40, 40, 39, 39, 40, 38,
40, 40, 39, 40, 40, 39, 40, 39, 38, 39, 38, 37, 36, 36, 35, 34, 33, 34, 31, 31, 33, 32, 33, 32, 32, 33, 32, 31, 33, 33, 32, 33, 32, 34, 33, 35, 35, 36, 37, 37, 39, 39, 38, 39, 39, 38, 41, 40, 41, 40, 39, 39,
39, 42, 39, 41, 40, 40, 38, 39, 39, 36, 38, 36, 36, 35, 35, 33, 33, 33, 32, 32, 32, 31, 32, 32, 32, 32, 33, 32, 32, 31, 33, 33, 33, 34, 33, 35, 36, 35, 36, 38, 38, 37, 39, 39, 39, 40, 41, 40, 38, 37, 40, 37,
40, 39, 39, 38, 38, 38, 38, 39, 38, 38, 37, 36, 36, 34, 33, 34, 34, 34, 33, 34, 31, 31, 33, 32, 32, 32, 31, 32, 31, 32, 31, 33, 33, 33, 35, 33, 36, 36, 35, 36, 37, 38, 38, 39, 39, 39, 39, 39, 40, 39, 41, 40,
41, 39, 40, 40, 39, 38, 39, 38, 38, 38, 34, 36, 35, 35, 34, 33, 35, 32, 33, 33, 32, 31, 31, 33, 31, 32, 31, 31, 31, 32, 31, 32, 32, 34, 33, 35, 35, 35, 36, 37, 36, 37, 37, 39, 39, 37, 39, 38, 39, 39, 39, 39,
40, 39, 38, 39, 39, 38, 39, 38, 38, 36, 36, 35, 34, 36, 35, 34, 32, 33, 32, 32, 32, 32, 33, 32, 33, 32, 32, 32, 31, 33, 31, 32, 31, 33, 33, 34, 34, 36, 36, 37, 39, 38, 38, 37, 38, 38, 38, 38, 39, 38, 39, 40,
39, 38, 40, 40, 39, 39, 39, 37, 37, 36, 37, 36, 36, 36, 35, 35, 34, 33, 32, 32, 32, 32, 32, 32, 31, 32, 32, 34, 31, 32, 34, 32, 33, 33, 34, 35, 34, 35, 37, 37, 37, 37, 38, 39, 38, 38, 38, 37, 38, 38, 38, 39,
40, 40, 39, 38, 39, 38, 37, 37, 38, 38, 37, 37, 36, 35, 34, 34, 33, 31, 33, 32, 32, 32, 32, 32, 31, 32, 32, 31, 33, 32, 31, 33, 33, 32, 34, 33, 35, 36, 37, 36, 37, 37, 37, 39, 39, 39, 37, 39, 39, 40, 39, 40,
40, 40, 39, 41, 41, 39, 41, 37, 40, 37, 37, 35, 35, 36, 35, 34, 33, 33, 34, 33, 33, 32, 33, 32, 31, 31, 32, 32, 32, 32, 32, 32, 34, 33, 33, 34, 35, 35, 36, 36, 38, 38, 39, 38, 39, 39, 39, 40, 38, 39, 39, 39,
39, 41, 39, 39, 40, 39, 39, 38, 38, 39, 36, 37, 36, 36, 34, 33, 34, 32, 33, 32, 32, 34, 32, 33, 31, 33, 31, 32, 33, 32, 33, 33, 34, 33, 34, 34, 36, 35, 38, 37, 37, 37, 38, 39, 38, 38, 38, 39, 39, 38, 39, 38,
40, 40, 40, 39, 38, 38, 39, 38, 39, 38, 37, 36, 37, 36, 34, 35, 34, 34, 33, 33, 32, 33, 32, 31, 32, 33, 32, 32, 35, 33, 33, 34, 33, 34, 33, 36, 36, 34, 35, 37, 37, 38, 38, 40, 41, 39, 39, 38, 40, 41, 39, 39,
39, 40, 37, 39, 38, 38, 40, 39, 38, 37, 38, 38, 37, 35, 36, 34, 34, 33, 32, 33, 33, 32, 33, 34, 32, 33, 32, 32, 33, 32, 32, 33, 34, 34, 34, 34, 36, 37, 38, 38, 39, 39, 38, 40, 39, 38, 39, 41, 39, 39, 39, 40,
39, 39, 40, 39, 41, 40, 40, 38, 38, 40, 37, 37, 36, 36, 35, 33, 34, 33, 33, 33, 31, 33, 32, 31, 32, 32, 33, 32, 33, 33, 34, 33, 34, 35, 34, 36, 36, 36, 36, 39, 37, 39, 40, 39, 38, 38, 38, 40, 41, 39, 40, 40,
40, 38, 40, 42, 41, 39, 39, 39, 39, 39, 38, 38, 37, 35, 35, 35, 35, 36, 33, 33, 34, 34, 33, 32, 33, 32, 34, 33, 33, 33, 34, 34, 35, 35, 35, 36, 35, 35, 39, 37, 39, 38, 40, 41, 40, 41, 38, 40, 40, 38, 40, 40,
39, 39, 39, 39, 39, 39, 41, 39, 39, 39, 40, 35, 37, 36, 36, 36, 34, 35, 35, 33, 33, 32, 35, 33, 34, 34, 33, 32, 34, 34, 35, 35, 35, 34, 36, 35, 36, 37, 37, 38, 38, 39, 41, 40, 38, 41, 38, 40, 40, 40, 40, 39,
42, 39, 40, 41, 41, 39, 40, 40, 38, 40, 37, 38, 38, 36, 35, 36, 37, 36, 34, 34, 33, 34, 36, 34, 34, 33, 34, 33, 34, 35, 35, 35, 36, 36, 36, 38, 38, 36, 39, 39, 38, 40, 40, 40, 41, 40, 40, 39, 40, 40, 41, 38,
40, 42, 40, 40, 41, 40, 39, 40, 39, 40, 37, 37, 37, 37, 37, 36, 36, 37, 35, 35, 34, 35, 34, 36, 35, 35, 35, 36, 35, 37, 36, 35, 36, 36, 36, 37, 39, 38, 40, 40, 40, 40, 40, 41, 40, 40, 42, 40, 39, 39, 40, 39,
40, 41, 39, 38, 40, 40, 40, 39, 40, 39, 38, 39, 39, 37, 37, 37, 37, 35, 37, 35, 34, 34, 35, 34, 35, 35, 36, 37, 36, 35, 36, 37, 36, 38, 38, 38, 39, 40, 39, 39, 40, 41, 41, 40, 41, 41, 41, 40, 40, 40, 40, 40,
40, 41, 42, 41, 40, 40, 41, 40, 41, 40, 40, 39, 39, 38, 38, 39, 38, 36, 36, 36, 36, 35, 36, 35, 36, 36, 35, 36, 36, 36, 37, 36, 36, 39, 38, 38, 38, 39, 41, 39, 40, 40, 39, 41, 42, 43, 42, 41, 39, 42, 41, 42,
40, 40, 40, 41, 42, 40, 40, 40, 39, 40, 39, 38, 38, 40, 39, 39, 37, 37, 38, 36, 37, 36, 36, 36, 35, 37, 35, 36, 37, 37, 35, 38, 39, 38, 37, 40, 39, 38, 40, 40, 43, 43, 42, 41, 41, 41, 42, 43, 41, 40, 40, 41,
41, 39, 40, 41, 39, 39, 41, 41, 39, 40, 40, 40, 40, 40, 39, 39, 38, 39, 38, 37, 38, 36, 35, 35, 37, 38, 35, 38, 35, 37, 37, 38, 38, 40, 38, 39, 41, 38, 42, 42, 42, 40, 40, 42, 42, 43, 43, 41, 41, 40, 42, 40,
40, 39, 41, 40, 38, 41, 41, 39, 40, 40, 40, 43, 39, 40, 39, 38, 37, 37, 38, 37, 38, 38, 37, 37, 37, 37, 37, 36, 38, 38, 38, 38, 39, 38, 39, 39, 40, 42, 41, 42, 42, 42, 43, 40, 41, 43, 41, 42, 41, 38, 41, 40,
39, 40, 40, 41, 40, 41, 40, 42, 42, 40, 41, 40, 38, 39, 39, 38, 38, 40, 39, 37, 39, 38, 38, 38, 37, 36, 38, 38, 37, 38, 39, 38, 38, 40, 40, 39, 42, 40, 40, 40, 41, 42, 43, 40, 43, 41, 42, 42, 41, 42, 40, 38,
39, 39, 40, 41, 40, 40, 42, 43, 39, 42, 40, 41, 40, 40, 40, 39, 39, 39, 41, 40, 38, 38, 36, 39, 38, 37, 39, 38, 38, 38, 37, 39, 41, 39, 41, 41, 41, 41, 43, 41, 42, 41, 40, 41, 42, 39, 41, 44, 41, 41, 39, 38,
//Gr - Ch 2
56, 58, 57, 58, 60, 59, 59, 57, 59, 60, 57, 60, 57, 55, 56, 55, 56, 55, 55, 55, 53, 52, 52, 52, 52, 53, 51, 51, 54, 53, 54, 55, 56, 56, 59, 57, 58, 62, 61, 60, 60, 60, 62, 59, 63, 63, 63, 62, 59, 58, 57, 59,
54, 56, 57, 60, 59, 57, 57, 59, 58, 56, 58, 57, 59, 56, 54, 55, 54, 51, 54, 51, 53, 52, 51, 50, 51, 52, 51, 51, 53, 52, 54, 52, 58, 55, 57, 56, 59, 60, 59, 59, 63, 61, 60, 62, 60, 64, 61, 61, 60, 60, 59, 62,
57, 58, 58, 60, 61, 58, 61, 58, 59, 61, 59, 57, 56, 56, 55, 57, 53, 54, 53, 51, 53, 52, 51, 51, 50, 52, 51, 52, 50, 52, 53, 52, 53, 53, 57, 55, 53, 59, 59, 59, 58, 59, 62, 65, 62, 62, 62, 62, 63, 61, 58, 58,
57, 59, 60, 59, 57, 60, 59, 61, 58, 58, 57, 59, 57, 56, 53, 54, 53, 54, 55, 52, 49, 51, 52, 50, 50, 49, 49, 51, 49, 48, 50, 52, 52, 55, 56, 56, 58, 58, 59, 57, 60, 60, 59, 61, 61, 61, 63, 62, 65, 60, 58, 57,
57, 59, 62, 62, 57, 57, 61, 59, 56, 57, 55, 55, 55, 55, 54, 50, 54, 50, 50, 50, 48, 49, 47, 49, 49, 49, 49, 49, 47, 51, 52, 50, 50, 54, 56, 54, 55, 56, 57, 58, 56, 59, 61, 62, 60, 62, 60, 62, 62, 60, 60, 60,
56, 58, 61, 60, 57, 56, 58, 60, 56, 56, 56, 54, 53, 53, 52, 52, 49, 50, 49, 48, 46, 47, 48, 46, 46, 46, 49, 47, 47, 46, 47, 49, 48, 53, 52, 51, 55, 53, 55, 55, 59, 59, 61, 60, 63, 62, 62, 62, 61, 60, 60, 57,
57, 57, 59, 59, 57, 63, 57, 57, 56, 57, 57, 54, 52, 52, 51, 51, 49, 47, 47, 47, 46, 44, 45, 44, 44, 45, 44, 46, 46, 46, 45, 46, 48, 50, 50, 51, 52, 54, 56, 57, 55, 58, 58, 62, 60, 60, 63, 63, 60, 61, 59, 59,
59, 59, 62, 58, 61, 58, 58, 56, 58, 55, 54, 52, 52, 52, 50, 49, 48, 47, 46, 44, 44, 43, 41, 42, 42, 44, 44, 43, 42, 43, 47, 48, 47, 48, 49, 49, 51, 53, 55, 53, 56, 57, 60, 57, 61, 61, 60, 60, 62, 61, 59, 59,
61, 58, 59, 58, 57, 56, 56, 57, 55, 56, 52, 52, 52, 49, 50, 48, 46, 44, 42, 44, 43, 42, 41, 42, 42, 42, 42, 42, 41, 42, 45, 42, 46, 47, 48, 48, 51, 50, 51, 55, 56, 59, 58, 57, 58, 62, 60, 60, 61, 57, 59, 62,
58, 58, 59, 57, 58, 57, 55, 55, 56, 53, 53, 52, 50, 48, 47, 46, 46, 44, 43, 41, 40, 40, 40, 40, 39, 40, 39, 39, 40, 40, 41, 42, 43, 44, 46, 46, 47, 50, 50, 53, 51, 56, 57, 56, 59, 58, 60, 58, 60, 59, 56, 60,
59, 57, 60, 53, 58, 53, 56, 56, 54, 53, 52, 51, 48, 47, 47, 45, 42, 42, 41, 40, 39, 38, 38, 37, 38, 36, 37, 37, 38, 39, 40, 41, 43, 43, 44, 46, 45, 49, 52, 50, 54, 54, 55, 57, 58, 58, 61, 58, 61, 61, 58, 62,
59, 58, 57, 58, 56, 55, 53, 53, 53, 54, 51, 50, 47, 45, 46, 42, 42, 40, 40, 39, 39, 38, 37, 36, 36, 37, 38, 37, 36, 37, 38, 39, 41, 42, 43, 43, 46, 47, 50, 50, 53, 54, 56, 58, 57, 57, 60, 57, 59, 58, 61, 56,
58, 58, 58, 57, 56, 56, 54, 51, 51, 51, 49, 48, 48, 45, 44, 41, 41, 39, 39, 37, 36, 36, 36, 36, 36, 36, 35, 35, 36, 38, 37, 38, 39, 40, 43, 42, 45, 48, 46, 51, 50, 53, 55, 55, 59, 55, 57, 57, 59, 59, 58, 58,
56, 55, 57, 56, 57, 56, 54, 54, 50, 50, 47, 46, 45, 44, 43, 40, 40, 38, 36, 37, 36, 34, 33, 34, 34, 35, 34, 34, 35, 36, 35, 37, 38, 38, 41, 42, 43, 45, 48, 47, 51, 51, 53, 54, 55, 56, 57, 57, 58, 57, 60, 61,
56, 56, 61, 56, 56, 54, 55, 55, 50, 50, 47, 46, 46, 43, 41, 40, 38, 38, 37, 35, 34, 34, 33, 34, 34, 33, 33, 34, 34, 35, 36, 37, 36, 38, 40, 41, 43, 47, 48, 47, 50, 50, 54, 55, 54, 53, 55, 57, 58, 57, 57, 57,
55, 57, 57, 55, 56, 53, 54, 52, 50, 48, 49, 46, 43, 43, 41, 38, 39, 35, 35, 35, 34, 34, 33, 31, 34, 32, 33, 34, 34, 35, 34, 35, 36, 37, 39, 42, 41, 44, 49, 49, 50, 48, 51, 51, 54, 56, 55, 58, 55, 56, 57, 57,
58, 56, 57, 57, 52, 55, 53, 52, 50, 50, 48, 44, 43, 41, 40, 38, 37, 36, 34, 33, 33, 33, 32, 31, 31, 32, 32, 32, 32, 34, 34, 36, 36, 38, 38, 41, 43, 43, 45, 46, 49, 48, 50, 53, 57, 55, 58, 57, 58, 59, 58, 57,
56, 56, 57, 53, 54, 51, 52, 52, 50, 47, 48, 47, 45, 44, 40, 38, 37, 36, 35, 34, 34, 32, 32, 32, 32, 32, 31, 32, 33, 33, 32, 34, 36, 35, 37, 39, 39, 43, 44, 44, 48, 51, 50, 51, 55, 55, 54, 58, 57, 56, 55, 56,
57, 53, 56, 53, 54, 53, 53, 52, 48, 47, 46, 45, 43, 41, 40, 38, 37, 36, 34, 34, 33, 33, 32, 32, 31, 31, 31, 33, 33, 32, 33, 34, 33, 36, 37, 37, 40, 43, 44, 44, 48, 50, 52, 51, 54, 54, 57, 56, 59, 56, 57, 53,
54, 57, 54, 55, 53, 53, 50, 53, 48, 48, 46, 44, 43, 41, 40, 37, 36, 35, 34, 32, 33, 32, 32, 31, 31, 32, 31, 32, 32, 33, 33, 34, 35, 35, 37, 39, 40, 42, 44, 46, 48, 46, 50, 52, 53, 53, 53, 57, 54, 57, 55, 58,
56, 55, 54, 57, 54, 52, 54, 51, 49, 47, 46, 43, 42, 40, 40, 37, 35, 35, 34, 33, 32, 32, 32, 33, 31, 31, 32, 30, 32, 32, 32, 33, 34, 36, 36, 38, 39, 42, 45, 45, 48, 49, 50, 52, 52, 56, 55, 58, 55, 56, 57, 57,
57, 54, 54, 55, 54, 52, 52, 49, 49, 48, 47, 44, 43, 40, 40, 38, 37, 35, 34, 33, 32, 33, 31, 32, 31, 32, 32, 32, 32, 31, 33, 33, 34, 37, 38, 40, 39, 42, 43, 46, 47, 48, 49, 51, 51, 52, 56, 52, 55, 55, 55, 56,
57, 53, 54, 53, 54, 53, 51, 51, 48, 47, 46, 45, 43, 41, 38, 38, 36, 36, 34, 34, 33, 33, 32, 33, 32, 31, 32, 32, 32, 33, 34, 34, 36, 35, 39, 39, 40, 41, 43, 45, 47, 47, 51, 50, 52, 56, 54, 56, 56, 58, 56, 54,
55, 54, 54, 55, 51, 54, 53, 49, 50, 47, 46, 46, 42, 40, 41, 38, 37, 36, 35, 34, 33, 31, 32, 32, 31, 32, 31, 32, 32, 33, 33, 34, 35, 37, 38, 40, 40, 43, 43, 44, 47, 50, 52, 52, 53, 51, 53, 56, 56, 56, 56, 56,
57, 54, 54, 54, 53, 52, 50, 51, 48, 48, 47, 46, 43, 42, 40, 37, 38, 36, 35, 34, 33, 32, 33, 32, 32, 33, 32, 33, 32, 33, 34, 34, 35, 36, 38, 40, 41, 43, 46, 47, 48, 48, 51, 52, 54, 53, 54, 57, 57, 55, 60, 57,
55, 53, 54, 54, 52, 53, 52, 50, 49, 47, 48, 43, 43, 40, 40, 39, 38, 37, 35, 34, 34, 33, 34, 33, 33, 32, 32, 33, 34, 34, 34, 35, 36, 36, 38, 40, 41, 43, 44, 47, 48, 49, 49, 52, 56, 55, 54, 54, 54, 56, 55, 56,
56, 57, 53, 51, 53, 52, 53, 51, 50, 48, 47, 45, 42, 42, 40, 40, 39, 36, 36, 35, 34, 34, 33, 34, 32, 33, 34, 35, 35, 34, 35, 36, 37, 38, 38, 38, 41, 43, 44, 45, 49, 49, 50, 53, 53, 55, 54, 56, 56, 56, 55, 52,
55, 54, 56, 54, 53, 53, 52, 52, 50, 49, 47, 46, 43, 42, 42, 41, 39, 38, 36, 35, 35, 34, 33, 34, 34, 34, 34, 36, 35, 36, 37, 37, 39, 38, 41, 41, 42, 44, 45, 47, 50, 50, 50, 52, 54, 56, 56, 55, 53, 57, 56, 54,
53, 53, 54, 55, 52, 54, 51, 52, 48, 50, 48, 46, 43, 43, 43, 40, 40, 38, 38, 36, 36, 34, 36, 34, 35, 35, 36, 35, 35, 35, 37, 38, 38, 39, 40, 41, 43, 44, 46, 47, 49, 51, 51, 52, 54, 57, 57, 56, 57, 55, 54, 58,
53, 55, 54, 54, 53, 52, 53, 51, 50, 47, 48, 45, 45, 43, 43, 42, 41, 39, 38, 37, 37, 37, 36, 36, 36, 36, 36, 36, 35, 37, 38, 39, 38, 41, 43, 43, 44, 45, 47, 48, 52, 52, 53, 53, 53, 56, 56, 55, 56, 54, 55, 56,
54, 54, 55, 54, 53, 51, 53, 51, 51, 48, 49, 48, 44, 44, 43, 42, 41, 41, 38, 37, 39, 39, 38, 37, 37, 37, 38, 38, 37, 39, 39, 39, 41, 42, 42, 43, 44, 46, 48, 49, 52, 53, 54, 56, 53, 54, 55, 58, 55, 57, 57, 54,
55, 54, 55, 55, 53, 54, 52, 52, 49, 50, 49, 47, 46, 45, 46, 43, 40, 41, 40, 40, 40, 40, 40, 39, 39, 39, 39, 39, 40, 39, 41, 41, 41, 41, 42, 44, 45, 46, 49, 47, 49, 54, 55, 53, 55, 55, 53, 54, 53, 54, 57, 57,
52, 53, 55, 54, 54, 54, 51, 53, 50, 51, 48, 48, 48, 46, 44, 44, 42, 44, 42, 40, 41, 40, 40, 40, 40, 39, 41, 39, 40, 42, 42, 42, 42, 43, 43, 45, 49, 47, 50, 48, 51, 53, 54, 55, 55, 56, 53, 52, 55, 53, 55, 56,
52, 51, 52, 54, 55, 55, 52, 51, 50, 50, 50, 48, 49, 46, 46, 44, 43, 44, 43, 42, 42, 42, 41, 41, 43, 41, 41, 40, 43, 43, 43, 44, 44, 44, 46, 48, 46, 49, 50, 50, 49, 54, 55, 53, 52, 53, 54, 59, 55, 53, 55, 52,
53, 52, 54, 53, 51, 53, 51, 50, 52, 51, 50, 49, 48, 49, 48, 49, 45, 44, 44, 44, 42, 43, 42, 42, 41, 43, 43, 43, 43, 44, 43, 46, 46, 47, 46, 47, 49, 49, 52, 51, 51, 56, 55, 53, 55, 55, 55, 55, 54, 56, 55, 54,
51, 52, 52, 51, 51, 53, 52, 53, 51, 50, 51, 50, 49, 47, 45, 48, 48, 47, 45, 44, 45, 44, 42, 43, 45, 43, 46, 43, 44, 44, 44, 47, 46, 47, 47, 48, 49, 51, 51, 52, 52, 57, 53, 55, 54, 55, 54, 57, 55, 54, 54, 54,
51, 51, 52, 51, 52, 51, 51, 52, 51, 51, 50, 50, 49, 47, 48, 48, 46, 47, 47, 46, 46, 44, 45, 45, 46, 44, 44, 46, 43, 45, 45, 46, 47, 47, 47, 49, 51, 49, 53, 50, 56, 54, 53, 55, 55, 53, 52, 54, 55, 53, 50, 51,
50, 50, 53, 52, 51, 51, 50, 51, 52, 50, 51, 50, 50, 49, 48, 47, 49, 47, 48, 46, 46, 47, 47, 46, 46, 47, 47, 47, 47, 48, 47, 48, 50, 49, 50, 52, 49, 49, 51, 54, 53, 52, 54, 54, 56, 53, 52, 53, 52, 51, 52, 51,
50, 51, 51, 50, 51, 52, 51, 51, 51, 51, 53, 48, 49, 49, 50, 49, 49, 46, 48, 48, 49, 49, 46, 48, 46, 46, 46, 47, 46, 48, 47, 47, 49, 49, 49, 50, 53, 51, 53, 53, 53, 53, 55, 53, 53, 54, 52, 53, 51, 52, 51, 50,
//Gb - Ch 1
56, 55, 58, 57, 60, 58, 57, 59, 58, 60, 60, 61, 58, 58, 56, 58, 56, 56, 56, 56, 57, 58, 57, 57, 55, 56, 55, 55, 56, 57, 57, 57, 57, 57, 58, 57, 60, 61, 62, 61, 64, 64, 63, 64, 61, 61, 62, 61, 59, 57, 57, 60,
56, 56, 56, 57, 58, 60, 58, 60, 61, 60, 59, 58, 60, 57, 56, 56, 58, 56, 54, 55, 54, 54, 56, 54, 53, 55, 54, 55, 54, 53, 56, 57, 59, 55, 58, 60, 58, 59, 59, 61, 61, 60, 63, 61, 62, 64, 61, 60, 59, 61, 57, 57,
55, 56, 59, 59, 57, 60, 59, 57, 58, 58, 59, 58, 59, 58, 58, 55, 57, 56, 53, 55, 53, 56, 53, 53, 55, 51, 54, 53, 53, 55, 55, 54, 56, 57, 57, 57, 58, 61, 60, 60, 63, 59, 60, 62, 62, 60, 62, 62, 61, 59, 58, 60,
56, 57, 59, 58, 56, 59, 59, 61, 59, 60, 58, 57, 58, 56, 54, 54, 53, 55, 51, 54, 52, 52, 53, 51, 54, 52, 51, 53, 51, 53, 53, 55, 54, 56, 56, 56, 56, 56, 60, 61, 61, 61, 61, 64, 62, 62, 60, 61, 61, 61, 60, 57,
57, 57, 57, 58, 60, 60, 58, 58, 58, 58, 57, 55, 54, 55, 54, 54, 53, 53, 53, 50, 50, 50, 50, 53, 49, 48, 49, 50, 51, 52, 53, 51, 53, 53, 52, 54, 55, 58, 57, 60, 60, 59, 60, 58, 59, 61, 63, 60, 59, 61, 58, 61,
59, 56, 58, 57, 56, 57, 58, 59, 57, 59, 55, 55, 55, 53, 53, 53, 51, 50, 49, 51, 48, 48, 48, 46, 48, 48, 47, 48, 49, 49, 48, 49, 53, 52, 52, 52, 53, 57, 57, 60, 56, 57, 60, 62, 58, 62, 59, 59, 61, 58, 56, 60,
57, 58, 56, 55, 58, 58, 57, 57, 56, 56, 56, 55, 53, 53, 52, 50, 48, 48, 47, 47, 49, 47, 46, 46, 45, 46, 45, 46, 47, 47, 47, 49, 48, 51, 53, 52, 53, 52, 56, 57, 57, 58, 61, 61, 59, 59, 59, 62, 59, 60, 61, 59,
55, 57, 56, 57, 57, 55, 56, 57, 56, 54, 52, 53, 50, 51, 49, 50, 47, 48, 46, 47, 46, 45, 44, 43, 43, 44, 44, 44, 44, 45, 45, 46, 46, 49, 52, 50, 51, 53, 55, 55, 55, 58, 58, 60, 59, 61, 58, 59, 60, 56, 58, 56,
56, 57, 55, 56, 55, 54, 54, 57, 56, 54, 54, 51, 49, 49, 49, 48, 47, 46, 45, 42, 44, 42, 43, 43, 43, 43, 40, 42, 42, 43, 43, 44, 44, 48, 49, 48, 49, 50, 51, 53, 53, 56, 58, 57, 55, 59, 58, 56, 59, 60, 56, 60,
55, 56, 55, 55, 54, 56, 53, 55, 54, 51, 53, 50, 48, 48, 46, 46, 43, 44, 44, 41, 43, 41, 42, 40, 40, 40, 40, 39, 41, 43, 42, 43, 45, 44, 45, 47, 49, 51, 52, 52, 52, 57, 55, 56, 57, 56, 59, 59, 56, 58, 56, 58,
57, 54, 56, 56, 54, 53, 53, 56, 53, 50, 49, 48, 47, 46, 46, 44, 42, 42, 40, 40, 39, 39, 39, 40, 37, 38, 38, 37, 39, 40, 40, 41, 40, 43, 44, 46, 46, 49, 48, 50, 50, 53, 54, 56, 55, 58, 56, 57, 57, 57, 57, 54,
56, 55, 56, 54, 55, 53, 53, 52, 49, 51, 50, 47, 47, 45, 43, 43, 42, 42, 39, 37, 38, 36, 37, 36, 37, 36, 38, 36, 38, 38, 38, 37, 40, 43, 42, 45, 46, 45, 49, 48, 50, 53, 52, 53, 56, 56, 55, 58, 55, 59, 56, 58,
55, 55, 55, 53, 56, 55, 55, 50, 49, 50, 48, 47, 44, 45, 43, 41, 39, 39, 38, 38, 37, 36, 35, 35, 35, 36, 35, 36, 36, 35, 38, 39, 39, 40, 42, 42, 43, 44, 48, 48, 50, 52, 54, 53, 54, 55, 54, 55, 54, 54, 57, 57,
53, 54, 55, 54, 54, 56, 52, 53, 51, 48, 46, 45, 45, 42, 43, 42, 39, 37, 36, 37, 34, 35, 35, 34, 34, 35, 35, 34, 35, 35, 36, 37, 37, 40, 41, 41, 43, 44, 47, 49, 49, 51, 54, 54, 54, 53, 54, 56, 54, 56, 55, 56,
54, 55, 54, 55, 52, 51, 50, 52, 49, 49, 47, 46, 42, 43, 41, 39, 38, 38, 36, 35, 35, 33, 34, 35, 32, 33, 33, 34, 35, 35, 35, 36, 36, 37, 39, 40, 42, 43, 45, 47, 47, 51, 52, 52, 56, 53, 54, 55, 53, 53, 55, 55,
53, 53, 55, 55, 52, 53, 52, 50, 47, 46, 46, 44, 43, 41, 40, 40, 38, 37, 36, 34, 35, 33, 33, 33, 33, 33, 32, 33, 33, 34, 35, 34, 36, 36, 37, 40, 42, 43, 44, 47, 48, 48, 51, 51, 52, 53, 53, 53, 54, 56, 53, 55,
51, 53, 55, 56, 54, 51, 52, 50, 48, 46, 46, 44, 44, 42, 40, 40, 36, 35, 34, 34, 33, 33, 33, 33, 32, 32, 33, 32, 32, 33, 34, 34, 36, 37, 37, 40, 42, 43, 45, 47, 48, 49, 50, 49, 54, 53, 55, 51, 55, 54, 55, 54,
55, 53, 54, 51, 50, 49, 51, 50, 48, 45, 45, 45, 42, 42, 38, 36, 37, 35, 33, 33, 33, 33, 33, 32, 31, 33, 31, 32, 32, 33, 33, 34, 35, 36, 37, 38, 40, 42, 42, 45, 45, 47, 47, 49, 51, 50, 51, 53, 56, 52, 53, 57,
51, 53, 52, 52, 52, 51, 50, 50, 48, 47, 43, 43, 40, 39, 38, 37, 36, 34, 34, 33, 32, 32, 32, 32, 31, 31, 32, 32, 31, 33, 33, 34, 34, 35, 37, 39, 39, 42, 42, 45, 45, 46, 50, 51, 49, 53, 53, 54, 54, 53, 52, 51,
53, 55, 54, 51, 50, 49, 50, 49, 48, 45, 45, 45, 40, 40, 39, 38, 36, 34, 34, 34, 32, 32, 33, 31, 31, 31, 32, 31, 32, 32, 32, 33, 34, 36, 38, 39, 40, 42, 41, 45, 46, 47, 48, 47, 51, 52, 53, 51, 53, 54, 53, 53,
54, 56, 54, 51, 51, 54, 49, 50, 46, 45, 44, 42, 40, 41, 39, 37, 36, 35, 34, 32, 32, 32, 32, 32, 31, 32, 32, 31, 32, 33, 33, 34, 34, 36, 35, 38, 40, 41, 43, 44, 46, 48, 48, 49, 51, 51, 49, 51, 53, 52, 53, 53,
53, 54, 53, 51, 52, 48, 48, 48, 48, 46, 43, 43, 41, 39, 40, 38, 36, 35, 35, 33, 32, 32, 32, 32, 31, 32, 32, 31, 33, 32, 33, 34, 33, 35, 37, 39, 39, 41, 41, 46, 47, 48, 49, 50, 52, 53, 51, 53, 52, 52, 52, 54,
53, 53, 53, 51, 51, 51, 53, 50, 48, 48, 44, 43, 41, 40, 39, 37, 36, 36, 33, 34, 33, 33, 32, 32, 32, 32, 32, 32, 31, 33, 33, 34, 34, 36, 38, 38, 41, 41, 43, 43, 46, 47, 50, 51, 55, 50, 53, 53, 54, 57, 53, 54,
51, 53, 52, 50, 50, 50, 51, 49, 48, 46, 45, 44, 42, 39, 37, 36, 36, 35, 35, 33, 34, 32, 32, 32, 32, 31, 33, 33, 33, 32, 34, 33, 35, 36, 37, 38, 40, 41, 43, 45, 45, 47, 50, 49, 52, 51, 53, 52, 53, 55, 54, 56,
53, 51, 50, 52, 50, 52, 50, 49, 48, 48, 43, 45, 42, 41, 39, 37, 38, 35, 34, 35, 33, 33, 33, 32, 33, 33, 32, 33, 33, 34, 34, 35, 36, 36, 38, 39, 40, 40, 44, 45, 48, 49, 49, 49, 51, 51, 51, 53, 53, 55, 53, 53,
50, 50, 52, 53, 52, 54, 49, 48, 47, 46, 45, 42, 43, 41, 38, 37, 37, 36, 36, 35, 35, 33, 33, 32, 33, 33, 33, 33, 34, 34, 35, 35, 36, 38, 37, 40, 41, 42, 44, 47, 47, 49, 49, 52, 53, 51, 50, 53, 53, 55, 53, 51,
54, 53, 51, 52, 52, 51, 52, 49, 48, 47, 47, 44, 42, 42, 41, 39, 37, 37, 36, 35, 34, 34, 34, 34, 33, 33, 33, 34, 34, 33, 34, 35, 38, 37, 39, 40, 43, 44, 45, 45, 49, 51, 49, 50, 52, 53, 52, 54, 54, 54, 54, 51,
52, 51, 50, 56, 53, 52, 50, 48, 50, 50, 47, 45, 44, 44, 40, 40, 39, 38, 38, 37, 35, 36, 35, 35, 34, 35, 34, 36, 36, 36, 37, 37, 39, 39, 40, 41, 43, 44, 47, 46, 48, 48, 51, 50, 55, 54, 54, 54, 53, 55, 54, 54,
51, 51, 51, 54, 51, 51, 50, 50, 50, 48, 45, 45, 44, 43, 41, 42, 41, 38, 38, 37, 38, 36, 37, 36, 35, 35, 37, 37, 36, 38, 36, 39, 39, 40, 40, 41, 43, 45, 45, 47, 50, 48, 51, 52, 54, 54, 53, 55, 56, 53, 54, 54,
51, 53, 50, 53, 53, 53, 50, 51, 50, 48, 48, 46, 44, 43, 43, 42, 42, 39, 38, 38, 37, 37, 36, 39, 37, 37, 37, 37, 38, 38, 39, 39, 41, 41, 41, 43, 43, 46, 45, 50, 50, 52, 52, 51, 53, 54, 53, 54, 54, 54, 53, 51,
54, 52, 51, 51, 50, 49, 51, 51, 52, 50, 47, 47, 45, 47, 44, 43, 41, 41, 41, 39, 39, 38, 40, 38, 37, 39, 37, 39, 40, 40, 40, 41, 41, 42, 44, 44, 46, 47, 48, 50, 50, 50, 50, 53, 53, 53, 54, 55, 55, 56, 53, 51,
52, 50, 54, 54, 54, 52, 51, 50, 50, 49, 49, 46, 48, 44, 45, 43, 44, 42, 42, 42, 41, 40, 39, 41, 40, 39, 39, 40, 40, 41, 42, 42, 41, 44, 43, 45, 47, 48, 48, 51, 51, 53, 52, 53, 55, 53, 55, 54, 55, 52, 55, 54,
55, 53, 52, 51, 54, 51, 53, 51, 49, 49, 50, 48, 47, 47, 47, 44, 45, 42, 42, 41, 41, 42, 43, 42, 42, 43, 41, 43, 41, 41, 42, 44, 44, 44, 46, 47, 48, 50, 49, 50, 52, 52, 53, 52, 52, 56, 56, 57, 56, 54, 53, 53,
50, 51, 52, 52, 51, 52, 53, 51, 53, 53, 51, 50, 50, 48, 46, 45, 46, 45, 45, 45, 44, 43, 42, 42, 42, 42, 43, 45, 42, 42, 44, 45, 46, 46, 46, 48, 49, 48, 50, 53, 52, 51, 53, 56, 55, 54, 54, 57, 54, 56, 54, 54,
51, 51, 54, 53, 54, 54, 54, 53, 51, 51, 49, 51, 49, 49, 49, 48, 47, 47, 46, 47, 44, 46, 45, 45, 45, 45, 43, 44, 45, 45, 47, 46, 46, 47, 48, 49, 50, 51, 53, 51, 51, 54, 53, 53, 55, 57, 55, 54, 55, 53, 53, 53,
51, 51, 52, 52, 52, 52, 52, 52, 50, 51, 52, 49, 50, 47, 49, 48, 48, 47, 46, 48, 46, 45, 46, 45, 45, 45, 47, 47, 47, 45, 46, 47, 48, 49, 50, 50, 50, 52, 53, 52, 51, 55, 54, 54, 54, 56, 55, 54, 56, 52, 50, 52,
50, 51, 51, 51, 50, 52, 53, 50, 52, 51, 52, 52, 50, 47, 49, 50, 47, 51, 48, 48, 49, 47, 48, 48, 47, 47, 48, 47, 47, 48, 48, 49, 48, 48, 50, 53, 53, 51, 53, 53, 52, 51, 52, 54, 55, 56, 52, 56, 56, 56, 53, 51,
50, 50, 53, 51, 52, 51, 51, 52, 52, 53, 50, 51, 50, 50, 51, 50, 50, 48, 49, 49, 48, 48, 49, 49, 48, 48, 49, 49, 49, 51, 50, 48, 53, 52, 52, 52, 50, 51, 51, 55, 53, 56, 53, 54, 54, 53, 53, 56, 54, 54, 51, 52,
50, 51, 49, 52, 50, 50, 53, 52, 52, 54, 50, 53, 52, 50, 51, 50, 52, 52, 51, 50, 49, 48, 49, 48, 48, 49, 49, 49, 49, 50, 50, 50, 54, 49, 50, 51, 52, 53, 52, 55, 54, 53, 54, 54, 55, 52, 53, 53, 52, 53, 51, 51,
//B - Ch 0
47, 48, 50, 48, 50, 52, 51, 49, 49, 49, 50, 50, 50, 48, 48, 47, 49, 49, 48, 48, 47, 45, 47, 46, 46, 45, 47, 47, 48, 48, 47, 49, 49, 49, 49, 52, 51, 51, 52, 53, 53, 53, 54, 54, 52, 53, 52, 52, 51, 53, 50, 50,
49, 50, 49, 52, 50, 51, 54, 51, 51, 51, 51, 53, 50, 52, 49, 48, 49, 48, 48, 45, 45, 46, 47, 47, 46, 47, 45, 46, 45, 47, 44, 48, 48, 52, 49, 47, 50, 49, 52, 52, 53, 55, 55, 52, 53, 54, 55, 52, 52, 52, 51, 51,
48, 52, 52, 53, 49, 51, 53, 51, 50, 50, 54, 51, 50, 49, 48, 49, 47, 46, 46, 46, 45, 48, 45, 44, 45, 45, 45, 47, 46, 45, 46, 46, 48, 48, 48, 47, 50, 50, 52, 52, 51, 52, 54, 53, 54, 55, 51, 52, 52, 51, 52, 48,
50, 51, 51, 51, 50, 52, 52, 51, 51, 50, 51, 49, 49, 49, 47, 47, 46, 47, 45, 45, 47, 44, 47, 44, 43, 42, 45, 44, 45, 45, 44, 47, 45, 46, 47, 50, 48, 49, 51, 52, 51, 52, 53, 52, 52, 52, 51, 53, 54, 53, 52, 49,
51, 50, 51, 52, 50, 53, 51, 52, 51, 49, 50, 48, 48, 48, 47, 47, 46, 46, 44, 44, 44, 43, 43, 44, 43, 41, 44, 44, 43, 43, 45, 46, 46, 45, 49, 47, 47, 50, 51, 52, 50, 53, 52, 52, 53, 54, 53, 54, 51, 52, 50, 52,
51, 49, 50, 52, 49, 50, 53, 50, 50, 48, 50, 46, 47, 47, 45, 46, 44, 44, 43, 42, 44, 41, 41, 42, 42, 42, 41, 43, 41, 43, 43, 44, 44, 45, 47, 48, 48, 48, 49, 50, 51, 50, 51, 54, 52, 53, 55, 52, 51, 51, 51, 51,
49, 50, 50, 52, 50, 50, 49, 49, 47, 48, 49, 49, 49, 46, 45, 44, 44, 42, 43, 44, 40, 41, 41, 41, 42, 40, 40, 41, 41, 42, 42, 42, 45, 42, 44, 45, 47, 47, 48, 49, 51, 48, 53, 52, 51, 53, 52, 50, 52, 49, 50, 53,
51, 48, 51, 49, 50, 51, 52, 49, 50, 49, 48, 46, 45, 46, 44, 44, 41, 41, 43, 42, 40, 41, 42, 39, 39, 38, 39, 40, 39, 41, 41, 41, 42, 43, 43, 42, 45, 47, 48, 48, 49, 50, 52, 51, 50, 52, 51, 53, 51, 52, 51, 55,
50, 51, 50, 50, 51, 49, 48, 48, 46, 48, 47, 46, 46, 45, 43, 42, 42, 39, 39, 39, 39, 39, 38, 38, 38, 38, 37, 38, 38, 38, 39, 41, 41, 40, 43, 45, 45, 48, 48, 48, 47, 49, 49, 50, 51, 49, 51, 53, 52, 52, 49, 48,
50, 49, 47, 48, 48, 49, 49, 50, 47, 46, 47, 45, 44, 44, 40, 40, 39, 39, 38, 38, 38, 38, 37, 36, 36, 37, 36, 38, 37, 38, 36, 38, 38, 41, 40, 42, 45, 45, 46, 46, 47, 46, 50, 50, 50, 52, 50, 51, 49, 47, 51, 51,
48, 48, 48, 50, 49, 47, 48, 47, 48, 43, 45, 45, 43, 42, 43, 39, 39, 37, 39, 38, 36, 36, 36, 35, 35, 37, 35, 36, 36, 37, 37, 39, 38, 39, 39, 41, 43, 44, 45, 47, 47, 50, 47, 48, 49, 51, 49, 50, 50, 47, 49, 50,
48, 48, 50, 48, 48, 49, 48, 48, 46, 46, 44, 43, 42, 39, 38, 39, 39, 37, 35, 36, 36, 36, 35, 35, 34, 34, 33, 34, 35, 36, 36, 35, 37, 38, 39, 40, 41, 42, 43, 45, 47, 47, 46, 47, 47, 50, 51, 50, 49, 49, 49, 52,
48, 50, 48, 47, 46, 47, 47, 46, 44, 45, 43, 42, 41, 40, 39, 37, 38, 36, 35, 35, 34, 35, 34, 35, 33, 34, 33, 35, 34, 34, 35, 35, 36, 38, 38, 41, 42, 42, 42, 44, 45, 48, 46, 48, 48, 50, 49, 46, 49, 48, 50, 48,
49, 47, 47, 48, 47, 46, 46, 47, 46, 41, 42, 42, 42, 38, 37, 36, 36, 36, 33, 34, 34, 33, 33, 33, 32, 33, 33, 33, 33, 33, 35, 35, 35, 36, 37, 38, 41, 42, 41, 43, 45, 45, 47, 47, 49, 48, 48, 50, 50, 50, 49, 49,
49, 49, 47, 47, 48, 45, 46, 45, 44, 43, 42, 40, 40, 39, 38, 37, 36, 35, 34, 34, 33, 32, 33, 33, 32, 32, 33, 32, 32, 33, 34, 34, 35, 34, 37, 38, 40, 40, 42, 42, 45, 43, 46, 45, 48, 47, 47, 48, 49, 49, 47, 48,
47, 47, 46, 45, 45, 45, 44, 46, 46, 43, 41, 41, 39, 38, 36, 37, 36, 34, 33, 33, 32, 33, 32, 32, 32, 32, 32, 32, 31, 34, 32, 34, 35, 34, 36, 37, 38, 40, 40, 44, 42, 45, 46, 46, 47, 46, 47, 48, 48, 46, 48, 47,
48, 47, 48, 46, 46, 45, 45, 45, 45, 42, 42, 40, 39, 37, 36, 35, 35, 35, 33, 32, 33, 31, 32, 32, 32, 31, 33, 31, 33, 32, 32, 33, 33, 34, 36, 37, 38, 39, 41, 40, 42, 43, 45, 46, 46, 48, 46, 47, 49, 48, 49, 48,
48, 46, 46, 45, 47, 45, 45, 45, 43, 43, 40, 41, 40, 38, 38, 35, 35, 34, 33, 32, 32, 32, 32, 34, 31, 31, 31, 31, 31, 31, 31, 32, 32, 33, 35, 36, 38, 39, 41, 41, 42, 43, 45, 45, 45, 46, 48, 45, 47, 46, 49, 46,
46, 46, 46, 45, 46, 45, 45, 44, 43, 42, 43, 41, 38, 37, 37, 36, 34, 34, 32, 32, 32, 31, 32, 31, 31, 31, 33, 31, 31, 31, 32, 32, 34, 34, 36, 37, 37, 39, 41, 41, 42, 43, 42, 45, 46, 46, 46, 46, 48, 47, 47, 48,
47, 47, 48, 45, 45, 45, 45, 44, 43, 42, 41, 42, 38, 38, 35, 34, 34, 33, 33, 32, 32, 31, 32, 33, 31, 32, 31, 32, 31, 31, 32, 34, 32, 34, 35, 36, 36, 40, 40, 41, 41, 44, 44, 44, 46, 47, 47, 47, 46, 46, 48, 50,
47, 47, 45, 47, 46, 42, 45, 46, 43, 43, 41, 39, 37, 36, 36, 35, 34, 35, 33, 31, 32, 31, 32, 32, 31, 31, 32, 32, 31, 31, 32, 33, 33, 34, 36, 37, 37, 40, 39, 40, 41, 44, 44, 44, 45, 46, 47, 45, 46, 46, 47, 46,
48, 47, 47, 45, 45, 45, 44, 46, 43, 43, 39, 40, 39, 38, 36, 35, 35, 33, 34, 32, 32, 32, 31, 31, 31, 31, 32, 32, 31, 32, 33, 32, 33, 33, 35, 35, 38, 38, 40, 42, 41, 42, 44, 45, 45, 45, 47, 47, 47, 46, 47, 46,
46, 46, 45, 45, 47, 45, 43, 46, 42, 43, 43, 41, 38, 39, 36, 35, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31, 32, 32, 33, 32, 32, 33, 33, 34, 36, 37, 37, 39, 41, 41, 42, 43, 44, 47, 46, 46, 45, 46, 47, 47, 45, 46,
45, 46, 47, 47, 46, 43, 45, 44, 43, 42, 41, 41, 39, 39, 37, 35, 34, 33, 32, 33, 32, 32, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 43, 43, 44, 47, 46, 46, 47, 46, 48, 46, 48,
46, 48, 46, 48, 44, 44, 46, 44, 42, 43, 42, 41, 39, 37, 36, 36, 35, 34, 34, 32, 32, 31, 31, 32, 32, 32, 31, 33, 32, 32, 33, 34, 34, 36, 36, 38, 39, 39, 40, 41, 44, 44, 45, 46, 45, 46, 47, 46, 48, 48, 48, 51,
46, 47, 47, 45, 46, 44, 45, 43, 41, 41, 42, 41, 39, 38, 37, 35, 36, 35, 35, 33, 31, 32, 32, 32, 32, 31, 32, 32, 32, 33, 33, 34, 34, 35, 35, 39, 39, 40, 40, 41, 44, 44, 45, 44, 47, 46, 48, 47, 45, 48, 47, 47,
48, 46, 46, 46, 47, 46, 46, 43, 44, 42, 40, 39, 41, 38, 38, 37, 36, 34, 34, 33, 33, 33, 33, 33, 32, 33, 32, 33, 34, 33, 34, 34, 35, 35, 37, 37, 40, 42, 43, 42, 43, 44, 45, 44, 45, 46, 46, 46, 47, 47, 49, 50,
45, 48, 47, 46, 47, 47, 44, 43, 42, 41, 43, 40, 40, 38, 38, 37, 36, 35, 35, 34, 34, 33, 33, 33, 34, 34, 33, 34, 33, 34, 34, 35, 35, 37, 38, 37, 38, 41, 41, 42, 44, 42, 47, 46, 45, 47, 47, 47, 47, 47, 47, 48,
45, 46, 46, 46, 45, 44, 45, 46, 44, 42, 43, 41, 40, 39, 40, 37, 35, 36, 35, 35, 35, 35, 34, 34, 34, 33, 34, 35, 34, 34, 35, 36, 37, 36, 38, 38, 39, 41, 41, 43, 44, 45, 45, 46, 46, 49, 47, 47, 46, 47, 48, 47,
46, 45, 47, 49, 47, 44, 45, 44, 43, 43, 41, 41, 40, 38, 39, 38, 37, 37, 36, 34, 35, 35, 34, 33, 35, 34, 34, 35, 35, 35, 37, 37, 36, 38, 39, 39, 40, 41, 43, 44, 45, 44, 46, 47, 48, 49, 48, 47, 47, 49, 48, 49,
49, 47, 45, 47, 46, 46, 45, 47, 45, 42, 43, 43, 41, 40, 40, 40, 38, 37, 38, 37, 37, 36, 36, 35, 35, 35, 35, 36, 36, 36, 37, 37, 37, 36, 40, 39, 42, 43, 45, 44, 43, 45, 46, 46, 47, 49, 48, 49, 48, 50, 49, 49,
47, 44, 47, 46, 48, 46, 46, 44, 44, 45, 45, 43, 41, 41, 40, 39, 39, 38, 38, 37, 38, 37, 36, 36, 36, 35, 36, 38, 37, 37, 39, 39, 38, 40, 40, 41, 42, 42, 44, 46, 47, 47, 48, 46, 48, 49, 48, 50, 49, 49, 47, 48,
46, 46, 47, 47, 46, 45, 46, 46, 46, 45, 45, 44, 44, 42, 40, 40, 41, 37, 39, 39, 38, 38, 37, 38, 38, 37, 39, 37, 38, 38, 39, 39, 40, 41, 42, 40, 42, 45, 44, 47, 44, 49, 47, 48, 48, 46, 48, 49, 48, 48, 46, 48,
47, 46, 48, 47, 47, 47, 45, 45, 43, 45, 45, 43, 42, 42, 45, 42, 42, 39, 39, 39, 39, 40, 39, 40, 38, 38, 37, 39, 39, 39, 39, 41, 39, 40, 42, 42, 45, 46, 44, 45, 47, 47, 47, 50, 48, 47, 50, 49, 48, 47, 47, 51,
47, 47, 46, 45, 47, 45, 46, 45, 45, 46, 45, 44, 46, 43, 45, 44, 43, 41, 40, 41, 41, 39, 41, 39, 39, 41, 39, 39, 41, 42, 41, 41, 42, 43, 43, 43, 45, 45, 46, 47, 49, 47, 48, 47, 47, 48, 48, 50, 48, 49, 47, 46,
45, 44, 47, 45, 48, 46, 45, 45, 47, 44, 44, 44, 43, 44, 42, 42, 43, 42, 42, 42, 40, 41, 40, 40, 40, 41, 40, 42, 42, 41, 40, 43, 43, 42, 45, 44, 45, 46, 46, 47, 48, 48, 47, 50, 48, 49, 48, 51, 47, 48, 46, 49,
45, 46, 48, 48, 46, 46, 46, 47, 46, 45, 45, 47, 44, 45, 43, 43, 42, 41, 43, 43, 43, 43, 41, 42, 41, 42, 41, 44, 42, 44, 42, 42, 43, 45, 44, 43, 44, 46, 48, 47, 48, 48, 48, 48, 48, 48, 49, 49, 47, 48, 45, 50,
45, 46, 45, 46, 45, 44, 46, 45, 45, 46, 47, 46, 47, 44, 44, 45, 44, 42, 42, 43, 43, 43, 43, 43, 42, 41, 42, 42, 44, 41, 42, 42, 44, 44, 45, 44, 46, 47, 46, 46, 47, 47, 48, 49, 48, 48, 47, 47, 48, 47, 45, 45,
44, 45, 46, 46, 45, 45, 46, 46, 48, 48, 45, 47, 45, 46, 46, 47, 43, 43, 44, 44, 44, 44, 44, 43, 43, 43, 43, 42, 43, 44, 46, 46, 45, 45, 47, 44, 45, 46, 48, 48, 48, 48, 51, 48, 49, 47, 48, 47, 47, 46, 47, 47,
};
uint32_t ref_transform = 3;
uint32_t grid_width = 52;
uint32_t grid_height = 39;

you will notice some entries with value 31, corresponding to a multiplier of 0.96875 < 1.0 (scroll down a few lines and look at the center of the lines). This behavior is reproducible, I checked that with several other test images.

The python script listed above ensures that all multipliers are greater or at most equal to 32 (=1.0), so there are no bad pixels in this case. Besides this, the results from either lens_shading_analyse.c or the python script are pretty similar.

However, there is a noticeable difference between the default lens shading compensation shipped with the Raspberry Pi distribution and any of the custom lens shading corrections. At the moment, I do not really know what this difference is caused by.

Of course, there are various possible causes. For example, the optical setup I used (overcast sky/gray card) is not bad, but not optimal. In reality, one would not work with only a single image. Furthermore, the test images were taken in automode (autoISO, autoExposure, autoAWB) which certainly has a profound influence on the outcome of such experiments.

So the next steps forward are improvements in the setup. This will include (of course) controlled illumination as well as using an average over a range of images as data input.

One further important improvement is certainly the ability to control and set all relevant camera parameters which define the image pipeline. I can do this currently quite well within the picamera environment, so the next step will be to see if within the picamera environment I can get the ls_table.h correctly into the hardware and have that table actually used.

I am afraid that this will require some further studies of the mmal-layer and how to use it from within python.

I do know the picamera source and how interfacing to mmal is done here, but is there some further information about this available somewhere? Any hint from the community would be appreciated!

Fer
Posts: 35
Joined: Tue Feb 21, 2017 10:27 am

Re: Custom lens shading tables

Sat Nov 18, 2017 11:53 pm

cpixip wrote:
Sat Nov 18, 2017 12:39 pm
The left half of the image shows the result with the lens shading raspistill, the right side the result obtained with the original raspistill.

Clearly, the custom lens shading works in principle, but it is clear that there's still some progress possible.
Well, actually, to me it looks like lens shading/color shift is not corrected at all with the recompiled raspistill (with custom lens shading enabled).
Or maybe peripheral illumination is corrected, but not color shift: there's a very strong green/magenta radial cast on the left image. :|

cpixip
Posts: 25
Joined: Mon May 01, 2017 7:56 am

Re: Custom lens shading tables

Sun Nov 19, 2017 12:50 pm

Fer wrote:
Sat Nov 18, 2017 11:53 pm
cpixip wrote:
Sat Nov 18, 2017 12:39 pm
The left half of the image shows the result with the lens shading raspistill, the right side the result obtained with the original raspistill.

Clearly, the custom lens shading works in principle, but it is clear that there's still some progress possible.
Well, actually, to me it looks like lens shading/color shift is not corrected at all with the recompiled raspistill (with custom lens shading enabled).
Well, there are still some things with the custom lens shading which do not work as desired. But generally, the thing does what it claims to be able to.

Here's another example, a comparison between an image taken with a Schneider Componon-S 2.8/50, with and without lens shading compensation. To the right is the uncompensated image, to the left the current result of the lens shading compensation:
comparsion.jpg
comparsion.jpg (45.95 KiB) Viewed 2182 times
Clearly, there is an improvement!

In yet another test, I ran the procedures with a gray background. Now, if I display the values recorded along the horizontal center line of the raw image, I get the following plot
horizontal_center_line_raw.png
horizontal_center_line_raw.png (71.03 KiB) Viewed 2182 times
which shows nicely the different variations of the RGB-signals from center to edge of the image. (Dropped one of the green channels as they are essentially identical in value.)

Performing a lens shading compensation, I arrive at the follow result for the jpg which the camera outputs:
horizontal_center_line_compensated.png
horizontal_center_line_compensated.png (74.35 KiB) Viewed 2182 times
This is, admittedly, not perfect. If so, all three lines would be just horizontal lines. There seems to be some sort of under-compensation towards the edges of the image, whereas the center portion looks just fine.

Currently, I have no idea where this comes from. Might be a problem with the multipliers calculated, not properly matching the lens shading computation done in the hardware. The effect is present independent of the way of calculation for ls_table.h (above python-script or lens_shading_analyse.c). It might be some alignment issue, which is easily introduced in GPU-processing. I noticed a small horizontal line of color variation right at the lower edge of the compensated image, approximately half a pixel or one pixel wide vertically. "Pixel widths" referring here to the ls_table.h resolution. In the real image, the color variation is larger, pixel-wise. The variation is probably only affecting the Red channel - but I don't know yet for sure.

There might be other causes - I will do further experiments to find out more...

Fer
Posts: 35
Joined: Tue Feb 21, 2017 10:27 am

Re: Custom lens shading tables

Sun Nov 19, 2017 3:00 pm

Thank you very much, it's much clearer now.

By the way I have a similar issue: I use the Camera Board with an enlarger lens, and stock lens shading correction (and sharpness! There's sharpness correction too) kicks in, giving that green center/magenta border cast you show in your Componon sample.
Problem is, I did not succeed in disabling correction, so now I'm applying post-processing to correct the correction (with obviously lackluster results...).

Return to “Camera board”

Who is online

Users browsing this forum: No registered users and 7 guests