BVH filter program
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
03-13-2008 20:09
I made an animation using QAvimator (poorly, no doubt), and wasn't surprised at how jerky the results were. So, I wrote a simple Pythong program to low-pass filter the BVH file and the results are quite pleasing! For Python source and example input & output files, see http://learjeff.net/SL/anim: - bvFilt.py - f-s8.bvh - the original - f-s8-filt.bvh - the filtered version I'd love for someone to snag them and post a simple machinima, but I've never done that. To use it, you have to go to python.org and install Python. There's no GUI, so copy the python source into the same directory as your anims, open a command window there (on Windows, or whatever Mac folks do to get a Linux shell there), and issue the command: From: someone bvFilt.py myAnim.bvh 8 where 8 is the filter constant; bigger numbers for smoother motion. It creates myAnim-filt.bvh. No range checking on the filter constant: 1 produces the same as the input file, and smaller numbers will cause nervous fits. If it's too big, you'll see a glitch at the loop point. (For a 60-frame 30 FPS Qavimator file, max filter value seems to be about 24. The longer the animation, the more filtering you can do without getting this problem.) Note: This program assumes the whole animation is looped (i.e., in 0%, out 100%). I haven't yet thought of a good way to handle other types without glitches. All frames other than the first frame (T frame) are modified. Therefore, you might want to postprocess with BVHacker to avoid key frame joints that look like zero to SL; the filtering process can easily undo your hard work to manually avoid this problem (where SL thinks the joint is not used in the anim, so it fiddles with it). Careful, though, this may cause a glitch. Enjoy! I'd *love* it if someone embedded the simple logic into a nice GUI program like BVHacker or QAvimator. Source is free to use for any purpose other than illegal, immoral, or fattening behavior. Is this kind of feature built into Poser? It should be, especially since the coding is trivial.
|
Lee Ponzu
What Would Steve Do?
Join date: 28 Jun 2006
Posts: 1,770
|
03-14-2008 10:59
I can't open the python file. I didn't try the .bvh files.
Permission bits maybe?
_____________________
So many monkeys, so little Shakespeare.
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
03-14-2008 18:27
Hmmm, evidently my server thinks, if it's a script, don't allow downloads. I'll have to rename it to fool the damn server into thinking it's just a text file, dammit.
However, meanwhile, I can't upload anything to my server: FTP no longer works.
StartLogic has been a very reliable service up to now, so I'll cut 'em a little slack, but this is very annoying.
Also, I have to update the script anyway. It put commas in the output, which should be space-separated only. QAvimator didn't mind, but SL doesn't like it.
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
03-14-2008 19:12
Here's the Python source, for anyone who's interested. To copy this source, Quote this message (as though you were going to quote it in a reply) and copy the source. That way it'll be indented properly, and Python doesn't work without correct indentation.
# Filter BVC files
# Changes: # 0.3: filter constant specification by RC or cutoff freq # Use as many repetitions of the anim as necessary to get 1% accuracy (overkill)
Version = "0.3"
import math import time import profile import warnings
import struct
FrameCount = 0 FrameTime = 0.0
########################################## Usage = """ %s: low-pass filter BVH animation
Usage: %s <infile> [<fc>]
where: <infile> is the .bvh file to read. <fc> is the filter constant, one of the following: a number greater than 1, higher numbers yeiled more filtering -f <number> - cutoff frequency in Hz, for DSP guys -rc <number> - RC time constant, in sec, for hardware types
The output file is created by inserting "-filt" before the ".bvh" extension on the intput file name.
Examples: %s myAnim.bvh 8.0 # alpha = 1/8.0 %s myAnim.bvh -f 0.682 %s myAnim.bvh -rc 0.23333
All the above do the same thing, just different ways to specify the amount of filtering.
Version: %s """ ##########################################
def usage(args):
import os.path cmd = os.path.basename(args[0]) print Usage % (cmd, cmd, cmd, cmd, cmd, Version)
def copyHeader(inf, outf): global FrameCount global FrameTime
line = inf.readline() while line != "MOTION\n": outf.write(line) line = inf.readline() outf.write(line)
line = inf.readline() # Frames: n outf.write(line) FrameCount = int(line.split(":")[1])
line = inf.readline() # Frame Time: n.nnnn outf.write(line) print line FrameTime = float(line.split(":")[1])
def getVals(line):
fvals = []
if line == "": return None
strvals = line.split(" ") for ix in range(len(strvals)): val = strvals[ix] fvals += [float(strvals[ix])]
return fvals
def bvhFilter( inf, # input file outf, # output file invAlpha, # 1/decay constant rc, # RC time constant (ignored unless alpha is zero) freq, # cutoff frequency (ignored unless alpha and rc are zero) ):
global FrameCount global FrameTime
copyHeader(inf, outf)
duration = (FrameCount - 1) * FrameTime
print " # frames =", FrameCount print " frame time =", FrameTime, "sec" print " duration =", duration, "sec"
if invAlpha != 0.0: alpha = 1.0 / invAlpha else: if rc == 0.0: if freq == 0.0: print "Zero filter constant not permitted" sys.exit(1)
rc = 1.0 / (2 * math.pi * freq) alpha = FrameTime / (FrameTime + rc)
rc = FrameTime/alpha - FrameTime freq = 1.0 / (2 * math.pi * rc)
print " 1/alpha =", 1.0/alpha print " RC constant =", rc, "sec" print " cutoff freq =", freq, "Hz"
if alpha < 0.0001: print "Filter constant too low" sys.exit(1)
# skip first frame line = inf.readline() outf.write(line) FrameCount -= 1 # don't count the T frame
data = inf.read()
# Load the input several times to handle larger filter constants. # We'll only use the last copy for output.
req_dur = rc * 5.0 # required duration for 1% accuracy at loop repeats = int(math.ceil(req_dur / duration)) print " Buffer Repeats =", repeats
if repeats > 100: print "Surely you must be joking! Little motion would remain; use a static pose." sys.exit(1)
alldata = data for ix in range(0, repeats): alldata += data
import StringIO inp = StringIO.StringIO(alldata)
frameIndex = -(repeats * FrameCount)
# preload filter with first line fvals = getVals(inp.readline().strip()) frameIndex += 1
# For remaining lines, filter: # fv += alpha * (iv - fv)
ivals = getVals(inp.readline().strip()) frameIndex += 1
while ivals != None: ovals = [] for ix in range(len(fvals)): fvals[ix] += alpha * (ivals[ix] - fvals[ix]) ovals += [str(fvals[ix])] if 0 <= frameIndex < FrameCount: outf.write(" ".join(ovals)) outf.write("\n") ivals = getVals(inp.readline().strip()) frameIndex += 1
def bvFilt(args): infname = args[1] del args[1]
try: inf = file(infname, "r") except IOError, msg: print msg sys.exit(1)
basename = ".".join(infname.split(".")[0:-1])
outfname = basename + "-filt.bvh" print "output file:", outfname
try: outf = file(outfname, "w") except IOError, msg: print msg sys.exit(1)
invAlpha = 0.0 freq = 0.0 rc = 0.0
if len(args) < 2: invAlpha = 2.0 elif (args[1] == "-f"): if (len(args) < 3): print "Expecting filter cutoff frequency" sys.exit(1) freq = float(args[2]) del args[1:2] elif (args[1] == "-rc"): if (len(args) < 3): print "Expecting filter time constant" sys.exit(1) rc = float(args[2]) del args[1:2] else: invAlpha = float(args[1]) del args[1]
bvhFilter(inf, outf, invAlpha, rc, freq)
if __name__ == "__main__":
import sys if len(sys.argv) < 2: usage(sys.argv) else: bvFilt(sys.argv)
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
03-15-2008 06:23
Got things working with my service provider again. I've uploaded the latest version, but added a ".txt" extension so you can download it. Delete the ".txt" extension after downloading.
I also have two filtered copies of the animation, one using a filter constant of 3 and one using 8.
BTW, the program also allows you to specify the filter constant as an RC time constant (for hardware jocks) or as a cutoff frequency (for signal processing types). They all do the same thing, just different ways to specify the amount of smoothness.
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
03-17-2008 12:37
Update. I changed the filter from an IIR lowpass to an FIR (convolution), and I added the ability to add noise to the animation. Noise can make a static pose look less static, and it can add a bit of naturalness to normal animations. The FIR filter has two benefits. First, it's phase-linear, meaning that the peak effect of a movement doesn't get delayed, as it did with the first filter. This will help folks doing coordinated animations. Second, it filters the noise better. The IIR filter would jump, whereas the FIR is much smoother. For those interesed, I used a symmetric kernel with exponentially decreasing values from the midpoint, normalized for unity gain. If it sounds like I know what I'm talking about ... well, guess again  The RC and cutoff values are no longer accurate, but I suspect they're in the ballpark, so I left them in for now. Same website as above, find "bvFilt1.py", and an example static pose that's been randomized.
|
Ravanne Sullivan
Pole Dancer Extraordinair
Join date: 10 Dec 2005
Posts: 674
|
03-17-2008 17:02
Getting a 500 error when trying to download the scripts.
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
03-17-2008 18:14
OK, please try again. It wouldn't even allow "*.py.txt", so they're now just "*.txt" files. Rename to "*.py" after downloading.
|
Lear Cale
wordy bugger
Join date: 22 Aug 2007
Posts: 3,569
|
04-12-2008 14:41
I added a "breathing" feature to the filter program. It simply arches the back a bit and rotates shoulders to comensate. You specify the number of breaths for the animation and the depth, where 1.0 is normal breathing. Filtering happens after breathing is added, and I find that filtering using a constant of at least 4 works best.
Find the Python source as "bfFilt2.txt"; you'll need to rename it to "bvFilt.py". Also, in case it matters, I use 8 spaces per tab. Probably forgot to detab before posting the latest.
If I do any more to this thing, it would be to allow the user to select which joints to process. Sure would be nice to have a GUI for that -- any GUI programmers who are interested please pipe up!
|