Whitebalance experiment (WIP)

Upcoming mother of all image editors

Whitebalance experiment (WIP)

Postby JEL » Mon Apr 13, 2015 11:00 pm

I tried to see if I could change Oscar's "Gaussian redistribution of histogram" code into one that whitebalances the red and blue channel after the green.

It doesn't quite work yet, because I have virtually NO clue about what I'm doing in CPP :roll:

Anyway, the idea is to get the averages of the 3 color-channels and then push or pull the red and blue in the direction of the green at each single level (from 0 to 255).
So kind of like a whitebalance that does it at 256 different stages.

The idea I have tried to see if I could program into the cpp-script, is one that tries to factor in the difference between the green to red and green to blue, and then move the red and blue with their individual factors. This is to avoid the image being perfectly whitebalanced (and thus turning grey-scale)

For example; at the green value of 100; if the red is between 50 and 60 (average 55) = add 45 to red channel's original value (so it becomes 95 to 105, IE a new average value of 100 while still leaving in the differences so that color remains)

I don't know if such a whitebalance idea will even work, but here's the code I have so far (which is really not working too well, but does indeed do something along what I'm trying)
I'm posting it here if somebody with CPP-skills wants to have a go at it.


Code: Select all
//##NAME:Redistribute
//##DESCRIPTION:Gaussian redistribution of histogram
//##INPUTS:1
//##VAR1:60
//##VAR1_NAME:Sigma (gauss width)
//##VAR2:50
//##VAR2_NAME:Mean (shift)

// Please retain this intro-comment
// This software is FREE!!  Open Source software, do what you want with it, open to the community, etc.....
// GNU General Public License
// see http://www.gnu.org/licenses/gpl-2.0.html

// Inspired by: Fred Weinhaus has developed a script, called "redist" for imagemagick
// It redistributes the histogram of an image into a uniform distribution, while appling the same change to all color channels equally.

// modified and adapted for Photo-Reactor Script-block by Oscar

int SHOWGRAPH = 0;

void countRedist(image &img)
{

   int width = img.width;
   int height = img.height;

   ////////////////// MAX
   float maximum = 1;
   float maximumgauss = 1;
   
   ////////////////// MIN
   int minimum = 65535;
   float minimumgauss =65535;

   //////HISTOGRAM
   int[] histogram(256);
   int[] countR(256);
   int[] countG(256);
   int[] countB(256);
   int[] valueR(256);
   int[] valueG(256);
   int[] valueB(256);

   for (int z=0; z< 256; z++)
      histogram[z] = 0;

   for (int zR=0; zR< 256; zR++)
      countR[zR] = 0;
   for (int zG=0; zG< 256; zG++)
      countG[zG] = 0;
   for (int zB=0; zB< 256; zB++)
      countB[zB] = 0;

   for (int zvR=0; zvR< 256; zvR++)
      valueR[zvR] = 0;
   for (int zvG=0; zvG< 256; zvG++)
      valueG[zvG] = 0;
   for (int zvB=0; zvB< 256; zvB++)
      valueB[zvB] = 0;


   for (int y=0; y<height; y++)
   {
      for (int x=0; x<width; x++)
      {

         // one way to get color from pixel
         pixel color = img.Pixel(x,y);

         // Average
         int gray = (color.r+color.g+color.b)/3;
         // luminosity
         // int gray = (0.21*color.r+0.72*color.g+0.07*color.b);
         if (gray>255) gray =255;

         histogram[gray] +=1;

         if (histogram[gray]>maximum) maximum = histogram[gray];

         int G = color.g;
         if (G>255) G =255;
         countG[G] +=1;
         valueG[G] = valueG[G] + G;
         //if (countG[G]>maximum) maximum = countG[G];

         int R = color.r;
         if (R>255) R =255;
         countR[R] +=1;
         //valueR[R] = valueR[R] + R;
         valueR[G] = valueR[G] + R;
         //if (countR[R]>maximum) maximum = countR[R];

         int B = color.b;
         if (B>255) B =255;
         countB[B] +=1;
         //valueB[B] = valueB[B] + B;
         valueB[G] = valueB[G] + B;
         //if (countB[B]>maximum) maximum = countB[B];

      }
   
   }

   float fx = width/256.0;

// display gray histogram
// this is for testing purposes


if (SHOWGRAPH>0)
   for (int x=0; x<255; x++)
   {


      float nhisto = (histogram[x]/maximum);

      int ypos = nhisto*height;
      if (ypos>height-1) ypos = height-1;

     
      for (int y=0; y<ypos; y++)
      {
         pixel color = img.Pixel(x*fx, height-y-1);

         img.SetRGB(x*fx, height-y-1, (255+color.r)/2, (255+color.g)/2, (255+color.b)/2);
         img.SetRGB(x*fx+1, height-y-1, (255+color.r)/2, (255+color.g)/2, (255+color.b)/2);
      }

      img.SetRGB(x*fx, height-ypos-1, 0, 0, 0);

   }

   //make cummulative
   
   int cumulative = 0;

   for (int x=0; x<256; x++)
   {

      cumulative += histogram[x];
      histogram[x] = cumulative;

   }

   maximum = histogram[255];
   minimum = 0;

   //enerate gaussian distribution graph
   int[] gaussian(256);

   for (int z=0; z< 256; z++)
      gaussian[z] = 0;


   double temp;

   int KSize = (VAR2*2.55); // mean
   float sigma  = VAR1;

   for (int j= 0; j< 256; j++)
   {
       temp = (j-KSize)/sigma;
       gaussian[j] = 256* (pow(2.71828,-temp * temp / 2.0));

       if (gaussian[j]>maximumgauss) maximumgauss = gaussian[j];

      //traceint(gaussian[j]);

   }
 

// display gaussian
// this is for testing purposes

if (SHOWGRAPH>0)
   for (int x=0; x<255; x++)
   {


      float nhisto = (gaussian[x]/maximumgauss);

      int ypos = nhisto*height;
      if (ypos>height-1) ypos = height-1;

     
      for (int y=0; y<ypos; y++)
      {
         pixel color = img.Pixel(x*fx, height-y-1);

         img.SetRGB(x*fx, height-y-1, color.r/2, color.g/2, (255+color.b)/2);
      }

      img.SetRGB(x*fx, height-ypos-1, 0, 0, 0);

   }



   cumulative = 0;

   for (int x=0; x<256; x++)
   {

      cumulative += gaussian[x];
      gaussian[x] = cumulative;

   }

   maximumgauss = gaussian[255];
   minimumgauss = 0;



   // move the normal histogram to the gaussian historgram


   int[] clut(256);

   int[] clutR(256);
   int[] clutG(256);
   int[] clutB(256);

    int k=0; 
    for (int j=0; j<256; j++ )
    {
        while ( k<255 && (gaussian[k]/maximumgauss <= histogram[j]/maximum ))
        {
            k++;
        }

          clut[j] = k;
        //traceint (clut[j]);

          clutG[j] = (valueG[j] / (countG[j] + 0.01));

          //clutR[j] = clutG[j];
          //clutB[j] = clutG[j];
          clutR[j] = clutG[j] + (clutG[j] - (valueR[j] / (countG[j] + 0.01)));
          clutB[j] = clutG[j] + (clutG[j] - (valueB[j] / (countG[j] + 0.01)));
          //clutR[j] = (valueR[j] / (countG[j] + 0.01));
          //clutB[j] = (valueB[j] / (countG[j] + 0.01));
     }

   // now we have lut
   // apply it to the image

   int r,g,b;

   for (int y=0; y<height; y++)
   {
      for (int x=0; x<width; x++)
      {

         // one way to get color from pixel
         pixel color = img.Pixel(x,y);

         r = clutR[color.r];
         g = clutG[color.g];
         b = clutB[color.b];
         img.SetRGB(x, y, r, g, b);

      }
   
   }


// display curve
// this is for testing purposes

if (SHOWGRAPH>0)
   for (int x=0; x<255; x++)
   {


      float nhisto = clut[x]/255.0;

      int ypos = nhisto*height;
      if (ypos>height-1) ypos = height-1;

     
      img.SetRGB(x*fx, height-ypos-1,255, 0, 0);
      img.SetRGB(x*fx+1, height-ypos-1,255, 0, 0);

   }

}



void main()
{
   // get the image from input socket
   image img(INPUT);
   
   
   // sample function call
   countRedist(img);
   
   
   // send the image to output
   img.SetOutput();
   
}
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm

Re: Whitebalance experiment (WIP)

Postby JEL » Tue Apr 14, 2015 7:23 am

Here's an example-image showing the current status of the script.

The original image is on the left
(Very yellow/orange in tone. It was taken without flash at night under what I guess is a sodium street-light. Camera was on auto-WB. The blue sky-color is accurate, so a classic 'nightmare-to-whitebalance' image :) )
The vector-scope (center) is biased toward orange.

