Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion.

Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 

Go Back   Doom9's Forum > Capturing and Editing Video > Avisynth Usage

Reply
 
Thread Tools Search this Thread Display Modes
Old 12th April 2010, 16:02   #1  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Selective desaturation in YV12 for given luma range: possible?

I'm trying to find a method of desaturating a YV12 source (PAL DV or FFDShow-HuffYV12) for a defined luma range. Purpose: primarily to be able to selectively desaturate highlights, mid-tones, shadows etc.

I've tried every combination of Levels, MaskTools and Swap (UVtoY etc) I can think of, without success...chroma always gets scrambled (like my brain).

I am sure it can be done (more easily) in RGB, but, if at all possible, I'd like to stay in YV12, without color space conversion.

Seems that the key lies in the ability to apply transformations, for a given pixel, between luma(Y) and chroma (u,v) planes i.e. given transformation A on luma results in transformation B on chroma, whilst not affecting the delicate uv color information in the rest of the image.

Is this possible ?

Cheers.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 12th April 2010 at 16:10.
WorBry is offline   Reply With Quote
Old 12th April 2010, 18:21   #2  |  Link
Didée
Registered User
 
Join Date: Apr 2002
Location: Germany
Posts: 5,389
The "easy-but-complicated" way is to create a luma mask, then merge-in a desaturated copy by that mask.

The "complicated-but-easy" way is to use mt_lutxy, and do the transformation directly.


Code:
source = whatever
pale = source.tweak(sat=0.5)
lmask = source.mt_lut("x 50 < x 100 > | 0 255 ?") # a primitive lut - affect only Y=[50,100], without roll-off)
result = mt_merge(source,pale,lmask,luma=true,Y=2,U=3,V=3) # without roll-off, "lmask.blur(1)" is probably more nice
Code:
source = whatever
source.reduceby2()
result = YtoUV(last,last).mt_lutxy(source,"x 50 < x 100 > | y y 128 - 2 / 128 + ?",Y=4,U=3,V=3)
Those are the black-or-white primitves - either leave a pixel unchanged, or desaturate it by [fixed_value]. Making a soft roll-off for the range boundaries requires only some refinement of the LUTs. Which is left "as an exercise for the reader".
__________________
- We´re at the beginning of the end of mankind´s childhood -

My little flickr gallery. (Yes indeed, I do have hobbies other than digital video!)
Didée is offline   Reply With Quote
Old 12th April 2010, 19:47   #3  |  Link
julius666
Registered User
 
julius666's Avatar
 
Join Date: May 2009
Location: Hungary
Posts: 79
You should try these filters:

Virtualdub filters:
http://fdump.narod.ru/rgb.htm
http://fdump.narod.ru/equalizer.htm

Avisnyth filters:
http://expsat.sourceforge.net/
julius666 is offline   Reply With Quote
Old 12th April 2010, 20:56   #4  |  Link
Didée
Registered User
 
Join Date: Apr 2002
Location: Germany
Posts: 5,389
The Vdub's work in RGB, don't they? The task was to avoid that YUV>RGB>YUV conversion ...

ExpSat does not have Luma<>Chroma linking.
__________________
- We´re at the beginning of the end of mankind´s childhood -

My little flickr gallery. (Yes indeed, I do have hobbies other than digital video!)
Didée is offline   Reply With Quote
Old 12th April 2010, 21:57   #5  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Thanks Didee.

Aha, so that's how you create a mt_lut mask for a defined luma range - I'd been trying to figure that out. I'd been making some progress, using a 'difference' approach (mt_makediff and mt_adddiff) with greyscaled mt_luts and blankclips, but I was sure there had to be a more direct solution.

Will experiment some more with your scripts.

Cheers.
__________________
Nostalgia's not what it used to be
WorBry is offline   Reply With Quote
Old 14th April 2010, 04:53   #6  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
@Didee,

OK, I got this far with the second approach:

Code:
clp = last
#
Sat=1.0 # (Default) saturation.  Desat < 1.0 > Increased sat  
Sat= Sat <0.0 ? 0.0 : Sat  
Low= 0 # (Default) lower 'chroma' range point
Low= Low <0.0 ? 0.0 : Low >255 ? 255 : Low
High= 255  # (Default) upper 'chroma' range point
High= High <0.0 ? 0.0 : High >255 ? 255 : High
#
clp.reduceby2()
#
Expr="x "+string(Low)+" < x "+string(High)+" > | y y 128 - "+string(sat)+" * 128 + ?" 
result = YtoUV(last,last).mt_lutxy(clp,expr=Expr,Y=4,U=3,V=3) 
Return(result)
But I cant, for the life of me, figure out how to do the soft roll off at the range boundaries. I imagine it involves extending the mt_lutxy expression line, via an 'otherwise' argument, to include a second expression that defines a progressive increase in saturation (from set SAT value to 1.0) over a suitable overshoot range...maybe (Low - 10) and (High + 10). The only way I can see being able to do that is by changing the first expression to:

Code:
ExprI="x "+string(Low)+" > x "+string(High)+" < | y 128 - "+string(sat)+" * 128 + y ?"
Which leaves an 'otherwise' argument open for a second string expression (replacing the terminal `otherwise y`), but that excludes the actual low and high luma range set points; > = seems to throw up an access violation in the reverse polish notation.

Any chance you could help me out with this? I am trying and learning, honest
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 14th April 2010 at 05:24.
WorBry is offline   Reply With Quote
Old 14th April 2010, 06:20   #7  |  Link
OvejaNegra
ekTOMBE STUDIOS
 
OvejaNegra's Avatar
 
Join Date: Dec 2005
Location: Cuba
Posts: 254
i think i have the same problem, more or less, i have a source with the highlights (or the whites) tinted with cyan.
I tryed with tweak and a color selection but it kills all the similar colors.
Using photshop and desaturating the highlights worked, and now im trying to reproduce that with avisynth.
i will try the Didée scripts to see what happens.
__________________
So, it works or not???
OvejaNegra is offline   Reply With Quote
Old 14th April 2010, 08:52   #8  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,431
Quote:
Originally Posted by WorBry View Post
... changing the first expression to:
Code:
ExprI="x "+string(Low)+" > x "+string(High)+" < | y 128 - "+string(sat)+" * 128 + y ?"
Which leaves an 'otherwise' argument open for a second string expression (replacing the terminal `otherwise y`), but that excludes the actual low and high luma range set points; > = seems to throw up an access violation in the reverse polish notation.
You need ">=", not "> =". Also, by negating the conditions, you need to change the "|" to "&".
But why change the order (except perhaps for clarity)? You could just replace the first 'y' by the new expression.
Gavino is offline   Reply With Quote
Old 14th April 2010, 13:02   #9  |  Link
Didée
Registered User
 
Join Date: Apr 2002
Location: Germany
Posts: 5,389
Code:
c = colorbars().converttoyv12()

c = c.mt_lutspa(relative=true,expr="x 255 *").greyscale()

# [0] <--black--> [p1] <--gradient--> [p2] <--white--> [p3] <--gradient--> [p4] <--black--> [255]

p1 =  40 
p2 =  80 
p3 = 120 
p4 = 160

p1=string(p1) p2=string(p2) p3=string(p3) p4=string(p4)
 
blak = "x "+p1+" < x "+p4+" > | 0 "

wite = "x "+p2+" >= x "+p3+" <= & 255 "

gradlo = "x "+p2+" < 255 x "+p1+" - "+p2+" "+p1+" - / * "

gradhi = "x "+p3+" > 255 1 x "+p3+" - "+p4+" "+p3+" - / - * "

expr = blak + wite + gradlo + gradhi + "x ? ? ? ?"


c.mt_lut(expr).greyscale()

interleave(c,last)
__________________
- We´re at the beginning of the end of mankind´s childhood -

My little flickr gallery. (Yes indeed, I do have hobbies other than digital video!)
Didée is offline   Reply With Quote
Old 14th April 2010, 13:13   #10  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Quote:
Originally Posted by Gavino View Post
You need ">=", not "> =". Also, by negating the conditions, you need to change the "|" to "&".
Ah, yes you're right. Thanks. I thought operators needed to be separated by at least a space.

Quote:
But why change the order (except perhaps for clarity)? You could just replace the first 'y' by the new expression.
Cos' then it gets too convoluted for my monochromatic mind, but I'll try that also.

Thankfully the mt_infix function (reverse polish to infix notation), that Manao added in MaskTools-v2.0a37, helps arithmetically desaturated numbskulls like me to decipher the work of others, but I sure wish there was more documentation available on MaskTools usage, by way of defined examples.

