Log in

View Full Version : Color/luma matching only MOST of a frame/footage?


dcxero
30th January 2017, 22:56
I'm trying to color/light match a particular scene from one source to replace in a different source. I have a script that works great normally, but the problem I'm having is due to the footage I'm sourcing the color/luma from, as it has hardcoded subtitles, which end up ghosting into the encoded footage

clip1=FFmpegSource2("NewSource.mkv").Trim(49,634).Spline36Resize(1920,1080,0,24,0,-24)
clip2=FFmpegSource2("ColorsFrom.mkv").Trim(362,947)
chroma=clip2 #BD
luma=clip1 #720p
MergeChroma(luma,chroma, 1.0).Tweak(0.0,1.0,1,1.3)

Here's an closeup of the ghosting
Source (hardsubbed): http://i.imgur.com/3eEes2X.png
Script output: http://i.imgur.com/4Ddof3I.png

As you can see, around the ghosting, the image looks pretty closely matched (aside from the upscaling). I was hoping I could somehow save the values it's using, and apply them a different way, or only have the script ignore a particular area

Any suggestions for my script? Or is it pretty much useless for this particular source? If anyone wants to take a look at the 2 sources I'm working with, they're a bit large, but I could upload them to my Dropbox or Mega, just PM me. Thanks in advance

StainlessS
31st January 2017, 01:04
Suggest you try GamMatch from the GamMac thread (they are two different plugins) a few posts before this one at time of my posting.

Do not resize the detection clip (your clip1) but do crop borders (from both).
You could deal with the hard coded subs by either cropping them off (losing as little image as possible),
OR, overlay subs with mid grey to obliterate them,
OR, pick an area from anywhere in subs clip and overlay on top of the subs to get rid of them (exclude them from histograms).

The two clips will need to be completely in sync, and there will need be a conversion to RGB, but results I imagine will be acceptable.

StainlessS
31st January 2017, 04:38
OK, I tried to knock up something for this, but am not really set for testing, in the middle of doing a load of OS setups, configs and Imaging.

Here what I've got, not tested much.

first COMMON part of script

# GammaFixFrameY.avs
# Req GScript(), Grunt(), RT_Stats.

GSCript("""

Function GammaFixFrameY(clip c,clip dc,int n,Float "Th",clip "dcMask",Bool "OrigChroma",Bool "DEBUG") {
/*
c, frame without subs
dc, frame with subs but better luma
n, frame number
Th, as for YPlaneMin/Max(Threshold)
dcMask, Optional planarY Mask (Y8 OK), where 128 <= MaskY <= 255 is not used in Histograms (ie Subs area).
OrigChroma, True re-Merge original Chroma.
*/
myName="GammaFixFrameY: "
n=min(max(n,0),c.FrameCount-1)
c=c.Trim(n,-1) dc=dc.Trim(n,-1)
Th=Default(Th,0.04) # as for YPlaneMin/Max(threshold)
OrigChroma=Default(OrigChroma,True)
DEBUG=Default(DEBUG,False)
# 1st get dc clip yMin, YMax, and YAve where mask 128 or greater is ignored
Assert(dc.RT_Ystats(n=0,threshold=Th,flgs=$13,prefix="DC_",mask=dcMask,MaskMin=0,MaskMax=127)!=0,"All Masked Out")
# Next get c clip yMin and YMax
c.RT_Ystats(n=0,threshold=Th,flgs=3,prefix="C_") # sets C_YMin and C_YMax
ALDif = 0.00001
PrevAveL = -1.0
gamHi = 10.0 gamLo = 1.0/gamHi
Result= c # Prep not found (original)
while(GamLo < gamHi) {
gamMid = (gamLo + gamHi) / 2.0
TmpC = c.Levels(C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,coring=false)
AveL = TmpC.RT_AverageLuma(0)
if(abs(AveL-PrevAveL)<=ALDif) {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, Not getting any nearer
} else if(AveL < DC_YAve) {
gamLo = gamMid
} else if(AveL > DC_YAve) {
gamHi = gamMid
} else {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, exact match
}
PrevAveL = AveL
}
if(DEBUG) {
if(Result != c) { # Result is not original clip.
RT_DebugF("%d] Levels(%d,%.3f,%d, %d,%d,coring=False)",n,C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,name=myName)
} Else {
RT_DebugF("%d] NOT FOUND",n,name=myName)
}
}
return (OrigChroma) ? Result.MergeChroma(c) : Result
}
""") # End of GScript

Function Sub(clip c,string tit) {
StackVertical(c.BlankClip(height=20).ScriptClip("""Subtitle(RT_String("%d] %s",current_frame,tit))""",Args="tit",Local=true),c)
}


My Testing Part (Might make what we are doing more clear, just choose any old clip name as source)

####################
### MY TESTING ROUTINE
####################
FN = "ParadeFixed.Avi" # Some clip I had to hand [ Cheers John :) ].
c = AviSource(FN)