The image on the right is corrected by the script I posted in the first post.
It's a bit of hit'n'miss in regard to which images it works on and which images it messes up. If you look closely you can see it has introduced a few cyan hot-pixels on the bottom-right. It seems it does that on areas (or values rather) where there is no green. On some images it does a brilliant job on correcting WB, but on most it does introduce artifacts and 'solarized' color-gradients.
The vector-scope (far right) is very balanced.

I've noticed it comes close to Andy's WB-plugin, except; on the images where it does work it does so without over-exposing (the 'grey-world' option of Andy's plugin is shown with a monitor-node, and is much brighter with blown highlights, but is clear of artifacts. All other algorithms in Andy's plugin doesn't correct this image (so like I said; a perfect image to really stress-test WB algorithms, as Andy's plugin is generally very good :) ))
The vector-scope (far left) is balanced, but spikes between yellow and blue.

I also noticed that this script WB in a way that seems more similar to how film is WB (I ran it on some test-images I have to directly compare film with digital, and this script could get the WB of the digital images closer to the film WB, than any other method I've seen so far. But alas ONLY on some images, but on those it works really great. On most images, as I said, it introduces color-artifacts and gradients. I'm thinking the sampling is where the solution is; in this script each single value from 0 to 255 is sampled, which perhaps is too much. Maybe an interpolation between fewer sample-points would work better. Or maybe the math in the script is not optimized well enough. I'm not sure, but the script is a step toward a type of WB that I think could become very generally useful if it could be improved to avoid introducing those artifacts I mentioned. I'll tinker with it some more, and see if I can get anywhere with it, but if anybody wants a go at it, please do :) CPP is NOT my skill :lol: (Oscar's histogram-stretch code is still in there, because I haven't taken the time to clean it up (I was using it to get started, so I didn't want to remove anything until I was sure I wouldn't need it for this script. So that's why it's so messy), but it isn't being used by the script as it is just now))
Attachments
JEL_WBbyCurvesWIP.jpg
JEL_WBbyCurvesWIP.jpg (244.46 KiB) Viewed 7330 times
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm

Re: Whitebalance experiment (WIP)

Postby andydansby » Sat Apr 18, 2015 7:59 am

Could you post a larger size of the test image. I used a screen shot grabber to get the image, but it's pretty small. The results are very nice indeed.

### I'll tinker with it some more, and see if I can get anywhere with it, but if anybody wants a go at it, please do :) CPP is NOT my skill :lol:

Actually, you are programming in a CPP code. There are not too many differences between what you are doing and what I am doing.

The CPP plugin in PR is very close to me programming in Visual Studio. In fact, I only dabble in CPP, (how I allocate and destroy my dynamic arrays). Instead of using Malloc and Dealloc, I prefer to use
float* Array= new float[SIZE]; and delete [] Array; as I find it not as cumbersome.

Everything else is in C. I never really caught on to CPP, probably for the silliest of reasons (I mistrust templates and overloading for the most part).

As for your code, if you sat down in front of Visual Studio and had you code, you could almost directly(with some changes) port your code directly to C/C++, it's that close.

For Example your standard loop in the PR CPP plugin is

Code: Select all
for (int y=0; y<height; y++)
{
   for (int x=0; x<width; x++)
   {
      // one way to get color from pixel
      pixel color = img.Pixel(x,y);
   }
}


and mine would be
Code: Select all
for (int y=0; y < nHeight; y++)
{
   for (int x=0; x < nWidth; x++)
   {
      int nIdx1 = x * 4 + y * 4 * nWidth;
      
      int red = pBGRA_in[nIdx1 + CHANNEL_R];
      int green = pBGRA_in[nIdx1 + CHANNEL_G];
      int blue = pBGRA_in[nIdx1 + CHANNEL_B];
   }
}


The only real difference is that I am using an index. The index is not too difficult, it just takes x and multiplies it by the number of color channels, does the same for y and multiplies it by the width of the image. It could also be written out as.

Code: Select all
int nIdx1 = (x * 4) + (y * 4) * nWidth;

or
Code: Select all
int nIdx1 = (x + y) * 4 * nWidth;


If you like, drop me a PM with your email and I can email a complete Visual Studio project with some quick instructions on how to load the project and compile it (I use VS2012 express, which is free from Microsoft). Once you have that down, then you can compile your code which is magnitudes faster and allows you to create even fancier effects. My SDK guide, beginning and intermediate guides will make sense to you as well.


Ps, talking about my intermediate guide, I have updated it to show a floating point median with different types of sorting routines.

Andy Dansby
andydansby
 
Posts: 160
Joined: Fri Oct 29, 2010 6:00 am
Location: Syracuse, NY

Re: Whitebalance experiment (WIP)

Postby JEL » Wed Apr 22, 2015 6:42 pm

andydansby wrote:Could you post a larger size of the test image.


Included (had to recompress it a bit to get below a megabyte)

andydansby wrote:Actually, you are programming in a CPP code. There are not too many differences between what you are doing and what I am doing.


The difference between you and I, Andy, is that I have almost no idea what I'm doing when coding in CPP :)
I just go by trial'n'error until I arrive at something useful. And when working like that, the script-node is ideal because you get instant feed-back. If I had to compile the code in MSVC and re-load it in PR every time I changed something... well, it's only useful if you know at least a bit about what you're doing. Where you are able to spot what you need to do to get your code working, I'm really coding in the blind, so there's a major difference in what you and I do :)
The reason I've never taken the time to really learn CPP is that I honestly don't like the language. It's never 'clicked' with me, for some reason. I do know it's powerful, but the few times I've tried MSVC it's always ended in more frustration than fruition.
Attachments
WBtest.JPG
WBtest.JPG (1016.25 KiB) Viewed 7267 times
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm

Re: Whitebalance experiment (WIP)

Postby andydansby » Sat Apr 25, 2015 6:16 am

Jel:

Sorry for being so late for this reply, I've been experimenting with a median function that is driving me crazy. I've noticed something interesting about the output of your script. While the image at first looks like there are errors (those blue pixels on the shirt and on the pathway to the right of the subject. When I use the 1:1 view of the image, those errors disappear. I do not know why, other than the image size being approximated in PR.

When I perform an output of the image, I find no error pixels, your white balance is working great! Nice effect.

Here is the output I get when I fully process the image.
jel whitebalance.jpg
jel whitebalance.jpg (756.91 KiB) Viewed 7236 times


I will sometime soon look at your code in more detail, it might just be a simple clamping function in your code, meaning that certain values just go out of range of 0-255. I might be completely wrong about that one.

Andy Dansby
andydansby
 
Posts: 160
Joined: Fri Oct 29, 2010 6:00 am
Location: Syracuse, NY

Re: Whitebalance experiment (WIP)

Postby JEL » Sat Apr 25, 2015 11:27 pm

Oh my :) I hadn't made it that far :)
I missed the difference between the preview and the export, but yes you're right; the hot-pixel error seem to go away on the exported image.

until... I ran tests on a number of other images...

The error can be made to appear on exported images, but only on those where it appears massively on the preview.

