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
@@ -0,0 +1,78 @@
|
||||
"""Optischer Bewegungs-Test, ohne Personenmodell oder Besucherzuordnung."""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class MotionDetector:
|
||||
"""Bildbewegung im Bereich entlang der sichtbaren Pfeilnormalen prüfen.
|
||||
|
||||
Feste, konservative Startwerte für den Nahbereichstest, keine Zusage für
|
||||
Außenbetrieb. Nur zwei aufeinanderfolgende gerichtete Beobachtungen lösen
|
||||
aus. Ein ruhiges Bild bedeutet hier keine Personen-/Bereichsfreiheit.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
import cv2
|
||||
import numpy as np
|
||||
self.cv, self.np = cv2, np
|
||||
self.previous = None
|
||||
self.previous_at = None
|
||||
self.directed_count = 0
|
||||
|
||||
def detect(self, jpeg, config, captured_at):
|
||||
cv, np = self.cv, self.np
|
||||
image = Image.open(BytesIO(jpeg)).convert('L')
|
||||
scale = min(1., 240 / max(image.size))
|
||||
size = (max(1, round(image.width * scale)), max(1, round(image.height * scale)))
|
||||
gray = np.asarray(image.resize(size, Image.Resampling.BILINEAR))
|
||||
height, width = gray.shape
|
||||
x1, y1, x2, y2 = config['region']
|
||||
region = gray[round(y1*height):round(y2*height), round(x1*width):round(x2*width)]
|
||||
if min(region.shape) < 8:
|
||||
raise ValueError('Bewegungs-Test: Bereich im Kamerabild zu klein')
|
||||
previous, previous_at = self.previous, self.previous_at
|
||||
self.previous = region.copy()
|
||||
self.previous_at = captured_at
|
||||
result = {'ready': False, 'moving': False, 'directed': False,
|
||||
'trigger': False, 'vector': [0., 0.], 'changed_fraction': 0.,
|
||||
'confirmed_frames': 0, 'note': 'Neue Bildbasis; kurz ruhig halten'}
|
||||
if (previous is None or previous.shape != region.shape or
|
||||
captured_at <= previous_at or captured_at - previous_at > 0.8):
|
||||
self.directed_count = 0
|
||||
return result
|
||||
result['ready'] = True
|
||||
changed = cv.absdiff(previous, region) > 12
|
||||
changed_fraction = float(changed.mean())
|
||||
result['changed_fraction'] = round(changed_fraction, 5)
|
||||
# Small near-camera motions must not be diluted by a large quiet ROI.
|
||||
result['moving'] = changed_fraction >= 0.001
|
||||
if not result['moving']:
|
||||
result['note'] = 'Bild ruhig / Änderung zu klein'
|
||||
self.directed_count = 0
|
||||
return result
|
||||
flow = cv.calcOpticalFlowFarneback(previous, region, None,
|
||||
0.5, 3, 15, 3, 5, 1.2, 0)
|
||||
magnitude = np.linalg.norm(flow, axis=2)
|
||||
# Only changed pixels with meaningful, bounded displacement vote.
|
||||
mask = changed & (magnitude >= 0.35) & (magnitude <= 30)
|
||||
vectors = flow[mask]
|
||||
if len(vectors) < 6:
|
||||
result['note'] = 'Bildänderung erkannt; Richtung noch nicht messbar'
|
||||
self.directed_count = 0
|
||||
return result
|
||||
a, b = config['line']
|
||||
dx, dy = (b[0]-a[0])*width, (b[1]-a[1])*height
|
||||
normal = np.array([-dy, dx], dtype=np.float32)
|
||||
normal *= config['direction'] / np.linalg.norm(normal)
|
||||
projection = vectors @ normal
|
||||
aligned = (projection >= 0.35) & (projection / np.linalg.norm(vectors, axis=1) >= 0.65)
|
||||
result['vector'] = [round(float(v), 2) for v in np.median(vectors, axis=0)]
|
||||
result['directed'] = bool(aligned.mean() >= 0.65)
|
||||
self.directed_count = self.directed_count + 1 if result['directed'] else 0
|
||||
result['confirmed_frames'] = min(2, self.directed_count)
|
||||
result['note'] = ('Bewegung in Pfeilrichtung' if result['directed']
|
||||
else 'Bewegung nicht in Pfeilrichtung')
|
||||
result['trigger'] = self.directed_count >= 2
|
||||
return result
|
||||
Reference in new issue
Block a user