本次提交实现了完整的人员行为分析系统,包括: 1. 新增基于位置和跟踪ID的两种行为检测算法 2. 新增徘徊检测服务与行为处理器模块 3. 前后端集成算法配置界面与告警展示 4. 支持图片和视频流场景下的行为分析 5. 新增算法配置接口与文档说明 具体改动: - 新增loitering_detection模型目录与算法实现 - 新增AlgorithmConfig组件实现可视化配置 - 扩展图片/视频检测接口支持算法参数传递 - 新增行为告警推送与前端展示页面 - 优化检测服务,集成行为分析逻辑 - 移除冗余日志输出,完善代码注释
162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
import cv2
|
||
import numpy as np
|
||
import base64
|
||
import logging
|
||
import json
|
||
from typing import Optional
|
||
from fastapi import APIRouter, UploadFile, File, Form, Query
|
||
from models.schemas import ImageDetectionResult
|
||
|
||
router = APIRouter()
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@router.post("/detect/image", response_model=ImageDetectionResult)
|
||
async def detect_image(
|
||
file: UploadFile = File(...),
|
||
model_id: str = Query("fire_detection"),
|
||
confidence: float = Query(0.5),
|
||
iou: float = Query(0.45),
|
||
algorithm_config: Optional[str] = Query(None, description="算法配置JSON字符串")
|
||
):
|
||
"""
|
||
图片检测接口
|
||
|
||
Args:
|
||
algorithm_config: 算法配置JSON,例如:
|
||
{
|
||
"enable_stationary_detection": true,
|
||
"enable_loitering_detection": false,
|
||
"stationary_threshold": 10.0,
|
||
"position_tolerance": 50,
|
||
"loitering_threshold": 300.0,
|
||
"movement_threshold": 5.0
|
||
}
|
||
"""
|
||
from main import model_service
|
||
from services.detection_service import DetectionService
|
||
|
||
detection_service = DetectionService(model_service)
|
||
|
||
# 解析算法配置
|
||
algo_config = None
|
||
if algorithm_config:
|
||
try:
|
||
algo_config = json.loads(algorithm_config)
|
||
except json.JSONDecodeError as e:
|
||
logger.warning(f"算法配置解析失败: {e}")
|
||
|
||
try:
|
||
contents = await file.read()
|
||
nparr = np.frombuffer(contents, np.uint8)
|
||
frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
||
|
||
if frame is None:
|
||
return ImageDetectionResult(
|
||
success=False,
|
||
message="无法读取图片",
|
||
data={}
|
||
)
|
||
|
||
result = await detection_service.detect_image(
|
||
frame, model_id, confidence, iou, algorithm_config=algo_config
|
||
)
|
||
|
||
if result['success']:
|
||
annotated_frame = detection_service.draw_detections(
|
||
frame, result['detections'], algorithm_config=algo_config
|
||
)
|
||
|
||
# 将标注后的图片转换为 base64
|
||
_, buffer = cv2.imencode('.jpg', annotated_frame)
|
||
img_base64 = base64.b64encode(buffer).decode('utf-8')
|
||
|
||
return ImageDetectionResult(
|
||
success=True,
|
||
message="检测完成",
|
||
data={
|
||
"detections": result['detections'],
|
||
"image_base64": img_base64,
|
||
"stats": result['stats'],
|
||
"alerts": result.get('alerts', []),
|
||
"behavior_stats": result.get('behavior_stats', {})
|
||
}
|
||
)
|
||
else:
|
||
return ImageDetectionResult(
|
||
success=False,
|
||
message=result['message'],
|
||
data={}
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"图片检测失败: {e}")
|
||
return ImageDetectionResult(
|
||
success=False,
|
||
message=f"检测失败: {str(e)}",
|
||
data={}
|
||
)
|
||
|
||
|
||
@router.get("/algorithms/config")
|
||
async def get_algorithm_config():
|
||
"""获取算法配置选项"""
|
||
return {
|
||
"algorithms": [
|
||
{
|
||
"id": "stationary_detection",
|
||
"name": "静止检测",
|
||
"description": "检测人员在同一位置静止停留",
|
||
"params": [
|
||
{
|
||
"name": "stationary_threshold",
|
||
"label": "静止阈值",
|
||
"type": "number",
|
||
"default": 10.0,
|
||
"min": 1.0,
|
||
"max": 300.0,
|
||
"unit": "秒",
|
||
"description": "超过此时间视为静止"
|
||
},
|
||
{
|
||
"name": "position_tolerance",
|
||
"label": "位置容差",
|
||
"type": "number",
|
||
"default": 50,
|
||
"min": 10,
|
||
"max": 200,
|
||
"unit": "像素",
|
||
"description": "位置匹配容差范围"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "loitering_detection",
|
||
"name": "徘徊检测",
|
||
"description": "检测人员长时间停留(需要跟踪ID)",
|
||
"params": [
|
||
{
|
||
"name": "loitering_threshold",
|
||
"label": "徘徊阈值",
|
||
"type": "number",
|
||
"default": 300.0,
|
||
"min": 60.0,
|
||
"max": 1800.0,
|
||
"unit": "秒",
|
||
"description": "超过此时间视为徘徊"
|
||
},
|
||
{
|
||
"name": "movement_threshold",
|
||
"label": "移动阈值",
|
||
"type": "number",
|
||
"default": 5.0,
|
||
"min": 1.0,
|
||
"max": 50.0,
|
||
"unit": "像素",
|
||
"description": "小于此移动视为静止"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|