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. |
3rd April 2011, 23:01 | #1 | Link |
Registered User
Join Date: Jan 2011
Posts: 121
|
Help debugging recursive runtime script
I have some interlaced VHS captures with jittery/bouncing fields. The fields randomly move up or down a few [whole] pixels vertically, screwing up the interlacing (and potentially making deinterlacing harder). I've tried an AVT-8710 TBC, but it only makes things worse (in addition to compressing some of the upper luma range). Maybe it's broken. I've also tried solutions like Depan/DepanStabilize, Stab, and the function g-force created that preceded Stab, but they don't work nearly as well as a brute force routine.
My naive brute force version does the following:
It's giving me fantastic results, but there are three weaknesses:
In response, I'm trying to create a recursive version that starts with small shifts and moves on to progressively larger shifts when the result seems to be improving. Unfortunately, this requires runtime recursion. According to Gavino's GRunT thread, you should be able to call runtime functions within user functions called by runtime functions, but...I'm getting a crash with this version. Any pointers? Note that this requires Masktools 2 and GRunT. The main function is Stabilize, and it requires planar input due to the mt_edge call. It also assumes TFF order for now, since that's what my captures are...but come to think of it, the AssumeTFF calls are probably unnecessary in this case, regardless of field order. Code:
### MAIN FUNCTION ### function Stabilize(clip orig) { # Get the clip's combed edges and weave them with the clip itself. origedges = orig.GetCombedEdges() origandedgeswoven = Interleave(orig, origedges).AssumeFieldBased.Weave() # Stabilize in "clockwise" and "counterclockwise" directions and choose the best result. # If orig is best, both clockwise and counterclockwise directions will return it. # No additional test is necessary. bestandedgeswoven = ChooseLessCombedFramesWovenEdges(StabilizeClockwise(origandedgeswoven), \ StabilizeCounterclockwise(origandedgeswoven)) # Extract and return the actual clip best = bestandedgeswoven.SeparateFields.SelectEven.AssumeFrameBased() return best } ### STABILIZE SUBROUTINES ### function GetCombedEdges(clip c) { # Vertical edges between adjacent lines are a decent measure of combing. # The Greyscale call is probably useless, so consider removing it. # The levels call could probably use more testing and better parameters, # but it's working pretty well. return c.mt_edge("0 0 0 0 -1 0 0 1 0", 0, 255, 0, 255).Greyscale().Levels(0, 5, 64, 0, 255) } function ChooseLessCombedFramesWovenEdges(clip try1andedgeswoven, clip try2andedgeswoven) { # Separate edges from clips, compare brightness of edges, and return the edge-woven clip # with the dimmest edges. Chances are one or both args are redundant, but whatev. return GConditionalFilter(try1andedgeswoven, try1andedgeswoven, try2andedgeswoven, """ AverageLuma(try1andedgeswoven.SeparateFields.SelectOdd()) < AverageLuma(try2andedgeswoven.SeparateFields.SelectOdd()) """, args = "try1andedgeswoven, try2andedgeswoven", local = true) } # Moving even fields up is almost equivalent to moving odd fields down. # Do them together, and call them "clockwise." function StabilizeClockwise(clip currentandedgeswoven) { # Get clips with odd fields shifted down a line and even fields shifted up a line current = currentandedgeswoven.SeparateFields.SelectEven.AssumeFrameBased().AssumeTFF.SeparateFields() evenup = current.ShiftEvenLumaAndChroma(0.0, 1.0).Weave() odddown = current.ShiftOddLumaAndChroma(0.0, -1.0).Weave() # Get combed edges of the new clips evenupedges = evenup.GetCombedEdges() odddownedges = odddown.GetCombedEdges() # Weave clips with edges evenupandedgeswoven = Interleave(evenup, evenupedges).AssumeFieldBased.Weave() odddownandedgeswoven = Interleave(odddown, odddownedges).AssumeFieldBased.Weave() # Which is better? oddorevenandedgeswoven = ChooseLessCombedFramesWovenEdges(evenupandedgeswoven, odddownandedgeswoven) # Which is best? bestandedgeswoven = ChooseLessCombedFramesWovenEdges(currentandedgeswoven, oddorevenandedgeswoven) # If current is not the best, recurse again using the best return GScriptClip(currentandedgeswoven, """ return AverageLuma(currentandedgeswoven) == AverageLuma(bestandedgeswoven) ? currentandedgeswoven \ : StabilizeClockwise(bestandedgeswoven) """, args = "currentandedgeswoven, bestandedgeswoven", local = True) } # Moving even fields down is almost equivalent to moving odd fields up. # Do them together, and call them "counterclockwise." function StabilizeCounterclockwise(clip currentandedgeswoven) { # Get clips with odd fields shifted down a line and even fields shifted up a line current = currentandedgeswoven.SeparateFields.SelectEven.AssumeFrameBased().AssumeTFF.SeparateFields() evendown = current.ShiftEvenLumaAndChroma(0.0, -1.0).Weave() oddup = current.ShiftOddLumaAndChroma(0.0, 1.0).Weave() # Get combed edges of the new clips evendownedges = evendown.GetCombedEdges() oddupedges = oddup.GetCombedEdges() # Weave clips with edges evendownandedgeswoven = Interleave(evendown, evendownedges).AssumeFieldBased.Weave() oddupandedgeswoven = Interleave(oddup, oddupedges).AssumeFieldBased.Weave() # Which is better? oddorevenandedgeswoven = ChooseLessCombedFramesWovenEdges(evendownandedgeswoven, oddupandedgeswoven) # Which is best? bestandedgeswoven = ChooseLessCombedFramesWovenEdges(currentandedgeswoven, oddorevenandedgeswoven) # If current is not the best, recurse again using the best return GScriptClip(currentandedgeswoven, """ return AverageLuma(currentandedgeswoven) == AverageLuma(bestandedgeswoven) ? currentandedgeswoven \ : StabilizeCounterclockwise(bestandedgeswoven) """, args = "currentandedgeswoven, bestandedgeswoven", local = True) } ### UTILITY FUNCTIONS ### function SubpixelShift(clip input, float deltax, float deltay, bool "sharp") { sharp = Default(sharp, True) return sharp ? Spline36Resize(input, input.Width(), input.Height(), deltax, deltay, input.Width(), input.Height()) \ : BilinearResize(input, input.Width(), input.Height(), deltax, deltay, input.Width(), input.Height()) } function ShiftOddLumaAndChroma(clip c, float deltax, float deltay, bool "sharp") { deltax = Default(deltax, 0.0) deltay = Default(deltay, 0.0) sharp = Default(sharp, True) return Interleave(c.SelectEven(), c.SubpixelShift(deltax, deltay, sharp).SelectOdd()) } Last edited by Mini-Me; 4th April 2011 at 02:41. Reason: Fixed infinite loop. It works now! I'll create a thread once I have a release-ready version. |
4th April 2011, 00:29 | #3 | Link | ||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,433
|
I don't yet fully understand how your code is designed to work, but are you sure the recursion bottoms out?
Basically, the recursion occurs in the functions StabilizeClockwise and StabilizeCounterclockwise: Quote:
Quote:
Last edited by Gavino; 4th April 2011 at 00:32. |
||
4th April 2011, 01:47 | #4 | Link | ||
Registered User
Join Date: Jan 2011
Posts: 121
|
Quote:
I think I need to pass the recursion level as a parameter though and put a limit on it. Ordinary frames seem to be going quite fast, but it's taking too long to find the nonexistent "sweet spot" for the garbage frames at the beginning of the capture. Quote:
I know there are some other threads where posters needed something like this, so once I have the script "release ready," I'll create a new thread and officially "publish" it for use under the GPL. In the meantime, I'll fix the infinite loop above script in case anyone could use it ASAP. Last edited by Mini-Me; 4th April 2011 at 03:18. |
||
4th April 2011, 04:21 | #6 | Link |
Registered User
Join Date: Jan 2011
Posts: 121
|
I'm glad to hear it!
The biggest problem right now is that it's TOO good at lining up fields. In its current form, it can't differentiate between VCR jitter and a vertically panning camera, so whenever the camera pans vertically, it lines up the fields so they match. This eliminates a lot of combing in the woven image, but running it through a bob deinterlacer (or simply separating fields) shows jerkier vertical panning. Instead of smoothly panning up or down every field, it's only panning on even fields, and it's vertically aligning the odd fields with the even ones. (Doing clip.Stabilize.SeparateFields.Doubleweave() shows almost no combing on even frames and tons of combing on odd frames when the camera pans vertically.) I'm thinking I should base the algorithm on a doubleweave and select the permutation that has the least combined combing between the two frames associated with each field. Coding it is going to get hairy, but it probably needs to be done. UPDATE: I'm working on the doubleweave solution to eliminate the field-pairing bias, but it's pretty difficult. Since each field's shift decision affects two frames in the doubleweave, it affects the other shift decisions of those frames, which affect their neighbors, etc. Technically, I guess this is something like a sparse linear system with diagonal and near-diagonal nonzero elements in a gigantic numfields x numfields size matrix. Obviously that isn't solvable in Avisynth, but hopefully I can hack together something sane. Last edited by Mini-Me; 4th April 2011 at 06:39. |
28th May 2011, 17:26 | #7 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,433
|
@Mini-Me
Is this the same as (or similar to) the code that you are currently having memory problems with (reported here)? In post #3 above, I said: Quote:
|
|
31st May 2011, 04:30 | #8 | Link | |
Registered User
Join Date: Jan 2011
Posts: 121
|
Quote:
The new version shares the ScriptClip recursion of the original, and it's actually a lot longer, so what you say seems very plausible. Does the string table accumulate entries every frame without ever being cleared? If so, is there a reason this is necessary, or could it be changed someday to recycle the memory? Last edited by Mini-Me; 31st May 2011 at 04:32. |
|
31st May 2011, 11:07 | #9 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,433
|
Quote:
I can think of a number of ways this could be improved/fixed, including changes to ScriptClip itself, but normally there is a simple workaround - use functions to cut down the size of the run-time script. Taking this approach to its limit, you can even replace the entire run-time script by a function call. Instead of writing Code:
ScriptClip(""" ... # very long script """) Code:
function f(clip c, int current_frame) { c ... # code from run-time script } ... ScriptClip("f(current_frame)") (BTW The current_frame parameter is not needed if using GRunT, since it makes current_frame global). In your case, the recursive use of ScriptClip adds a further complication, but I think you could restructure your code to call ScriptClip once and do the recursion inside it. In fact, you said in post #4 that you were able to do this with your earlier code. Last edited by Gavino; 31st May 2011 at 11:09. |
|
1st June 2011, 15:02 | #10 | Link | |
Registered User
Join Date: Jan 2011
Posts: 121
|
Quote:
Although most of the work (and all of the recursion) is already done inside of helper functions, I'll take that approach to its limit as you suggest and call a single function from within GScriptClip, which then continues to delegate work. Thanks for the advice, and I'll report back after some testing! Last edited by Mini-Me; 1st June 2011 at 15:30. |
|
1st June 2011, 16:52 | #11 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,433
|
Quote:
|
|
3rd June 2011, 17:40 | #12 | Link | |
Registered User
Join Date: Jan 2011
Posts: 121
|
Quote:
Reducing the ScriptClip text to a single function call improved things considerably. I have some other [local] recursive runtime functions that still eventually run out of memory, but it takes about five times longer now. It actually takes almost a million frames now before memory usage gets high enough to crash. Practically speaking, that only happens when I run the functions on multiple source files in the same script (long story ). The stabilization function doesn't seem to crash at all anymore, because I can only use it once per script anyway (it uses a global variable to track changes to the previous frame, and it uses a second global to store the previous frame number and check whether the first can be used). It seems that any ScriptClip function will eventually crash given enough frames, and mine are probably pushing that limit now. I'm looking forward to releasing them, but I'm still tweaking them for now. Thank you for your help! |
|
24th February 2017, 03:46 | #13 | Link | |
Registered User
Join Date: Nov 2016
Posts: 157
|
Quote:
Then, I followed another, maybe unhortodox, path, and strangely it worked - still don't know why! Can't remember what was in the functions, but it was something like the following Before: Code:
function x(clip clip, int parameter) { global parameter=parameter Gscriptclip(""" ... #filter 1 using current_frame ... #filter 2 using current_frame ... #filter 3 using current_frame """)} Code:
function x(clip clip, int parameter) { global parameter=parameter clip Gscriptclip(""" ... #filter 1 using current_frame """)} function y(clip clip, int parameter) { global parameter=parameter clip Gscriptclip(""" ... #filter 2 using current_frame """)} function z(clip clip, int parameter) { global parameter=parameter clip Gscriptclip(""" ... #filter 3 using current_frame """)} function all(clip clip, int parameter) { global parameter=parameter clip.x(parameter).y(parameter).z(parameter) } |
|
24th February 2017, 12:22 | #14 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,433
|
Quote:
Regarding string table memory use, there is no difference between your 'before' and 'after' variants - the total run-time script size is effectively the same - so I don't understand why one should work and not the other. But I note that there is a potential problem with your use of global variables (in both versions). If any of the functions is called more than once (with different parameter values), the run-time scripts for all except the last invocation of the function will see the wrong value for the global. This is because all the assignments to the global occur at compile-time, before any of the run-time scripts are evaluated, and so the run-time script for each invocation of the function sees only the final value of the global. (See The script execution model/Scope and lifetime of variables) For example, with Code:
function x(clip clip, int parameter) { global parameter=parameter ScriptClip(clip, """ ... """) } ... x(1) ... x(2) That's why GRunT (GScriptClip, etc) introduced the args parameter to 'freeze' compile-time values into a run-time script - the problem above is solved by writing Code:
function x(clip clip, int parameter) { # global parameter=parameter : removed Gscriptclip(clip, """ ... """, args="parameter") } |
|
24th February 2017, 14:22 | #15 | Link |
Registered User
Join Date: Nov 2016
Posts: 157
|
AFAIR, it was called just once. And probably there where more than one parameter, there was one for each filter... can't remember...
I'm no expert, but maybe calling all these filter within a single Gscriptclip instance made avisynth (and/or virtualdub) goes south! Honestly, I expected it to consume more memory splitting the tasks, but the contrary was true... I add that I saved the clip using Lagarith, if it can be of some help to discover this strange behaviour. Last thing: to discover if your avisynth script has some kind of memory leak, instead of waiting hours (days) before it will crash, just load it in virtualdub, and point randomly to the seeking bar - I mean, a lot of times, grab the cursor and drag it left and right... you will see memory rise; then, memory will eventually stops at a certain point, or raises until an out of memory message will appear. It can save A LOT of time, wasted otherwise... |
Thread Tools | Search this Thread |
Display Modes | |
|
|