From 7aa71c5f83b6a1f22fc2bb273e41162482cec353 Mon Sep 17 00:00:00 2001
From: wwh <496479012@qq.com>
Date: Tue, 19 May 2026 09:17:09 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E4=BA=BA=E5=91=98?=
=?UTF-8?q?=E5=BE=98=E5=BE=8A/=E9=9D=99=E6=AD=A2=E8=A1=8C=E4=B8=BA?=
=?UTF-8?q?=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
本次提交实现了完整的人员行为分析系统,包括:
1. 新增基于位置和跟踪ID的两种行为检测算法
2. 新增徘徊检测服务与行为处理器模块
3. 前后端集成算法配置界面与告警展示
4. 支持图片和视频流场景下的行为分析
5. 新增算法配置接口与文档说明
具体改动:
- 新增loitering_detection模型目录与算法实现
- 新增AlgorithmConfig组件实现可视化配置
- 扩展图片/视频检测接口支持算法参数传递
- 新增行为告警推送与前端展示页面
- 优化检测服务,集成行为分析逻辑
- 移除冗余日志输出,完善代码注释
---
apps/server/api/detection.py | 103 +++++-
apps/server/services/camera_service.py | 14 +-
apps/server/services/detection_service.py | 251 ++++++++++----
apps/server/services/loitering_service.py | 168 +++++++++
apps/server/services/model_service.py | 1 -
apps/web/src/api/detection.js | 16 +-
apps/web/src/components/AlgorithmConfig.vue | 326 ++++++++++++++++++
apps/web/src/components/ImageDetection.vue | 118 ++++++-
apps/web/src/components/VideoDetection.vue | 148 +++++++-
models/README.md | 163 +++++++++
.../algorithms/__init__.py | 9 +
.../algorithms/loitering_detector.py | 251 ++++++++++++++
.../algorithms/stationary_detector.py | 236 +++++++++++++
.../processors/__init__.py | 8 +
.../processors/behavior_processor.py | 201 +++++++++++
15 files changed, 1937 insertions(+), 76 deletions(-)
create mode 100644 apps/server/services/loitering_service.py
create mode 100644 apps/web/src/components/AlgorithmConfig.vue
create mode 100644 models/loitering_detection/algorithms/__init__.py
create mode 100644 models/loitering_detection/algorithms/loitering_detector.py
create mode 100644 models/loitering_detection/algorithms/stationary_detector.py
create mode 100644 models/loitering_detection/processors/__init__.py
create mode 100644 models/loitering_detection/processors/behavior_processor.py
diff --git a/apps/server/api/detection.py b/apps/server/api/detection.py
index d986789..a79c7bd 100644
--- a/apps/server/api/detection.py
+++ b/apps/server/api/detection.py
@@ -2,24 +2,50 @@ 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)
+ 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)
@@ -32,10 +58,14 @@ async def detect_image(
data={}
)
- result = await detection_service.detect_image(frame, model_id, confidence, iou)
+ 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'])
+ annotated_frame = detection_service.draw_detections(
+ frame, result['detections'], algorithm_config=algo_config
+ )
# 将标注后的图片转换为 base64
_, buffer = cv2.imencode('.jpg', annotated_frame)
@@ -47,7 +77,9 @@ async def detect_image(
data={
"detections": result['detections'],
"image_base64": img_base64,
- "stats": result['stats']
+ "stats": result['stats'],
+ "alerts": result.get('alerts', []),
+ "behavior_stats": result.get('behavior_stats', {})
}
)
else:
@@ -64,3 +96,66 @@ async def detect_image(
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": "小于此移动视为静止"
+ }
+ ]
+ }
+ ]
+ }
diff --git a/apps/server/services/camera_service.py b/apps/server/services/camera_service.py
index 5ddaecf..46d2783 100644
--- a/apps/server/services/camera_service.py
+++ b/apps/server/services/camera_service.py
@@ -249,11 +249,21 @@ class CameraService:
logger.info(f"发送检测结果: {len(result['detections'])} 个目标, {result['stats']}")
- await websocket.send_json({
+ detection_message = {
'type': 'detection',
'detections': result['detections'],
'stats': result['stats']
- })
+ }
+
+ # 包含行为告警信息
+ if 'alerts' in result and result['alerts']:
+ detection_message['alerts'] = result['alerts']
+ logger.info(f"发送告警: {len(result['alerts'])} 个")
+
+ if 'behavior_stats' in result:
+ detection_message['behavior_stats'] = result['behavior_stats']
+
+ await websocket.send_json(detection_message)
_, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
import base64
diff --git a/apps/server/services/detection_service.py b/apps/server/services/detection_service.py
index 9aa0b9f..0f64e3e 100644
--- a/apps/server/services/detection_service.py
+++ b/apps/server/services/detection_service.py
@@ -7,6 +7,8 @@ import logging
from typing import Dict, List, Optional
from PIL import Image, ImageDraw, ImageFont
+from .loitering_service import get_loitering_service
+
logger = logging.getLogger(__name__)
class DetectionService:
@@ -18,64 +20,20 @@ class DetectionService:
os.makedirs(self.results_dir, exist_ok=True)
os.makedirs(self.temp_dir, exist_ok=True)
-
- def draw_detections(self, frame: np.ndarray, detections: List[Dict], fps: float = 0) -> np.ndarray:
- try:
- img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
- pil_img = Image.fromarray(img_rgb)
- draw = ImageDraw.Draw(pil_img)
-
- try:
- font = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 20)
- font_large = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 24)
- except:
- font = ImageFont.load_default()
- font_large = font
-
- class_colors = {
- 'Fire': (255, 0, 0),
- 'Smoke': (128, 128, 128),
- 'person': (0, 255, 0),
- 'helmet': (255, 255, 0),
- 'no_helmet': (255, 0, 255),
- 'cigarette': (0, 165, 255) # 橙色,用于抽烟检测
- }
-
- for det in detections:
- x1, y1, x2, y2 = det['bbox']
- class_name = det['class']
- conf = det['confidence']
- label = det['label']
-
- color = class_colors.get(class_name, (0, 255, 0))
-
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
-
- label_text = f"{label} {conf:.2f}"
- bbox = draw.textbbox((0, 0), label_text, font=font)
- text_w = bbox[2] - bbox[0]
- text_h = bbox[3] - bbox[1]
- draw.rectangle([x1, y1 - text_h - 4, x1 + text_w + 4, y1], fill=color)
- draw.text((x1 + 2, y1 - text_h - 2), label_text, fill=(255, 255, 255), font=font)
-
- if fps > 0:
- fps_text = f"FPS: {fps:.1f} | Detections: {len(detections)}"
- draw.text((10, 10), fps_text, fill=(0, 255, 0), font=font)
-
- return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
- except Exception as e:
- logger.error(f"绘制检测结果失败: {e}")
- return frame
+
+ # 初始化徘徊检测服务(懒加载,实际初始化在第一次使用时)
+ self.loitering_service = get_loitering_service()
async def detect_image(
- self,
+ self,
image: np.ndarray,
model_id: str,
confidence: float = 0.5,
- iou: float = 0.45
+ iou: float = 0.45,
+ algorithm_config: Optional[Dict] = None
) -> Dict:
start_time = time.time()
-
+
model = await self.model_service.load_model(model_id)
if not model:
return {
@@ -84,10 +42,10 @@ class DetectionService:
'detections': [],
'stats': None
}
-
+
try:
results = model(image, conf=confidence, iou=iou, verbose=False)
-
+
detections = []
for result in results:
boxes = result.boxes
@@ -96,21 +54,21 @@ class DetectionService:
conf = float(box.conf[0].cpu().numpy())
cls = int(box.cls[0].cpu().numpy())
class_name = result.names[cls]
-
+
label_map = self.model_service.model_configs[model_id]['labels']
label = label_map.get(class_name, class_name)
-
+
detections.append({
'class': class_name,
'label': label,
'confidence': round(conf, 3),
'bbox': [int(x1), int(y1), int(x2), int(y2)]
})
-
+
processing_time = time.time() - start_time
avg_confidence = sum(d['confidence'] for d in detections) / len(detections) if detections else 0
-
- return {
+
+ result_data = {
'success': True,
'message': '检测完成',
'detections': detections,
@@ -121,6 +79,14 @@ class DetectionService:
'model_used': model_id
}
}
+
+ # 如果启用了行为检测算法
+ if algorithm_config and detections:
+ result_data = self._apply_behavior_analysis(
+ result_data, algorithm_config
+ )
+
+ return result_data
except Exception as e:
logger.error(f"图片检测失败: {e}")
return {
@@ -186,9 +152,40 @@ class DetectionService:
}
}
+ # 如果是人员检测模型,进行行为分析
+ logger.info(f"[DetectionService] 模型: {model_id}, 检测目标: {len(detections)}")
+ if model_id == 'loitering_detection' and detections:
+ logger.info("[DetectionService] 调用行为分析...")
+
+ # 确保服务已初始化
+ if not self.loitering_service.is_initialized:
+ logger.info("[DetectionService] 初始化徘徊检测服务...")
+ self.loitering_service.initialize(
+ # 检测阈值(用于判断是否静止/徘徊)
+ stationary_threshold=10.0,
+ position_tolerance=50,
+ loitering_threshold=300.0,
+ movement_threshold=5.0,
+ # 告警阈值(用于触发告警,应该比检测阈值高)
+ stationary_alert_threshold=30.0,
+ loitering_alert_threshold=600.0,
+ # 启用告警
+ enable_stationary_alert=True,
+ enable_loitering_alert=True
+ )
+
+ behavior_result = self.loitering_service.process_detections(
+ detections,
+ use_tracking=False # 可以改为 True 如果使用跟踪
+ )
+ detections = behavior_result['detections']
+ result_data['alerts'] = behavior_result['alerts']
+ result_data['behavior_stats'] = behavior_result['stats']
+ logger.info(f"[DetectionService] 行为分析完成: alerts={len(behavior_result['alerts'])}, stats={behavior_result['stats']}")
+
if draw:
frame = self.draw_detections(frame, detections, fps)
-
+
return frame, result_data
except Exception as e:
logger.error(f"帧检测失败: {e}")
@@ -197,3 +194,139 @@ class DetectionService:
'detections': [],
'stats': None
}
+
+ def _apply_behavior_analysis(
+ self,
+ result_data: Dict,
+ algorithm_config: Dict
+ ) -> Dict:
+ """
+ 应用行为分析算法
+
+ Args:
+ result_data: 检测结果
+ algorithm_config: 算法配置
+ {
+ "enable_stationary_detection": true,
+ "enable_loitering_detection": false,
+ "stationary_threshold": 10.0,
+ "position_tolerance": 50,
+ ...
+ }
+
+ Returns:
+ 添加行为分析结果的检测结果
+ """
+ detections = result_data['detections']
+
+ # 检查是否需要行为分析
+ enable_stationary = algorithm_config.get('enable_stationary_detection', False)
+ enable_loitering = algorithm_config.get('enable_loitering_detection', False)
+
+ if not enable_stationary and not enable_loitering:
+ return result_data
+
+ try:
+ # 使用前端传入的配置初始化服务
+ self.loitering_service.initialize(
+ stationary_threshold=algorithm_config.get('stationary_threshold', 10.0),
+ position_tolerance=algorithm_config.get('position_tolerance', 50),
+ loitering_threshold=algorithm_config.get('loitering_threshold', 300.0),
+ movement_threshold=algorithm_config.get('movement_threshold', 5.0),
+ enable_stationary_alert=enable_stationary,
+ enable_loitering_alert=enable_loitering
+ )
+
+ # 处理检测
+ behavior_result = self.loitering_service.process_detections(
+ detections,
+ use_tracking=enable_loitering # 只有启用徘徊检测时才使用跟踪
+ )
+
+ result_data['detections'] = behavior_result['detections']
+ result_data['alerts'] = behavior_result['alerts']
+ result_data['behavior_stats'] = behavior_result['stats']
+
+ except Exception as e:
+ logger.error(f"行为分析失败: {e}")
+ result_data['behavior_error'] = str(e)
+
+ return result_data
+
+ def draw_detections(
+ self,
+ frame: np.ndarray,
+ detections: List[Dict],
+ fps: float = 0,
+ algorithm_config: Optional[Dict] = None
+ ) -> np.ndarray:
+ """
+ 绘制检测结果和行为告警
+
+ Args:
+ frame: 图像帧
+ detections: 检测结果列表(可能包含 stationary_info/loitering_info)
+ fps: 帧率
+ algorithm_config: 算法配置(已废弃,保留用于向后兼容)
+ """
+ try:
+ img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
+ pil_img = Image.fromarray(img_rgb)
+ draw = ImageDraw.Draw(pil_img)
+
+ try:
+ font = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 20)
+ font_large = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 24)
+ except:
+ font = ImageFont.load_default()
+ font_large = font
+
+ class_colors = {
+ 'Fire': (255, 0, 0),
+ 'Smoke': (128, 128, 128),
+ 'person': (0, 255, 0),
+ 'helmet': (255, 255, 0),
+ 'no_helmet': (255, 0, 255),
+ 'cigarette': (0, 165, 255)
+ }
+
+ for det in detections:
+ x1, y1, x2, y2 = det['bbox']
+ class_name = det['class']
+ conf = det['confidence']
+ label = det['label']
+
+ # 根据是否有行为告警选择颜色
+ color = class_colors.get(class_name, (0, 255, 0))
+
+ # 检查行为告警
+ if algorithm_config:
+ if 'stationary_info' in det:
+ info = det['stationary_info']
+ if info.get('is_stationary'):
+ color = (0, 0, 255) # 红色警告
+ label = f"静止{int(info['duration'])}s"
+
+ if 'loitering_info' in det:
+ info = det['loitering_info']
+ if info.get('is_loitering'):
+ color = (255, 0, 0) # 蓝色警告
+ label = f"徘徊{int(info['loitering_duration']//60)}min"
+
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
+
+ label_text = f"{label} {conf:.2f}"
+ bbox = draw.textbbox((0, 0), label_text, font=font)
+ text_w = bbox[2] - bbox[0]
+ text_h = bbox[3] - bbox[1]
+ draw.rectangle([x1, y1 - text_h - 4, x1 + text_w + 4, y1], fill=color)
+ draw.text((x1 + 2, y1 - text_h - 2), label_text, fill=(255, 255, 255), font=font)
+
+ if fps > 0:
+ fps_text = f"FPS: {fps:.1f} | Detections: {len(detections)}"
+ draw.text((10, 10), fps_text, fill=(0, 255, 0), font=font)
+
+ return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
+ except Exception as e:
+ logger.error(f"绘制检测结果失败: {e}")
+ return frame
diff --git a/apps/server/services/loitering_service.py b/apps/server/services/loitering_service.py
new file mode 100644
index 0000000..c9c401b
--- /dev/null
+++ b/apps/server/services/loitering_service.py
@@ -0,0 +1,168 @@
+"""
+徘徊检测服务
+集成行为检测算法到后端服务
+"""
+
+import sys
+import os
+from typing import Dict, List, Optional
+import logging
+
+# 添加算法模块路径
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'models', 'loitering_detection'))
+
+from processors import BehaviorProcessor
+
+logger = logging.getLogger(__name__)
+
+
+class LoiteringService:
+ """
+ 徘徊检测服务
+
+ 为视频流检测提供行为分析功能:
+ - 静止检测(基于位置,无需跟踪)
+ - 徘徊检测(基于跟踪ID)
+ """
+
+ def __init__(self):
+ self.processor = None
+ self.is_initialized = False
+
+ def initialize(
+ 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: Optional[float] = None,
+ loitering_alert_threshold: Optional[float] = None
+ ):
+ """
+ 初始化服务
+
+ Args:
+ stationary_threshold: 静止检测阈值(秒)- 用于判断是否静止
+ position_tolerance: 位置容差(像素)
+ loitering_threshold: 徘徊检测阈值(秒)- 用于判断是否徘徊
+ movement_threshold: 移动阈值(像素)
+ enable_stationary_alert: 是否启用静止告警
+ enable_loitering_alert: 是否启用徘徊告警
+ stationary_alert_threshold: 静止告警阈值(秒)- 超过此时间产生告警,默认等于 stationary_threshold
+ loitering_alert_threshold: 徘徊告警阈值(秒)- 超过此时间产生告警,默认等于 loitering_threshold
+ """
+ try:
+ self.processor = BehaviorProcessor(
+ stationary_threshold=stationary_threshold,
+ position_tolerance=position_tolerance,
+ loitering_threshold=loitering_threshold,
+ movement_threshold=movement_threshold,
+ enable_stationary_alert=enable_stationary_alert,
+ enable_loitering_alert=enable_loitering_alert,
+ stationary_alert_threshold=stationary_alert_threshold if stationary_alert_threshold is not None else stationary_threshold,
+ loitering_alert_threshold=loitering_alert_threshold if loitering_alert_threshold is not None else loitering_threshold
+ )
+ self.is_initialized = True
+ logger.info(f"徘徊检测服务初始化成功: 静止阈值={stationary_threshold}s, 告警阈值={stationary_alert_threshold or stationary_threshold}s")
+ except Exception as e:
+ logger.error(f"徘徊检测服务初始化失败: {e}")
+ self.is_initialized = False
+
+ def process_detections(
+ self,
+ detections: List[Dict],
+ use_tracking: bool = False,
+ track_id_key: str = 'track_id'
+ ) -> Dict:
+ """
+ 处理检测结果
+
+ Args:
+ detections: YOLO检测结果列表
+ use_tracking: 是否使用跟踪ID
+ track_id_key: 跟踪ID字段名
+
+ Returns:
+ {
+ 'detections': 添加行为信息的检测结果,
+ 'alerts': 触发的告警列表,
+ 'stats': 统计信息
+ }
+ """
+ if not self.is_initialized or not self.processor:
+ return {
+ 'detections': detections,
+ 'alerts': [],
+ 'stats': {'error': '服务未初始化'}
+ }
+
+ try:
+ return self.processor.process(
+ detections=detections,
+ use_tracking=use_tracking,
+ track_id_key=track_id_key
+ )
+ except Exception as e:
+ logger.error(f"处理检测结果失败: {e}")
+ return {
+ 'detections': detections,
+ 'alerts': [],
+ 'stats': {'error': str(e)}
+ }
+
+ def get_stationary_persons(self) -> List[Dict]:
+ """获取所有静止人员"""
+ if not self.is_initialized or not self.processor:
+ return []
+ return self.processor.get_stationary_persons()
+
+ def get_loitering_persons(self) -> List[Dict]:
+ """获取所有徘徊人员"""
+ if not self.is_initialized or not self.processor:
+ return []
+ return self.processor.get_loitering_persons()
+
+ def reset(self):
+ """重置检测器"""
+ if self.processor:
+ self.processor.reset()
+ logger.info("徘徊检测器已重置")
+
+ def get_config(self) -> Dict:
+ """获取当前配置"""
+ if not self.is_initialized or not self.processor:
+ return {'error': '服务未初始化'}
+ return self.processor.get_config()
+
+ def get_stats(self) -> Dict:
+ """获取统计信息"""
+ if not self.is_initialized or not self.processor:
+ return {'error': '服务未初始化'}
+
+ stats = {
+ 'stationary_count': len(self.get_stationary_persons()),
+ 'loitering_count': len(self.get_loitering_persons()),
+ 'config': self.get_config()
+ }
+ return stats
+
+
+# 全局服务实例
+_loitering_service: Optional[LoiteringService] = None
+
+
+def get_loitering_service() -> LoiteringService:
+ """获取全局徘徊检测服务实例"""
+ global _loitering_service
+ if _loitering_service is None:
+ _loitering_service = LoiteringService()
+ return _loitering_service
+
+
+def initialize_loitering_service(**kwargs):
+ """初始化全局徘徊检测服务"""
+ service = get_loitering_service()
+ service.initialize(**kwargs)
+ return service
diff --git a/apps/server/services/model_service.py b/apps/server/services/model_service.py
index 0657600..644d7ef 100644
--- a/apps/server/services/model_service.py
+++ b/apps/server/services/model_service.py
@@ -82,7 +82,6 @@ class ModelService:
return None
if model_id in self.models:
- logger.info(f"模型已加载: {model_id}")
return self.models[model_id]
config = self.model_configs[model_id]
diff --git a/apps/web/src/api/detection.js b/apps/web/src/api/detection.js
index cb36f76..b935bfb 100644
--- a/apps/web/src/api/detection.js
+++ b/apps/web/src/api/detection.js
@@ -9,12 +9,22 @@ export const detectionApi = {
getModels() {
return api.get('/models')
},
-
- detectImage(formData) {
+
+ getAlgorithmConfig() {
+ return api.get('/algorithms/config')
+ },
+
+ detectImage(formData, algorithmConfig = null) {
+ const params = {}
+ if (algorithmConfig) {
+ params.algorithm_config = JSON.stringify(algorithmConfig)
+ }
+
return api.post('/detect/image', formData, {
headers: {
'Content-Type': 'multipart/form-data'
- }
+ },
+ params
})
}
}
diff --git a/apps/web/src/components/AlgorithmConfig.vue b/apps/web/src/components/AlgorithmConfig.vue
new file mode 100644
index 0000000..583efef
--- /dev/null
+++ b/apps/web/src/components/AlgorithmConfig.vue
@@ -0,0 +1,326 @@
+
+