feat(edge): main-loop wiring (run_once) + pipeline factory
This commit is contained in:
parent
e74cc7f898
commit
b621627863
2 changed files with 103 additions and 0 deletions
55
edge/app.py
Normal file
55
edge/app.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from edge.config import Config
|
||||
from edge.tracking import IouTracker
|
||||
from edge.smoothing import KeypointSmoother
|
||||
from edge.detectors.fall import FallDetector
|
||||
from edge.detectors.immobility import ImmobilityWatchdog
|
||||
from edge.detectors.ghost import GhostIncidentDetector
|
||||
from edge.detectors.zone import ZoneIntrusionDetector
|
||||
from edge.engine import EventEngine
|
||||
from edge.snapshot import RingBuffer, FaceBlurrer, Snapshotter
|
||||
from edge.models import Person, BBox
|
||||
|
||||
|
||||
def _head_boxes(persons: list) -> list:
|
||||
# 배경인물 머리영역 근사: 코 주변 작은 박스 (MVP; 실제는 얼굴 검출 포트로 교체)
|
||||
boxes = []
|
||||
for p in persons:
|
||||
nose = p.keypoints[0]
|
||||
boxes.append(BBox(nose.x - 20, nose.y - 25, nose.x + 20, nose.y + 15))
|
||||
return boxes
|
||||
|
||||
|
||||
def run_once(source, pose, tracker, smoother, engine, ring, snapshotter, sink, now_fn):
|
||||
ok, frame = source.read()
|
||||
if not ok:
|
||||
return []
|
||||
now = now_fn()
|
||||
persons = pose.infer(frame)
|
||||
smoothed = [Person(p.bbox, smoother.smooth(p.keypoints, now)) for p in persons]
|
||||
tracks = tracker.update(smoothed, now)
|
||||
lost = tracker.lost()
|
||||
ring.push(now, frame)
|
||||
events = engine.process(tracks, lost, now)
|
||||
if events:
|
||||
snaps = snapshotter.capture([frame], [_head_boxes(persons)])
|
||||
for e in events:
|
||||
sink.try_send(e, snaps)
|
||||
return events
|
||||
|
||||
|
||||
def build_pipeline(config: Config, source, pose) -> dict:
|
||||
return {
|
||||
"source": source,
|
||||
"pose": pose,
|
||||
"tracker": IouTracker(),
|
||||
"smoother": KeypointSmoother(),
|
||||
"engine": EventEngine(
|
||||
FallDetector(config.fall),
|
||||
ImmobilityWatchdog(config.immobility),
|
||||
GhostIncidentDetector(config.ghost),
|
||||
ZoneIntrusionDetector([], config.zone),
|
||||
config.engine,
|
||||
),
|
||||
"ring": RingBuffer(),
|
||||
"snapshotter": Snapshotter(FaceBlurrer()),
|
||||
}
|
||||
48
tests/test_app.py
Normal file
48
tests/test_app.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import numpy as np
|
||||
from edge.models import Person, BBox, Keypoint
|
||||
from edge.tracking import IouTracker
|
||||
from edge.smoothing import KeypointSmoother
|
||||
from edge.detectors.fall import FallDetector, FallConfig
|
||||
from edge.detectors.immobility import ImmobilityWatchdog, ImmobilityConfig
|
||||
from edge.detectors.ghost import GhostIncidentDetector, GhostConfig
|
||||
from edge.detectors.zone import ZoneIntrusionDetector, ZoneConfig
|
||||
from edge.engine import EventEngine, EngineConfig
|
||||
from edge.snapshot import RingBuffer, FaceBlurrer, Snapshotter
|
||||
from edge.events import EventType
|
||||
from edge.app import run_once
|
||||
|
||||
def _lying_person():
|
||||
kps = [Keypoint(150, 300, 0.9) for _ in range(17)]
|
||||
kps[5] = Keypoint(100, 300, 0.9); kps[6] = Keypoint(100, 310, 0.9)
|
||||
kps[11] = Keypoint(200, 300, 0.9); kps[12] = Keypoint(200, 310, 0.9)
|
||||
kps[0] = Keypoint(90, 320, 0.9)
|
||||
return Person(BBox(90, 290, 210, 320), kps)
|
||||
|
||||
class _Source:
|
||||
def read(self): return True, np.full((360, 640, 3), 128, np.uint8)
|
||||
|
||||
class _Pose:
|
||||
def infer(self, frame): return [_lying_person()]
|
||||
|
||||
class _Sink:
|
||||
def __init__(self): self.events = []
|
||||
def try_send(self, event, snapshots):
|
||||
self.events.append(event); return True
|
||||
|
||||
def test_run_once_pipeline_emits_fall_after_prone():
|
||||
src, pose = _Source(), _Pose()
|
||||
tracker = IouTracker()
|
||||
smoother = KeypointSmoother()
|
||||
engine = EventEngine(
|
||||
FallDetector(FallConfig(prone_confirm_s=0.5)),
|
||||
ImmobilityWatchdog(ImmobilityConfig(immobile_s=999)),
|
||||
GhostIncidentDetector(GhostConfig()),
|
||||
ZoneIntrusionDetector([], ZoneConfig()),
|
||||
EngineConfig(camera_id="cam-01"),
|
||||
)
|
||||
ring = RingBuffer(); snap = Snapshotter(FaceBlurrer()); sink = _Sink()
|
||||
times = iter([0.0, 0.6])
|
||||
def now_fn(): return next(times)
|
||||
run_once(src, pose, tracker, smoother, engine, ring, snap, sink, now_fn)
|
||||
run_once(src, pose, tracker, smoother, engine, ring, snap, sink, now_fn)
|
||||
assert any(e.event_type == EventType.FALL for e in sink.events)
|
||||
Loading…
Reference in a new issue