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 5th February 2014, 09:43   #1  |  Link
HappyLee
Registered User
 
Join Date: Mar 2013
Posts: 19
Smooth hue adjustment using Masktools?

Hi. I'm dealing with a video source that needs to turn the blue color a bit to green, leaving other color untouched. The result should look a bit similar with this:
Tweak(hue=30,starthue=325,endhue=15)

But unfortunately it's not smooth using Tweak. There's a filter MaskHS which can create a mask using starthue and endhue, but it's not smooth either, cause the mask would only be pure black and white.

I want to create a mask using Masktools2, but I don't know how to calculate the value of hue using U and V values and put the formula into mt_lut function.

Once the calculation is done, then it would be easy to create a mask, for example, making parts where the hue is equal to 350 pure white, and parts greater than 20 or smaller than 320 pure black, and making rest of the parts in gradient grey. Then I'll be able to use mt_merge(Tweak(30),mask,luma=true) to adjust the hue smoothly.

I've been looking everywhere but couldn't find a solution. Please help. Thanks in advance.

Last edited by HappyLee; 5th February 2014 at 09:46.
HappyLee is offline   Reply With Quote
Old 5th February 2014, 10:59   #2  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,377
Hue is calculated by Avisynth as atan2(v-128, u-128) * 180.0 / Pi

Masktools doesn't have atan2(), but it does have atan().
Note that to be able to access both u and v at the same time, you will have to use mt_lutxy(UToY(), VToY(), ...).
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 7th February 2014, 04:35   #3  |  Link
HappyLee
Registered User
 
Join Date: Mar 2013
Posts: 19
Quote:
Originally Posted by Gavino View Post
Hue is calculated by Avisynth as atan2(v-128, u-128) * 180.0 / Pi

Masktools doesn't have atan2(), but it does have atan().
Note that to be able to access both u and v at the same time, you will have to use mt_lutxy(UToY(), VToY(), ...).
Thank you. How do you use atan2() in mt_polish()? I found the atan2() formula quite complicated, which depends on whether x=0 and such. I wonder if there's an easier way to calculate all this. Thanks.

