feat(edge): One-Euro keypoint smoothing (INT8 jitter)
This commit is contained in:
parent
444c01407a
commit
991178a896
2 changed files with 71 additions and 0 deletions
49
edge/smoothing.py
Normal file
49
edge/smoothing.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import math
|
||||
|
||||
from edge.models import Keypoint
|
||||
|
||||
|
||||
class OneEuroFilter:
|
||||
"""1€ filter (Casiez et al.). 저지연 지터 감쇠."""
|
||||
|
||||
def __init__(self, min_cutoff: float = 1.0, beta: float = 0.0, d_cutoff: float = 1.0):
|
||||
self.min_cutoff = min_cutoff
|
||||
self.beta = beta
|
||||
self.d_cutoff = d_cutoff
|
||||
self._x_prev: float | None = None
|
||||
self._dx_prev: float = 0.0
|
||||
self._t_prev: float | None = None
|
||||
|
||||
@staticmethod
|
||||
def _alpha(cutoff: float, dt: float) -> float:
|
||||
tau = 1.0 / (2 * math.pi * cutoff)
|
||||
return 1.0 / (1.0 + tau / dt)
|
||||
|
||||
def filter(self, value: float, t: float) -> float:
|
||||
if self._x_prev is None or self._t_prev is None or t <= self._t_prev:
|
||||
self._x_prev, self._t_prev, self._dx_prev = value, t, 0.0
|
||||
return value
|
||||
dt = t - self._t_prev
|
||||
dx = (value - self._x_prev) / dt
|
||||
a_d = self._alpha(self.d_cutoff, dt)
|
||||
dx_hat = a_d * dx + (1 - a_d) * self._dx_prev
|
||||
cutoff = self.min_cutoff + self.beta * abs(dx_hat)
|
||||
a = self._alpha(cutoff, dt)
|
||||
x_hat = a * value + (1 - a) * self._x_prev
|
||||
self._x_prev, self._dx_prev, self._t_prev = x_hat, dx_hat, t
|
||||
return x_hat
|
||||
|
||||
|
||||
class KeypointSmoother:
|
||||
"""관절 17개 각각 x,y에 독립 1€ 필터."""
|
||||
|
||||
def __init__(self, min_cutoff: float = 1.0, beta: float = 0.007):
|
||||
self._fx = [OneEuroFilter(min_cutoff, beta) for _ in range(17)]
|
||||
self._fy = [OneEuroFilter(min_cutoff, beta) for _ in range(17)]
|
||||
|
||||
def smooth(self, keypoints: list[Keypoint], t: float) -> list[Keypoint]:
|
||||
out = []
|
||||
for i, k in enumerate(keypoints):
|
||||
out.append(Keypoint(self._fx[i].filter(k.x, t),
|
||||
self._fy[i].filter(k.y, t), k.conf))
|
||||
return out
|
||||
22
tests/test_smoothing.py
Normal file
22
tests/test_smoothing.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from edge.models import Keypoint
|
||||
from edge.smoothing import OneEuroFilter, KeypointSmoother
|
||||
|
||||
|
||||
def test_one_euro_first_sample_returns_input():
|
||||
f = OneEuroFilter(min_cutoff=1.0, beta=0.0)
|
||||
assert f.filter(5.0, t=0.0) == 5.0
|
||||
|
||||
|
||||
def test_one_euro_attenuates_jitter():
|
||||
f = OneEuroFilter(min_cutoff=0.5, beta=0.0)
|
||||
f.filter(0.0, t=0.0)
|
||||
out = f.filter(10.0, t=0.1) # big jump at low cutoff -> damped below input
|
||||
assert 0.0 < out < 10.0
|
||||
|
||||
|
||||
def test_keypoint_smoother_preserves_conf_and_length():
|
||||
s = KeypointSmoother()
|
||||
kps = [Keypoint(float(i), float(i), 0.8) for i in range(17)]
|
||||
out = s.smooth(kps, t=0.0)
|
||||
assert len(out) == 17
|
||||
assert out[3].conf == 0.8
|
||||
Loading…
Reference in a new issue