feat: 新增人员徘徊/静止行为分析功能
本次提交实现了完整的人员行为分析系统,包括: 1. 新增基于位置和跟踪ID的两种行为检测算法 2. 新增徘徊检测服务与行为处理器模块 3. 前后端集成算法配置界面与告警展示 4. 支持图片和视频流场景下的行为分析 5. 新增算法配置接口与文档说明 具体改动: - 新增loitering_detection模型目录与算法实现 - 新增AlgorithmConfig组件实现可视化配置 - 扩展图片/视频检测接口支持算法参数传递 - 新增行为告警推送与前端展示页面 - 优化检测服务,集成行为分析逻辑 - 移除冗余日志输出,完善代码注释
This commit is contained in:
8
models/loitering_detection/processors/__init__.py
Normal file
8
models/loitering_detection/processors/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
徘徊检测处理器模块
|
||||
用于对检测结果进行后处理
|
||||
"""
|
||||
|
||||
from .behavior_processor import BehaviorProcessor
|
||||
|
||||
__all__ = ['BehaviorProcessor']
|
||||
201
models/loitering_detection/processors/behavior_processor.py
Normal file
201
models/loitering_detection/processors/behavior_processor.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
行为检测处理器
|
||||
集成基于位置和基于跟踪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
|
||||
}
|
||||
Reference in New Issue
Block a user