Spaces:
Sleeping
Sleeping
File size: 2,359 Bytes
11fab85 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | """時間域平滑與固定時窗差分的小工具(無狀態機邏輯,只做數值處理)。
差分一律以「時間」為窗(而非幀數),確保換 fps 不需重調閾值。
"""
from __future__ import annotations
from collections import deque
class RollingMedian:
"""固定長度滑動中位數;窗未滿時回傳現有樣本的中位數。
選中位數而非平均:單幀關鍵點跳動(離群值)會拉壞平均,
中位數在 5 幀窗內即可壓掉單幀 outlier 而不引入明顯延遲。
"""
def __init__(self, window: int):
self._buf: deque[float] = deque(maxlen=max(1, int(window)))
def push(self, x: float) -> float:
self._buf.append(float(x))
s = sorted(self._buf)
n = len(s)
if n % 2:
return s[n // 2]
return 0.5 * (s[n // 2 - 1] + s[n // 2])
def __len__(self) -> int:
return len(self._buf)
class TimedBuffer:
"""(t, value) 緩衝:支援取「t−Δ 附近」的樣本做固定時窗差分。"""
def __init__(self, horizon_s: float):
self.horizon_s = float(horizon_s)
self._buf: deque[tuple[float, float]] = deque()
def push(self, t: float, v: float) -> None:
self._buf.append((float(t), float(v)))
while self._buf and self._buf[0][0] < t - self.horizon_s - 1e-9:
self._buf.popleft()
def sample_at_or_before(self, t_query: float) -> tuple[float, float] | None:
"""時間戳 ≤ t_query 的最新樣本;若全部樣本都比 t_query 新,退回最舊樣本
(讓差分在歷史稍短時仍可用,由呼叫端以最短時距把關)。"""
if not self._buf:
return None
best: tuple[float, float] | None = None
for t, v in self._buf:
if t <= t_query + 1e-9:
best = (t, v)
else:
break
return best if best is not None else self._buf[0]
def rate(self, t_now: float, v_now: float, delta_s: float) -> float | None:
"""(v_now − v(t_now−Δ)) / 實際時距;歷史不足(時距 < Δ/2)回 None。"""
got = self.sample_at_or_before(t_now - delta_s)
if got is None:
return None
t0, v0 = got
span = t_now - t0
if span <= 0 or span < 0.5 * delta_s:
return None
return (v_now - v0) / span
|