Files
jc-video-recognize/apps/server/services/loitering_service.py
wwh 7aa71c5f83 feat: 新增人员徘徊/静止行为分析功能
本次提交实现了完整的人员行为分析系统,包括:
1. 新增基于位置和跟踪ID的两种行为检测算法
2. 新增徘徊检测服务与行为处理器模块
3. 前后端集成算法配置界面与告警展示
4. 支持图片和视频流场景下的行为分析
5. 新增算法配置接口与文档说明

具体改动:
- 新增loitering_detection模型目录与算法实现
- 新增AlgorithmConfig组件实现可视化配置
- 扩展图片/视频检测接口支持算法参数传递
- 新增行为告警推送与前端展示页面
- 优化检测服务,集成行为分析逻辑
- 移除冗余日志输出,完善代码注释
2026-05-19 09:17:09 +08:00

169 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
徘徊检测服务
集成行为检测算法到后端服务
"""
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