I've attached an image where the error makes it through to the exported image also (the attached image is the clean test-image with no effect applied, so you can download it to do your own tests if you like). So there's still some work needed to this code for sure, but I like that it's suddenly a bit more useful than I previously thought :)

The funny thing is that it doesn't appear to be linked to white pixels (the attached image doesn't have any that maxes out), so I'm guessing it must be pixels that max out at the same value (like when all 3 are at 240 for example). It could be that the code gets confused when trying to match the red and blue to a green that has the same value (I haven't checked what the resulting math from such a match (in all 3 color-channels) becomes, but since the hot-pixels turn to a full cyan, yellow or magenta it would seem like it is related to bad math somehow)
I'm guessing the preview, being a smaller version of the full image, has more pixels that land at the same rgb-value and that that might be why it shows more errors.

Great find by you Andy! :) I completely missed that bit myself.

EDIT: I've pin-pointed a situation where hot-pixels are predictably introduced (although I don't know if it's the only situation): Whenever the red or blue channel are maxed out relative to the green channel, hot-pixels will occur. So if the green max is 240, hot-pixels will occur where-ever the red or blue is above 240.
Attachments
10master.jpg
10master.jpg (838.32 KiB) Viewed 7228 times
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm

Re: Whitebalance experiment (WIP)

