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

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

202 lines
7.4 KiB
Python
Raw 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.
"""
行为检测处理器
集成基于位置和基于跟踪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
}