SUBS_X=20
SUBS_Y=20
SUBS_W=128
SUBS_H=128
dc = c
TH=0.04 # YMin/Max detection sensitivity
OrigChroma=True
DEBUG=False # Output to DebugView (Google)
OPACITY=1.0 # For viewing subs positional coords
dcMask = Overlay(dc.BlankClip,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF),x=SUBS_X,y=SUBS_Y).Trim(0,-1)
# Synth subs as Yellow block
dc = Overlay(dc,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFF00),x=SUBS_X,y=SUBS_Y,opacity=OPACITY)
# Make synthesized bad Luma, original Chroma
BADSRC=c.Levels(0,1.5,255,32,255-32,coring=false)
BADSRC=(OrigChroma) ? BADSRC.MergeChroma(c) : BADSRC # Handle chroma the same in both directions.
SSS="""return Last.GammaFixFrameY(dc,current_frame,Th,dcMask,OrigChroma,DEBUG)"""
BADSRC.ScriptClip(SSS,Args="dc,Th,dcMask,OrigChroma,DEBUG",local=true)
TOP = StackHorizontal(Sub(BADSRC,"Source (synthesized bad luma)"),Sub(dc,"dc Detect clip good Luma (synth subs as Yellow block)"))
BOT = StackHorizontal(Sub(dcMask,"dcMask, Subs area White"),Sub(Last,"Result (source modified to match dc)"))
return StackVertical(TOP,BOT)



Your part

####################
### YOUR STUFF
####################
# These BOTH need to be cropped of borders
c = FFmpegSource2("ColorsFrom.mkv").Trim(362,947) # BD
#c = c.Crop(,,,)
dc = FFmpegSource2("NewSource.mkv").Trim(49,634) # 720P
dc = dc.Crop(0,24,0,-24)

# Where the Subs Live
SUBS_X=20
SUBS_Y=20
SUBS_W=128
SUBS_H=128
OPACITY=0.25 # For viewing subs positional coords

# Show synth subs as block for setting above coords, Then Comment out
return Overlay(dc,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFF00),x=SUBS_X,y=SUBS_Y,opacity=OPACITY)

TH=0.04 # YMin/Max detection sensitivity
OrigChroma=True # WARNING, YUV, Levels will modify chroma too to try avoid color shift, this may or may not be desirable.
DEBUG=False # Output Levels() correction args to DebugView (Google)

dcMask = Overlay(dc.BlankClip,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF),x=SUBS_X,y=SUBS_Y).Trim(0,-1)
#dcMask=Undefined # If No Subs (or RT_Undefined if v2.58)

SSS="""return Last.GammaFixFrameY(dc,current_frame,Th,dcMask,OrigChroma,DEBUG)"""
c.ScriptClip(SSS,Args="dc,Th,dcMask,OrigChroma,DEBUG",local=true)


Good Luck

From my test stuff
https://s20.postimg.cc/4qgn2v6v1/Gamma_Fix_Frame_Y_zps8nin2jff.png (https://postimg.cc/image/3o6gkbo1l/)

EDIT: You can add whatever tweaking you like afterwards.

EDIT: Script Modified a bit, another image.
https://s20.postimg.cc/427sjx859/Gamma_Fix_Frame_Y_2_zpsn8s8itbn.png (https://postimg.cc/image/m50vb53zt/)

EDIT: Script modified again, + re-did above two images after putting the parade clip through GamMac() first to remove red cast.

https://s20.postimg.cc/9ewmy1w1p/Gamma_Fix_Frame_Y_zpszhdd29bt.png (https://postimg.cc/image/8cmgfid89/)

https://s20.postimg.cc/6yutk7dz1/Gamma_Fix_Frame_Y_2_zpsplkp9jwt.png (https://postimg.cc/image/xjxcfrgc9/)

StainlessS
31st January 2017, 11:14
Another go here for Optional RGB32

COMMON

# FixFrame.avs
# Req GScript(), Grunt(), RT_Stats.