Edit: Ah, Didee's post bumped mine. See, I would never have thought of that approach. Was getting hung up on arithmetic progressions (radioactive decay curves, law of diminishing returns etc ) . Thanks Didee. Now to apply it.....more food for thought.

BTW:
Quote:
Originally Posted by OvejaNegra View Post
i think i have the same problem, more or less, i have a source with the highlights (or the whites) tinted with cyan.
I tryed with tweak and a color selection but it kills all the similar colors.
Using photshop and desaturating the highlights worked, and now im trying to reproduce that with avisynth.
i will try the Didée scripts to see what happens.
Yes, it works quite well in such situations. Adding the 'roll-off' refinement, should help to avoid chroma zoning at strong/full desaturation.

Actually, I can see more applications for this, once I've got the script down, one being a modification of the Tint function for greater control over the selective application of color tints.

http://avisynth.org/oldwiki/index.php?page=Tint
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 14th April 2010 at 14:12.
WorBry is offline   Reply With Quote
Old 14th April 2010, 13:49   #11  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,431
Quote:
Originally Posted by WorBry View Post
I thought operators needed to be separated by at least a space.
They do - but "<=" is a single operator.
Writing it as "< =" makes it appear as two, confusing the hell out of the reverse polish interpreter.
Gavino is offline   Reply With Quote
Old 14th April 2010, 14:06   #12  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Got it.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 14th April 2010 at 14:41.
WorBry is offline   Reply With Quote
Old 14th April 2010, 17:31   #13  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Well, application of the roll-off gradient to the mt_merge approach is straightforward enough, even for me:

Code:
Function SelSat (clip clp, float "Sat", int "Luma_Low", int "Low_OS", 
\                          int "Luma_High", int "High_OS") 
{
# Allows selective control of saturation for a defined luma range.
# Merely assembled by WorBry from component functions kindly provided by Didee.    
# Requires YV12 clp input and assumes full (pc) luma range 0-255.
# Requires MaskTools v2 
#Defaults: 
Sat       = default(Sat, 1.0) # Desaturation 0.0 < 1.0 > Increased saturation
Luma_Low  = default(Luma_Low, 0.0) # Lower luma range point
Luma_High = default(Luma_High, 255) # Upper luma range point 
Low_OS    = default(Low_OS, 0.0) # Lower over-shoot (roll-off) point for Luma_Low
High_OS   = default(High_OS,255) # Upper over-shoot (roll-off) point for Luma_high 
#
#Limits: 
Sat       = Sat <0.0 ? 0.0 : Sat  
Luma_Low  = Luma_Low  <0.0 ? 0.0 : Luma_Low  >255 ? 255 : Luma_Low
Low_OS    = Low_OS    <0.0 ? 0.0 : Low_OS >Luma_Low ? Luma_Low : Low_OS
Luma_High = Luma_High <0.0 ? 0.0 : Luma_High >255 ? 255 : Luma_High
High_OS   = High_OS   <Luma_High ? Luma_High : High_OS >255 ? 255 : High_OS
#
pale = clp.tweak(sat=Sat) # Saturation control clip.  
#Create luma mask with low/high range points and roll-off gradient at boundaries:   
# [0] <-black-> [p1] <-gradient-> [p2] <-white-> [p3] <-gradient-> [p4] <-black-> [255]
p1 =  Low_OS 
p2 =  Luma_Low 
p3 =  Luma_High 
p4 =  High_OS 
p1=string(p1) p2=string(p2) p3=string(p3) p4=string(p4)
blak = "x "+p1+" < x "+p4+" > | 0 "
wite = "x "+p2+" >= x "+p3+" <= & 255 "
gradlo = "x "+p2+" < 255 x "+p1+" - "+p2+" "+p1+" - / * "
gradhi = "x "+p3+" > 255 1 x "+p3+" - "+p4+" "+p3+" - / - * "
expr = blak + wite + gradlo + gradhi + "x ? ? ? ?"
lmask= clp.mt_lut(expr).greyscale()
#
# Apply luma mask:
result = mt_merge(clp,pale,lmask,luma=true,Y=2,U=3,V=3) 
Return(result)
}
I guess the Low_OS and High_OS limit control should be refined to ensure that Low_OS is never higher than Luma_Low and High_OS is never lower than Luma_High. Not sure how to do that.
Edit: Figured it. Function modified accordingly.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 14th April 2010 at 19:12.
WorBry is offline   Reply With Quote
Old 14th April 2010, 18:49   #14  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,431
Quote:
Originally Posted by WorBry View Post
I guess the Low_OS and High_OS limit control should be refined to ensure that Low_OS is never higher than Luma_Low and High_OS is never lower than Luma_High. Not sure how to do that.
Use Assert, eg Assert(Low_OS <= Luma_Low, " ... ")
You could also use Assert to totally reject values outside of 0-255 instead of just clamping them.

BTW I think it would be more logical to have the parameters (apart from Sat) as ints, not floats, as luma values are discrete.
Gavino is offline   Reply With Quote
Old 14th April 2010, 19:11   #15  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Thanks. Yes, my oversight. Changed the floats to int.
Will come back on the 'Assert' way, when I've thought about it. Works, as is for now.
Cheers.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 14th April 2010 at 19:18.
WorBry is offline   Reply With Quote
Old 16th April 2010, 03:48   #16  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Didee,

I thought it would be helpful to include an option for excluding the selected luma range e.g. if want to desaturate the shadows and highlights, but not the mid-tones.

Mere inversion of the luma mask (with mt_invert) works OK for the Luma_Low and Luma_High points, but of course the boundary roll-offs are then mis-placed.

So, I've tried to modify the mask mt_lut expressions instead:

Code:
c = colorbars().converttoyv12()
c = c.mt_lutspa(relative=true,expr="x 255 *").greyscale() 
# [0] <-black-> [p1] <-gradient-> [p2] <-white-> [p3] <-gradient-> [p4] <-black-> [255] # Inclusive
# [0] <-white-> [p2] <-gradient-> [p1] <-black-> [p4] <-gradient-> [p3] <-white-> [255] # Exclusive
p1 = 100  # Low_OS
p2 = 60   # Luma_Low 
p3 = 180  # Luma_High
p4 = 140  # High_OS
p1=string(p1) p2=string(p2) p3=string(p3) p4=string(p4)
#Inclusive: 
#blak = "x "+p1+" < x "+p4+" > | 0 "
#wite = "x "+p2+" >= x "+p3+" <= & 255 "
#gradlo = "x "+p2+" < 255 x "+p1+" - "+p2+" "+p1+" - / * "
#gradhi = "x "+p3+" > 255 1 x "+p3+" - "+p4+" "+p3+" - / - * "
#expr = blak + wite + gradlo + gradhi + "x ? ? ? ?"
#Exclusive:
blak = "x "+p1+" > x "+p4+" < & 0 "
wite = "x "+p2+" <= x "+p3+" >= | 255 "
gradlo = "x "+p2+" > 255 x "+p1+" - "+p2+" "+p1+" - / * "
gradhi = "x "+p3+" < 255 1 x "+p3+" - "+p4+" "+p3+" - / - * " # Not right !!
expr = blak + wite + gradlo + gradhi +  "x ? ? ? ?"
c.mt_lut(expr).greyscale()
interleave(c,last) 
return(last)
The blak, wite and gradlo expressions seem to work OK, but gradhi I just can not get right. Would appreciate your help.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 16th April 2010 at 03:59.
WorBry is offline   Reply With Quote
Old 16th April 2010, 08:36   #17  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,431
Quote:
Originally Posted by WorBry View Post
Mere inversion of the luma mask (with mt_invert) works OK for the Luma_Low and Luma_High points, but of course the boundary roll-offs are then mis-placed.
Code:
# [0] <-black-> [p1] <-gradient-> [p2] <-white-> [p3] <-gradient-> [p4] <-black-> [255] # Inclusive
# [0] <-white-> [p2] <-gradient-> [p1] <-black-> [p4] <-gradient-> [p3] <-white-> [255] # Exclusive
p1 = 100  # Low_OS
p2 = 60   # Luma_Low 
p3 = 180  # Luma_High
p4 = 140  # High_OS
It seems more logical to me to keep the roll-offs outside the specified range, ie
Code:
# [0] <-white-> [p1] <-gradient-> [p2] <-black-> [p3] <-gradient-> [p4] <-white-> [255] # Exclusive
Then you could simply invert the previous mask.

