movement-tracker / tests /test_motion.py
blambert's picture
feat: scan continuously while detecting motion
a9f58f8
Raw History Blame Contribute Delete
2.41 kB
import cv2
import numpy as np
from movement_tracker.motion import MotionDetector, ScanDetector
rng = np.random.default_rng(0)
BG = rng.integers(60, 120, (720, 1280, 3), dtype=np.uint8)
def noisy(frame: np.ndarray) -> np.ndarray:
return np.clip(frame.astype(int) + rng.integers(-6, 7, frame.shape), 0, 255).astype(
np.uint8
)
def warmed_up() -> MotionDetector:
d = MotionDetector()
for _ in range(40):
d.detect(noisy(BG))
return d
def test_static_scene_has_no_motion():
assert warmed_up().detect(noisy(BG)) is None
def test_picks_largest_blob():
frame = BG.copy()
frame[100:300, 900:1100] = 250
frame[500:560, 100:160] = 250
x, y = warmed_up().detect(noisy(frame))
assert abs(x - 1000) < 10 and abs(y - 200) < 10
def test_ignores_motion_during_warmup():
frame = BG.copy()
frame[100:300, 900:1100] = 250
assert MotionDetector().detect(frame) is None
def test_detects_object_slightly_darker_than_background():
# MOG2 shadow detection would discard this as a shadow
bg = np.full((720, 1280, 3), 150, np.uint8)
d = MotionDetector()
for _ in range(30):
d.detect(bg)
frame = bg.copy()
frame[200:500, 400:700] = 110
x, y = d.detect(frame)
assert abs(x - 550) < 10 and abs(y - 350) < 10
def textured_world(width: int) -> np.ndarray:
"""A scene with enough texture for the scan detector to track features."""
rng = np.random.default_rng(1)
world = rng.integers(0, 255, (720, width, 3), dtype=np.uint8)
world = cv2.GaussianBlur(world, (0, 0), 6)
return cv2.normalize(world, None, 30, 220, cv2.NORM_MINMAX)
def pan(world: np.ndarray, frames: int, obj_x=None) -> list:
"""Detections while the camera pans right 6px per frame across the world."""
d, hits = ScanDetector(), []
for i in range(frames):
w = world.copy()
if obj_x is not None:
x = obj_x(i)
w[250:480, x : x + 90] = 240
hits.append(d.detect(w[:, i * 6 : i * 6 + 1280]))
return hits
def test_scan_ignores_camera_motion():
assert not any(pan(textured_world(1800), 60))
def test_scan_finds_moving_object():
# object walks right 8px/frame; in view coordinates its center is 545 + 2i
hits = pan(textured_world(1800), 40, obj_x=lambda i: 500 + 8 * i)
x, y = hits[-1]
assert abs(x - (545 + 2 * 39)) < 60 and abs(y - 365) < 30