GSCript("""
Function GammaFixFrameY(clip c,clip dc,int n,Float "Th",clip "dcMask",Bool "OrigChroma",Bool "DEBUG") {
/*
c, frame without subs
dc, frame with subs but better luma
n, frame number
Th, as for YPlaneMin/Max(Threshold)
dcMask, Optional planarY Mask (Y8 OK), where 128 <= MaskY <= 255 is not used in Histograms (ie Subs area).
OrigChroma, True re-Merge original Chroma.
*/
myName="GammaFixFrameY: "
n=min(max(n,0),c.FrameCount-1)
c=c.Trim(n,-1) dc=dc.Trim(n,-1)
Th=Default(Th,0.04) # as for YPlaneMin/Max(threshold)
OrigChroma=Default(OrigChroma,True)
DEBUG=Default(DEBUG,False)
# 1st get dc clip yMin, YMax, and YAve where mask 128 or greater is ignored
Assert(dc.RT_Ystats(n=0,threshold=Th,flgs=$13,prefix="DC_",mask=dcMask,MaskMin=0,MaskMax=127)!=0,"All Masked Out")
# Next get c clip yMin and YMax
c.RT_Ystats(n=0,threshold=Th,flgs=3,prefix="C_") # sets C_YMin and C_YMax
ALDif = 0.00001
PrevAveL = -1.0
gamHi = 10.0 gamLo = 1.0/gamHi
Result= c # Prep not found (original)
while(GamLo < gamHi) {
gamMid = (gamLo + gamHi) / 2.0
TmpC = c.Levels(C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,coring=false)
AveL = TmpC.RT_AverageLuma(0)
if(abs(AveL-PrevAveL)<=ALDif) {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, Not getting any nearer
} else if(AveL < DC_YAve) {
gamLo = gamMid
} else if(AveL > DC_YAve) {
gamHi = gamMid
} else {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, exact match
}
PrevAveL = AveL
}
if(DEBUG) {
if(Result != c) { # Result is not original clip.
RT_DebugF("%d] Levels(%d,%.3f,%d, %d,%d,coring=False)",n,C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,name=myName)
} Else {
RT_DebugF("%d] NOT FOUND",n,name=myName)
}
}
return (OrigChroma) ? Result.MergeChroma(c) : Result
}

Function GammaFixFrameRGB(clip c,clip dc,int n,Float "Th",clip "dcMask",Bool "DEBUG") {
myName="GammaFixFrameRGB: "
DEBUG=Default(DEBUG,False)
(DEBUG)?RT_DebugF("%d] Red Channel",n,name=myName):NOP
R=GammaFixFrameY(c.ShowRed (Pixel_Type="Y8"),dc.ShowRed (Pixel_Type="Y8"),n,Th,dcMask,False,DEBUG)
(DEBUG)?RT_DebugF("%d] Grn Channel",n,name=myName):NOP
G=GammaFixFrameY(c.ShowGreen(Pixel_Type="Y8"),dc.ShowGreen(Pixel_Type="Y8"),n,Th,dcMask,False,DEBUG)
(DEBUG)?RT_DebugF("%d] Blu Channel",n,name=myName):NOP
B=GammaFixFrameY(c.ShowBlue (Pixel_Type="Y8"),dc.ShowBlue (Pixel_Type="Y8"),n,Th,dcMask,False,DEBUG)
return MergeRGB(R,G,B)
}

""") # End of GScript

Function Sub(clip c,string tit) {
StackVertical(c.BlankClip(height=20).ScriptClip("""Subtitle(RT_String("%d] %s",current_frame,tit))""",Args="tit",Local=true),c)
}


My Test

####################
### MY TESTING ROUTINE RGB
####################

FN = "ParadeFixed.Avi" # Some clip I had to hand [ Cheers John :) ].
c = AviSource(FN)
c = c.ConvertToRGB32 # TEST works in RGB
dc = c

ISYUV=(c.IsYUV)
SUBS_X=20
SUBS_Y=20
SUBS_W=128
SUBS_H=128
TH=0.04 # YMin/Max detection sensitivity
OrigChroma=True # YUV ONLY
DEBUG=False # Output to DebugView (Google)
OPACITY=1.0 # For viewing subs positional coords
dcMask = Overlay(dc.BlankClip,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF),x=SUBS_X,y=SUBS_Y).Trim(0,-1)
dcMaskY= (!IsYUV) ? dcMask.ConvertToY8 : dcMask # Must Be Planar
# Synth subs as Yellow block
dc = Overlay(dc,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFF00),x=SUBS_X,y=SUBS_Y,opacity=OPACITY)
# Make synthesized bad Luma, original Chroma
BadC =c.Levels(0,1.5,255,32,255-32,coring=false)
BadC =(OrigChroma&&ISYUV) ? BadC.MergeChroma(c) : BadC # Handle chroma the same in both directions.
ARGS="Th,dcMaskY"+(ISYUV?",OrigChroma":"")+",DEBUG"
SSS=RT_String("return Last.GammaFixFrame%s(dc,current_frame,%s)",ISYUV?"Y":"RGB",ARGS)
BadC.ScriptClip(SSS,Args="dc,"+ARGS,local=true)

TOP = StackHorizontal(Sub(BadC,"Source (synthesized bad luma)"),Sub(dc,"dc Detect clip good Luma (synth subs as Yellow block)"))
BOT = StackHorizontal(Sub(dcMask,"dcMask, Subs area White"),Sub(Last,"Result (source modified to match dc)"))
return StackVertical(TOP,BOT)


Yours

####################
### YOUR STUFF, IF RGB
####################
# These BOTH need to be cropped of borders (BOTH COLORSPACE MUST BE SAME, YV12 or RGB32)
c = FFmpegSource2("ColorsFrom.mkv").Trim(362,947) # BD
#c = c.Crop(,,,)
dc = FFmpegSource2("NewSource.mkv").Trim(49,634) # 720P
dc = dc.Crop(0,24,0,-24)
c=c.ConvertToRGB32 dc=dc.ConvertToRGB32 # If wanna try in RGB32

ISYUV=(c.IsYUV)
# Where the Subs Live
SUBS_X=20
SUBS_Y=20
SUBS_W=128
SUBS_H=128
OPACITY=0.25 # For viewing subs positional coords

# Show synth subs as block for setting above coords, Then Comment out
#return Overlay(dc,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFF00),x=SUBS_X,y=SUBS_Y,opacity=OPACITY)

TH=0.04 # YMin/Max detection sensitivity
OrigChroma=True # YUV ONLY
DEBUG=False # Output Levels() correction args to DebugView (Google)

dcMask = Overlay(dc.BlankClip,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF),x=SUBS_X,y=SUBS_Y).Trim(0,-1)
dcMaskY= (!IsYUV) ? dcMask.ConvertToY8 : dcMask # Must Be Planar
#dcMaskY=Undefined # If No Subs (or RT_Undefined if v2.58)

