zee944
1st January 2013, 19:00
I wrote a function which automatically fixes the chroma shift errors. It can happen after colorspace conversions/pixel cropping/adding was done on the video with not enough far-seeing. Or at least, that was my target.
The main idea is to select the edges both on the chroma and luma plane, compare them, and find out in which position they would match the best. If the necessary shift values has been figured out, the shift can be executed (or just you could just display the values). Since the method is quite slow and there's no averaging involved, it is more practical to use it only as guide, checking how much chroma shifting the video roughly need and decide on the exact values on your own.
The script needs GScript and mt_masktools. I've tried to use the For loop of Gavino's GScript, but it didn't seem to work within GScriptClip environment (I'm not sure if it should or shouldn't), so I made my own For loop.
It's still a beta, and not a finished function. It works, however I'm not sure it works perfectly right. I've tested it on bad material and on good material too which I ruined with this line on purpose:
MergeChroma(Lanczos4Resize(width, height, 1.5, -1.0)) # generates chroma shift error on the video
AutoChromaFix() does it back roughly, and by looking at it, the result is fine, but I'm not sure it's as accurate as it should be. I'm quite unsure about this part of the script:
tempU=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).UToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
tempV=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).VToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
The PointResize part, to be exact. Is this the right way to resize the U and V plane? Will the U and V values at their right place, where they should be to be compared with the Y plane? Don't they need a half-pixel or one pixel shift after resizing? Can someone tell me?
The script itself:
LoadPlugin("mt_masktools[2.0a30].dll")
LoadPlugin("GRunT[1.0.1].dll")
MPEG2Source("whatever.d2v")
AssumeFPS(23.976)
MergeChroma(Lanczos4Resize(width, height, 1.5, -1.0)) # generates chroma shift error on the video
#AutoChromaFix(referenceframe=15000)
#AutoChromaFix(accuracy=0.1)
AutoChromaFix()
function AutoChromaFix(clip clp, float "referenceframe", bool "nodisplay", bool "nofix", float "accuracy", int "maxshift")
{
# 1.0beta, by z. ndmn, 2013.I.1.
referenceframe = default(referenceframe, -1) # if no reference frame is given then the function will do different chroma correction on each frame
# if reference frame is given then it'll do chroma correction on the full video based on one (reference) frame
accuracy = default(accuracy, 0.25) # 0 < precistiy <= 0.5. '0.5' mean half-pixel accuracy. The lower the better but slower. Default is quarter-pixel accuracy.
maxshift = default(maxshift, 2) # maximum assumed chroma shift error to fix. A two pixel shift (default value) is quite a robust chroma error, but if your source
# possibly have bigger shift than that, raise it, but the script will be slower. Has to be even number. (Note: Actually the function
# goes a little beyond the given limit (between +0.25 and +0.5, depending on the accuracy). For instance if 2 is given, then it'll
# look for shifts up to somewhere between 2.25..2.5.)
nodisplay = default(nodisplay, False) # displays detected shift values by default, it can be turned off
nofix = default(nofix, False) # fixes chroma by default, it can be turned off (minimal speed gain only, the time consuming part is finding the right values)
clp
StartFrame = referenceframe<0 ? 0 : referenceframe
EndFrame = referenceframe<0 ? FrameCount : referenceframe
GScriptClip("""
global BestChromaMatchHorizontal = 0
global BestChromaMatchVertical = 0
ForLoop(last, -1*(maxshift), maxshift, "CheckChromaHorizontal",
\step=1.0 , INT1=StartFrame, INT2=EndFrame, FLO1=-1*(maxshift), FLO2=maxshift, FLO3=1.0)
ForLoop(last, BestChromaMatchHorizontal-0.5, BestChromaMatchHorizontal+0.5, "CheckChromaHorizontal",
\step=accuracy , INT1=StartFrame, INT2=EndFrame, FLO1=BestChromaMatchVertical-0.5, FLO2=BestChromaMatchVertical+0.5, FLO3=accuracy)
function CheckChromaHorizontal(clip clp, float n, float loopvariable, string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3",
\float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3") { clp
ForLoop(last, FLO1, FLO2, "CheckChromaVertical", step=FLO3, INT1=INT1, INT2=INT2, FLO1=loopvariable)
function CheckChromaVertical(clip clp, float n, float loopvariable, string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3",
\float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3") { clp
StartFrame=INT1
EndFrame=INT2
Xtotry=FLO1
Ytotry=loopvariable
Trim(StartFrame, EndFrame)
Tweak(sat=2.0)
LumaEdges=GreyScale().mt_edge("sobel", U=2, V=2)
tempU=MergeChroma(Lanczos4Resize(width, height, BestChromaMatchHorizontal, BestChromaMatchVertical)).
\UToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
tempV=MergeChroma(Lanczos4Resize(width, height, BestChromaMatchHorizontal, BestChromaMatchVertical)).
\VToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
ChromaEdgesBestSoFar=Overlay(tempU, tempV, mode="Add")
#ChromaEdgesBestSoFar=tempU
tempU=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).UToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
tempV=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).VToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
ChromaEdgesToTry=Overlay(tempU, tempV, mode="Add")
#ChromaEdgesToTry=tempU
IsItBetter = AverageLuma(Overlay(LumaEdges, ChromaEdgesToTry, mode="subtract")) <
\AverageLuma(Overlay(LumaEdges, ChromaEdgesBestSoFar, mode="subtract")) ? \
True : False
global BestChromaMatchHorizontal = (IsItBetter==True) ? Xtotry : BestChromaMatchHorizontal
global BestChromaMatchVertical = (IsItBetter==True) ? Ytotry : BestChromaMatchVertical
}
}
(nodisplay==True) ? last : subtitle("chroma correction h shift="+string(BestChromaMatchHorizontal)+" "+"v shift="+string(BestChromaMatchVertical))
(nofix==True) ? last : MergeChroma(Lanczos4Resize(width, height, BestChromaMatchHorizontal, BestChromaMatchVertical)) # we're fixing the chroma eventually
""", args="clp, StartFrame, EndFrame, accuracy, nodisplay, nofix, maxshift", local=true)
function ForLoop(clip clp, float from, float to, string commtoex, float "step", string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3",
\float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3")
{
# 1.0, by z. ndmn, 2012.XII.23.
step = default(step, 1) # if no step is given, it'll be 1
NumberOfTotalRuns = floor (((to - from) / step) + 1) # number of times the loop body will run
ForLoopEngine(clp, NumberOfTotalRuns, NumberOfTotalRuns, from, step, commtoex, STR1=STR1, STR2=STR2, STR3=STR3, INT1=INT1, INT2=INT2, INT3=INT3,
\FLO1=FLO1, FLO2=FLO2, FLO3=FLO3, BOO1=BOO1, BOO2=BOO2, BOO3=BOO3)
function ForLoopEngine(clip clp, int NumberOfTotalRuns, float NumberOfRemainingRuns, float loopvariable, float step, string commtoex,
\string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3", float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3")
{
clp
Eval(commtoex+"(last, (NumberOfTotalRuns - NumberOfRemainingRuns) + 1, loopvariable, STR1=STR1, STR2=STR2, STR3=STR3,
\INT1=INT1, INT2=INT2, INT3=INT3, FLO1=FLO1, FLO2=FLO2, FLO3=FLO3, BOO1=BOO1, BOO2=BOO2, BOO3=BOO3)")
clp=last
Assert(NumberOfRemainingRuns > 0)
return (NumberOfRemainingRuns == 1)
\ ? clp
\ : ForLoopEngine(clp, NumberOfTotalRuns, NumberOfRemainingRuns - 1, loopvariable + step, step, commtoex, STR1=STR1, STR2=STR2, STR3=STR3,
\INT1=INT1, INT2=INT2, INT3=INT3, FLO1=FLO1, FLO2=FLO2, FLO3=FLO3, BOO1=BOO1, BOO2=BOO2, BOO3=BOO3)
}
}
}
The main idea is to select the edges both on the chroma and luma plane, compare them, and find out in which position they would match the best. If the necessary shift values has been figured out, the shift can be executed (or just you could just display the values). Since the method is quite slow and there's no averaging involved, it is more practical to use it only as guide, checking how much chroma shifting the video roughly need and decide on the exact values on your own.
The script needs GScript and mt_masktools. I've tried to use the For loop of Gavino's GScript, but it didn't seem to work within GScriptClip environment (I'm not sure if it should or shouldn't), so I made my own For loop.
It's still a beta, and not a finished function. It works, however I'm not sure it works perfectly right. I've tested it on bad material and on good material too which I ruined with this line on purpose:
MergeChroma(Lanczos4Resize(width, height, 1.5, -1.0)) # generates chroma shift error on the video
AutoChromaFix() does it back roughly, and by looking at it, the result is fine, but I'm not sure it's as accurate as it should be. I'm quite unsure about this part of the script:
tempU=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).UToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
tempV=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).VToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
The PointResize part, to be exact. Is this the right way to resize the U and V plane? Will the U and V values at their right place, where they should be to be compared with the Y plane? Don't they need a half-pixel or one pixel shift after resizing? Can someone tell me?
The script itself:
LoadPlugin("mt_masktools[2.0a30].dll")
LoadPlugin("GRunT[1.0.1].dll")
MPEG2Source("whatever.d2v")
AssumeFPS(23.976)
MergeChroma(Lanczos4Resize(width, height, 1.5, -1.0)) # generates chroma shift error on the video
#AutoChromaFix(referenceframe=15000)
#AutoChromaFix(accuracy=0.1)
AutoChromaFix()
function AutoChromaFix(clip clp, float "referenceframe", bool "nodisplay", bool "nofix", float "accuracy", int "maxshift")
{
# 1.0beta, by z. ndmn, 2013.I.1.
referenceframe = default(referenceframe, -1) # if no reference frame is given then the function will do different chroma correction on each frame
# if reference frame is given then it'll do chroma correction on the full video based on one (reference) frame
accuracy = default(accuracy, 0.25) # 0 < precistiy <= 0.5. '0.5' mean half-pixel accuracy. The lower the better but slower. Default is quarter-pixel accuracy.
maxshift = default(maxshift, 2) # maximum assumed chroma shift error to fix. A two pixel shift (default value) is quite a robust chroma error, but if your source
# possibly have bigger shift than that, raise it, but the script will be slower. Has to be even number. (Note: Actually the function
# goes a little beyond the given limit (between +0.25 and +0.5, depending on the accuracy). For instance if 2 is given, then it'll
# look for shifts up to somewhere between 2.25..2.5.)
nodisplay = default(nodisplay, False) # displays detected shift values by default, it can be turned off
nofix = default(nofix, False) # fixes chroma by default, it can be turned off (minimal speed gain only, the time consuming part is finding the right values)
clp
StartFrame = referenceframe<0 ? 0 : referenceframe
EndFrame = referenceframe<0 ? FrameCount : referenceframe
GScriptClip("""
global BestChromaMatchHorizontal = 0
global BestChromaMatchVertical = 0
ForLoop(last, -1*(maxshift), maxshift, "CheckChromaHorizontal",
\step=1.0 , INT1=StartFrame, INT2=EndFrame, FLO1=-1*(maxshift), FLO2=maxshift, FLO3=1.0)
ForLoop(last, BestChromaMatchHorizontal-0.5, BestChromaMatchHorizontal+0.5, "CheckChromaHorizontal",
\step=accuracy , INT1=StartFrame, INT2=EndFrame, FLO1=BestChromaMatchVertical-0.5, FLO2=BestChromaMatchVertical+0.5, FLO3=accuracy)
function CheckChromaHorizontal(clip clp, float n, float loopvariable, string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3",
\float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3") { clp
ForLoop(last, FLO1, FLO2, "CheckChromaVertical", step=FLO3, INT1=INT1, INT2=INT2, FLO1=loopvariable)
function CheckChromaVertical(clip clp, float n, float loopvariable, string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3",
\float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3") { clp
StartFrame=INT1
EndFrame=INT2
Xtotry=FLO1
Ytotry=loopvariable
Trim(StartFrame, EndFrame)
Tweak(sat=2.0)
LumaEdges=GreyScale().mt_edge("sobel", U=2, V=2)
tempU=MergeChroma(Lanczos4Resize(width, height, BestChromaMatchHorizontal, BestChromaMatchVertical)).
\UToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
tempV=MergeChroma(Lanczos4Resize(width, height, BestChromaMatchHorizontal, BestChromaMatchVertical)).
\VToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
ChromaEdgesBestSoFar=Overlay(tempU, tempV, mode="Add")
#ChromaEdgesBestSoFar=tempU
tempU=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).UToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
tempV=MergeChroma(Lanczos4Resize(width, height, Xtotry, Ytotry)).VToY.PointResize(width, height).mt_edge("sobel", U=2, V=2)
ChromaEdgesToTry=Overlay(tempU, tempV, mode="Add")
#ChromaEdgesToTry=tempU
IsItBetter = AverageLuma(Overlay(LumaEdges, ChromaEdgesToTry, mode="subtract")) <
\AverageLuma(Overlay(LumaEdges, ChromaEdgesBestSoFar, mode="subtract")) ? \
True : False
global BestChromaMatchHorizontal = (IsItBetter==True) ? Xtotry : BestChromaMatchHorizontal
global BestChromaMatchVertical = (IsItBetter==True) ? Ytotry : BestChromaMatchVertical
}
}
(nodisplay==True) ? last : subtitle("chroma correction h shift="+string(BestChromaMatchHorizontal)+" "+"v shift="+string(BestChromaMatchVertical))
(nofix==True) ? last : MergeChroma(Lanczos4Resize(width, height, BestChromaMatchHorizontal, BestChromaMatchVertical)) # we're fixing the chroma eventually
""", args="clp, StartFrame, EndFrame, accuracy, nodisplay, nofix, maxshift", local=true)
function ForLoop(clip clp, float from, float to, string commtoex, float "step", string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3",
\float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3")
{
# 1.0, by z. ndmn, 2012.XII.23.
step = default(step, 1) # if no step is given, it'll be 1
NumberOfTotalRuns = floor (((to - from) / step) + 1) # number of times the loop body will run
ForLoopEngine(clp, NumberOfTotalRuns, NumberOfTotalRuns, from, step, commtoex, STR1=STR1, STR2=STR2, STR3=STR3, INT1=INT1, INT2=INT2, INT3=INT3,
\FLO1=FLO1, FLO2=FLO2, FLO3=FLO3, BOO1=BOO1, BOO2=BOO2, BOO3=BOO3)
function ForLoopEngine(clip clp, int NumberOfTotalRuns, float NumberOfRemainingRuns, float loopvariable, float step, string commtoex,
\string "STR1", string "STR2", string "STR3", int "INT1", int "INT2", int "INT3", float "FLO1", float "FLO2", float "FLO3", bool "BOO1", bool "BOO2", bool "BOO3")
{
clp
Eval(commtoex+"(last, (NumberOfTotalRuns - NumberOfRemainingRuns) + 1, loopvariable, STR1=STR1, STR2=STR2, STR3=STR3,
\INT1=INT1, INT2=INT2, INT3=INT3, FLO1=FLO1, FLO2=FLO2, FLO3=FLO3, BOO1=BOO1, BOO2=BOO2, BOO3=BOO3)")
clp=last
Assert(NumberOfRemainingRuns > 0)
return (NumberOfRemainingRuns == 1)
\ ? clp
\ : ForLoopEngine(clp, NumberOfTotalRuns, NumberOfRemainingRuns - 1, loopvariable + step, step, commtoex, STR1=STR1, STR2=STR2, STR3=STR3,
\INT1=INT1, INT2=INT2, INT3=INT3, FLO1=FLO1, FLO2=FLO2, FLO3=FLO3, BOO1=BOO1, BOO2=BOO2, BOO3=BOO3)
}
}
}