Saturation and Vibrance masks

Here everybody can post his problems with PhotoLine
cathodeRay
Mitglied
Beiträge: 151
Registriert: So 15 Nov 2015 12:37

Saturation and Vibrance masks

Beitrag von cathodeRay »

While working on the image in the IR Vision post, I wanted to add a saturation/vibrance mask, only to find I had inadvertently trodden on a can of worms. Every technique I tried (n = 6 or more) produced a different result (I won't list them all at this stage, if ever, because some of them are clearly bonkers), and none of them gave correct results, generally tested with the eyedropper/Picture info panels: the luminance value of the grey scale saturation mask should be (or be very close to) the saturation value in the full image at that pixel. It soon became apparent that even basic definitions and maths weren't universally agreed, which generally it seems to me they should be. So I want to try to tie down some starting conditions, which I may or may not have got right.

The saturation we are talking about is the one reflected in the S in HSB/HSV (HSB = HSV). The S in HSL is conceptually and mathematically different.

S in HSB is only and ever derived by calculation from RGB values, and the formula is S = (max(r,g,b)-min(r,g,b))/max(r,g,b)

A vibrance mask is an inverted saturation mask. Inversion is mathematically 255 - (pixel value) (in this case, pixel value = S)

So: load up a red gradient image where the S goes from 255 to 0 (and nothing else changes) and then load up FilterMeister and have the following in the filter (only key bit shown):

R = ((max(r,g,b)/255) - (min(r,g,b)/255))/(max(r,g,b)/255); <= should be the saturation mask in the red channel
G = 255 - (((max(r,g,b)/255) - (min(r,g,b)/255))/(max(r,g,b)/255)); <= should be the vibrance mask in the green channel
B = 255; <= just get the blue channel to something

But no, it didn't work...

Or rather, it did, briefly, I think, or maybe I blinked, on one or the other channel, and then stopped working...

I also tried hue/sat/colour/luminosity blending with a grey layer - no dice.

Any idea why it isn't working as expected? Are my basic assumptions/definitions wrong?

Thanks in advance for any , err, light, that can be shed on this.

cathodeRay
bkh
Betatester
Beiträge: 3674
Registriert: Do 26 Nov 2009 22:59

Re: Saturation and Vibrance masks

Beitrag von bkh »

cathodeRay hat geschrieben:S in HSB is only and ever derived by calculation from RGB values, and the formula is S = (max(r,g,b)-min(r,g,b))/max(r,g,b)
I guess your problem is that the saturation you get by this formula is in the [0,1] range, so you'll have to multiply by 255 to obtain something visible. Moreover, saturation in your formula is undefined for solid black, you'll need a case distinction to handle that. Keep in dmind that you'll probably have to work in 16 bit or 32 bit mode to avoid rounding or clipping errors when computing the saturation.

So your formulas should read

R = ((max(r,g,b) - min(r,g,b))/(max(r,g,b)/255); <= should be the saturation mask in the red channel
G = 255 - (max(r,g,b) - min(r,g,b))/(max(r,g,b)/255); <= should be the vibrance mask in the green channel
or, simpler,
G = min(r,g,b)/(max(r,g,b)/255)
B = 255; <= just get the blue channel to something

As they stand, the formulas won't clip but will not work in 8 bit mode because max/255 will be 0 or 1.

Cheers

Burkhard.
cathodeRay
Mitglied
Beiträge: 151
Registriert: So 15 Nov 2015 12:37

Re: Saturation and Vibrance masks

Beitrag von cathodeRay »

Thanks Burkhard, that sort of makes sense, not because you've explained it badly but because I am still struggling with what saturation really means.

Taking one step back, the idea is to get a self feathering mask, just as luminosity masks are, but for colourfulness rather than lightness. Already I am in a mire of ambiguous and confusing terminology! There are intuitive meanings and mathematical meanings, and they may or may not match.

Take an under and an over exposed photo (or under/over exposed regions on the same photo - which is why we want a mask in the first place, to work selectively on the deficient regions). We can get luminosity/luminance out of the way quickly enough: under exposed regions = low luminance, over exposed regions = high luminance and the L value in say Lab colour behaves accordingly, so we can use a luminance mask to select the under or over exposed regions.

Perceptually, I suggest most of us understand saturation to describe the intensity (in the lay sense of the word; unfortunately this word is used in mathematical models to mean something else) or vividness of colour. A highly saturated region will have 'pop' and jump off the page/screen, while a low saturation image will appear dull and lifeless. The key concept here, noting the opposing words vividness/lifeless seems to be the 'aliveness' of the colour. Back in the real world, we want a mask that selects the dull, lifeless parts of the image, so we can pep them up a bit, without overdoing - making garish - the already alive enough parts of the image. Such a mask is usually called a vibrance mask, even if it is actually selecting areas of low vibrance.

Since conceptually we have equated vibrance with saturation, we might hope that we can get a vibrance mask either by getting a high saturation mask and inverting it, or by selecting areas of low saturation.

The problem it seems to me is that calculated, mathematical numbers for saturation do not correlate with what we perceive as saturation. The formula is set up to discern the range or spread in the RGB values, scaled by the absolute value of the highest RGB value. The bigger the range, relative to the max value, the greater the saturation. (Comceptually, something similar happens in Lab colour: the further a and b are from zero, ie the bigger the range, the more saturated the colour).

But, it seems to me, the RGB => HSB maths breaks down when compared to what we perceive. Take a web page set up with RGB sliders and panels showing the colour, along with HSB values (more terminology hell - value also has a mathematical meaning, as in HSV, which is the same as HSB). Just such a page can be found here.

If we set the sliders to 255,0,0 we get a fully saturated red. The min-max range is the maximum it can be (255); dividing by the max value gives us 1, or 100%. So far so good.

Now let us move both the G and B sliders to 230. In a sort of way, we have added a lot of light grey. The colour panel shows a rather washed out red colour - not very saturated, the distance between rgb(min) and rgb(max) is small and the S value is correspondingly low, at 10%. So far so good.

Now let us set the sliders at 25,0,0. On my (calibrated, at least it's supposed to be) monitor, I get a very dull almost black colour. Whatever it is, perceptually it is not saturated - I can hardly make out any colour at all. No way can it be said to be vivid. Yet the S value is 100%! This happens, of course, because S = the min-max range divided by ie relative to the max value. The range is low, but so too in the max value, so we get a high S value, even when our eyes see something distinctly unsaturated. Nor for that matter do the numbers 'stack up': a colour that is only 10% (25/255) red is nonetheless said to be 100% saturated (although might counter that by saying that although it hasn't got much colour, all the colour it has got is red, therefore it is very saturated, but I am not sure I buy that argument at all).

In conclusion, it seems that, given saturation is mathematically range (vividness) relative to brightness, if breaks down as a useful measure at low brightness levels. I suspect it is a specific case of the more general one where relative measures become unhelpful when the underlying whatever it is we are looking at is itself at low levels (you get misleadingly big/impressive numbers).

cathodeRay
bkh
Betatester
Beiträge: 3674
Registriert: Do 26 Nov 2009 22:59

Re: Saturation and Vibrance masks

Beitrag von bkh »

cathodeRay hat geschrieben:Now us set the sliders at 25,0,0. On my (calibrated, at least it's supposed to be) monitor, I get a very dull almost black colour. Whatever it is, perceptually it is not saturated - I can hardly make out any colour at all.
I think you are mixing up saturation and what's usually called chroma (as in LCh), although neither term is used consistently (saturation in PL's HIS is more like colour in this respect, if I remember correctly). If you need a simple measure of chroma, just use max(R,G,B)-min(R,G,B). Of course, Lab chroma C=√(a²+b²) is preferable if one needs to match the visual effect.

Cheers

Burkhard.
cathodeRay
Mitglied
Beiträge: 151
Registriert: So 15 Nov 2015 12:37

Re: Saturation and Vibrance masks

Beitrag von cathodeRay »

Thanks again Burkhard. I think you are right - chroma is what I'm after. It seems to consistently (as in consistent with perception) measure distance from greyness. Saturation, as it is standardised for brightness, as I noted earlier, starts to fail at low light levels, assigning high saturation levels to colours that in fact have very little colour in them.

In further support of this, I came across this website/plugin. Of particular interest is the strip of images just below the fold which shows the application of various colour models with, as the author notes, those using chroma getting the closest to perceived saturation. In fact, the page is a useful summary of what's wrong with using saturation. Like you, he favours Lab type spaces, which seems to make sense to me. I guess you square and then take take the root of the a and b values to deal with negative a and b values, and so get a useful measure of of distance from zero, ie distance from grey, unstandardised for brightness.

So, playing around in FilterMeister, I put in the following code (very verbose I know but it's for testing/debugging):

Code: Alles auswählen

%ffp

ctl(0):"Black Point",range=(0,255),val=0 
ctl(1):"White Point",range=(0,255),val=255 

ForEveryPixel: {

int r1,g1,b1,a,b2;

r1 = r;
g1 = g;
b1 = b;

a = rgb2lab(r1,g1,b1,1);
b2 = rgb2lab(r1,g1,b1,2);

//Get chroma using square/square root
//R = scl(sqr((a*a) + (b2*b2))/2,ctl(0),ctl(1),0,255);
//G = scl(sqr((a*a) + (b2*b2))/2,ctl(0),ctl(1),0,255);
//B = scl(sqr((a*a) + (b2*b2))/2,ctl(0),ctl(1),0,255);

//Get Chroma using abs
R = scl((abs(a)+abs(b2))/2,ctl(0),ctl(1),0,255);
G = scl((abs(a)+abs(b2))/2,ctl(0),ctl(1),0,255);
B = scl((abs(a)+abs(b2))/2,ctl(0),ctl(1),0,255);

}
And this works, after a fashion. As can be seen, I also tried using abs() to deal with negative numbers (both methods yield similar results). I also divided by two as I had added 2 channels, and added sliders that in effect set the black and white points. I say after a fashion, because while I get a grey image with lightness and darkness in proportion to chroma, it is very flat image, blocky (perhaps because I am using mostly if not all integer maths on an 8 bit image), and the sums clearly aren't coming out right. well, they are coming out 'right' according to their rules, but, for example, the eye dropper tells us that the Lab a and b values in the upper dark background areas of the image hover around 1, yet the chroma channels gets filled with mid grey (image below is after adjusting the B&W points but the pre-udjusted image was also mid grey in the top half) in this area:
scan.jpg
chroma.jpg
Clearly more work to do, but heading in the right direction. Meanwhile, Merry Christmas One and All!

cathodeRay

PS also meant to add that the max(r,g,b) we used earlier might be misleading - according to the FM documentation, the function is max(a,b) so it may just use the r and g, and discard the b, but without throwing an error
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.