ARGS="Th,dcMaskY"+(ISYUV?",OrigChroma":"")+",DEBUG"
SSS=RT_String("return Last.GammaFixFrame%s(dc,current_frame,%s)",ISYUV?"Y":"RGB",ARGS)
c.ScriptClip(SSS,Args="dc,"+ARGS,local=true)


EDIT: OOOooops,
Called RT_YStats with current frame n on single frame clip in GammaFixFrameY(), fixed [no effect anyway, corrected in plugin].
Marked in blue.


EDIT: If original tampering with colors was not by RGB levels/gamma type adjustment, then may not produce required improvement,
let us know how it goes please.

jmac698
31st January 2017, 14:01
I haven't followed closely what you're doing, but I should point out that there's a slight difference between these two approaches:

1) Adjusting one source to match another
2) Taking luma/chroma from different sources and merging them

In the case of 1, there can be a loss of precision in the operation causing banding and posterization. So the approach of 2 will give better quality, the only problem is the colour of white subtitles is being copied over, which isn't grey as you might logically expect but probably just some noisy tint of some type, influenced by surrounding colour.

The solution I would propose is to combine both solutions with a mask in the subtitle area. Masking can't be avoided for a high quality solution. And the way to create the mask has been solved in logo removing scripts. It's not hard to look for white areas, within a certain region, which stay for a minimum number of frames, and then dilate that to account for the influence of nearby colours.

In the end, even this solution will have some minor distortion in the subtitles area. A blending step can fix the transition and make it less noticeable.

Most repair scripts follow those general steps at least; the masking, the major repair, and the blending.

StainlessS
1st February 2017, 04:55
Just making images from first post directly visible here

Source (hardsubbed):
http://i.imgur.com/3eEes2X.png

dcxero script output
http://i.imgur.com/4Ddof3I.png
EDIT: oops, image order corrected.

Where JMac point out

1) Adjusting one source to match another
2) Taking luma/chroma from different sources and merging them

1) Produces banding.
2) Reduction in true luma resolution due to upscaling (hard subbed source is 1080P, non subbed is 720P).

EDIT: There seems to be some misalignment between clips, as seen above most noticeable in 2nd image, dark fringe down left hand side of b.

StainlessS
1st February 2017, 07:50
COMMON

# NewFixFrame.avs
# Req GScript(), Grunt(), RT_Stats.

GSCript("""

Function GammaFixFrameY(clip c,clip dc,int n,Float "Th",clip "dcMask",Bool "OrigChroma",Bool "DEBUG") {
/*
c, frame without subs
dc, frame with subs but better luma
n, frame number
Th, as for YPlaneMin/Max(Threshold)
dcMask, Optional planarY Mask (Y8 OK), where 128 <= MaskY <= 255 is not used in Histograms (ie Subs area).
OrigChroma, True re-Merge original Chroma.
*/
myName="GammaFixFrameY: "
n=min(max(n,0),c.FrameCount-1)
c=c.Trim(n,-1) dc=dc.Trim(n,-1)
Th=Default(Th,0.04) # as for YPlaneMin/Max(threshold)
OrigChroma=Default(OrigChroma,True)
DEBUG=Default(DEBUG,False)
# 1st get dc clip yMin, YMax, and YAve where mask 128 or greater is ignored
Assert(dc.RT_Ystats(n=0,threshold=Th,flgs=$13,prefix="DC_",mask=dcMask,MaskMin=0,MaskMax=127)!=0,"All Masked Out")
# Next get c clip yMin and YMax
c.RT_Ystats(n=0,threshold=Th,flgs=3,prefix="C_") # sets C_YMin and C_YMax
ALDif = 0.00001
PrevAveL = -1.0
gamHi = 10.0 gamLo = 1.0/gamHi
Result= c # Prep not found (original)
while(GamLo < gamHi) {
gamMid = (gamLo + gamHi) / 2.0
TmpC = c.Levels(C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,coring=false)
AveL = TmpC.RT_AverageLuma(0)
if(abs(AveL-PrevAveL)<=ALDif) {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, Not getting any nearer
} else if(AveL < DC_YAve) {
gamLo = gamMid
} else if(AveL > DC_YAve) {
gamHi = gamMid
} else {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, exact match
}
PrevAveL = AveL
}
if(DEBUG) {
if(Result != c) { # Result is not original clip.
RT_DebugF("%d] Levels(%d,%.3f,%d, %d,%d,coring=False)",n,C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,name=myName)
} Else {
RT_DebugF("%d] NOT FOUND",n,name=myName)
}
}
return (OrigChroma) ? Result.MergeChroma(c) : Result
}

""") # End of GScript

Function Sub(clip c,string tit) {
StackVertical(c.BlankClip(height=20).ScriptClip("""Subtitle(RT_String("%d] %s",current_frame,tit))""",Args="tit",Local=true),c)
}


My test

####################
### MY TESTING ROUTINE
####################

FN = "ParadeFixed.Avi" # Some clip I had to hand [ Cheers John :) ].
c = AviSource(FN)
dc = c
chroma = c # Final Chroma to be used
luma = dc # Final Luma Except where logo lives