Postby JEL » Mon May 02, 2016 7:44 am

update:

I have cut out all the 'fluff' from the code I posted at the start of this thread, so it now only has what is actually used for the white-balance effect.

It's fundamentally the same code though, so all the errors discussed are still present.

I'm still not sure exactly what goes wrong, but obviously some of the calculations need to be different for this white-balancing effect to work completely without error.




Code: Select all
//##NAME:Redistribute
//##DESCRIPTION:Gaussian redistribution of histogram
//##INPUTS:1

// Please retain this intro-comment
// This software is FREE!!  Open Source software, do what you want with it, open to the community, etc.....
// GNU General Public License
// see http://www.gnu.org/licenses/gpl-2.0.html

// Inspired by: Fred Weinhaus has developed a script, called "redist" for imagemagick
// It redistributes the histogram of an image into a uniform distribution, while appling the same change to all color channels equally.

// modified and adapted for Photo-Reactor Script-block by Oscar



void countRedist(image &img)
{

   int width = img.width;
   int height = img.height;



   //////HISTOGRAM
   int[] countR(256);
   int[] countG(256);
   int[] countB(256);
   int[] valueR(256);
   int[] valueG(256);
   int[] valueB(256);


   for (int zR=0; zR< 256; zR++)
      countR[zR] = 0;
   for (int zG=0; zG< 256; zG++)
      countG[zG] = 0;
   for (int zB=0; zB< 256; zB++)
      countB[zB] = 0;

   for (int zvR=0; zvR< 256; zvR++)
      valueR[zvR] = 0;
   for (int zvG=0; zvG< 256; zvG++)
      valueG[zvG] = 0;
   for (int zvB=0; zvB< 256; zvB++)
      valueB[zvB] = 0;


   for (int y=0; y<height; y++)
   {
      for (int x=0; x<width; x++)
      {

         // one way to get color from pixel
         pixel color = img.Pixel(x,y);


         int G = color.g;
         if (G>255) G =255;
         countG[G] = countG[G] + 1;
         valueG[G] = valueG[G] + G;

         int R = color.r;
         if (R>255) R =255;
         countR[R] = countR[R] + 1;
         valueR[G] = valueR[G] + R;

         int B = color.b;
         if (B>255) B =255;
         countB[B] = countB[B] + 1;
         valueB[G] = valueB[G] + B;

      }
   
   }






   int[] clutR(256);
   int[] clutG(256);
   int[] clutB(256);

    for (int j=0; j<256; j++ )
    {

            //find green average
          clutG[j] = (valueG[j] / (countG[j] + 0.01));

            //calculate red and blue relative to green average
          clutR[j] = clutG[j] + (clutG[j] - (valueR[j] / (countG[j] + 0.01)));
          clutB[j] = clutG[j] + (clutG[j] - (valueB[j] / (countG[j] + 0.01)));
     }

   // now we have lut
   // apply it to the image

   int r,g,b;

   for (int y=0; y<height; y++)
   {
      for (int x=0; x<width; x++)
      {

         // one way to get color from pixel
         pixel color = img.Pixel(x,y);

         r = clutR[color.r];
         g = clutG[color.g];
         b = clutB[color.b];
         img.SetRGB(x, y, r, g, b);

      }
   
   }




}



