import os import sys import time from collections import deque from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Tuple import cv2 import numpy as np sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) class LeavePostAlgorithm: STATE_ON_DUTY = "ON_DUTY" STATE_OFF_DUTY_COUNTDOWN = "OFF_DUTY_COUNTDOWN" STATE_NON_WORK_TIME = "NON_WORK_TIME" STATE_INIT = "INIT" def __init__( self, threshold_sec: int = 300, confirm_sec: int = 10, return_sec: int = 30, working_hours: Optional[List[Dict]] = None, ): self.threshold_sec = threshold_sec self.confirm_sec = confirm_sec self.return_sec = return_sec self.working_hours = working_hours or [] self.alert_cooldowns: Dict[str, datetime] = {} self.cooldown_seconds = 300 self.state: str = self.STATE_INIT self.state_start_time: Optional[datetime] = None self.on_duty_window = deque() self.alarm_sent: bool = False self.last_person_seen_time: Optional[datetime] = None self.last_detection_time: Optional[datetime] = None self.init_start_time: Optional[datetime] = None def is_in_working_hours(self, dt: Optional[datetime] = None) -> bool: if not self.working_hours: return True dt = dt or datetime.now() current_minutes = dt.hour * 60 + dt.minute for period in self.working_hours: start_minutes = period["start"][0] * 60 + period["start"][1] end_minutes = period["end"][0] * 60 + period["end"][1] if start_minutes <= current_minutes < end_minutes: return True return False def check_detection_in_roi(self, detection: Dict, roi_id: str) -> bool: matched_rois = detection.get("matched_rois", []) for roi in matched_rois: if roi.get("roi_id") == roi_id: return True return False def process( self, roi_id: str, camera_id: str, tracks: List[Dict], current_time: Optional[datetime] = None, ) -> List[Dict]: current_time = current_time or datetime.now() roi_has_person = False for det in tracks: if self.check_detection_in_roi(det, roi_id): roi_has_person = True break in_work = self.is_in_working_hours(current_time) alerts = [] if not in_work: self.state = self.STATE_NON_WORK_TIME self.last_person_seen_time = None self.last_detection_time = None self.on_duty_window.clear() self.alarm_sent = False self.init_start_time = None else: if self.state == self.STATE_NON_WORK_TIME: self.state = self.STATE_INIT self.init_start_time = current_time self.on_duty_window.clear() self.alarm_sent = False if self.state == self.STATE_INIT: if roi_has_person: self.state = self.STATE_ON_DUTY self.state_start_time = current_time self.on_duty_window.clear() self.on_duty_window.append((current_time, True)) self.last_person_seen_time = current_time self.last_detection_time = current_time self.init_start_time = None else: if self.init_start_time is None: self.init_start_time = current_time elapsed_since_init = (current_time - self.init_start_time).total_seconds() if elapsed_since_init >= self.threshold_sec: self.state = self.STATE_OFF_DUTY_COUNTDOWN self.state_start_time = current_time self.alarm_sent = False elif self.state == self.STATE_ON_DUTY: if roi_has_person: self.last_person_seen_time = current_time self.last_detection_time = current_time self.on_duty_window.append((current_time, True)) while self.on_duty_window and (current_time - self.on_duty_window[0][0]).total_seconds() > self.confirm_sec: self.on_duty_window.popleft() else: self.on_duty_window.append((current_time, False)) while self.on_duty_window and (current_time - self.on_duty_window[0][0]).total_seconds() > self.confirm_sec: self.on_duty_window.popleft() hit_ratio = sum(1 for t, detected in self.on_duty_window if detected) / max(len(self.on_duty_window), 1) if hit_ratio == 0: self.state = self.STATE_OFF_DUTY_COUNTDOWN self.state_start_time = current_time self.alarm_sent = False elif self.state == self.STATE_OFF_DUTY_COUNTDOWN: elapsed = (current_time - self.state_start_time).total_seconds() if roi_has_person: self.state = self.STATE_ON_DUTY self.state_start_time = current_time self.on_duty_window.clear() self.on_duty_window.append((current_time, True)) self.last_person_seen_time = current_time self.alarm_sent = False elif elapsed >= self.threshold_sec: if not self.alarm_sent: cooldown_key = f"{roi_id}" if cooldown_key not in self.alert_cooldowns or ( current_time - self.alert_cooldowns[cooldown_key] ).total_seconds() > self.cooldown_seconds: bbox = self.get_latest_bbox_in_roi(tracks, roi_id) elapsed_minutes = int(elapsed / 60) alerts.append({ "track_id": roi_id, "bbox": bbox, "off_duty_duration": elapsed, "alert_type": "leave_post", "message": f"离岗超过 {elapsed_minutes} 分钟", }) self.alarm_sent = True self.alert_cooldowns[cooldown_key] = current_time return alerts def get_latest_bbox_in_roi(self, tracks: List[Dict], roi_id: str) -> List[float]: for det in tracks: if self.check_detection_in_roi(det, roi_id): return det.get("bbox", []) return [] def reset(self): self.state = self.STATE_INIT self.state_start_time = None self.on_duty_window.clear() self.alarm_sent = False self.last_person_seen_time = None self.last_detection_time = None self.init_start_time = None self.alert_cooldowns.clear() def get_state(self, track_id: str) -> Optional[Dict[str, Any]]: return { "state": self.state, "alarm_sent": self.alarm_sent, "last_person_seen_time": self.last_person_seen_time, } class IntrusionAlgorithm: def __init__(self, cooldown_seconds: int = 300): self.cooldown_seconds = cooldown_seconds self.last_alert_time: Dict[str, float] = {} self.alert_triggered: Dict[str, bool] = {} def is_roi_has_person(self, tracks: List[Dict], roi_id: str) -> bool: for det in tracks: matched_rois = det.get("matched_rois", []) for roi in matched_rois: if roi.get("roi_id") == roi_id: return True return False def get_latest_bbox_in_roi(self, tracks: List[Dict], roi_id: str) -> List[float]: for det in tracks: matched_rois = det.get("matched_rois", []) for roi in matched_rois: if roi.get("roi_id") == roi_id: return det.get("bbox", []) return [] def process( self, roi_id: str, camera_id: str, tracks: List[Dict], current_time: Optional[datetime] = None, ) -> List[Dict]: roi_has_person = self.is_roi_has_person(tracks, roi_id) if not roi_has_person: return [] now = time.monotonic() key = f"{camera_id}_{roi_id}" if key not in self.last_alert_time: self.last_alert_time[key] = 0 self.alert_triggered[key] = False if now - self.last_alert_time[key] >= self.cooldown_seconds: self.last_alert_time[key] = now self.alert_triggered[key] = False if self.alert_triggered[key]: return [] bbox = self.get_latest_bbox_in_roi(tracks, roi_id) self.alert_triggered[key] = True return [{ "roi_id": roi_id, "bbox": bbox, "alert_type": "intrusion", "message": "检测到周界入侵", }] def reset(self): self.last_alert_time.clear() self.alert_triggered.clear() class AlgorithmManager: def __init__(self, working_hours: Optional[List[Dict]] = None): self.algorithms: Dict[str, Dict[str, Any]] = {} self.working_hours = working_hours or [] self.default_params = { "leave_post": { "threshold_sec": 300, "confirm_sec": 10, "return_sec": 30, }, "intrusion": { "cooldown_seconds": 300, }, } def register_algorithm( self, roi_id: str, algorithm_type: str, params: Optional[Dict[str, Any]] = None, ): if roi_id in self.algorithms: if algorithm_type in self.algorithms[roi_id]: return if roi_id not in self.algorithms: self.algorithms[roi_id] = {} algo_params = self.default_params.get(algorithm_type, {}) if params: algo_params.update(params) if algorithm_type == "leave_post": roi_working_hours = algo_params.get("working_hours") or self.working_hours self.algorithms[roi_id]["leave_post"] = LeavePostAlgorithm( threshold_sec=algo_params.get("threshold_sec", 300), confirm_sec=algo_params.get("confirm_sec", 10), return_sec=algo_params.get("return_sec", 30), working_hours=roi_working_hours, ) elif algorithm_type == "intrusion": self.algorithms[roi_id]["intrusion"] = IntrusionAlgorithm( cooldown_seconds=algo_params.get("cooldown_seconds", 300), ) def process( self, roi_id: str, camera_id: str, algorithm_type: str, tracks: List[Dict], current_time: Optional[datetime] = None, ) -> List[Dict]: algo = self.algorithms.get(roi_id, {}).get(algorithm_type) if algo is None: return [] return algo.process(roi_id, camera_id, tracks, current_time) def update_roi_params( self, roi_id: str, algorithm_type: str, params: Dict[str, Any], ): if roi_id in self.algorithms and algorithm_type in self.algorithms[roi_id]: algo = self.algorithms[roi_id][algorithm_type] for key, value in params.items(): if hasattr(algo, key): setattr(algo, key, value) def reset_algorithm(self, roi_id: str, algorithm_type: Optional[str] = None): if roi_id not in self.algorithms: return if algorithm_type: if algorithm_type in self.algorithms[roi_id]: self.algorithms[roi_id][algorithm_type].reset() else: for algo in self.algorithms[roi_id].values(): algo.reset() def reset_all(self): for roi_algorithms in self.algorithms.values(): for algo in roi_algorithms.values(): algo.reset() def remove_roi(self, roi_id: str): if roi_id in self.algorithms: self.reset_algorithm(roi_id) del self.algorithms[roi_id] def get_status(self, roi_id: str) -> Dict[str, Any]: status = {} if roi_id in self.algorithms: for algo_type, algo in self.algorithms[roi_id].items(): if algo_type == "leave_post": status[algo_type] = { "state": getattr(algo, "state", "INIT_STATE"), "alarm_sent": getattr(algo, "alarm_sent", False), } else: status[algo_type] = { "track_count": len(getattr(algo, "track_states", {})), } return status