SUBS_X=20
SUBS_Y=20
SUBS_W=128
SUBS_H=128
TH=0.04 # YMin/Max detection sensitivity
DEBUG=False # Output to DebugView (Google)
OPACITY=1.0 # For viewing subs positional coords
dcMask = Overlay(dc.BlankClip,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF),x=SUBS_X,y=SUBS_Y).Trim(0,-1)
# Synth subs as Yellow block
dc = Overlay(dc,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFF00),x=SUBS_X,y=SUBS_Y,opacity=OPACITY)
# Make synthesized bad Luma
c = c.Levels(0,1.5,255,32,255-32,coring=false)

# Luma from c clip (BD) but Level'ed to match 720P clip. Want Luma Logo area ONLY from this.
PatchY = c.ScriptClip("return Last.GammaFixFrameY(dc,current_frame,Th,dcMask,FALSE,DEBUG)")
dcY = luma.Overlay(PatchY.Crop(SUBS_X,SUBS_Y,SUBS_W,SUBS_H),x=SUBS_X,y=SUBS_Y)

dcY.MergeChroma(chroma)

TOP = StackHorizontal(Sub(c,"Source (synthesized bad luma)"),Sub(dc,"dc Detect clip good Luma (synth subs as Yellow block)"))
BOT = StackHorizontal(Sub(dcMask,"dcMask, Subs area White"),Sub(Last,"Result: Src chroma, dc luma except src subs area"))
return StackVertical(TOP,BOT)


Your stuff

####################
### YOUR STUFF
####################
# These BOTH need to be cropped of borders (BOTH COLORSPACE MUST BE SAME, YV12 or RGB32)
c = FFmpegSource2("ColorsFrom.mkv").Trim(362,947) # BD
#c = c.Crop(,,,)
dc = FFmpegSource2("NewSource.mkv").Trim(49,634).Spline36Resize(1920,1080,0,24,0,-24) # 720P

chroma = c # Final Chroma to be used
luma = dc # Final Luma Except where logo lives

# Where the Subs Live (c and dc both same dimensions) (MAKE SURE all EVEN coords)
SUBS_X=20
SUBS_Y=20
SUBS_W=128
SUBS_H=128
OPACITY=0.25 # For viewing subs positional coords

# Show synth subs as block for setting above coords, Then Comment out
#return Overlay(dc,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFF00),x=SUBS_X,y=SUBS_Y,opacity=OPACITY)

TH=0.04 # YMin/Max detection sensitivity
DEBUG=False # Output Levels() correction args to DebugView (Google)

dcMask = Overlay(dc.BlankClip,dc.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF),x=SUBS_X,y=SUBS_Y).Trim(0,-1)

# Luma from c clip (BD) but Level'ed to match 720P clip. Want Luma Logo area ONLY from this.
PatchY = c.ScriptClip("return Last.GammaFixFrameY(dc,current_frame,Th,dcMask,FALSE,DEBUG)").ConvertToY8
dcY = luma.ConvertToY8.Overlay(PatchY.Crop(SUBS_X,SUBS_Y,SUBS_W,SUBS_H),x=SUBS_X,y=SUBS_Y)

dcY.MergeChroma(chroma)


Here we used original BD nosubs chroma, and 720P subs luma (upscaled) except in subtitles area where we use BD nosubs luma remapped via GammaFixFrameY to match 720P upscaled luma.

https://s20.postimg.cc/bys9s5jlp/Gamma_Fix_Frame_Y_3_zpspligt7gu.png (https://postimg.cc/image/9u7wr2hyx/)

I'm gonna need two sample clips of a couple of dozen frames [same from each clip] (ideally each from a few seconds [or more] apart in source).
If you see any fringing, its down to mismatch between two clips due to misalignment, there is none in my test as from exact same frames.

jmac698
1st February 2017, 10:03
Looks great btw :) Could try a bit of subpixel shift if there's fringing, and include the edges of the frame in the mask if that's a problem too.

Edit:
I was just thinking, as far as banding in a matched source, I wonder if that watershed denoiser would fix that, or one of the debanding solutions like gradfun. I was just dreaming of a banding interpolator based on watershed principle and filling in with a physically correct Lambertan or Phong (or whatever it's supposed to be) lighting model. What I mean is that in natural light, with an infinite source, a solid material is lighted about linearly (in gamma corrected image) or is metal with highlights (which has sine lighting). Problem with that idea is it doesn't account for textures. Oh well.

StainlessS
19th February 2017, 05:15
dcxero,
Here does the trick :)

Modified GammaFixFrameY (dont use earlier versions, this much better), it not only masks out subtitles area in dc, it also masks out same area
when estimating Gamma for c correction, better match.
All needs done is perhaps get better sub pixel matching alignment pre usage and perhaps some feathering at edge of fixed area (I dont really think I would bother, is pretty damn good as it stands).

Here