void main()
{
   // get the image from input socket
   image img(INPUT);
   
   
   // sample function call
   countRedist(img);
   
   
   // send the image to output
   img.SetOutput();
   
}
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm

Re: Whitebalance experiment (WIP)

Postby JEL » Thu Jul 07, 2016 10:21 pm

I've created a small script that samples the average global color of an image, which can then be used in a flow to remove global color-cast.

Inside PR everything works, but when I export anything exported by this script I get a different color. I don't know if it's some kind of unknown bug in PR, or if it's something with my script.

Odd thing is, the color exports correctly when using the export flow-snapshot (see attached image)
But whenever I do an actual image-export, the color is wrong.
A mystery :/

Here's the code, and the flow can be grabbed from the attached image (the script must be copied from here and inserted in the script-block after you've loaded the flow, since it doesn't get saved within the jpeg image)

Am I the only one who gets this error, or ??

Anyway, the concept is to whitebalance the image by removing the global color-cast. Instead of my script, a massive blur-node can also be used (that will serve more like local-whitebalancing (a bit more like how the human vision works :) ), sometimes better sometimes worse)


Code: Select all
//A sample Reactor Script - desaturates image
//##NAME:Find average color
//##DESCRIPTION:Useful to remove global color-cast
//##INPUTS:1


void desaturate(image &img)
{
     int width = img.width;
   int height = img.height;
   
   int Total_red = 0;
   int Total_green = 0;
   int Total_blue = 0;

   int Total_pixels = height * width;

   for (int y=0; y<height; y++)
   {
      for (int x=0; x<width; x++)
      {
   
         // one way to get color from pixel
         pixel color = img.Pixel(x,y);
         
         // average the colors
         Total_red = Total_red + color.r;
         Total_green = Total_green + color.g;
         Total_blue = Total_blue + color.b;
         
         
      }
   
   }

   for (int y=0; y<height; y++)
   {
      for (int x=0; x<width; x++)
      {
         // safest and fastest way to set color to pixels (clamps the value to 0..255)
         img.SetRGB(x, y, Total_red / Total_pixels, Total_green / Total_pixels, Total_blue / Total_pixels);
      }
   
   }

}



void main()
{
   // get the image from input socket
   image img(INPUT);
   
   
   // sample function call
   desaturate(img);
   
   
   // send the image to output
   img.SetOutput();
   
}
 
Attachments
AutoColorCastRemover.jpg
AutoColorCastRemover.jpg (129.47 KiB) Viewed 6398 times
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm

Re: Whitebalance experiment (WIP)

Postby JEL » Tue Feb 21, 2017 5:59 pm

Found a different way to sample the average color-cast and remove it :)
PhotoReactor is the best software :)
Attachments
colorcastremover.jpg
colorcastremover.jpg (394.82 KiB) Viewed 2238 times
DAP (AOPs): http://jelstudio.dk/DAP/
PhotoReactor (flows, effects and scripts): http://jelstudio.dk/PhotoReactor/
JEL
 
Posts: 294
Joined: Fri Jan 06, 2012 9:35 pm


Return to Photo Reactor

Who is online

Users browsing this forum: Bing [Bot] and 1 guest