However, if you still want to keep your meaning of the parameters, then
Code:
gradhi = "x "+p3+" < 255 1 x "+p3+" - "+p4+" "+p3+" - / - * " # Not right !!
should be
Code:
gradhi = "x "+p3+" < 255 x "+p4+" - "+p3+" "+p4+" - / * " # 255*(x-p4)/(p3-p4)
Gavino is offline   Reply With Quote
Old 16th April 2010, 15:40   #18  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Quote:
Originally Posted by Gavino View Post
It seems more logical to me to keep the roll-offs outside the specified range, ie
Code:
# [0] <-white-> [p1] <-gradient-> [p2] <-black-> [p3] <-gradient-> [p4] <-white-> [255] # Exclusive
Then you could simply invert the previous mask.
Yes, but in that case the over-shoot set points (OS_Low, OS_High) are within the 'selected' domains defined by the Luma_Low and Luma_High set points i.e. the roll-off's begin within the white's, when they should roll-off from the Luma_Low and Luma_High set points into the black.

Quote:
Originally Posted by Gavino View Post
However, if you still want to keep your meaning of the parameters, then
Code:
gradhi = "x "+p3+" < 255 1 x "+p3+" - "+p4+" "+p3+" - / - * " # Not right !!
should be
Code:
gradhi = "x "+p3+" < 255 x "+p4+" - "+p3+" "+p4+" - / * " # 255*(x-p4)/(p3-p4)
Tried that before, and it's not right either; unless, of course, I've got the other expressions wrong as well. Example, using the above set points:

Code:
c = colorbars().converttoyv12()
c = c.mt_lutspa(relative=true,expr="x 255 *").greyscale() 
# [0] <--black--> [p1] <--gradient--> [p2] <--white--> [p3] <--gradient--> [p4] <--black--> [255] # Inclusive
# [0] <--white--> [p2] <--gradient--> [p1] <--black--> [p4] <--gradient--> [p3] <--white--> [255] # Exclusive
p1 = 100
p2 = 60
p3 = 180
p4 = 140
p1=string(p1) p2=string(p2) p3=string(p3) p4=string(p4)
#Exclusive:
blak = "x "+p1+" > x "+p4+" < & 0 "
wite = "x "+p2+" <= x "+p3+" >= | 255 "
gradlo = "x "+p2+" > 255 x "+p1+" - "+p2+" "+p1+" - / * "
gradhi = "x "+p3+" < 255 x "+p4+" - "+p3+" "+p4+" - / * " # 255*(x-p4)/(p3-p4)
expr = blak + wite + gradlo + gradhi +  "x ? ? ? ?"
c.mt_lut(expr).greyscale()
return(last)
Result:
http://rapidshare.com/files/37657940..._Mask.jpg.html

I always get that hard black/white boundary at the high set point.
__________________
Nostalgia's not what it used to be
WorBry is offline   Reply With Quote
Old 16th April 2010, 16:20   #19  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,431
Ah, yes, you also need to change the comparison to get the logic to work:
Code:
gradlo = "x "+p1+" <= 255 x "+p1+" - "+p2+" "+p1+" - / * "
Quote:
Originally Posted by WorBry View Post
Yes, but in that case the over-shoot set points (OS_Low, OS_High) are within the 'selected' domains defined by the Luma_Low and Luma_High set points i.e. the roll-off's begin within the white's, when they should roll-off from the Luma_Low and Luma_High set points into the black.
I still see that as more consistent. For one thing, it preserves the relationship OS_Low <= Luma_Low <= Luma_High <= OS_High. But of course, it's your function so you should specify it the way that makes sense to you.
Gavino is offline   Reply With Quote
Old 16th April 2010, 17:33   #20  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Quote:
Originally Posted by Gavino View Post
Ah, yes, you also need to change the comparison to get the logic to work:
Code:
gradlo = "x "+p1+" <= 255 x "+p1+" - "+p2+" "+p1+" - / * "
Yes, that looks right. Thanks.

Quote:
I still see that as more consistent. For one thing, it preserves the relationship OS_Low <= Luma_Low <= Luma_High <= OS_High. But of course, it's your function so you should specify it the way that makes sense to you.
Well, for sure, in changing from 'Inclusive' to 'Exclusive' modes, the Low_OS and High_OS points, need to be moved accordingly, but I guess I can set up their defaults, at least, to account for this.

In practice though, I cant see anyone using this merely wanting to flip back and to between the modes; 'Inclusive' is for one purpose, 'Exclusive' for another. I guess it could be refined further to allow selection of multiple ranges, but the mt_lut computation for that would be way too zany for me.

Cheers.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 16th April 2010 at 17:49.
WorBry is offline   Reply With Quote
Reply

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 00:05.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.