GSCript("""
Function GammaFixFrameY(clip c,clip dc,int n,Float "Th",clip "dcMask",Bool "OrigChroma",Bool "DEBUG") {
/*
Modified, If dcMask Supplied, THEN c AND dc AND dcMask MUST all be same size.
c, frame To modify to match dc.
dc, frame that c frame is modified to match.
n, frame number
Th, as for YPlaneMin/Max(Threshold)
dcMask, Optional planarY Mask (Y8 OK), where 128 <= MaskY <= 255 is not used in Histograms (ie Subs area).
OrigChroma, True re-Merge original Chroma.
*/
myName="GammaFixFrameY_Mod: "
n=min(max(n,0),c.FrameCount-1)
c=c.Trim(n,-1) dc=dc.Trim(n,-1)
Th=Default(Th,0.04) # as for YPlaneMin/Max(threshold)
DEBUG=Default(DEBUG,False)
Assert(!dcMask.Defined || (dc.width==c.width&&dc.height==c.height&&dcmask.width==c.width&&dcMask.height==c.height),
\ "MisMatched clip sizes")
OrigChroma=c.IsYUV ? Default(OrigChroma,True) : False
# 1st get dc clip yMin, YMax, and YAve where mask 128 or greater is ignored
Assert(dc.RT_Ystats(n=0,threshold=Th,flgs=$13,prefix="DC_",mask=dcMask,MaskMin=0,MaskMax=127)!=0,"All Masked Out")
# Next get c clip yMin and YMax
c.RT_Ystats(n=0,threshold=Th,flgs=3,prefix="C_") # sets C_YMin and C_YMax
ALDif = 0.00001
PrevAveL = -1.0
gamHi = 10.0 gamLo = 1.0/gamHi
Result= c # Prep not found (original)
while(GamLo < gamHi) {
gamMid = (gamLo + gamHi) / 2.0
TmpC = c.Levels(C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,coring=false)
# TmpC clip YAve where mask 128 or greater is ignored
TmpC.RT_Ystats(n=0,threshold=Th,flgs=$10,prefix="TmpC_",mask=dcMask,MaskMin=0,MaskMax=127)
AveL=TmpC_YAve
if(abs(AveL-PrevAveL)<=ALDif) {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, Not getting any nearer
} else if(AveL < DC_YAve) {
gamLo = gamMid
} else if(AveL > DC_YAve) {
gamHi = gamMid
} else {
Result = TmpC
gamLo = gamHi + 1.0 # Force Exit, exact match
}
PrevAveL = AveL
}
if(DEBUG) {
if(Result != c) { # Result is not original clip.
RT_DebugF("%d] Levels(%d,%.3f,%d, %d,%d,coring=False)",n,C_YMin,gamMid,C_YMax,DC_YMin,DC_YMax,name=myName)
} Else {
RT_DebugF("%d] NOT FOUND",n,name=myName)
}
}
return (OrigChroma) ? Result.MergeChroma(c) : Result
}

Function GammaFixFrameRGB(clip c,clip dc,int n,Float "Th",clip "dcMask",Bool "DEBUG") {
myName="GammaFixFrameRGB: "
DEBUG=Default(DEBUG,False)
(DEBUG)?RT_DebugF("%d] Red Channel",n,name=myName):NOP
R=GammaFixFrameY(c.ShowRed (Pixel_Type="Y8"),dc.ShowRed (Pixel_Type="Y8"),n,Th,dcMask,False,DEBUG)
(DEBUG)?RT_DebugF("%d] Grn Channel",n,name=myName):NOP
G=GammaFixFrameY(c.ShowGreen(Pixel_Type="Y8"),dc.ShowGreen(Pixel_Type="Y8"),n,Th,dcMask,False,DEBUG)
(DEBUG)?RT_DebugF("%d] Blu Channel",n,name=myName):NOP
B=GammaFixFrameY(c.ShowBlue (Pixel_Type="Y8"),dc.ShowBlue (Pixel_Type="Y8"),n,Th,dcMask,False,DEBUG)
return MergeRGB(R,G,B)
}

""") # End of GScript

Function Sub(clip c,string tit,Int "Size") {
Size=Default(Size,20)
StackVertical(
\ c.BlankClip(height=Size).
\ ScriptClip("""Subtitle(RT_String("%d] %s",current_frame,tit),Size=Size)""",
\ Args="tit,Size"),c)
}


/*
BluNoSubs.h264
Trim(49,634)
Crop(0,191,0,-191)
Spline36Resize(3840,1396)

4KHardSubs.h264
Trim(362,947)
*/

FN_Hi="4KHardSubs.h264.AVI" # 3840,1396 SUBS

FN_Lo="BluNoSubs.h264.AVI" # 1920,1080

Hi = AviSource(FN_Hi).ConvertToYV24.Trim(362,947)
Lo = AviSource(FN_Lo).ConvertToYV24.Trim(49,634).Crop(0,191,0,-191).Spline36Resize(3840,1396)

Global SUBS_X = 512
Global SUBS_Y = 1024
Global SUBS_W = 3840 - 2*(SUBS_X)
Global SUBS_H = 268
OPACITY=0.15 # For viewing subs positional coords

AREA = Hi.BlankClip(Width=SUBS_W,Height=SUBS_H,Color=$FFFFFF)

dcMask = Hi.BlankClip.Overlay(AREA,x=SUBS_X,y=SUBS_Y).Trim(0,-1)

dc = Hi.Overlay(AREA,x=SUBS_X,y=SUBS_Y,opacity=OPACITY) # For Display
c = Lo
#return dc # For Setting SUBS Coords

Th=0.04 # YMin/Max detection sensitivity
OrigChroma=False # WARNING, YUV, Levels will modify chroma too to try avoid color shift, this may or may not be desirable.
DEBUG=False # Output Levels() correction args to DebugView (Google)

