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. |
|
![]() |
#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. |
![]() |
![]() |
![]() |
#3 | Link | ||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,425
|
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. |
||
![]() |
![]() |
![]() |
#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. ![]() Last edited by Mini-Me; 4th April 2011 at 03:18. |
||
![]() |
![]() |
![]() |
#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. ![]() Last edited by Mini-Me; 4th April 2011 at 06:39. |
![]() |
![]() |
![]() |
#7 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,425
|
@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:
|
|
![]() |
![]() |
![]() |
#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. |
|
![]() |
![]() |
![]() |
#9 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,425
|
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. |
|
![]() |
![]() |
![]() |
#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. |
|
![]() |
![]() |
![]() |
#11 | Link | |
Registered User
Join Date: Nov 2016
Posts: 149
|
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) } |
|
![]() |
![]() |
![]() |
#12 | Link |
Registered User
Join Date: Nov 2016
Posts: 149
|
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! ![]() 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 | |
|
|