恢复清场解锁与菜名去重,修复订单帧清理数量。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
LiXin01 2026-10-12 15:15:16 +08:00
parent 2c0aa81cab
commit 53956015bc
2 changed files with 58 additions and 26 deletions

View File

@ -96,6 +96,8 @@ DISH_LIVE_STILL_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_STILL_HOLD_SECOND
DISH_LIVE_MOTION_SIM_WITH_ORDER = float(os.environ.get('DISH_LIVE_MOTION_SIM_WITH_ORDER', '0.970'))
# 同一批菜品订单防抖:菜名未变不重复存图/统计;短间隔内亦抑制重复落单
DISH_LIVE_ORDER_DEBOUNCE_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_DEBOUNCE_SECONDS', '0.5'))
# 无餐盘持续该秒数后清场解锁,允许下一盘(含相同菜名)重新存图/上报
DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS', '1.5'))
DEBUG = True

View File

@ -19,6 +19,7 @@ import os
import sys
import threading
import time
from collections import Counter
from pathlib import Path
import cv2
@ -49,6 +50,7 @@ from dao.settings import (
DISH_LIVE_STILL_HOLD_SECONDS,
DISH_LIVE_MOTION_SIM_WITH_ORDER,
DISH_LIVE_ORDER_DEBOUNCE_SECONDS,
DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS,
)
from dao.base_registry import bootstrap_default_base, get_active_base, set_default_base
from models.App_settings import AppSetting
@ -132,9 +134,11 @@ class BaseDishApp(PendingLearningUIMixin):
self._live_stable_since = None
self._live_stable_committed_sig = None
self._live_stable_snapshot = None # 稳定期间保留的 dishes,供写入
self._last_reported_sig = None # 实时上报去重:相同结果只报一次
self._last_reported_sig = None # 实时上报去重:相同菜名集合只报一次
self._last_order_dish_sig = None # 仅菜名签名:菜未变不重复存图/统计
self._last_order_at = 0.0
self._order_clear_since = None # 无盘连续计时起点
self._order_clear_hold = float(DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS)
self._last_detect_fp = None # 识别后锚点指纹(静止等待运动;仅餐盘区域)
self._last_live_dishes = None # 上一轮识别结果(跳过检测时推进稳定计时)
self._last_motion_boxes = None # 上一轮餐盘框,运动判定只看菜品区域
@ -228,13 +232,13 @@ class BaseDishApp(PendingLearningUIMixin):
save_path = os.path.abspath(os.path.join("images", f"_live_frame_{stamp}.jpg"))
try:
if cv2.imwrite(save_path, frame_bgr):
self._cleanup_old_live_frames(keep=3000000)
self._cleanup_old_live_frames(keep=3000)
return save_path
except Exception as e:
print(f"保存订单帧失败: {e}")
return None
def _cleanup_old_live_frames(self, keep=30):
def _cleanup_old_live_frames(self, keep=3000):
"""清理过期订单帧,防止 images/ 无限增长。"""
try:
folder = os.path.abspath("images")
@ -1753,22 +1757,17 @@ class BaseDishApp(PendingLearningUIMixin):
return (len(parts), tuple(parts))
def _report_signature(self, dishes):
"""上报去重签名:已知菜品名 + 粗量化框(放置不动时不重复上报)。"""
parts = []
"""上报去重签名:已知菜名多重集合(不含框,避免框抖拆单)。"""
names = []
for d in dishes or []:
if d.get("is_unknown"):
continue
name = (d.get("label") or d.get("name") or "").strip()
if not name:
continue
box = d.get("box") or []
if isinstance(box, (list, tuple)) and len(box) >= 4:
qb = tuple(int(round(float(x) / 40.0)) * 40 for x in box[:4])
else:
qb = (0, 0, 0, 0)
parts.append((name, qb))
parts.sort(key=lambda x: (x[1], x[0]))
return tuple(parts) if parts else None
if name:
names.append(name)
if not names:
return None
return tuple(sorted(Counter(names).items()))
def _order_dish_signature(self, dishes):
"""订单菜品签名:只看菜名组合(忽略框位置),手晃框抖不算新单。"""
@ -1784,10 +1783,39 @@ class BaseDishApp(PendingLearningUIMixin):
names.sort()
return tuple(names)
def _reset_order_gate(self):
"""重置订单防重门闩(清场解锁 / 清空清单 / 启停扫描)。"""
self._order_clear_since = None
self._last_order_dish_sig = None
self._last_order_at = 0.0
self._last_reported_sig = None
def _note_order_scene_busy(self):
"""画面上仍有托盘/已识别菜:清场计时清零。"""
self._order_clear_since = None
def _note_order_scene_clear(self):
"""无餐盘:累计清场时间,满 hold 后解锁,允许下一盘(含相同菜名)重新上报。"""
now = time.time()
if self._order_clear_since is None:
self._order_clear_since = now
return False
hold = float(getattr(self, "_order_clear_hold", DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS) or 1.5)
if now - self._order_clear_since < hold:
return False
had_order = bool(getattr(self, "_last_order_dish_sig", None) or getattr(self, "_last_reported_sig", None))
self._reset_order_gate()
if had_order:
print(f"[order] 清场解锁(无盘持续 ≥ {hold:g}s,允许下一单)")
else:
print(f"[order] 清场,已点清单可被下一盘覆盖(无盘 ≥ {hold:g}s)")
return True
def _should_record_new_order(self, known_dishes):
"""菜品未变 → 不存图、不重新统计订单。
例:0.5s 内手在中间晃动再次触发识别,但菜名组合未变 → 保持当前单。
例:手在中间晃动再次触发识别,但菜名组合未变 → 保持当前单。
清场解锁后 `_last_order_dish_sig` 已被清空,相同菜名可再报一单。
"""
sig = self._order_dish_signature(known_dishes)
if not sig:
@ -1979,9 +2007,7 @@ class BaseDishApp(PendingLearningUIMixin):
def stop_live_scan(self):
"""暂停识别;预览继续(仍显示上一轮框)。"""
self._live_enabled = False
self._last_reported_sig = None
self._last_order_dish_sig = None
self._last_order_at = 0.0
self._reset_order_gate()
self._last_detect_fp = None
self._last_live_dishes = None
self._last_motion_boxes = None
@ -2056,8 +2082,7 @@ class BaseDishApp(PendingLearningUIMixin):
self._last_detect_fp = None
self._last_live_dishes = None
self._last_motion_boxes = None
self._last_order_dish_sig = None
self._last_order_at = 0.0
self._reset_order_gate()
self._reset_motion_gate(initial_state="moving")
if hasattr(self, "camera_btn"):
try:
@ -2428,15 +2453,20 @@ class BaseDishApp(PendingLearningUIMixin):
self._last_live_dishes = []
self._last_motion_boxes = None
self._reset_live_stability()
unlocked = self._note_order_scene_clear()
def _no_plate():
def _no_plate(did_unlock=unlocked):
self._live_overlay_dishes = []
# 未检出新菜:左侧已点清单保留上次结果,不清空
self._set_result_text("扫描中:未检测到餐盘(已点清单保持上次)", "warn")
tip = "扫描中:未检测到餐盘(已点清单保持上次)"
if did_unlock:
tip += "|已清场,可报下一单"
self._set_result_text(tip, "warn")
if hasattr(self, "scan_tip_label"):
try:
self.scan_tip_label.config(
text="扫描中 · 未检出餐盘 · 左侧保持上次已点 · 等待下一次运动"
+ (" · 已解锁下一单" if did_unlock else "")
)
except Exception:
pass
@ -2469,6 +2499,7 @@ class BaseDishApp(PendingLearningUIMixin):
save_path = None
order_unchanged = False
if known_dishes:
self._note_order_scene_busy()
should_record, order_sig = self._should_record_new_order(known_dishes)
if should_record:
save_path = self._save_order_image(frame)
@ -2481,6 +2512,7 @@ class BaseDishApp(PendingLearningUIMixin):
if reported:
self._last_order_dish_sig = order_sig
self._last_order_at = time.time()
print("[order] 本单已上报,等待清场后再报下一单")
elif order_sig:
# dedupe 跳过也记为当前单,避免反复落盘
self._last_order_dish_sig = order_sig
@ -2593,9 +2625,7 @@ class BaseDishApp(PendingLearningUIMixin):
self._cart = []
self._last_dishes = []
self._live_overlay_dishes = []
self._last_order_dish_sig = None
self._last_order_at = 0.0
self._last_reported_sig = None
self._reset_order_gate()
self.render_cart()
if not self._preview_enabled and not self._live_enabled and hasattr(self, "img_label"):
self.img_label.config(