本次提交实现了完整的人员行为分析系统,包括: 1. 新增基于位置和跟踪ID的两种行为检测算法 2. 新增徘徊检测服务与行为处理器模块 3. 前后端集成算法配置界面与告警展示 4. 支持图片和视频流场景下的行为分析 5. 新增算法配置接口与文档说明 具体改动: - 新增loitering_detection模型目录与算法实现 - 新增AlgorithmConfig组件实现可视化配置 - 扩展图片/视频检测接口支持算法参数传递 - 新增行为告警推送与前端展示页面 - 优化检测服务,集成行为分析逻辑 - 移除冗余日志输出,完善代码注释
202 lines
7.4 KiB
Python
202 lines
7.4 KiB
Python
"""
|
||
行为检测处理器
|
||
集成基于位置和基于跟踪ID的检测算法
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import logging
|
||
from typing import Dict, List, Optional, Tuple
|
||
from dataclasses import dataclass
|
||
|
||
# 添加算法模块路径
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||
|
||
from algorithms import PositionBasedStationaryDetector, LoiteringDetector
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@dataclass
|
||
class BehaviorAlert:
|
||
"""行为告警"""
|
||
alert_type: str # 'stationary', 'loitering'
|
||
level: str # 'low', 'medium', 'high'
|
||
message: str
|
||
person_id: Optional[str] = None
|
||
position_id: Optional[str] = None
|
||
duration: float = 0.0
|
||
bbox: Optional[Tuple[int, int, int, int]] = None
|
||
|
||
|
||
class BehaviorProcessor:
|
||
"""
|
||
行为检测处理器
|
||
|
||
整合两种检测方式:
|
||
1. 基于位置的静止检测(无需跟踪ID)
|
||
2. 基于跟踪ID的徘徊检测(需要跟踪ID)
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
# 静止检测参数
|
||
stationary_threshold: float = 10.0,
|
||
position_tolerance: int = 50,
|
||
|
||
# 徘徊检测参数
|
||
loitering_threshold: float = 300.0,
|
||
movement_threshold: float = 5.0,
|
||
|
||
# 告警参数
|
||
enable_stationary_alert: bool = True,
|
||
enable_loitering_alert: bool = True,
|
||
stationary_alert_threshold: float = 10.0, # 超过此时间产生告警
|
||
loitering_alert_threshold: float = 300.0 # 超过此时间产生告警
|
||
):
|
||
# 初始化检测器
|
||
self.stationary_detector = PositionBasedStationaryDetector(
|
||
stationary_threshold=stationary_threshold,
|
||
position_tolerance=position_tolerance
|
||
)
|
||
|
||
self.loitering_detector = LoiteringDetector(
|
||
loitering_threshold=loitering_threshold,
|
||
stationary_threshold=stationary_threshold,
|
||
movement_threshold=movement_threshold
|
||
)
|
||
|
||
# 配置
|
||
self.enable_stationary_alert = enable_stationary_alert
|
||
self.enable_loitering_alert = enable_loitering_alert
|
||
self.stationary_alert_threshold = stationary_alert_threshold
|
||
self.loitering_alert_threshold = loitering_alert_threshold
|
||
|
||
def process(
|
||
self,
|
||
detections: List[Dict],
|
||
use_tracking: bool = False,
|
||
track_id_key: str = 'track_id'
|
||
) -> Dict:
|
||
"""
|
||
处理检测结果,检测行为
|
||
|
||
Args:
|
||
detections: 检测结果列表
|
||
use_tracking: 是否使用跟踪ID(如果有的话)
|
||
track_id_key: 跟踪ID字段名
|
||
|
||
Returns:
|
||
{
|
||
'detections': 添加行为信息的检测结果,
|
||
'alerts': 触发的告警列表,
|
||
'stats': 统计信息
|
||
}
|
||
"""
|
||
logger.info(f"[BehaviorProcessor] 开始处理 {len(detections)} 个检测结果")
|
||
logger.info(f"[BehaviorProcessor] 配置: stationary={self.enable_stationary_alert}, loitering={self.enable_loitering_alert}")
|
||
|
||
alerts = []
|
||
|
||
# 1. 始终进行基于位置的静止检测
|
||
logger.info(f"[BehaviorProcessor] 调用静止检测器...")
|
||
detections = self.stationary_detector.detect(detections)
|
||
logger.info(f"[BehaviorProcessor] 静止检测完成,检测到 {len(detections)} 个结果")
|
||
|
||
# 检查静止告警
|
||
stationary_alerts = 0
|
||
if self.enable_stationary_alert:
|
||
for det in detections:
|
||
info = det.get('stationary_info', {})
|
||
if info.get('is_stationary') and info.get('duration', 0) >= self.stationary_alert_threshold:
|
||
alert = BehaviorAlert(
|
||
alert_type='stationary',
|
||
level='medium' if info['duration'] < 30 else 'high',
|
||
message=f"人员静止停留 {int(info['duration'])} 秒",
|
||
position_id=info.get('position_id'),
|
||
duration=info['duration'],
|
||
bbox=tuple(det['bbox'])
|
||
)
|
||
alerts.append(alert)
|
||
stationary_alerts += 1
|
||
logger.info(f"[BehaviorProcessor] 静止告警: {stationary_alerts} 个")
|
||
|
||
# 2. 如果有跟踪ID,进行徘徊检测
|
||
logger.info(f"[BehaviorProcessor] use_tracking={use_tracking}")
|
||
if use_tracking:
|
||
detections = self.loitering_detector.detect(detections, id_key=track_id_key)
|
||
|
||
# 检查徘徊告警
|
||
if self.enable_loitering_alert:
|
||
for det in detections:
|
||
info = det.get('loitering_info', {})
|
||
if info.get('is_loitering') and info.get('loitering_duration', 0) >= self.loitering_alert_threshold:
|
||
alert = BehaviorAlert(
|
||
alert_type='loitering',
|
||
level='high',
|
||
message=f"人员徘徊 {int(info['loitering_duration'] // 60)} 分钟",
|
||
person_id=str(info.get('person_id')),
|
||
duration=info['loitering_duration'],
|
||
bbox=tuple(det['bbox'])
|
||
)
|
||
alerts.append(alert)
|
||
|
||
# 统计信息
|
||
stats = {
|
||
'total_detections': len(detections),
|
||
'stationary_count': len(self.stationary_detector.get_all_stationary()),
|
||
'alert_count': len(alerts)
|
||
}
|
||
|
||
if use_tracking:
|
||
stats.update({
|
||
'loitering_count': len(self.loitering_detector.get_all_loitering()),
|
||
'tracking_count': self.loitering_detector.get_stats()['total_tracks']
|
||
})
|
||
|
||
logger.info(f"[BehaviorProcessor] 处理完成: {stats}")
|
||
|
||
return {
|
||
'detections': detections,
|
||
'alerts': [self._alert_to_dict(a) for a in alerts],
|
||
'stats': stats
|
||
}
|
||
|
||
def _alert_to_dict(self, alert: BehaviorAlert) -> Dict:
|
||
"""将告警对象转换为字典"""
|
||
return {
|
||
'type': alert.alert_type,
|
||
'level': alert.level,
|
||
'message': alert.message,
|
||
'person_id': alert.person_id,
|
||
'position_id': alert.position_id,
|
||
'duration': round(alert.duration, 2),
|
||
'bbox': alert.bbox
|
||
}
|
||
|
||
def get_stationary_persons(self) -> List[Dict]:
|
||
"""获取所有静止人员"""
|
||
return self.stationary_detector.get_all_stationary()
|
||
|
||
def get_loitering_persons(self) -> List[Dict]:
|
||
"""获取所有徘徊人员"""
|
||
return self.loitering_detector.get_all_loitering()
|
||
|
||
def reset(self):
|
||
"""重置所有检测器"""
|
||
self.stationary_detector.reset()
|
||
self.loitering_detector.reset()
|
||
|
||
def get_config(self) -> Dict:
|
||
"""获取当前配置"""
|
||
return {
|
||
'stationary_threshold': self.stationary_detector.stationary_threshold,
|
||
'position_tolerance': self.stationary_detector.position_tolerance,
|
||
'loitering_threshold': self.loitering_detector.loitering_threshold,
|
||
'movement_threshold': self.loitering_detector.movement_threshold,
|
||
'enable_stationary_alert': self.enable_stationary_alert,
|
||
'enable_loitering_alert': self.enable_loitering_alert,
|
||
'stationary_alert_threshold': self.stationary_alert_threshold,
|
||
'loitering_alert_threshold': self.loitering_alert_threshold
|
||
}
|