View Full Version : How can I get fdecimate to use only the Luminance?
rec
19th February 2011, 19:52
I'm working with fdecimate on some footage with really screwed up chroma. I would like to ignore the chroma and have fdecimate base it's decision only by the luminance channel. How can I do this?
:thanks:
Mini-Me
20th February 2011, 15:52
Assuming FDecimate does not alter any frames in any way (and only deletes some), I think this is feasible. Conceptually, you need to use FDecimate after calling Greyscale() on the original clip, and then you need to merge the chroma frame by frame, using the correct frames for the original clip for each frame of the decimated clip. The hard part is figuring out which original clip frame to borrow the chroma from for each frame in the decimated clip.
Clip A is the original clip with the chroma, clip B is the shorter decimated clip, and clip C is the output clip. If you construct C one frame at a time from beginning to end, you'll always borrow A's chroma from a higher-numbered frame than last time, but because you're trying to mirror the decimation operation, you'll be regularly skipping frames. You could use ScriptClip for frame-by-frame filtering and maintain a global counter to keep track of how far you are in A as you traverse B, so that's workable. To figure out which frame's chroma from A you should match up with the current frame of B, you need to compare B's current luma frame with luma frames from A until you find a match. Once you find a match, you can call MergeChroma and return the frame.
ScriptClip and FrameEvaluate can access global variables, and they provide access to a function called LumaDifference(clip1, clip2), which returns a float value between 0 and 255 indicating the absolute differences between two luma planes. If you pass it two single-frame clips, it should work nicely for checking for a frame match.
Sadly, I'm a total noob with ScriptClip, and I don't really know how to use a loop (or recursion) within a runtime script to repeatedly check frames until finding a match. However, I'm pretty sure it's possible, at least by using recursion in regular functions, setting globals using FrameEvaluate, and then using them in ScriptClip. It can probably be done in a relatively short script (I just don't know what the script would be, especially given the weird order issues with runtime functions ;)). Hopefully this has at least set you on the right track, and maybe someone who knows what they're doing can take you the rest of the way.
Gavino
20th February 2011, 18:40
To figure out which frame's chroma from A you should match up with the current frame of B, you need to compare B's current luma frame with luma frames from A until you find a match. Once you find a match, you can call MergeChroma and return the frame.
Basically you have the right idea, but there's no need to MergeChroma, just return the entire frame from A (since luma is already known to be the same). No need for globals either!
A = ... # original
skip = 0 # no of frames skipped
A.Greyscale().FDecimate(...)
ScriptClip("""
GScript("
while (LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
return A.Trim(skip, 0)
""")
You could replace the GScript loop with a recursive function if you're feeling masochistic.
Mini-Me
20th February 2011, 20:13
Basically you have the right idea, but there's no need to MergeChroma, just return the entire frame from A (since luma is already known to be the same). No need for globals either!
A = ... # original
skip = 0 # no of frames skipped
A.Greyscale().FDecimate(...)
ScriptClip("""
GScript("
while (LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
return A.Trim(skip, 0)
""")
You could replace the GScript loop with a recursive function if you're feeling masochistic.
That's beautiful. I didn't realize you could mix GScript with ScriptClip. :D If GScript didn't exist, how would you go about using the recursive function? (LumaDifference isn't accessible to a non-runtime function called by a runtime function, is it?)
Gavino
20th February 2011, 21:10
If GScript didn't exist, how would you go about using the recursive function? (LumaDifference isn't accessible to a non-runtime function called by a runtime function, is it?)
That's an added part of the problem, although you can use run-time functions inside a user function if you use GRunT. ;)
Without it, you can 'cheat' by passing current_frame as a parameter (which allows LumaDifference to 'see' it).
You could replace the GScript code by:
skip = SkipToDup(A, skip, current_frame)
where the function is defined as:
function SkipToDup(clip B, clip A, int skip, int current_frame) {
return (LumaDifference(B, A.Trim(skip, 0)) > 0 ? SkipToDup(B, A, skip+1, current_frame) : skip)
}
I don't know about you, but to me the GScript version seems far clearer (and hence easier to get right). :)
rec
21st February 2011, 00:15
Basically you have the right idea, but there's no need to MergeChroma, just return the entire frame from A (since luma is already known to be the same). No need for globals either!
A = ... # original
skip = 0 # no of frames skipped
A.Greyscale().FDecimate(...)
ScriptClip("""
GScript("
while (LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
return A.Trim(skip, 0)
""")
You could replace the GScript loop with a recursive function if you're feeling masochistic.
Thank you so much! I eagerly inserted your code into my script and it worked...up to 4 seconds, when Vdub locked up. I hit abort and got this message.
http://img14.imageshack.us/img14/289/vduberror.jpg (http://img14.imageshack.us/i/vduberror.jpg/)
I tried a different clip. This one got to 16 seconds and then froze.
This stuff is pretty advanced for me, and I have no idea how to troubleshoot it. Any help would be greatly appreciated.
Gavino
21st February 2011, 00:52
The script assumes that the FDecimate clip contains frames which match exactly on luma to a subset of the original frames. If that assumption is wrong, it could go into an endless loop looking for the next match.
To guard against that, change the 'while' condition to:
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0)
and see what you get.
rec
21st February 2011, 01:07
The script assumes that the FDecimate clip contains frames which match exactly on luma to a subset of the original frames. If that assumption is wrong, it could go into an endless loop looking for the next match.
To guard against that, change the 'while' condition to:
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0)
and see what you get.
Sorry, locked up again in the same place.
Gavino
21st February 2011, 01:58
Try with a small section of your clip to see if that works.
Then gradually increase the size to try and determine where it is going wrong.
Does the following test script work? (it does for me):
ColorBars(pixel_type="YV12")
Trim(0,99).ShowFrameNumber()
A = last # original
skip = 0 # no of frames skipped
A.Greyscale()
DeleteFrame(12,23,31,48,49,50,57,65,79,82) # Simulate decimation
ScriptClip("""
GScript("
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
return A.Trim(skip, 0)
""")
Edit: Another point is that the script must be rendered linearly from start to finish (to correctly keep track of the skipped frames), so if you jump around on the timeline you will get wrong results.
However, this should not cause it to hang (with the added guard condition now in the code), and encoding the file should produce correct results.
rec
21st February 2011, 03:03
Try with a small section of your clip to see if that works.
Then gradually increase the size to try and determine where it is going wrong.
Does the following test script work? (it does for me):
ColorBars(pixel_type="YV12")
Trim(0,99).ShowFrameNumber()
A = last # original
skip = 0 # no of frames skipped
A.Greyscale()
DeleteFrame(12,23,31,48,49,50,57,65,79,82) # Simulate decimation
ScriptClip("""
GScript("
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
return A.Trim(skip, 0)
""")
Edit: Another point is that the script must be rendered linearly from start to finish (to correctly keep track of the skipped frames), so if you jump around on the timeline you will get wrong results.
However, this should not cause it to hang (with the added guard condition now in the code), and encoding the file should produce correct results.
Your test script works, but if I add in fdecimate and increase the frame count, it locks up.
Try this.
ColorBars(pixel_type="YV12")
Trim(0,9999).ShowFrameNumber()
A = last # original
skip = 0 # no of frames skipped
A.Greyscale().FDecimate(rate=59.94, threshold=1.6)
ScriptClip("""
GScript("
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
return A.Trim(skip, 0)
""")
Gavino
21st February 2011, 12:20
After some experimenting with VirtualDub, I have found the source of the problem. It's not actually hanging up (with the fix I added at post #7) and although the UI appears to freeze, it does terminate eventually.
What happens is that after encoding (and reaching the last frame), VirtualDub goes back to redisplay frame 0 (or whatever frame you've selected on the timeline) - to do this it doesn't open the script again, but simply requests the appropriate frame. In effect, it is jumping backwards in the frame sequence and, as I explained above, this non-linear access causes the script to start frame matching in the wrong place, leading it to scan almost the entire input clip again, looking for a match that it never finds. This will take some time for a long clip (perhaps almost as long as the encode itself).
This modified script should prevent that and work correctly:
A = ... # original
skip = 0 # no of frames skipped
pf = 0 # previous frame
A.Greyscale().FDecimate(...)
ScriptClip("""
GScript("
if (current_frame < pf) { skip = 0 }
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
pf = current_frame
return A.Trim(skip, 0)
""")
rec
21st February 2011, 21:14
Sorry, still not working. It 'locked' up at the same place. This time, I let it run. After two minutes, it finally resumed and went through to the end. But, when I played the output movie, the particular frame that occured at the point of the 'lockup', was written all the way to the end (like a lonnnng freeze frame).
Gavino
22nd February 2011, 00:33
How are you using FDecimate()? What are the input and output frame rates?
In the ColorBars example you posted, you used rate=59.94. But since the ColorBars source has a rate of 29.97, this will end up increasing the rate, adding frames instead of removing them. That will certainly cause the script to go wrong, since it is expecting the decimated clip to have fewer frames than the original, and so it searches forwards, not backwards, when looking for a match for the current frame. At the first mismatch, it will scan the entire clip till it gets to the end, then the last frame will simply be repeated in the output from that point on.
The results you described in your last post seem consistent with that behaviour. Are you by any chance trying to increase the rate with FDecimate?
rec
22nd February 2011, 01:34
No. The source clip is 60.0 fps and I'm using fdecimate to reduce it to 59.94 fps. I know you're asking, "why would you want to do that?" Well, I'm trying to undo the damage created by a video camera that inserts duplicate fields into a 59.94i stream to make it a 60.0i stream.
Actually, I have fdecimate (in it's normal state) working fine at this task. The problem is the duplicate fields are only duplicates in luma. Their chroma is different, and they can sometimes fool fdecimate. So, I'm trying to get fdecimate to work only off the luma.
Gavino
22nd February 2011, 02:55
This problem is very puzzling. Does the output from running FDecimate on your grayscale clip look right? Is it eliminating duplicates and generating frames in the right order?
Note down the frame number where VirtualDub locks up, and check the content of the FDecimate output around that point. I am wondering if there is some sort of glitch that causes FDecimate to output a previous frame (ie jump backwards at some point) which would cause the frame-matching code in the script to go astray.
Can you also confirm my analysis of the symptoms by checking whether the multi-repeated frame produced after the lockup is the same as the last frame of the original clip.
StainlessS
22nd February 2011, 04:58
Sorry to butt in here, I have nothing to contribute,
but it might be prudent to now supply
a short source clip together with a clue as to the actual
source filter, "A = ... # original" is not terribly explicit.
EDIT: This might not be relevant to the original post but
might be to the continued lockup problem.
Gavino
22nd February 2011, 11:07
Does the output from running FDecimate on your grayscale clip look right? Is it eliminating duplicates and generating frames in the right order?
Note that you can use FDecimate(..., show=true) to see which frames are being returned.
I am wondering if there is some sort of glitch that causes FDecimate to output a previous frame (ie jump backwards at some point) which would cause the frame-matching code in the script to go astray.
I have found that this certainly can happen under some circumstances, in particular if you have 3 consecutive identical frames (or similar enough to be taken as duplicates within the given threshold). Say you have frames A1, A2, A3, B, where A1-3 are sufficiently similar, then FDecimate can potentially return A1, B, A3, B, as the following code demonstrates:
ColorBars(pixel_type="YV12").ShowFrameNumber()
Interleave(last, last, last).AssumeFPS(60)
FDecimate(rate=59.94, threshold=1.6, show=true)
# returns frames 0, 3, 2, 3, 6, 5, 6, 9, ...
Perhaps the same effect occurs in your clip?
I think the script can be modified to allow for such cases. Change this line:
if (current_frame < pf) { skip = 0 }
to this:
skip = current_frame < pf ? 0 : max(skip-2, 0)
If there are still problems then, as StainlessS suggests, a short sample (up to the lockup point) would be useful.
rec
22nd February 2011, 22:21
I've done some testing and have some intersting results.
Here are the metrics (from fdecimate) of the first four frames of a clip.
0 - 0.00
1 - 2.21
2 - 1.95
3 - 2.41
If I set the threshold in fdecimate to 0.5, it runs through this with no problem. But, if I set the threshold to 2.0, it returns the first frame and then locks up. BTW, an easy way to determine the last good frame is to render as an image sequence.
Gavino
22nd February 2011, 23:24
By my understanding of how FDecimate works (from looking at the source code), with your sequence when threshold=2.0, it should drop frame 2 (as a 'duplicate') and produce the following frames: 0, 1, 3. What comes next depends on the metrics of frames 4 and 5 - if these are both no more than 2.0, it will return frame 3 again.
You should be able to see this if you run FDecimate(..., show=true) on its own (without the ScriptClip part).
Did you try the latest amendment I posted above?
I was expecting it to fix the lockup problem.
rec
24th February 2011, 06:52
Yes, it works! You're a genius! Thank you, thank you, thank you!
rec
10th March 2011, 13:21
I'm now trying turn the script into a function, but I get this error:
I don't know what "pf" means ([Gscript], line 2) ([Scriptclip],line 7).
It returns a grayscale picture, so it's working OK through the line, A.Greyscale().FDecimate(rate=59.94, threshold=2.5).
The script:
# ======= Process_Short function =======
function Process_Short (clip clp)
{
ComplementParity(clp)
Yadif(mode=1, order=1)
trim(1,0)
A=converttoyv12()
skip = 0 # no of frames skipped
pf = 0 # previous frame
A.Greyscale().FDecimate(rate=59.94, threshold=2.5)
ScriptClip("""
GScript("
skip = current_frame < pf ? 0 : max(skip-2, 0)
while (current_frame+skip < Framecount(A) && LumaDifference(A.Trim(skip, 0)) > 0) {
skip = skip+1 # skip decimated frame
}
")
pf = current_frame
return A.Trim(skip, 0)
""")
}
I'm sure there's a simple solution, but I can't figure it out!
Gavino
10th March 2011, 14:54
It's the classic problem of ScriptClip being unable to see local variables inside a function.
You will need to make pf global by adding the word 'global' to both assignments:
global pf = 0
...
global pf = current_frame
That means your function can only be called once in a script.
Unfortunately GRunT does not help here as the value of pf must be retained between frames.
rec
10th March 2011, 20:23
I inserted the globals, but now it says "I don't know what 'skip' means".
Gavino
10th March 2011, 20:36
Ah, yes, 'skip' needs to be made global too.
Sorry I missed that earlier.
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.