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. |
18th June 2010, 00:42 | #21 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
You're right that any vector derived from a 'bad' frame is of little value. But if you look at my explanation of MFlowInter again, you will see that with delta=2, neither the forward nor the backward vector used actually involves the 'bad' frame. Using your notation, the vectors used would be F2->F4 and F4->F2, and to replace F3 you would use the frame of MFlowInter corresponding to F2. Your filldrops function written this way becomes: Code:
function filldrops(clip c) { previous = Loop(c,2,0,0) # needed to select correct frame super=MSuper(previous,pel=2) vfe=manalyse(super,truemotion=true,isb=false,delta=2) vbe=manalyse(super,truemotion=true,isb=true,delta=2) filldrops = mflowinter(previous,super,vbe,vfe,time=50) fixed = ConditionalFilter(c, filldrops, c, "YDifferenceFromPrevious()", "lessthan", "0.1") return fixed } Your original code does work correctly (possibly by accident) as long as the detected frame is an exact duplicate. So does your filldropsnext function, and for the same reason. Because of the Loop function, it replaces frame n by interpolating this time between n-1 and n, and since n is a duplicate of n+1, this produces the required interpolation between n-1 and n+1. Last edited by Gavino; 18th June 2010 at 00:52. Reason: fix bad thread link |
|
18th June 2010, 02:01 | #22 | Link |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Well, I have now spent several hours on this and can say with a fairly high degree of confidence that the code example in the MVTools2 documentation does NOT work. I am not trying to be argumentative. Instead, I am just reporting what I have found from actually using the code on a real piece of video that has a totally bad frame.
I created a sample clip where I took one frame from a completely unrelated piece of video and replaced a good frame in a video where someone was moving their hand rapidly. The moving hand makes it easy to see what is going on with the interpolated video. Here is that clip: http://dl.dropbox.com/u/1561578/test...ame%20clip.avi I then copied the code from the MVTools2 documentation, and modified it to point to my video and to compare the original video with the synthesized video: Code:
loadplugin("C:\Program Files\AviSynth 2.5\plugins\MVTools\mvtools2.dll") source=AVISource("E:\Disney Films\test bad frame clip.avi").ConvertToYV12 super = MSuper(source) backward_vectors = MAnalyse(super, isb = true, delta=2) forward_vectors = MAnalyse(super, isb = false, delta=2) inter = MFlowInter(source,super, backward_vectors, forward_vectors, time=50, ml=70) #return inter stackvertical(source,inter) What you will see is that when the "bad" frame appears in the top of the stacked video, the interpolated frame in the bottom actually contains part of the bad frame. This is obviously NOT what we want. We don't want to see any part of that bad frame. I have tried many, many permutations to try to get this to work, including using delta=1, delta=2, and "mixed deltas" where I use a different delta for the forward vector than the backward vector. I also have tried using the previous frame as the reference and also the next frame as the reference. This actually gets me quite close and I think will be the way I ultimate solve this puzzle. This is what I was proposing in my previous post. I am getting close to the correct solution to this problem and will post it when I get it working correctly. BTW, the conditional code to only use the interpolated frame when needed works just fine (I haven't included that above), and as I posted earlier in this thread, if it occasionally uses an interpolated frame rather than the original, it doesn't matter much for most frames (because the interpolated version is usually pretty close to the original). Quick P.S.: The code you posted does work, and it is exactly what I am working on. However, it is NOT the code in the documentation because it uses the previous frame for the vectors. I am trying to get it to work delta=1 for the backwards vector and delta=2 for the forward vector in order to get a more accurate estimation. It seems wrong to look at something two frames away in the backwards direction when the frame immediate preceding that frame is perfectly OK. Last edited by johnmeyer; 18th June 2010 at 02:08. Reason: Add postscript |
18th June 2010, 02:43 | #23 | Link | |||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
stackvertical(source, inter.Loop(2,0,0)) Quote:
Quote:
To quote the documentation: "It uses backward "mvbw" and forward "mvfw" motion vectors to create picture at some intermediate time moment between current and next (by delta) frame. It uses pixel-based (by MFlow method) motion compensation from both frames." There are only two frames involved (current and current+delta) and it uses vectors in both directions between those frames. Last edited by Gavino; 18th June 2010 at 02:51. Reason: More explanation and quote from docs |
|||
18th June 2010, 03:31 | #24 | Link | |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Quote:
Now everything makes sense. I'll spend a little more time on this, just to make sure it all makes sense and make sure the resulting code works, and then I'll post the result, just in case someone stumbles on this thread later on. The only contribution to the world as we know it that will come from posting that code is that it integrates the conditional code with the interpolation code. Thank you for helping me. Last edited by johnmeyer; 18th June 2010 at 03:31. Reason: Added thank you. |
|
18th June 2010, 10:47 | #25 | Link | ||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
For anyone else still not sure, reread my posts above (also the relevant explanations in this thread). Quote:
|
||
18th June 2010, 18:36 | #26 | Link | |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Quote:
So, bottom line, without this double test, you don't get very good identification of a bad frame. Doing two comparisons instead of one is easy and I'm sure you could do that in ten seconds (it took me ten minutes because I am stupid, and also because I didn't realize you have to declare a variable before it can be used inside of a conditional). But I like to always strive for perfection. So, what I'd like to do, especially given the nature of the original post and also given the number of other posts in this forum about similar problems, is to derive a "local threshold" using a moving average of the YDifference value. Ideally, that average should be reset after a scene change. Given how AVISynth works, I don't know if I can do that. It turns out that my script works pretty well without it, but I thought I'd take a little time an try to add this. One way or the other, I'll post the result. |
|
18th June 2010, 19:23 | #27 | Link |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Yes, sorry John, I was forgetting that replacing duplicate frames wasn't the original purpose of this thread.
I don't want to hijack this thread even further, but I'm not sure what you mean by this - could you elaborate? |
18th June 2010, 20:36 | #28 | Link | |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Quote:
Code:
ConditionalFilter(clip, filldrops, clip, "YDifferenceFromPrevious(clip)>10 && YDifferenceToNext(clip)>10", "greaterthan", "0") Code:
global clip = c Also, while we have posted a lot, I think what we've done should help the original poster, so we haven't completely taken this thread away from its original purpose (although we have concentrated on only one aspect of his problem). Finally, I may have to take back everything I said about how MVTools "delta" works. Both the documentation as well as the tests I am doing today make it seem that my original understand of how MAnalyze uses Delta is correct, namely that it does look at the preceding frame. I think our confusion comes from the fact that the Mflowinter function DOES only look forward and DOES use the delta for determining how many frames ahead to interpolate. Thus delta is being used in one way by MAnalyse, but in a different way by MFlowInter. This is why you have to use delta=2 so it (MFlowInter) looks beyond the bad frame. And it is why you can't use a different delta for the forward and backward vectors in MAnalyze because this causes MFlowinter to interpolate to the wrong frame. However, it seems increasingly clear to me that my initial understanding of MAnalyse is correct: the isb vectors look to the previous frame and the next frame rather than to the next frame and from the next frame. The more I think about that, the more I realize that it MUST be correct. After all, what would be the difference between a vector to a frame, and a vector back from that same frame, other than a negative sign? I have now actually been able to prove this with the solution I am creating. This solution uses the motion vectors from the previous frame and from two frames ahead, and then feeds these to MFlowInter. This does produces output that is different, and better, than the code in the documentation which I now once again believe to be wrong, or at least less than optimal. The solution I am developing uses the conditional filter to create a frame stream that replaces the bad frame with the next frame. This in essence creates the scenario that I've dealt with in the past, namely a perfect, exact duplicate in place of the bad frame. Once you have this, you can use delta=1 for both the forward and backward vectors, but the delta=1 for the forward vector is now actually pointing to a frame that is two frames ahead. MFlowInter is "happy" because it is being told to interpolate to the next frame, but since the "next" frame is now actually two frames ahead, I can use 50 for the time parameter and get the correct outcome. Now, anticipating what you are thinking, no this is NOT the equivalent of what you've posted, with delta=2 because the vector which points to the previous frame (and I really do think it points to the previous frame) is now pointing to the previous frame, and not the frame prior to the previous frame. Thus, I get a much more accurate interpolation because the isb=true vector is constructed from the immediately adjacent previous frame rather than the frame before that. The more I thought about it, and the more times I read the MVTools2 documentation, the more I have become convinced that your explanation of the isb delta vectors cannot be correct for MAnalyse. The MVTools2 documentation for MAnalyse states: delta : set the frame interval between the reference frame and the current frame. By default, it's 1, which means that the motion vectors are searched between the current frame and the previous ( or next ) frame. More to come ... |
|
18th June 2010, 20:55 | #29 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
Code:
ConditionalFilter(clip, filldrops, clip, "YDifferenceFromPrevious()>10 && YDifferenceToNext()>10", "greaterthan", "0") |
|
18th June 2010, 21:54 | #30 | Link | |||
Registered User
Join Date: Apr 2002
Location: Germany
Posts: 5,391
|
Quote:
With "delta=1", the vectors produced by MAnalyse for frame "c" look like Code:
(isb = false) (isb = true) (c-1) --------------> (c) <-------------- (c+1) (forward vec) (backward vec) Quote:
The "vector situation" for MFlowInter (with delta=2 vectors) : Code:
<---------- A B C D E ----------> Well, call it a "trick", or rather a "technical necessity" ... it is done this way because it can't be done in any other way. Quote:
Look at post#18. pbristow hasn't visited since then so I don't know if it works for him. For me, it certainly does work for exactly the posed problem.
__________________
- We´re at the beginning of the end of mankind´s childhood - My little flickr gallery. (Yes indeed, I do have hobbies other than digital video!) |
|||
18th June 2010, 22:59 | #31 | Link | |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Code:
ConditionalFilter(clip, filldrops, clip, "YDifferenceFromPrevious(clip)>10 && YDifferenceToNext(clip)>10", "greaterthan", "0") Quote:
except ... when clip is something other than "last." As my script has evolved, some versions used "last" and others used frames other than last, so I definitely did need it in earlier versions of the script. I see that while I was preparing this post that Didée provided an excellent explanation of how vectors are created and used within MVTools2. His explanation reinforces what I always believed to be true prior to this thread and, after a temporary misunderstanding by me, was what I tried to explain in my most recent post. Even when Didée said I was partially incorrect, it was only because I used the word "look" to describe what Mflowinter was doing. Obviously it has to use the vectors from previous as well as future frames, so in that sense it is "looking" in both directions. However, the interpolation is only done in the forward direction (i.e., it does not interpolate a frame at a time prior to the current frame). So, I totally agree with Didée's explanation, and very much appreciate his post. Now, to hopefully finish off my contributions here, the following is my final (for now) script for fixing bad frames. It uses a much better (in most ways) method of finding a bad frame (although it of course fails on the first and last two frames of the input video). To detect bad frames the script looks at ratios of YDifference values between this frame and the previous frame, and the previous frame and the frame before that. It also does the same thing with the following frames. This achieves something similar to the moving average idea, but without having to compute a moving average. In other words, it normalizes the YDifference values to whatever is "normal" for this portion of this clip, and only takes action when there is a discontinuity from the norm in BOTH the positive and negative direction from the current frame. Thus, scene changes are ignored (because the frame AFTER the first frame of a scene change has little difference), and it doesn't really matter whether YDifference values are large are small, as long as they are relatively similar from frame to frame. This code uses this logic to determine when to replace a "bad" frame with a duplicate of the frame following. This corrected stream is then fed to MAnalyse which then feeds its vectors to Mflowinter. By feeding the stream which now contains duplicates in place of the bad frames, the "filldrops" logic developed years ago by MugFunky now works just as it always has. The advantage this approach has over what is shown in the MVTools documentation is that it avoids using a delta of 2 in the backwards direction. Using this backwards delta of 2 skips the previous frame which, being closer in time is going to provide a better starting point for the estimation vectors. Indeed, I was able to prove this by comparing the frame I got using the code shown in the MVTools2 documentation with what I got with this code. This produces a better result. So, I am finished here, and I hope this has helped someone. Code:
loadplugin("C:\Program Files\AviSynth 2.5\plugins\MVTools\mvtools2.dll") global bad_thresh = 20 source=AVISource("E:\frameserver.avi").ConvertToYV12 corrected=replacebad(source).AssumeFPS(29.97, false) return corrected function replacebad (clip c) { global clip = c # Replace bad frame with a duplicate of the frame immediately after the bad frame goodframes = ConditionalFilter(clip, trim(clip,1,0), clip, \ "(YDifferenceFromPrevious(clip)/YDifferenceFromPrevious(loop(clip,2,0,0))) * \ (YDifferenceToNext(clip)/YDifferenceToNext(trim(clip,1,0)))", "greaterthan", "bad_thresh") # This next line gets the previous frame in the stream that now contains duplicates instead of bad frames previousframe = Loop(goodframes,2,0,0) super=MSuper(previousframe,pel=2) vfe=manalyse(super,truemotion=true,isb=false,delta=1) vbe=manalyse(super,truemotion=true,isb=true,delta=1) replacement = mflowinter(previousframe,super,vbe,vfe,time=50) fixed = ConditionalFilter(clip, replacement, clip, "(YDifferenceFromPrevious(clip)/YDifferenceFromPrevious(loop(clip,2,0,0))) * \ (YDifferenceToNext(clip)/YDifferenceToNext(trim(clip,1,0)))", "greaterthan", "bad_thresh") return fixed } Last edited by johnmeyer; 18th June 2010 at 23:02. Reason: Deleted repeated word |
|
18th June 2010, 23:14 | #32 | Link | |
Registered User
Join Date: Apr 2002
Location: Germany
Posts: 5,391
|
Nitpick:
Quote:
It's correct the the final interpolation of MFLowInter is only "forward in time", in relation to the "current" frame. But in order to produce that interpolation, MFlowInter indees does both a forward interpolation and a backward interpolation, and mixes them acc. to the internal metrics. (After all, you are handing *two* vectors over to MFlowInter ... the 2nd vector isn't bogus. It is used.) The twist is just that when creating the interpolation between this-and-next-frame, MFlowInter does not use the forward and backward vector of the current frame. It uses the backward vector of the current frame, and the forward vector of the (current+delta) frame.
__________________
- We´re at the beginning of the end of mankind´s childhood - My little flickr gallery. (Yes indeed, I do have hobbies other than digital video!) |
|
18th June 2010, 23:32 | #33 | Link | ||||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
Remember "F2->F4 and F4->F2"? Note that these vectors are not simply the reverse of each other (unless the motion analysis is perfect and no object moves into or out of the picture). Quote:
But of course each vector is a clip, so the vector for current->next can be found in the next frame of the forward vector, and so on. Quote:
Quote:
EDIT: Just seen you have posted again in the interim. Perhaps more to come... Last edited by Gavino; 18th June 2010 at 23:36. |
||||
19th June 2010, 00:20 | #34 | Link | |||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
I said that, whatever the first argument is, it is available as 'last' within the 'run-time' script (the bit inside the quotes). So there is no need to use 'clip' there and the global is not needed. This is independent of whether 'clip' itself is equal to 'last' in the 'outside' script. Quote:
Quote:
You can't get any 'closer in time' to the bad frame than the immediately surrounding frames, which is what MFlowInter (with delta=2) uses. Last edited by Gavino; 19th June 2010 at 00:27. |
|||
19th June 2010, 01:23 | #35 | Link | |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Quote:
As long as we both agree that Didee's explanation is correct, which I do, then I don't think we need to go further. Thank you VERY much for all your help. |
|
19th June 2010, 08:17 | #36 | Link |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Didée's explanation is the same as mine (but clearer!), and would suggest that the delta=2 approach cannot be improved upon. Your results are therefore puzzling and invite further explanation, which I believe I have found.
By looking at the source code, I have discovered that for occlusion areas, MVFlowInter also uses information from the two further surrounding frames (current-delta and current+2*delta). So the diagram becomes, when interpolating between C and C+d, Code:
----> <---- C-d C C+d C+2d ----> <---- Just to clarify further my remark about ConditionalFilter, what I am saying is this. In ConditionalFilter(c1, c2, c3, "...", "...", "..."), 'last' is always set to the value of 'c1' before the expression strings are evaluated (on each frame). Therefore your code Code:
goodframes = ConditionalFilter(clip, trim(clip,1,0), clip, \ "(YDifferenceFromPrevious(clip)/YDifferenceFromPrevious(loop(clip,2,0,0))) * \ (YDifferenceToNext(clip)/YDifferenceToNext(trim(clip,1,0)))", "greaterthan", "bad_thresh") Code:
goodframes = ConditionalFilter(c, trim(c,1,0), c, \ "(YDifferenceFromPrevious()/YDifferenceFromPrevious(loop(2,0,0))) * \ (YDifferenceToNext()/YDifferenceToNext(trim(1,0)))", "greaterthan", "bad_thresh") Using a global variable for this purpose in a function is in general a bad idea as the function will not work if called more than once. Last edited by Gavino; 19th June 2010 at 08:25. Reason: formatting |
19th June 2010, 18:01 | #37 | Link | |
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Quote:
Interesting what you found in the source code. That confirms the behavior I have been observing. I went ahead and modified the script to remove the global variable. This works for all ConditionalFilter calls that use "c", but I had a few debug calls (which I didn't post, and am not posting below) where I used ScriptClip to show the YDifference vectors so I could see what threshold value to use. This was done later in the script at a point where "last" was now referencing the stream where the bad frame had been replaced with a duplicate. Obviously the YDifference values for this stream no longer can identify the bad frame. So, at your suggestion, I removed the global variable for the normal working script, but I still have to use it for debugging because it IS needed for ScriptClip even though it isn't for ConditionalFilter. Finally, I decided that my YDifference ratio approach wasn't such a good idea, because it can't differentiate between slightly bad frames and scene changes. So, I went back to the approach I've used for several years. It works great for finding single frame anomalies, but it isn't "automatic" so you have to spend a few minutes to determine the threshold value for each particular video stream. Here's the script with these two revisions: Code:
loadplugin("C:\Program Files\AviSynth 2.5\plugins\MVTools\mvtools2.dll") global bad_thresh = 20 source=AVISource("E:\frameserver.avi").ConvertToYV12.killaudio() corrected=replacebad(source).AssumeFPS(29.97, false) return corrected function replacebad (clip c) { # This next statement replaces bad frame with a duplicate of the frame immediately after the bad frame goodframes = ConditionalFilter(c, trim(c,1,0), c, \ "YDifferenceFromPrevious()>bad_thresh && YDifferenceToNext()>bad_thresh", "greaterthan", "0") # This next statement gets the previous frame in the stream that now contains duplicates instead of bad frames previousframe = Loop(goodframes,2,0,0) super=MSuper(previousframe,pel=2) vfe=manalyse(super,truemotion=true,isb=false,delta=1) vbe=manalyse(super,truemotion=true,isb=true,delta=1) replacement = mflowinter(previousframe,super,vbe,vfe,time=50) fixed = ConditionalFilter(c, replacement, c, "YDifferenceFromPrevious()>bad_thresh && \ YDifferenceToNext()>bad_thresh", "greaterthan", "0") return fixed } # End function replacebad Last edited by johnmeyer; 19th June 2010 at 19:26. Reason: Added clarification about global variable needed for ScriptClip vs. ConditionalFilter |
|
19th June 2010, 20:02 | #38 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
ScriptClip(clip, " # here 'last' refers to 'clip', regardless of the value of 'last' outside ... ") Or did you mean you needed to refer to a different clip 'inside', one not the same as (or derivable from) ScriptClip's input clip? That's an example of what I call the 'variable binding problem' (alluded to here) and I developed the GRunT plugin specifically to solve this (and other usability issues with the standard run-time filters). Incidentally, being a 'language lawyer' guy, I was suprised to see that this works in ConditionalFilter: Code:
"YDifferenceFromPrevious()>bad_thresh && YDifferenceToNext()>bad_thresh", "greaterthan", "0" It obviously works, but it might be clearer to rewrite this as Code:
"YDifferenceFromPrevious()>bad_thresh && YDifferenceToNext()>bad_thresh", "==", "true" |
|
19th June 2010, 22:13 | #39 | Link | ||
Registered User
Join Date: Feb 2002
Location: California
Posts: 2,695
|
Quote:
Code:
global clip = c fixed = ConditionalFilter(c, replacement.ScriptClip("""subtitle(string(YDifferenceFromPrevious(clip) ) \ + ", " + string(YDifferenceToNext(clip) ) )"""), c, \ "YDifferenceFromPrevious()>bad_thresh && YDifferenceToNext()>bad_thresh", "greaterthan", "0") Quote:
Using "==" and ">" etc. is probably a good idea. I can't remember whether that was allowed in early versions of AVISynth. There must be some reason I used the non-symbolic version, but I can't remember. A lot of my code is "cut/pasted" from various places and, if it works, I don't mess with it. |
||
19th June 2010, 23:03 | #40 | Link | ||
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,431
|
Quote:
If you need to do something like this in the future, it can be written like this using GRunT: Code:
fixed = ConditionalFilter(c, replacement.ScriptClip("""subtitle(string(YDifferenceFromPrevious(c) ) \ + ", " + string(YDifferenceToNext(c) ) )""", args="c"), c, \ "YDifferenceFromPrevious()>bad_thresh && YDifferenceToNext()>bad_thresh", "greaterthan", "0") Quote:
(Nor, if I recall, even did Fortran IV - yeah, I started back in the punched card era too ) In fact, GRunT's ConditionalFilter make this simpler too, by allowing the three separate strings to be replaced by a single boolean expression. Code:
ConditionalFilter(..., "YDifferenceFromPrevious()>bad_thresh && YDifferenceToNext()>bad_thresh") |
||
|
|