feat:集成事件决策/规则/聚合管道
This commit is contained in:
@@ -10,6 +10,10 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
import torch
|
||||
|
||||
from .loitering_service import get_loitering_service
|
||||
from .adapters import DetectionAdapter
|
||||
from .event import AlertRuleEngine, EventAggregator, EventDecisionEngine
|
||||
from core.settings import get_settings
|
||||
from models.event_schemas import DetectionSource
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,12 +23,24 @@ class DetectionService:
|
||||
self.base_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
self.results_dir = os.path.join(self.base_dir, "static", "results")
|
||||
self.temp_dir = os.path.join(self.base_dir, "static", "temp")
|
||||
|
||||
|
||||
os.makedirs(self.results_dir, exist_ok=True)
|
||||
os.makedirs(self.temp_dir, exist_ok=True)
|
||||
|
||||
|
||||
# 初始化徘徊检测服务(懒加载,实际初始化在第一次使用时)
|
||||
self.loitering_service = get_loitering_service()
|
||||
|
||||
# 事件管道 (MVP-1 / P1-P2-P5)
|
||||
settings = get_settings()
|
||||
self.decision_engine = EventDecisionEngine(
|
||||
min_confidence=settings.detection.min_confidence,
|
||||
)
|
||||
rules_dir = os.path.join(self.base_dir, settings.event_engine.rules_dir)
|
||||
self.rule_engine = AlertRuleEngine.from_directory(rules_dir)
|
||||
self.event_aggregator = EventAggregator(
|
||||
dedup_window_seconds=settings.event_engine.dedup_window_seconds,
|
||||
max_active_events=settings.event_engine.max_active_events,
|
||||
)
|
||||
|
||||
async def detect_image(
|
||||
self,
|
||||
@@ -133,6 +149,9 @@ class DetectionService:
|
||||
result_data, algorithm_config
|
||||
)
|
||||
|
||||
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
||||
result_data = self._apply_event_pipeline(result_data, model_id)
|
||||
|
||||
return result_data
|
||||
except Exception as e:
|
||||
logger.error(f"图片检测失败: {e}")
|
||||
@@ -170,7 +189,8 @@ class DetectionService:
|
||||
|
||||
if draw:
|
||||
frame = self.draw_detections(frame, detections, fps)
|
||||
|
||||
|
||||
# detect_fire_composite 已自带事件管道,此处无需再次调用
|
||||
return frame, result_data
|
||||
|
||||
# 普通单模型检测
|
||||
@@ -294,6 +314,9 @@ class DetectionService:
|
||||
if draw:
|
||||
frame = self.draw_detections(frame, detections, fps)
|
||||
|
||||
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
||||
result_data = self._apply_event_pipeline(result_data, model_id=model_id)
|
||||
|
||||
return frame, result_data
|
||||
except Exception as e:
|
||||
logger.error(f"帧检测失败: {e}")
|
||||
@@ -385,7 +408,7 @@ class DetectionService:
|
||||
processing_time = time.time() - start_time
|
||||
avg_confidence = sum(d['confidence'] for d in all_detections) / len(all_detections) if all_detections else 0
|
||||
|
||||
return {
|
||||
result_data = {
|
||||
'success': True,
|
||||
'message': '复合火灾检测完成',
|
||||
'detections': all_detections,
|
||||
@@ -400,7 +423,15 @@ class DetectionService:
|
||||
'model_used': 'fire_composite'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# 事件管道 (MVP-1): 复合火灾检测产出 candidate/alert 事件
|
||||
result_data = self._apply_event_pipeline(
|
||||
result_data,
|
||||
model_id='fire_composite',
|
||||
source=DetectionSource.COMPOSITE,
|
||||
)
|
||||
return result_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"复合火灾检测失败: {e}")
|
||||
import traceback
|
||||
@@ -470,6 +501,49 @@ class DetectionService:
|
||||
|
||||
return result_data
|
||||
|
||||
def _apply_event_pipeline(
|
||||
self,
|
||||
result_data: Dict,
|
||||
model_id: Optional[str] = None,
|
||||
source_id: Optional[str] = None,
|
||||
source: DetectionSource = DetectionSource.YOLO,
|
||||
) -> Dict:
|
||||
"""对检测结果执行 决策 → 规则 → 聚合 三段管道。
|
||||
|
||||
在 ``result_data`` 中追加两个字段:
|
||||
|
||||
- ``candidate_events``: List[dict] 决策引擎产出的候选事件
|
||||
- ``alert_events``: List[dict] 规则命中后经聚合的预警事件
|
||||
"""
|
||||
|
||||
if not result_data.get('success') or not result_data.get('detections'):
|
||||
result_data['candidate_events'] = []
|
||||
result_data['alert_events'] = []
|
||||
return result_data
|
||||
|
||||
try:
|
||||
unified_result = DetectionAdapter.from_yolo(
|
||||
result_data, model_id=model_id, source=source
|
||||
)
|
||||
candidates = self.decision_engine.decide(unified_result, source_id=source_id)
|
||||
alerts = self.rule_engine.evaluate(candidates)
|
||||
emitted = self.event_aggregator.aggregate(alerts)
|
||||
|
||||
result_data['candidate_events'] = [
|
||||
event.model_dump(mode='json') for event in candidates
|
||||
]
|
||||
result_data['alert_events'] = [
|
||||
event.model_dump(mode='json') for event in emitted
|
||||
]
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error(f"事件管道执行失败: {e}")
|
||||
result_data['candidate_events'] = []
|
||||
result_data['alert_events'] = []
|
||||
result_data['event_pipeline_error'] = str(e)
|
||||
|
||||
return result_data
|
||||
|
||||
|
||||
def draw_detections(
|
||||
self,
|
||||
frame: np.ndarray,
|
||||
|
||||
Reference in New Issue
Block a user