refactor(park-desktop): admin 后台新 UI + vision_yolo 手势接入,清理设计文档与安装包,补充工作区规范
This commit is contained in:
@@ -39,6 +39,10 @@ class Settings:
|
||||
DATA_FILE = Path(_env("DPM_DATA_FILE", str(BASE_DIR / "data.json")))
|
||||
DIST_DIR = Path(_env("DPM_DIST_DIR", str(BASE_DIR.parent / "dist"))) # 前端构建产物(可选托管)
|
||||
|
||||
# ---- 视频帧保存(摄像头识别帧,仅检测到人脸/手势时落盘,便于排查) ----
|
||||
SAVE_VISION_FRAMES = _env("DPM_SAVE_VISION_FRAMES", "1") == "1"
|
||||
VISION_FRAME_SAVE_DIR = Path(_env("DPM_VISION_SAVE_DIR", str(MEDIA_DIR / "video")))
|
||||
|
||||
# ---- MQTT(优先使用 .env 中的局域网 Broker 配置) ----
|
||||
MQTT_ENABLED = _env("DPM_MQTT_ENABLED", "1") == "1"
|
||||
MQTT_HOST = _env("DPM_MQTT_HOST", _env("MQTT_BROKER_HOST", "192.168.1.9"))
|
||||
|
||||
+5
-15
@@ -14,7 +14,7 @@ from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -171,17 +171,7 @@ async def update_check(body: UpdateCheckBody):
|
||||
# ---------- 前端静态托管 + SPA 回退(浏览器直接访问 :8000 即可) ----------
|
||||
|
||||
@app.get("/{path:path}", include_in_schema=False)
|
||||
async def spa_fallback(path: str):
|
||||
if not DIST.exists():
|
||||
return JSONResponse({"detail": "前端未构建(dist 目录不存在)"}, status_code=404)
|
||||
dist = DIST.resolve()
|
||||
if not path:
|
||||
target = dist / "index.html"
|
||||
else:
|
||||
target = (dist / path).resolve()
|
||||
if target.is_file() and target.is_relative_to(dist):
|
||||
return FileResponse(target)
|
||||
index = dist / "index.html"
|
||||
if index.exists():
|
||||
return FileResponse(index)
|
||||
return JSONResponse({"detail": "Not Found"}, status_code=404)
|
||||
async def spa_fallback(request: Request):
|
||||
"""默认导向管理后台:屏蔽通过后端地址直接访问大屏前端(含首页 / 及 /twin /ai /screen 等)。
|
||||
/api、/admin、/static、/file 等已在更早路由/挂载处理,不会走到这里。"""
|
||||
return RedirectResponse(url="/admin", status_code=307)
|
||||
|
||||
@@ -19,8 +19,12 @@ from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .config import settings
|
||||
|
||||
log = logging.getLogger("dpm.vision")
|
||||
|
||||
_frame_seq = 0
|
||||
|
||||
MODEL_DIR = Path(__file__).resolve().parent.parent / "models"
|
||||
FACE_MODEL_PATH = MODEL_DIR / "yolov8n-face.pt"
|
||||
POSE_MODEL_PATH = MODEL_DIR / "yolov8n-pose.pt"
|
||||
@@ -188,6 +192,40 @@ def predict_jpeg(jpeg_bytes: bytes):
|
||||
}
|
||||
|
||||
|
||||
def _should_save(data: dict) -> bool:
|
||||
"""仅当画面检测到人脸/手势时才值得落盘保存。"""
|
||||
return (
|
||||
int(data.get("faces") or 0) > 0
|
||||
or bool(data.get("raised"))
|
||||
or bool(data.get("fists"))
|
||||
or bool(data.get("both_up"))
|
||||
or bool(data.get("pointing"))
|
||||
or bool(data.get("hands_close"))
|
||||
)
|
||||
|
||||
|
||||
def maybe_save_frame(jpeg: bytes, data: dict):
|
||||
"""仅将命中(有人/有动作)的 JPEG 帧落盘到 MEDIA_DIR/video/<小时目录>/,便于排查识别链路。
|
||||
目录按小时分割(YYYYMMDD_HH),不自动清理。
|
||||
可通过 DPM_SAVE_VISION_FRAMES=0 关闭,或 DPM_VISION_SAVE_DIR 改目录。"""
|
||||
global _frame_seq
|
||||
if not settings.SAVE_VISION_FRAMES:
|
||||
return
|
||||
if not _should_save(data):
|
||||
return
|
||||
_frame_seq += 1
|
||||
ts = time.strftime("%Y%m%d_%H%M%S")
|
||||
hour = time.strftime("%Y%m%d_%H")
|
||||
try:
|
||||
save_dir = settings.VISION_FRAME_SAVE_DIR / hour
|
||||
save_dir.mkdir(parents=True, exist_ok=True)
|
||||
path = save_dir / f"{ts}_{_frame_seq:05d}.jpg"
|
||||
with open(path, "wb") as f:
|
||||
f.write(jpeg)
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("vision: 保存帧失败 %s", e)
|
||||
|
||||
|
||||
def predict_base64(b64: str):
|
||||
"""入口:base64 JPEG → 检测结果 dict(含 ok 标记,失败时带 error)"""
|
||||
try:
|
||||
@@ -198,6 +236,7 @@ def predict_base64(b64: str):
|
||||
try:
|
||||
data = predict_jpeg(jpeg)
|
||||
data["ok"] = True
|
||||
maybe_save_frame(jpeg, data)
|
||||
return data
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.error("vision: 推理失败 %s", e, exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user