feat: 新增PaddlePaddle检测支持,重构项目架构

1. 新增concurrently依赖用于并行启动服务
2. 新增服务器启动脚本统一管理环境变量和虚拟环境
3. 新增PaddlePaddle推理引擎和配套工具代码
4. 新增抽烟检测Paddle模型支持,完善模型管理
5. 重构开发启动脚本,优化开发体验
6. 更新.gitignore排除不必要的外部目录和缓存
7. 完善文档说明,新增PaddlePaddle部署指南
This commit is contained in:
wwh
2026-05-21 10:39:26 +08:00
parent 7aa71c5f83
commit e97bd503ec
31 changed files with 8759 additions and 199 deletions

View File

@@ -1,13 +1,13 @@
import os
import logging
from ultralytics import YOLO
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Union
logger = logging.getLogger(__name__)
class ModelService:
def __init__(self):
self.models: Dict[str, YOLO] = {}
self.models: Dict[str, Union[YOLO, object]] = {}
# 基础路径:从 apps/server/services/model_service.py 到 jc-video-web 根目录
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
@@ -46,7 +46,16 @@ class ModelService:
'labels': {'cigarette': '香烟', 'smoke': '烟雾'},
'size': '6MB',
'description': '基于YOLOv8的抽烟检测模型',
'name': '抽烟检测'
'name': '抽烟检测 (YOLOv8)'
},
'smoking_detection_paddle': {
'path': os.path.join(base_dir, 'models', 'smoking_detection_paddle', 'model.pdmodel'),
'type': 'paddle',
'classes': ['cigarette'],
'labels': {'cigarette': '香烟'},
'size': '27MB',
'description': '基于PaddlePaddle PP-YOLOE-s的抽烟检测模型更高准确率',
'name': '抽烟检测 (Paddle)'
},
'loitering_detection': {
'path': os.path.join(base_dir, 'models', 'loitering_detection', 'yolov8n.pt'),
@@ -62,7 +71,21 @@ class ModelService:
def get_available_models(self) -> List[Dict]:
available_models = []
for model_id, config in self.model_configs.items():
if os.path.exists(config['path']):
model_path = config['path']
# 检查模型是否存在Paddle模型检查目录YOLO模型检查文件
model_exists = False
if config['type'] == 'paddle':
model_dir = os.path.dirname(model_path)
required_files = ['model.pdmodel', 'model.pdiparams', 'infer_cfg.yml']
model_exists = all(
os.path.exists(os.path.join(model_dir, f))
for f in required_files
)
else:
model_exists = os.path.exists(model_path)
if model_exists:
available_models.append({
'id': model_id,
'name': config['name'],
@@ -73,10 +96,10 @@ class ModelService:
'type': config['type']
})
else:
logger.warning(f"模型文件不存在: {config['path']}")
logger.warning(f"模型文件不存在: {model_path}")
return available_models
async def load_model(self, model_id: str) -> Optional[YOLO]:
async def load_model(self, model_id: str) -> Optional[Union[YOLO, object]]:
if model_id not in self.model_configs:
logger.error(f"未知模型ID: {model_id}")
return None
@@ -86,6 +109,19 @@ class ModelService:
config = self.model_configs[model_id]
# 处理 PaddleDetection 模型
if config['type'] == 'paddle':
try:
from .paddle_detection_service import SmokingDetectionModel
logger.info(f"正在加载 PaddlePaddle Docker 服务: {model_id}")
model = SmokingDetectionModel()
self.models[model_id] = model
logger.info(f"PaddlePaddle Docker 服务加载成功: {model_id}")
return model
except Exception as e:
logger.error(f"PaddlePaddle Docker 服务加载失败: {model_id}, 错误: {e}")
return None
# 处理 YOLO 模型
model_path = config['path']
@@ -94,16 +130,16 @@ class ModelService:
return None
try:
logger.info(f"正在加载模型: {model_id} from {model_path}")
logger.info(f"正在加载 YOLO 模型: {model_id} from {model_path}")
model = YOLO(model_path)
self.models[model_id] = model
logger.info(f"模型加载成功: {model_id}")
logger.info(f"YOLO 模型加载成功: {model_id}")
return model
except Exception as e:
logger.error(f"模型加载失败: {model_id}, 错误: {e}")
logger.error(f"YOLO 模型加载失败: {model_id}, 错误: {e}")
return None
def get_model(self, model_id: str) -> Optional[YOLO]:
def get_model(self, model_id: str) -> Optional[Union[YOLO, object]]:
return self.models.get(model_id)
async def unload_model(self, model_id: str) -> bool: