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!
This commit is contained in:
parent
95a71fb815
commit
9d276937de
Notes:
blender-bot
2023-02-14 20:02:02 +01:00
Referenced by issue #42476, Mocap addon: "Clean noise" didn't work properly.
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue