feat: 新增人员徘徊/静止行为分析功能
本次提交实现了完整的人员行为分析系统,包括: 1. 新增基于位置和跟踪ID的两种行为检测算法 2. 新增徘徊检测服务与行为处理器模块 3. 前后端集成算法配置界面与告警展示 4. 支持图片和视频流场景下的行为分析 5. 新增算法配置接口与文档说明 具体改动: - 新增loitering_detection模型目录与算法实现 - 新增AlgorithmConfig组件实现可视化配置 - 扩展图片/视频检测接口支持算法参数传递 - 新增行为告警推送与前端展示页面 - 优化检测服务,集成行为分析逻辑 - 移除冗余日志输出,完善代码注释
This commit is contained in:
168
apps/server/services/loitering_service.py
Normal file
168
apps/server/services/loitering_service.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
徘徊检测服务
|
||||
集成行为检测算法到后端服务
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
import logging
|
||||
|
||||
# 添加算法模块路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'models', 'loitering_detection'))
|
||||
|
||||
from processors import BehaviorProcessor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoiteringService:
|
||||
"""
|
||||
徘徊检测服务
|
||||
|
||||
为视频流检测提供行为分析功能:
|
||||
- 静止检测(基于位置,无需跟踪)
|
||||
- 徘徊检测(基于跟踪ID)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.processor = None
|
||||
self.is_initialized = False
|
||||
|
||||
def initialize(
|
||||
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: Optional[float] = None,
|
||||
loitering_alert_threshold: Optional[float] = None
|
||||
):
|
||||
"""
|
||||
初始化服务
|
||||
|
||||
Args:
|
||||
stationary_threshold: 静止检测阈值(秒)- 用于判断是否静止
|
||||
position_tolerance: 位置容差(像素)
|
||||
loitering_threshold: 徘徊检测阈值(秒)- 用于判断是否徘徊
|
||||
movement_threshold: 移动阈值(像素)
|
||||
enable_stationary_alert: 是否启用静止告警
|
||||
enable_loitering_alert: 是否启用徘徊告警
|
||||
stationary_alert_threshold: 静止告警阈值(秒)- 超过此时间产生告警,默认等于 stationary_threshold
|
||||
loitering_alert_threshold: 徘徊告警阈值(秒)- 超过此时间产生告警,默认等于 loitering_threshold
|
||||
"""
|
||||
try:
|
||||
self.processor = BehaviorProcessor(
|
||||
stationary_threshold=stationary_threshold,
|
||||
position_tolerance=position_tolerance,
|
||||
loitering_threshold=loitering_threshold,
|
||||
movement_threshold=movement_threshold,
|
||||
enable_stationary_alert=enable_stationary_alert,
|
||||
enable_loitering_alert=enable_loitering_alert,
|
||||
stationary_alert_threshold=stationary_alert_threshold if stationary_alert_threshold is not None else stationary_threshold,
|
||||
loitering_alert_threshold=loitering_alert_threshold if loitering_alert_threshold is not None else loitering_threshold
|
||||
)
|
||||
self.is_initialized = True
|
||||
logger.info(f"徘徊检测服务初始化成功: 静止阈值={stationary_threshold}s, 告警阈值={stationary_alert_threshold or stationary_threshold}s")
|
||||
except Exception as e:
|
||||
logger.error(f"徘徊检测服务初始化失败: {e}")
|
||||
self.is_initialized = False
|
||||
|
||||
def process_detections(
|
||||
self,
|
||||
detections: List[Dict],
|
||||
use_tracking: bool = False,
|
||||
track_id_key: str = 'track_id'
|
||||
) -> Dict:
|
||||
"""
|
||||
处理检测结果
|
||||
|
||||
Args:
|
||||
detections: YOLO检测结果列表
|
||||
use_tracking: 是否使用跟踪ID
|
||||
track_id_key: 跟踪ID字段名
|
||||
|
||||
Returns:
|
||||
{
|
||||
'detections': 添加行为信息的检测结果,
|
||||
'alerts': 触发的告警列表,
|
||||
'stats': 统计信息
|
||||
}
|
||||
"""
|
||||
if not self.is_initialized or not self.processor:
|
||||
return {
|
||||
'detections': detections,
|
||||
'alerts': [],
|
||||
'stats': {'error': '服务未初始化'}
|
||||
}
|
||||
|
||||
try:
|
||||
return self.processor.process(
|
||||
detections=detections,
|
||||
use_tracking=use_tracking,
|
||||
track_id_key=track_id_key
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"处理检测结果失败: {e}")
|
||||
return {
|
||||
'detections': detections,
|
||||
'alerts': [],
|
||||
'stats': {'error': str(e)}
|
||||
}
|
||||
|
||||
def get_stationary_persons(self) -> List[Dict]:
|
||||
"""获取所有静止人员"""
|
||||
if not self.is_initialized or not self.processor:
|
||||
return []
|
||||
return self.processor.get_stationary_persons()
|
||||
|
||||
def get_loitering_persons(self) -> List[Dict]:
|
||||
"""获取所有徘徊人员"""
|
||||
if not self.is_initialized or not self.processor:
|
||||
return []
|
||||
return self.processor.get_loitering_persons()
|
||||
|
||||
def reset(self):
|
||||
"""重置检测器"""
|
||||
if self.processor:
|
||||
self.processor.reset()
|
||||
logger.info("徘徊检测器已重置")
|
||||
|
||||
def get_config(self) -> Dict:
|
||||
"""获取当前配置"""
|
||||
if not self.is_initialized or not self.processor:
|
||||
return {'error': '服务未初始化'}
|
||||
return self.processor.get_config()
|
||||
|
||||
def get_stats(self) -> Dict:
|
||||
"""获取统计信息"""
|
||||
if not self.is_initialized or not self.processor:
|
||||
return {'error': '服务未初始化'}
|
||||
|
||||
stats = {
|
||||
'stationary_count': len(self.get_stationary_persons()),
|
||||
'loitering_count': len(self.get_loitering_persons()),
|
||||
'config': self.get_config()
|
||||
}
|
||||
return stats
|
||||
|
||||
|
||||
# 全局服务实例
|
||||
_loitering_service: Optional[LoiteringService] = None
|
||||
|
||||
|
||||
def get_loitering_service() -> LoiteringService:
|
||||
"""获取全局徘徊检测服务实例"""
|
||||
global _loitering_service
|
||||
if _loitering_service is None:
|
||||
_loitering_service = LoiteringService()
|
||||
return _loitering_service
|
||||
|
||||
|
||||
def initialize_loitering_service(**kwargs):
|
||||
"""初始化全局徘徊检测服务"""
|
||||
service = get_loitering_service()
|
||||
service.initialize(**kwargs)
|
||||
return service
|
||||
Reference in New Issue
Block a user