File size: 2,406 Bytes
a9f58f8
f2828f9
 
a9f58f8
f2828f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5547235
 
 
 
 
 
 
 
 
 
 
 
 
a9f58f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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