diff options
author | Bastien Montagne <montagne29@wanadoo.fr> | 2014-11-10 17:30:52 +0300 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2014-11-10 17:30:52 +0300 |
commit | 9d276937de26f82a4bbaded388ed474b951f338a (patch) | |
tree | d6aef9e3277d5d162f29644209f79180a6c94c82 | |
parent | 95a71fb815641a6b55c8d796c0106d1a159fd125 (diff) |
Fix T42476: Mocap addon: "Clean noise" didn't work properly.
Patch by sybrenstuvel (Sybren Stüvel), with own tweaking (main issue was,
fcurve needs to be updated when you have changed their keyframes), thanks!
-rw-r--r-- | mocap/__init__.py | 9 | ||||
-rw-r--r-- | mocap/mocap_tools.py | 89 |
2 files changed, 62 insertions, 36 deletions
diff --git a/mocap/__init__.py b/mocap/__init__.py index 62e3709f..fd69f89f 100644 --- a/mocap/__init__.py +++ b/mocap/__init__.py @@ -579,18 +579,19 @@ class OBJECT_OT_LooperButton(bpy.types.Operator): class OBJECT_OT_DenoiseButton(bpy.types.Operator): #Operator to denoise impluse noise on the active object's fcurves - """Denoise active armature's animation (good for dealing """ \ - """with 'bad' frames inherent in mocap animation)""" + """Removes spikes from all fcurves on the selected object""" bl_idname = "mocap.denoise" bl_label = "Denoise Mocap" def execute(self, context): - mocap_tools.denoise_median() + obj = context.active_object + mocap_tools.denoise(obj, obj.animation_data.action.fcurves) return {'FINISHED'} @classmethod def poll(cls, context): - return context.active_object.animation_data + obj = context.active_object + return obj and obj.animation_data and obj.animation_data.action class OBJECT_OT_LimitDOFButton(bpy.types.Operator): diff --git a/mocap/mocap_tools.py b/mocap/mocap_tools.py index 8ecd3c08..91c92a98 100644 --- a/mocap/mocap_tools.py +++ b/mocap/mocap_tools.py @@ -570,39 +570,64 @@ def fcurves_simplify(context, obj, sel_opt="all", error=0.002, group_mode=True): return -# Implementation of non-linear median filter, with variable kernel size -# Double pass - one marks spikes, the other smooths them -# Expects sampled keyframes on everyframe -# IN: None. Performs the operations on the active_object's fcurves. Expects animation_data.action to exist! -# OUT: None. Fixes the fcurves "in-place". -def denoise_median(): - context = bpy.context - obj = context.active_object - fcurves = obj.animation_data.action.fcurves - medKernel = 1 # actually *2+1... since it this is offset - flagKernel = 4 - highThres = (flagKernel * 2) - 1 - lowThres = 0 +def detect_min_max(v): + """ + Converted from MATLAB script at http://billauer.co.il/peakdet.html + + Yields indices of peaks, i.e. local minima/maxima. + + % Eli Billauer, 3.4.05 (Explicitly not copyrighted). + % This function is released to the public domain; Any use is allowed. + """ + + min_val, max_val = float('inf'), -float('inf') + + check_max = True + + for i, val in enumerate(v): + if val > max_val: + max_val = val + if val < min_val: + min_val = val + + if check_max: + if val < max_val: + yield i + min_val = val + check_max = False + else: + if val > min_val: + yield i + max_val = val + check_max = True + + +def denoise(obj, fcurves): + """ + Implementation of non-linear blur filter. + Finds spikes in the fcurve, and replaces spikes that are too big with the average of the surrounding keyframes. + """ for fcurve in fcurves: - orgPts = fcurve.keyframe_points[:] - flaggedFrames = [] - # mark frames that are spikes by sorting a large kernel - for i in range(flagKernel, len(fcurve.keyframe_points) - flagKernel): - center = orgPts[i] - neighborhood = orgPts[i - flagKernel: i + flagKernel] - neighborhood.sort(key=lambda pt: pt.co[1]) - weight = neighborhood.index(center) - if weight >= highThres or weight <= lowThres: - flaggedFrames.append((i, center)) - # clean marked frames with a simple median filter - # averages all frames in the kernel equally, except center which has no weight - for i, pt in flaggedFrames: - newValue = 0 - sumWeights = 0 - neighborhood = [neighpt.co[1] for neighpt in orgPts[i - medKernel: i + medKernel + 1] if neighpt != pt] - newValue = sum(neighborhood) / len(neighborhood) - pt.co[1] = newValue - return + org_pts = fcurve.keyframe_points[:] + + for idx in detect_min_max(pt.co.y for pt in fcurve.keyframe_points[1:-1]): + # Find the neighbours + prev_pt = org_pts[idx - 1].co.y + next_pt = org_pts[idx + 1].co.y + this_pt = org_pts[idx] + + # Check the distance from the min/max to the average of the surrounding points. + avg = (prev_pt + next_pt) / 2 + is_peak = abs(this_pt.co.y - avg) > avg * 0.02 + + if is_peak: + diff = avg - fcurve.keyframe_points[idx].co.y + fcurve.keyframe_points[idx].co.y = avg + fcurve.keyframe_points[idx].handle_left.y += diff + fcurve.keyframe_points[idx].handle_right.y += diff + + # Important to update the curve after modifying it! + fcurve.update() # Recieves armature, and rotations all bones by 90 degrees along the X axis |