Private
Public Access
addet test set
This commit is contained in:
1 parent
d370f992ce
commit
b0f35bf3cc
21 files changed
+1796
-183
No files matched your search
+58
-19
@@ -13,14 +13,22 @@ from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .motion import MotionDetector
|
||||
|
||||
DEFAULT_CONFIG = {"enabled": False, "line": [[0.2, 0.55], [0.8, 0.55]],
|
||||
|
||||
DEFAULT_CONFIG = {"enabled": False, "method": "person_line", "line": [[0.2, 0.55], [0.8, 0.55]],
|
||||
"direction": 1, "region": [0.1, 0.15, 0.9, 0.95]}
|
||||
|
||||
|
||||
def validate_config(value):
|
||||
# Migrate previous saved configurations and old clients without changing
|
||||
# their enabled setting or requiring a new geometry setup.
|
||||
if isinstance(value, dict) and 'method' not in value:
|
||||
value = {**value, 'method': 'person_line'}
|
||||
if set(value) != set(DEFAULT_CONFIG) or type(value["enabled"]) is not bool:
|
||||
raise ValueError("Ungültige Erkennungseinstellungen")
|
||||
if value['method'] not in ('person_line', 'motion_test'):
|
||||
raise ValueError('Unbekannte Erkennungsmethode')
|
||||
if type(value["direction"]) is not int or value["direction"] not in (-1, 1):
|
||||
raise ValueError("Richtung muss -1 oder 1 sein")
|
||||
line, region = value["line"], value["region"]
|
||||
@@ -180,7 +188,7 @@ class DetectionService:
|
||||
self.last_valid = None
|
||||
self.started = self.clock()
|
||||
self.fps = 0.
|
||||
self.result = {"frame_number": 0, "people": [], "image_base64": None}
|
||||
self.result = {"frame_number": 0, "people": [], "image_base64": None, "motion": None}
|
||||
|
||||
def reset_state(self):
|
||||
self.revision += 1
|
||||
@@ -211,7 +219,7 @@ class DetectionService:
|
||||
self.reset_state()
|
||||
self.started = self.clock()
|
||||
self.last_valid = None
|
||||
self.result = {"frame_number": 0, "people": [], "image_base64": None}
|
||||
self.result = {"frame_number": 0, "people": [], "image_base64": None, "motion": None}
|
||||
return self.get_config()
|
||||
|
||||
def get_config(self):
|
||||
@@ -243,8 +251,10 @@ class DetectionService:
|
||||
elif self.clock() - (self.last_valid if self.last_valid is not None else self.started) > 2:
|
||||
self.fail("Keine gültige Erkennung seit mehr als zwei Sekunden; zurücksetzen")
|
||||
|
||||
def process(self, people, number, jpeg, captured_at, version):
|
||||
def process(self, people, number, jpeg, captured_at, version, motion=None):
|
||||
event_mode = None
|
||||
event_method = None
|
||||
event_region = None
|
||||
with self.lock:
|
||||
now = self.clock()
|
||||
if self.stop_event.is_set():
|
||||
@@ -261,17 +271,26 @@ class DetectionService:
|
||||
if not self.fault:
|
||||
self.error = None
|
||||
image = Image.open(BytesIO(jpeg))
|
||||
people, crossing = self.tracker.update(people, self.config, now, image.width/image.height)
|
||||
self.result = {"frame_number": number, "people": people,
|
||||
if self.config['method'] == 'motion_test':
|
||||
if motion is None:
|
||||
self.fail('Bewegungs-Test liefert kein Ergebnis; zurücksetzen')
|
||||
return
|
||||
people = []
|
||||
crossing = motion['trigger']
|
||||
else:
|
||||
motion = None
|
||||
people, crossing = self.tracker.update(people, self.config, now, image.width/image.height)
|
||||
self.result = {"frame_number": number, "people": people, "motion": motion,
|
||||
"image_base64": base64.b64encode(jpeg).decode("ascii")}
|
||||
if not self.config["enabled"] or self.fault or self.controller.stopped:
|
||||
self.armed = False
|
||||
self.free_since = None
|
||||
return
|
||||
rx1, ry1, rx2, ry2 = self.config["region"]
|
||||
# Auch unbestätigte Personenrahmen verhindern eine falsche Freigabe.
|
||||
occupied = any(p["box"][0] <= rx2 and p["box"][2] >= rx1 and
|
||||
p["box"][1] <= ry2 and p["box"][3] >= ry1 for p in people)
|
||||
# In motion test mode, a quiet image is NOT proof of no visitors.
|
||||
occupied = (not motion['ready'] or motion['moving']) if motion is not None else any(
|
||||
p["box"][0] <= rx2 and p["box"][2] >= rx1 and
|
||||
p["box"][1] <= ry2 and p["box"][3] >= ry1 for p in people)
|
||||
if occupied:
|
||||
self.free_since = None
|
||||
else:
|
||||
@@ -288,17 +307,22 @@ class DetectionService:
|
||||
self.free_since = None
|
||||
if accepted:
|
||||
event_mode = self.controller.last_event["mode"]
|
||||
event_method = self.config['method']
|
||||
event_region = list(self.config['region'])
|
||||
# SQLite darf UI und unabhängigen Watchdog nicht blockieren.
|
||||
if event_mode is not None:
|
||||
try:
|
||||
self.events.record_detection_trigger(event_mode)
|
||||
if event_method == 'motion_test':
|
||||
self.events.record_motion_trigger(event_mode, jpeg=jpeg, region=event_region)
|
||||
else:
|
||||
self.events.record_detection_trigger(event_mode, jpeg=jpeg, region=event_region)
|
||||
except Exception as exc:
|
||||
self.fail(f"Ereignis nicht gespeichert: {exc}")
|
||||
self.fail(f"Ereignis/Bild nicht gespeichert: {exc}")
|
||||
|
||||
def status(self):
|
||||
with self.lock:
|
||||
age = None if self.last_valid is None else self.clock()-self.last_valid
|
||||
return {"enabled": self.config["enabled"], "ready": age is not None and age <= 2 and not self.error,
|
||||
return {"enabled": self.config["enabled"], "method": self.config["method"], "ready": age is not None and age <= 2 and not self.error,
|
||||
"error": self.config_error or self.error, "reset_required": self.fault,
|
||||
"armed": self.armed and not self.fault and not self.controller.stopped
|
||||
and self.controller.generation == self.generation
|
||||
@@ -309,7 +333,7 @@ class DetectionService:
|
||||
with self.lock:
|
||||
status = self.status()
|
||||
fresh = status["ready"]
|
||||
return {**self.result, "people": self.result["people"] if fresh else [],
|
||||
return {**self.result, "motion": self.result["motion"] if fresh else None, "people": self.result["people"] if fresh else [],
|
||||
"image_base64": self.result["image_base64"] if fresh else None,
|
||||
"status": status}
|
||||
|
||||
@@ -325,27 +349,42 @@ class DetectionService:
|
||||
self.thread.join(timeout=3)
|
||||
|
||||
def _run(self):
|
||||
detector, last_number, last_completed = None, 0, None
|
||||
detector, detector_key, last_number, last_completed = None, None, 0, None
|
||||
while not self.stop_event.is_set():
|
||||
began = self.clock()
|
||||
version = None
|
||||
try:
|
||||
if detector is None:
|
||||
detector = self.factory(self.model_path)
|
||||
with self.lock:
|
||||
if self.controller.generation != self.generation:
|
||||
self.reset_state()
|
||||
version = self.revision
|
||||
config = self.get_config()
|
||||
# A fresh motion baseline is required for every reconfiguration,
|
||||
# controller change and reset. Never reuse pre-change optical flow.
|
||||
key = (config['method'], version)
|
||||
if detector is None or detector_key != key:
|
||||
detector = MotionDetector() if config['method'] == 'motion_test' else self.factory(self.model_path)
|
||||
detector_key = key
|
||||
last_completed = None
|
||||
number, jpeg, captured_at = self.camera.latest_timed()
|
||||
if jpeg is not None and number != last_number:
|
||||
last_number = number
|
||||
people = detector.detect(jpeg)
|
||||
self.process(people, number, jpeg, captured_at, version)
|
||||
if config['method'] == 'motion_test':
|
||||
motion = detector.detect(jpeg, config, captured_at)
|
||||
self.process([], number, jpeg, captured_at, version, motion=motion)
|
||||
else:
|
||||
people = detector.detect(jpeg)
|
||||
self.process(people, number, jpeg, captured_at, version)
|
||||
completed = self.clock()
|
||||
with self.lock:
|
||||
self.fps = 0. if last_completed is None else 1/max(0.001, completed-last_completed)
|
||||
last_completed = completed
|
||||
except Exception as exc:
|
||||
self.fail(f"Personenerkennung nicht verfügbar: {exc}")
|
||||
# An old in-flight model failure must not fault a newly selected
|
||||
# method. A current failure still uses the usual reset latch.
|
||||
with self.lock:
|
||||
if version == self.revision and self.controller.generation == self.generation:
|
||||
self.fail(f"Erkennung nicht verfügbar: {exc}")
|
||||
detector = None
|
||||
self.stop_event.wait(1)
|
||||
self.stop_event.wait(max(0, 0.2-(self.clock()-began)))
|
||||
Reference in new issue
Block a user