feat:集成事件决策/规则/聚合管道
This commit is contained in:
@@ -10,6 +10,10 @@ from PIL import Image, ImageDraw, ImageFont
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from .loitering_service import get_loitering_service
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -19,12 +23,24 @@ class DetectionService:
|
|||||||
self.base_dir = os.path.dirname(os.path.dirname(__file__))
|
self.base_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
self.results_dir = os.path.join(self.base_dir, "static", "results")
|
self.results_dir = os.path.join(self.base_dir, "static", "results")
|
||||||
self.temp_dir = os.path.join(self.base_dir, "static", "temp")
|
self.temp_dir = os.path.join(self.base_dir, "static", "temp")
|
||||||
|
|
||||||
os.makedirs(self.results_dir, exist_ok=True)
|
os.makedirs(self.results_dir, exist_ok=True)
|
||||||
os.makedirs(self.temp_dir, exist_ok=True)
|
os.makedirs(self.temp_dir, exist_ok=True)
|
||||||
|
|
||||||
# 初始化徘徊检测服务(懒加载,实际初始化在第一次使用时)
|
# 初始化徘徊检测服务(懒加载,实际初始化在第一次使用时)
|
||||||
self.loitering_service = get_loitering_service()
|
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(
|
async def detect_image(
|
||||||
self,
|
self,
|
||||||
@@ -133,6 +149,9 @@ class DetectionService:
|
|||||||
result_data, algorithm_config
|
result_data, algorithm_config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
||||||
|
result_data = self._apply_event_pipeline(result_data, model_id)
|
||||||
|
|
||||||
return result_data
|
return result_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"图片检测失败: {e}")
|
logger.error(f"图片检测失败: {e}")
|
||||||
@@ -170,7 +189,8 @@ class DetectionService:
|
|||||||
|
|
||||||
if draw:
|
if draw:
|
||||||
frame = self.draw_detections(frame, detections, fps)
|
frame = self.draw_detections(frame, detections, fps)
|
||||||
|
|
||||||
|
# detect_fire_composite 已自带事件管道,此处无需再次调用
|
||||||
return frame, result_data
|
return frame, result_data
|
||||||
|
|
||||||
# 普通单模型检测
|
# 普通单模型检测
|
||||||
@@ -294,6 +314,9 @@ class DetectionService:
|
|||||||
if draw:
|
if draw:
|
||||||
frame = self.draw_detections(frame, detections, fps)
|
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
|
return frame, result_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"帧检测失败: {e}")
|
logger.error(f"帧检测失败: {e}")
|
||||||
@@ -385,7 +408,7 @@ class DetectionService:
|
|||||||
processing_time = time.time() - start_time
|
processing_time = time.time() - start_time
|
||||||
avg_confidence = sum(d['confidence'] for d in all_detections) / len(all_detections) if all_detections else 0
|
avg_confidence = sum(d['confidence'] for d in all_detections) / len(all_detections) if all_detections else 0
|
||||||
|
|
||||||
return {
|
result_data = {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': '复合火灾检测完成',
|
'message': '复合火灾检测完成',
|
||||||
'detections': all_detections,
|
'detections': all_detections,
|
||||||
@@ -400,7 +423,15 @@ class DetectionService:
|
|||||||
'model_used': 'fire_composite'
|
'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:
|
except Exception as e:
|
||||||
logger.error(f"复合火灾检测失败: {e}")
|
logger.error(f"复合火灾检测失败: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
@@ -470,6 +501,49 @@ class DetectionService:
|
|||||||
|
|
||||||
return result_data
|
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(
|
def draw_detections(
|
||||||
self,
|
self,
|
||||||
frame: np.ndarray,
|
frame: np.ndarray,
|
||||||
|
|||||||
Reference in New Issue
Block a user