SSS="""return Last.GammaFixFrameY(dc,current_frame,Th,dcMask,OrigChroma,DEBUG)"""

LoMod = c.ScriptClip(SSS,Args="dc,Th,dcMask,OrigChroma,DEBUG")

HiFixed=Hi.Overlay(LoMod.Crop(SUBS_X,SUBS_Y,SUBS_W,SUBS_H),x=SUBS_X,y=SUBS_Y)

SIZE=64

dc=dc.Sub("Hires with subs and subs Area Marker",Size=SIZE)
c=c.Sub("Lores without subs",Size=SIZE)
LoMod=LoMod.Sub("Luma Modified Lowres to match HiRes",Size=SIZE)
HiFixed=HiFixed.Sub("HiRes with Subs overlayed",Size=SIZE)
TOP=StackHorizontal(dc,c) # Hires with Subs Marker, LowRes,
BOT=StackHorizontal(LoMod,HiFixed) # LowRezModdedToHiResColor, Hires with Subs removed.
StackVertical(TOP,BOT)


Might not be on-line till wednesday (or mobile if so), give a yell if any problems.

EDIT: Where dcMask used, all three c, dc and dcMask need be same size for GammaFixFrameY.

EDIT: Top Subtitled, Bot with subs removed, Magenta is separator.
https://s20.postimg.cc/jssvdjrel/Fixed_zpsdw3wxjic.png (https://postimg.cc/image/mmw0qztkp/)

EDIT: Above, I should have shown edges of overlay somehow, top of images were flush with top of subtitle
area, below, the join is marked via nearly transparent white where subtitle area is.
https://s20.postimg.cc/t1v1no0al/Fixed2_zps7dlv6qud.png (https://postimg.cc/image/bokr8t4zd/)

EDIT: And below, Hires subs clip overlaid by nosubs clip, but without luma correction, is only slightly visible so
there was only a little bit of correction (if any) needed.
https://s20.postimg.cc/wa44egby5/Fixed3_zpspxetlr8l.png (https://postimg.cc/image/q96fhdpbt/)

EDIT: Levels used for correction of above frame Levels(18,1.043,191, 20,215,coring=False).

EDIT: You will probably want to mod subs coords.

EDIT: I converted to AVI UT_Video via ffmpeg, you mod to whatever frame accurate source filter you like.
NOTE, Output will be YV24, I use current MPC-HC and VirtualDubFilterMOD so no problems with YV24 (mod output if necessary).

jmac698
19th February 2017, 08:23
Looks great! gj

dcxero
20th February 2017, 11:25
Wow, thanks StainlessS. This indeed does the trick! I ran through a few test encodes, and it looks pretty great. I'm using the HiRest overlay for most shots, though some angles have too much brightness going on where the subtitles are, which makes the box appear more visibly, so I'm using the normal/upscaled overlay in place of those

Next up, I'm going to map out the locations for each subtitle and encode them in sections and then merge them, so if there's any hint of a visible overlay, it's only as small as it has to be for that particular line of text

:thanks:

StainlessS
20th February 2017, 16:57
dcxero, you (or I about Thursday or later), could create script to detect Subtitles in SUBS area of RGB version of HiRes, using
RT_Stats RGBInRangeLocate() [see RGBInRangeLocate_Test.avs in AVS directory of RT_Stats] and create a ClipClop()
Frames range file in format "ClipNumber_1 RangeStartFrame, RangeEndFRame".
Then use ClipClop to use Fixed clip in those ranges, whole helluva lot easier than doing it in bits.

EDIT: By manual editing of range file, could change ClipClop clip index to select fixed(1) or normalUpscale(2) or
Source(unspecified range in range file) where no subs.

For the RGBInRangeLocate scan could just crop out Subs area and scan on that (looking for Yellow text).

EDIT: Here is the locator demo, bit in blue is the active part, coords not necessary (all 0) if RGB area cropped out),
Easier to use RT_WriteFile for text output.


# RGBInRangeLocate_Test

# NOTE, Angle corners show detection.
# req RT_Stats & mt_tools_2

RED =$AA # Object Color to look for
GREEN =$CC
BLUE =$FF

OBJECT_W = 10 # Object Width
OBJECT_H = 10 # Object Height

AREA_X = 0 # Area of frame to search (0,0,0,0=full frame, As Crop)
AREA_Y = 0
AREA_W = 0
AREA_H = 0

R_MIN = RED R_MAX = RED
G_MIN = GREEN G_MAX = GREEN
B_MIN = BLUE B_MAX = BLUE

# Overlay Converts RGB->YUV->RGB (can set inexact color). Use Layer Instead, MUST set ALPHA
RGBA=((255 * 256 + RED)*256+GREEN)*256+BLUE

COORDINATES="RGB_Coordinates.txt"

# Fake input clip, for testing
a = BlankClip(length=480,width=480,height=480)
b = a.BlankClip(width=OBJECT_W, height=OBJECT_H, color=RGBA)
a.Animate (0, a.FrameCount () - 1, "Layer", b,"add",257, 0, 0, b,"add",257, 479, 479)

