[Bf-extensions-cvs] [9d27693] master: Fix T42476: Mocap addon: "Clean noise" didn't work properly.

Bastien Montagne noreply at git.blender.org
Mon Nov 10 15:32:13 CET 2014


Commit: 9d276937de26f82a4bbaded388ed474b951f338a
Author: Bastien Montagne
Date:   Mon Nov 10 15:30:52 2014 +0100
Branches: master
https://developer.blender.org/rBA9d276937de26f82a4bbaded388ed474b951f338a

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!

===================================================================

M	mocap/__init__.py
M	mocap/mocap_tools.py

===================================================================

diff --git a/mocap/__init__.py b/mocap/__init__.py
index 62e3709..fd69f89 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 8ecd3c0..91c92a9 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



More information about the Bf-extensions-cvs mailing list