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

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

162 lines
5.3 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.
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": "小于此移动视为静止"
}
]
}
]
}