MarkerColor = $FFFF00 # Detection marker color
MARKER_TYPE = false # False = Corners only marker, True=Full Rect Marker
Msk = Last.RectMarker(OBJECT_W+8,OBJECT_H+8,MARKER_TYPE)
Mrk = Msk.BlankClip(Color=MarkerColor)


NOTFOUND="NOT FOUND" # Will be NOT FOUND, As OBJECT moves out of frame at end of clip (can no longer find size OBJECT_W x OBJECT_H)
sep = ", "
Ket = "] "
RT_FileDelete(COORDINATES) # Delete coordinates file
DEBUG=False
ScriptClip ("""
Bingo=RT_RgbInRangeLocate(x=AREA_X,y=AREA_Y,w=AREA_W,h=AREA_H,
\ RLo=R_MIN,Rhi=R_MAX,GLo=G_MIN,Ghi=G_MAX,BLo=B_MIN,Bhi=B_MAX,Baffle_W=OBJECT_W,Baffle_H=OBJECT_H,debug=DEBUG)
(Bingo) ? WriteFile (COORDINATES,"current_frame", "Ket", "RGBIRL_X", "sep", "RGBIRL_Y")
\ : WriteFile (COORDINATES,"current_frame", "Ket", "NOTFOUND")


(Bingo) ? Overlay(Mrk,x=RGBIRL_X-4,y=RGBIRL_Y-4,Mask=Msk,Mode="Blend")
\ : NOP
Return Last
""")
Return Last

Function RectMarker(clip c,int W, Int H,Bool "Hit") {
# req RT_Stats & mt_tools_2, Returns YV12 frame WxH same FPS as c and without audio
Hit=Default(hit,False)
InFix = (Hit)
\ ? RT_string("(((x<=1|x>=%d|y<=1|y>=%d)&(x<4|x>=%d)&(y<4|y>=%d)|(x==0|x==%d|y==0|y==%d)))?255:0", W-2,H-2,W-4,H-4,W-1,H-1)
\ : RT_string("((x<=1|x>=%d|y<=1|y>=%d)&(x<4|x>=%d)&(y<4|y>=%d))?255:0", W-2,H-2,W-4,H-4)
c.Blankclip(width=W,height=H,Length=1,pixel_type="YV12").Killaudio
return mt_lutspa(relative = false,yExpr=mt_Polish(InFix), chroma = "128")
}



EDIT: might be tricky to get start and end of range for writing, above only detects individual frames (need keep CurrentlyInSubsRange status).

EDIT: Something like this should work (completely untested)

Last=CroppedSubsSrc

FRAMES="ClipClopCmd.Txt
RT_FileDelete(FRAMES)

Global OBJ_W = Mimumum_subs_CharString_width_in_Pixels
Global OBJ_H = Mimumum_subs_CharString_height_in_Pixels

Global R_MIN = Whatever min R component for Yellow # Allow for variation in color.
Global R_MAX = Whatever max R component for Yellow
Global G_MIN = Whatever min G component for Yellow
Global G_MAX = Whatever max G component for Yellow
Global B_MIN = Whatever min B component for Yellow
Global B_MAX = Whatever max B component for Yellow

Global RngS=-1
Global RngE=-1

GSCript("""
Function Fn(clip c, int n) {
c
Bingo=RT_RgbInRangeLocate(n=n,RLo=R_MIN,Rhi=R_MAX,GLo=G_MIN,Ghi=G_MAX,BLo=B_MIN,Bhi=B_MAX,Baffle_W=OBJ_W,Baffle_H=OBJ_H)
if(Bingo) {
if(RngS<0) {
Global RngS=n # New Range
}
Global RngE=n # Currently detected EndOfRange
if(n==c.FrameCount-1) {
RT_WriteFile (FRAMES,"1 %d,%d",RngS,RngE) # Closing last range
Global RngS=-1 Global RngE=-1 # Range Written and done
}
} else {
if(RngS>=0) {
RT_WriteFile (FRAMES,"1 %d,%d",RngS,RngE) # Closing range
}
Global RngS=-1 Global RngE=-1
}
Return (Bingo)
}
""")

SSS="""
RT_Subtitle("Detect=%s",Fn(current_frame))
Return Last
""")

ScriptClip (SSS)

Return Last


EDIT: Fixed bug in RT_WriteFile lines (we dont write frame n, only RngS and RngE).

EDIT: ClipClop script (untested)

Hi = HiSrc # Original hi clip with subs.
Lo = LoSrc # Original hi clip with overlayed low res in subs area.
Fx = FixedSrc # Original hi clip with overlayed gamma fixed low res in subs area.

CMD="ClipClopCmd.Txt
SHOW=True

# Below using frames file clip 1, Overlayed Lowres, No Gamma Fix, manual edited clip CMD indexes to frame 2 where Gamma fix wanted.
# If clip index 1 wanted for gamma fixed, then swap clip order in ClipClop and exchange clip indexes in Nicknames.
NickNames =""" # Psuedonyms for clips (clip index number). Shown when SHOW=true.
Overlayed_LowRez = 1 # Overlayed Lowres, No Gamma Fix
Fixed_Lowrez = 2 # Gamma Adjusted Lowres Overlayed, manual edited clip index to 2
"""

ClipClop(Hi,Lo,Fx,Cmd=CMD,nickname=NickNames,Show=SHOW)