Edit: Should I use this (http://upload.wikimedia.org/math/5/7/1/571efb70f630041f9e2b00019025171e.png) or this (http://upload.wikimedia.org/math/5/6/0/560894456327bac6cd16e27339df5c32.png)? Sorry my math's not that good...
HappyLee is offline   Reply With Quote
Old 7th February 2014, 16:16   #4  |  Link
HappyLee
Registered User
 
Join Date: Mar 2013
Posts: 19
Well, I just wrote a function that solves this problem using the stupidest way I know, but the result looks good as expected:

Code:
Function HLM(clip o,clip c,int s,float d,int n) {
o2=o.Overlay(c.MaskHS(s-d*n,(s+d*n>360)? s+d*n-360:s+d*n,coring=false).Levels(0,1,255,0,8,coring=false),mode="Add")
return (n==31)? o2:HLM(o2,c,s,d,n+1)
}
Function HLSH(clip c,int s,float d,int t) {  #s for the middle Hue, d for minimum range, t for Hue in function Tweak()
c.ConverttoYV24()
m=HLM(MaskHS(s,s,coring=false).Levels(0,1,255,0,8,coring=false),c.ConverttoYV24(),s,d,1).blur(1.58).blur(1.58).blur(1.58).blur(1.58).blur(1.58).blur(1.58)
mt_merge(c,c.Tweak(t),m,luma=true)
#m  #You can check what final mask looks like here
}
How to use: HLSH(350,0.8,30), which would turn color blue a bit to green. Simple, right?

Last edited by HappyLee; 7th February 2014 at 16:27.
HappyLee is offline   Reply With Quote
Old 7th February 2014, 17:33   #5  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,377
That's a neat solution.
I'm not sure about the final series of blurs applied to the mask though. I can see that this eliminates banding in the mask, but since the blur is applied in the spatial domain rather than the 'hue domain', doesn't it lead to the mask 'leaking' into adjacent parts of the image that are in a completely different hue, and hence potentially changing those too?
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 8th February 2014, 08:20   #6  |  Link
HappyLee
Registered User
 
Join Date: Mar 2013
Posts: 19
Quote:
Originally Posted by Gavino View Post
That's a neat solution.
I'm not sure about the final series of blurs applied to the mask though. I can see that this eliminates banding in the mask, but since the blur is applied in the spatial domain rather than the 'hue domain', doesn't it lead to the mask 'leaking' into adjacent parts of the image that are in a completely different hue, and hence potentially changing those too?
Yeah you're right, but I haven't found a better way to avoid banding in the mask. Maybe I should leave banding there since it's almost impossible to catch it with our eyes. Thanks again.
HappyLee is offline   Reply With Quote
Old 8th February 2014, 11:31   #7  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,377
Looking more closely, there are a couple of bugs lurking in your code:

- MaskHS(s, s, ...) actually selects all pixels rather than just those with hue exactly equal to s.
(Not sure if this is a bug in MaskHS, but that's the way it works).
Also, MaskHS(0, 0, ...) gives an error (endHue must be greater than zero).

- For small values of s, startHue can go negative during the iteration, producing an error.

Fixed version:
Code:
Function HLM(clip o,clip c,int s,float d,int n) {
o2=o.Overlay(c.MaskHS((s-d*n<0)? s-d*n+360:s-d*n,(s+d*n>360)? s+d*n-360:s+d*n,coring=false).Levels(0,1,255,0,8,coring=false),mode="Add")
return (n==31)? o2:HLM(o2,c,s,d,n+1)
}
Function HLSH(clip c,int s,float d,int t) {  #s for the middle Hue, d for minimum range, t for Hue in function Tweak()
c.ConverttoYV24()
m=HLM(MaskHS(s,s+.001,coring=false).Levels(0,1,255,0,8,coring=false),c.ConverttoYV24(),s,d,1).blur(1.58).blur(1.58).blur(1.58).blur(1.58).blur(1.58).blur(1.58)
mt_merge(c,c.Tweak(t),m,luma=true)
#m  #You can check what final mask looks like here
}
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 26th February 2014, 02:43   #8  |  Link
raffriff42
Retried Guesser
 
raffriff42's Avatar
 
Join Date: Jun 2012
Posts: 1,377
Quote:
Originally Posted by HappyLee View Post
I want to create a mask using Masktools2, but I don't know how to calculate the value of hue using U and V values and put the formula into mt_lut function.

Once the calculation is done, then it would be easy to create a mask, for example, making parts where the hue is equal to 350 pure white, and parts greater than 20 or smaller than 320 pure black, and making rest of the parts in gradient grey. Then I'll be able to use mt_merge(Tweak(30),mask,luma=true) to adjust the hue smoothly.

I've been looking everywhere but couldn't find a solution. Please help. Thanks in advance.
I've been hacking away at this problem, off and on, since you posted about it. Now I think I have a solution. The mt_lutxy expression is not pretty, to say the least. This solution is not really better than your iterative one (which is actually quite clever), but that's OK! At least it's not worse!
Code:
#Last = YUV

## TEST
return SmoothMaskedTweak(
\   120, /* h - center hue */
\   030, /* d - range or spread */
\   180, /* hue shift - see Tweak */
\   1.5, /* saturation - see Tweak */ 
\   0>0, /* if true, invert the mask */
\   1>0, /* if true, show vector scope (slow) */
\   0>0) /* if true, show mask; else show effect */


##################################
### Wrap call to Tweak, masked with SmoothHueMask
##
## @ h   - the middle Hue to select
## @ d   - approximate range in degrees
## @ hue - Tweak: hue +/- (default 0)
## @ sat - Tweak: sat (default 1.0)
## @ negate     - if true, invert the mask
## @ showvector - if true, add Histogram(mode="color")
## @ showmask   - if true, return the mask
## @ bypass     - if true, bypass effect but allow
##                 showvector & showmask
##
## @ raffriff42 Feb-2014
##
function SmoothMaskedTweak(clip C, int h, int d,
\            int hue, float sat, bool "negate",
\            bool "showvector", bool "showmask", bool "bypass")
{
    negate     = Default(negate,     false)
    showvector = Default(showvector, false)
    showmask   = Default(showmask,   false)
    bypass     = Default(bypass,   false)

    c_00 = c
    c  = (showvector==false) ? c
    \  : c.Histogram(mode="color").AddBorders(0, 40, 0, 0)
    \     .Crop(0, 0, 0, C.Height+40)

    mc = c.SmoothHueMask(h, d, minsat=0, showargs=showvector)
    mc = (negate) ? mc.Invert : mc

    ##################
    ## modify this function with different effects here!
    fx = c.mt_merge(
    \        c.Tweak(hue=hue, sat=sat, coring=false),
    \        mc.ConvertToY8,
    \        luma=true)
    ##################

    rc = (bypass) ? c : fx
    return (showmask)
    \  ? mc.BicubicResize(c_00.Width, c_00.Height).ConvertToYV12()
    \  : (showvector==false)
    \    ? rc
    \    : rc.Overlay(
    \         mc.Crop(mc.Width-256, 0, 256, 40+256),
    \         x=mc.Width-256, y=0, mode="add", opacity=0.25)
    \        .Overlay(mc.Crop(0, 0, mc.Width-256, 40))
    \        .hist_color_labels(y=40)
    \        .BicubicResize(c_00.Width, c_00.Height)
}

##################################
### mask a smooth range of source hues
##
## @ h  - the middle Hue to select
## @ d  - approximate range (clamped to 3 min, 90 max)
## @ maxSat, minSat - see MaskHS (not implemented yet)
## @ showmap  - if true, show active hue-to-luma map
## @ showargs - if true, Subtitle arguments for debugging
##
## @ raffriff42 Feb-2014
##
function SmoothHueMask(
\           clip c, int h, float d,
\           int "maxSat", int "minSat",
\           bool "showmap", bool "showargs")
{
    h = (h + 36000) % 360
    d = Min(Max(3, d), 90)
    showmap  = Default(showmap, false)
    showargs = Default(showargs, false)

    ## ** convert U,V to hue
    
    atd = "y 127 - x 127 - / atan 180 * pi / " ## arctan(V/U) in degrees

    tr0 = "x 127 == y 127 == & -1 "              ## undefined
    tr1 = "y 127 == x 127 > & 0 "                ##   0 degrees exactly (no arctan)
    tr2 = "x 127 == y 127 > & 90 "               ##  90   ''
    tr3 = "y 127 == x 127 < & 180 "              ## 180   ''
    tr4 = "x 127 == y 127 < & 270 "              ## 270   ''
    tr5 = "x 127 > y 127 < & " + atd + "360 + "  ## Quadrant 1
    tr6 = "x 127 > y 127 > & " + atd             ## Quadrant 2
    r78 = atd + "180 + "                         ## Quadrants 3, 4

    ## /mm/ == map mask string (translate hue to luma with offset)
    ## result hue(0)=luma(0); hue(180)=luma(180); hue(359)=luma(359); etc
    mm = tr0+tr1+tr2+tr3+tr4+tr5+tr6+r78+"? ? ? ? ? ? "

    ## /hc/ == "hue center", 180 from wrap/rollover; gray in final mask
    ## allowed values = (0|90|180|270); default = 180
    hc = (h>=315 || h<45)  ? 0
    \  : (h>=45  && h<135) ? 90
    \  : (h>=135 && h<225) ? 180
    \  : (h>=225 && h<315) ? 270
    \  : 180 ## not reached

    ## apply wrapped offset to put hue center at midrange
    ra = (hc==0)   ? "180 + "
    \  : (hc==90)  ?  "90 + "
    \  : (hc==270) ? "270 + "
    \  : " "
    mm = mm + ra + "360 % "

    ## translate hue to adjusted luma (0-359 scale)
    hl = (h>=315) ? h - 180
    \  : (h<45)   ? h + 180
    \  : (h>=45  && h<135) ? h + 90
    \  : (h>=135 && h<225) ? h
    \  : (h>=225 && h<315) ? h - 90
    \  : h ## not reached

    ## create soft hue mask;
    ## clamp to 0-359, scale to 0-255
    sm = (showmap)
    \  ? "359 > 359 " + mm + "? 256 * 360 / "
    \  : "359 > 359 " + mm + String(hl)
    \  + " - abs "+String(-230/d) + " * 360 + "
    \  + "? 256 * 360 / "

    mc = mt_lutxy(C.UtoY, C.VtoY, sm, U=1, V=1)
    \    .BilinearResize(c.Width, c.Height)

    return (showargs==false) ? mc
    \  : mc.Subtitle("SmoothHueMask: h="+String(h)
    \             +"; d="+String(d, "%1.0f"),
    \             align=8, size=mc.Height/20)
}

#############################################
### add labels to Histogram(mode="color")
##
## @ raffriff42 Feb-2014
##
function hist_color_labels(clip C, int "x", int "y")
{
    x = Default(x, C.Width-256)
    y = Default(y, 0)
    C
    Subtitle("B",    x=x+200, y=y+110, text_color=$9999ff)
    Subtitle("M",    x=x+180, y=y+200, text_color=$ff99ff)
    Subtitle("48",  x=x+180, y=y+220, text_color=$ff99ff)
    Subtitle("R",    x=x+90,  y=y+210, text_color=$ff9999)
    Subtitle("102", x=x+82,  y=y+230, text_color=$ff9999)
    Subtitle("Y",    x=x+40,  y=y+130, text_color=$cccc66)
    Subtitle("176", x=x+35,  y=y+150, text_color=$cccc66)
    Subtitle("G",    x=x+50,  y=y+40,  text_color=$66dd66)
    Subtitle("230", x=x+45,  y=y+60,  text_color=$66dd66)
    Subtitle("C",    x=x+145, y=y+35,  text_color=$66cccc)
    Subtitle("282", x=x+140, y=y+55,  text_color=$66cccc)

    Subtitle("0",   x=x+220, y=y+110, text_color=$9999ff)
    Subtitle("90",  x=x+117, y=y+210, text_color=$cccccc)
    Subtitle("180", x=x+15,  y=y+110, text_color=$cccccc)
    Subtitle("270", x=x+115, y=y+10,  text_color=$cccccc)

    Subtitle("U+",   x=x+220, y=y+130, text_color=$cccccc)
    Subtitle("V+",   x=x+120, y=y+230, text_color=$cccccc)
    Subtitle("U-",   x=x+20,  y=y+90,  text_color=$cccccc)
    Subtitle("V-",   x=x+120, y=y+25,  text_color=$cccccc)
}
One reason for the complication is the "hue wrap-around" problem [EDIT which Gavino resolves more elegantly in his solution below]...

After getting a useful hue map, a difference is taken with the hue of interest. Here is a sample mask, with hue approximately centered on skin tones (hue=120, 15)
Code:
## ..............  h,  d,  hue, sat, neg,  v,   m
SmoothMaskedTweak(120, 30, 000, 1.0, 0>0, 1>0, 1>0)
Here is the test image, with skin tones reversed:
Code:
## ..............  h,  d,  hue, sat, neg,  v,   m
SmoothMaskedTweak(120, 30, 180, 1.5, 0>0, 1>0, 0>0)
And here it is with normal skin tones, and the color of the flowers (hue=172, 15) reversed:
Code:
## ..............  h,  d,  hue, sat, neg,  v,   m
SmoothMaskedTweak(172, 30, 180, 1.5, 0>0, 1>0, 0>0)

Last edited by raffriff42; 18th March 2017 at 01:05. Reason: (fixed image links)
raffriff42 is offline   Reply With Quote
Old 26th February 2014, 16:51   #9  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,377
Hi raffriff. Thanks for that.
I had already put together something similar myself, but didn't get round to tidying it up for publication as the OP seemed satisfied with what he had. Your post has prompted me to do so now.

I first created a Hue() function that returns an (infix) expression for the hue. This can be used to construct further expressions derived from the hue, eg to derive a mask for any arbitrary hue selection.

My HueMask() function is based on the MaskHS() interface with startHue and endHue, adding an interp parameter (as used by Tweak() for saturation), with the mask falling off to zero over a distance of interp degrees (>0) outside the selected hue range.
I got round the 'hue wrap-around problem' using some tricks with the abs function.
I convert input to YV24 to avoid any inaccuracy from chroma subsampling or alignment.

I also have a ShowHue() function that shows hue 0-360 as luma 0-180 - I preferred this range over 0-255 as you can read off the luma directly in AvsP and just double it to get the hue.
Code:
function atan2(string y, string x) {
# Returns an expression for atan2(y, x) (in range 0-360), corresponding to
#  (x == 0 ? (y > 0 ? 90 : (y < 0 ? 270 : 0)) : atan(y/x)*180/pi + (x < 0 ? 180 : (y < 0 ? 360 : 0)))
# Form of expression chosen to evaluate atan() once only.
  return "("+x+" == 0 ? ("+y+" > 0 ? 90 : ("+y+" < 0 ? 270 : 0)) : " + 
\               "atan(("+y+")/("+x+"))*180/pi + " +
\                   "("+x+" < 0 ? 180 : ("+y+" < 0 ? 360 : 0)))"
}

function Hue(string u, string v) {
# Returns an expression for hue (0-360) derived from U and V expressions
  return atan2(v+"-128", u+"-128")
}

function HueMask(clip c, float startHue, float endHue, float interp) {
  midHue = (startHue+endHue)/2.0
  midHue = startHue > endHue ? (midHue<180 ? midHue+180 : midHue-180) : midHue
  r = (endHue-startHue)/2.0  # radius of zone to be selected around midHue
  r = r < 0 ? r+180 : r
  g = 255.0/interp  # gradient of mask roll-off outside radius
  # reqd mask given by 255 + g*(r-d), where d is the absolute difference of hue from midHue (0-180)
  # d = abs(Hue-midHue) > 180 ? 360-abs(Hue-midHue) : abs(Hue-midHue)
  # To avoid evaluating Hue more than once, this is recast as d = 180-abs(abs(Hue-midHue)-180),
  # hence mask = 255 + g*(r-180+abs(abs(Hue("x", "y")-midHue)-180))
  # will be clamped to [0,255] by MaskTools, so get 255 between startHue and endHue, and 0 beyond interp zone
  expr = "255 + "+string(g)+"*("+string(r)+"-180+abs(abs("+Hue("x", "y")+"-"+string(midHue)+")-180))"

  c.ConvertToYV24()
  mt_lutxy(UtoY(), VToY(), mt_polish(expr), chroma="128")
}

function ShowHue(clip c) {
  c.ConvertToYV24()
  mt_lutxy(UtoY(), VToY(), mt_polish(Hue("x", "y")+"/2"), chroma="128")  
}

ColorBars()
orig = last
ShowHue()
# To see mask, need to convert with PC matrix
StackHorizontal(ConvertToRGB32(matrix="PC.601"), orig.ConvertToRGB32())
There are a couple of RPN oddities in your code:
Quote:
Originally Posted by raffriff42 View Post
Code:
    mm = tr0+tr1+tr2+tr3+tr4+tr5+tr6+r78+"? ? ? ? ? ? "
...
    sm = (showmap)
    \  ? "359 > 359 " + mm + "? 256 * 360 / "
    \  : "359 > 359 " + mm + String(hl)
    \  + " - abs "+String(-230/d) + " * 360 + "
    \  + "? 256 * 360 / "
In the creation of mm, there is a missing '?' - there are 7 conditions but you only have 6 '?'s.
Later, two expressions both start with "359 >" which appears to have only one operand given for '>'.
I'm not sure what effect these have, as the result in MaskTools from ill-formed RPN is not defined. Your function seems to work, but there may be cases for which it does something unexpected or undesired.

Also, you use 127 for the neutral chroma point instead of the 128 used by Avisynth in determining hue.

Incidentally, where did you get your source images from? They look very useful for testing this sort of thing.

And I like your vectorscope labelling, which I have stolen for my utilities library.
__________________
GScript and GRunT - complex Avisynth scripting made easier

Last edited by Gavino; 27th February 2014 at 11:23. Reason: extend comments in code on mask creation
Gavino is offline   Reply With Quote
Old 26th February 2014, 21:58   #10  |  Link
raffriff42
Retried Guesser
 
raffriff42's Avatar
 
Join Date: Jun 2012
Posts: 1,377
Hi Gavino, yes "359 > " is a bug, but it always evaluates to false, so it has no effect. I don't know what I was thinking.

So throwing that out, we get six ?'s and then a final one at the end, which was the original intent, before I injected that nonsensical code at the last minute.

I need a course in RPN, which I have avoided all my life up until now.

Image source: it's called "DQ monitor" https://www.google.com/search?tbm=is...reference+file

I am pleased that you like my vector labels; steal away.
raffriff42 is offline   Reply With Quote
Old 27th February 2014, 00:52   #11  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,377
Quote:
Originally Posted by raffriff42 View Post
I need a course in RPN, which I have avoided all my life up until now.
It's very easy to make mistakes without realising it when constucting complex RPN expressions.
It doesn't help that MaskTools does no error checking (or at least fails to report ill-formed expressions) which can make mistakes hard to find.

So this time I decided to create the expressions as infix and use mt_polish() at the end. The MaskTools error checking is no better here, but at least it's easier for the script writer to get it right.
However, in doing so, I discovered a bug in mt_polish() that confused me for a while until I realised what was happening. So maybe I should have stuck with RPN.
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 10th August 2014, 16:41   #12  |  Link
StainlessS
HeartlessS Usurer
 
StainlessS's Avatar
 
Join Date: Dec 2009
Location: Over the rainbow
Posts: 6,205
Some really nice colour images contained in this PDF: http://www.comp.nus.edu.sg/~cs4243/lecture/colour.pdf
Need to extract as jpg, requires Acrobat (editor) to extract (or similar).

EDIT: eg


about 6 in total
__________________
I sometimes post sober.
StainlessS@MediaFire ::: AND/OR ::: StainlessS@SendSpace

"Some infinities are bigger than other infinities", but how many of them are infinitely bigger ???

Last edited by StainlessS; 16th July 2017 at 03:58.
StainlessS is offline   Reply With Quote
Old 6th November 2015, 18:11   #13  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 986
I came across this thread whilst researching available tools for targeted secondary color correction.

A few years back I put together a function SelSah, that applies Tweak through a luma range mask with high and low luma point 'roll-off' control - you may recall that you helped me with it, Gavino.

http://blog.niiyan.net/post/666717637/selsahv3

Actually, it's quite a useful tool and I still use it a fair bit in secondary color grading - not just for creating 'filmic looks' which was the original incentive. Using the Tweak hue and saturation range (start/End) parameters together with the luma range control, it is possible to target and isolate subjects with distinct and fairly uniform color characteristics (say a distracting brightly colored-saturated object) with a reasonable degree of precision. Rather more difficult, and finicky, is targeting subtle/diffuse tones, such as people skin, especially in 'real world' video, when the subject(s) is set in a natural background where base yellow tones are prevalent.

The SelSah function does include a Preview option that shows the original and adjusted chroma through the greyscale luma mask, but the faint chroma trace makes it difficult to visualize less-than-saturated subjects and one has to resort to exaggerating the chroma in a variety of ways (cranking up the saturation, desaturating and/or shifting the hue to some distinctive color) to help visualize the effects, or else crop down to particular area of interest - either way, rather tedious. I'll put up some examples when I have a mo.

What would be nice would be a tool that generates, and applies, a composite greyscale mask (ideally with intensity adjustment) from the set hue, saturation and luma ranges, with provision for controlling the 'softness' (roll-off) of the mask boundaries.

Seeing the Hue masks that raffriff42 and Gavino posted above, I'm sure such a function is quite do-able. Indeed, I tried applying raffriff42's SmoothMaskedTweak through a luma range mask and it works rather well for skin tones, especially with the approach taken for smooth roll-off from a target (centre) hue. What's lacking is the added refinement of a saturation range component.

Nothing new of course. All of the high end video editing and grading suites have far more sophisticated tools available for this type of thing...in RGB colorspace, that is.

Anyhow, I wonder if anyone would be interested in putting together such a function, if they haven't already. Something that could be easily used in AVSPMod with sliders. I'd have a go, but once again find myself limited by lack of knowledge of the theory and computations involved and my rather rudimentary experience in writing complex functions.

Edit: Or else, if someone could show me how to create greyscale saturation range mask (with boundary roll-off) using MaskTools2, I could have a go at creating the merged composite luma/hue/saturation mask.

Also Re:

Quote:
Originally Posted by WorBry View Post
I'll put up some examples when I have a mo.
An example of cranking up the sat and shifting the hue to help visualize the target skin tones - the "Avatar" approach. Probably could have tweaked it some more, but it illustrates the point.

__________________
Nostalgia's not what it used to be

Last edited by WorBry; 7th November 2015 at 03:08.
WorBry is offline   Reply With Quote
Old 6th November 2015, 20:24   #14  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 986
P.S. @raffriff42

I also like the Vectoscope with markers that you incorporated in SmoothMaskedTweak. I did something similar - lifted the graticule (with skin tone line and saturation targets) from a vectorscope in Premiere Pro (trial) and applied as a mask over an inverted (Color2 mode) vectoscope in Histogram. Pretty crude, but it does the job. Example:



In this example I used SelSah to target and shift the skin tones a tad:

Code:
SelSah_V3(Sat=0.98, Hue=-4, StartHue=80, EndHue=135, Luma_Low=60, Luma_High=220, minsat=0, maxsat=100,Preview=False)
Edit: minsat=10 gives a marginally better result for this shot

Edit2: Actually, an even "tighter" selection for the skin tones in that particular shot:

Code:
SelSah_V3(Sat=0.98, Hue=-4, StartHue=90, EndHue=129, Luma_Low=90, Luma_High=220, minsat=20, maxsat=40, Preview=False)
It would be nice if vectorscope markers could be incorporated as standard options in the native Histogram filter.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 17th November 2015 at 07:31.
WorBry is offline   Reply With Quote
Old 7th November 2015, 06:45   #15  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 986
Well, I've tried using the MaskHS mask together with a Luma range mask and I must say it does make it a lot easier to visually home in on the desired target. Assuming, as it would appear, that Tweak uses MaskHS internally, I guess I could use the combined masks to determine the optimal Hue, Saturation and Luma range values and then plug them into SelSah.

Still, I think a function that allows for smooth roll-off at the mask range boundaries would make this more refined.
__________________
Nostalgia's not what it used to be
WorBry is offline   Reply With Quote
Old 7th November 2015, 18:11   #16  |  Link
raffriff42
Retried Guesser
 
raffriff42's Avatar
 
Join Date: Jun 2012
Posts: 1,377
Nice graticules you have there!
Quote:
Originally Posted by WorBry View Post
What's lacking is the added refinement of a saturation range component.
This could be done using the same techniques as used for hue selection. The math is not a problem, but I don't think you would get useful selectivity based on saturation, in the real world.

(*thinks*)

OK here is a function that masks the image based on Saturation. Merge it with Hue and/or Luma mask(s) as needed.

It uses this equation:
sat = Sqrt((u-127.5)^2 + (v-127.5)^2)

BTW extracting Saturation by this method looks better (IMHO) than channel splitting by HSV/HSL in GIMP etc.

HSV emphasizes anything that's green. HSL emphasizes anything that's bright.

Code:
##################################
### mask a range of source saturation; transform saturation->lightness
##
## @ maxSat, minSat - see MaskHS (default 255, 0)
## @ sensitivity - increase saturation->lightness in output (default 1.5)
##
function SatMask(clip c, int "maxSat", int "minSat", float "sensitivity")
{
    Assert(c.IsYV12 || c.IsYV24, 
    \   "SatMask: source must be YV12 or YV24")

    xlo   = Min(Max(    0, Default(minSat,   0)), 250) * 3.0 / 256
    xhi   = Min(Max(xlo+5, Default(maxSat, 255)), 255) * 3.0 / 256
    sens  = Default(sensitivity, 1.5)

    mask_sat = mt_lutxy(C.UtoY, C.VtoY, 
    \       yexpr=mt_polish(
    \           "((((x-127.5)^2)+((y-127.5)^2))^0.5)*"+String(2.0 * sens, "%0.3f")), 
    \       U=-128, V=-128)
    \    .BilinearResize(c.Width, c.Height)

    mask_lo = mask_sat.mt_lut(
    \       yexpr=mt_polish(
    \           "((-0.01 * x) + "+String(xlo)+") * 256"),
    \       u=-128, v=-128)
    
    mask_hi = mask_sat.mt_lut(
    \       yexpr=mt_polish(
    \           "1 - ((-0.01 * x) + "+String(xhi)+") * 256"),
    \       u=-128, v=-128)
    
    mask_mid = mt_lutxy(mask_lo, mask_hi, 
    \       yexpr=mt_polish(
    \           "-1 * (x + y) + 256"), 
    \       u=-128, v=-128)

    return (xlo<=0 && xhi>=(255*3.0/256)) ? mask_sat
    \ : mt_lutxy(mask_sat, mask_mid, 
    \       yexpr=mt_polish("min(x, y)"), 
    \       u=-128, v=-128)
}
P.S. The mask_lo/mask_hi logic was adapted from some code I wrote earlier. It's explained (poorly) here:
http://forum.doom9.org/showthread.ph...26#post1676326
It might be overkill. It's probably enough to manipulate SatMask's default output with Levels as needed, instead of using maxSat & minSat.

Last edited by raffriff42; 17th March 2017 at 00:02. Reason: (fixed image links)
raffriff42 is offline   Reply With Quote
Old 8th November 2015, 15:09   #17  |  Link
WorBry
Registered User
 
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 986
Quote:
Originally Posted by raffriff42 View Post
Nice graticules you have there!
That's what they all say

Quote:
Originally Posted by raffriff42 View Post
This could be done using the same techniques as used for hue selection. The math is not a problem, but I don't think you would get useful selectivity based on saturation, in the real world.
Well, maybe I'm going a bit overboard on this soft roll off thing, at least as it applies to saturation, but closing down the saturation range, along with luma, can help to screen out like hue tones in the background, and preserve the color of more saturated objects (when that is desirable) as well as keeping white balance. Of course, it's always a judgement call as to what is more important.....even when using high end grading software:

https://www.youtube.com/watch?v=bcfWvRY3uTc

https://www.youtube.com/watch?v=iI1Ed5oEwqc

https://www.youtube.com/watch?v=XQx1ZJa_PIA From about 7.5 mins onwards.

That first Apple Color tutorial is a bit dated now, but this is basically the kind of control I'm aiming at, as far as is possible...in YV12 colorspace.

Anyhow, thanks a lot for the sat mask. I'll play around some more with the different mask options.

Edit:

Quote:
Originally Posted by raffriff42 View Post

BTW extracting Saturation by this method looks better (IMHO) than channel splitting by HSV/HSL in GIMP etc.
.......
HSV emphasizes anything that's green. HSL emphasizes anything that's bright.
Interesting
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 23rd December 2015 at 16:52.
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 07:24.


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