恢复清场解锁与菜名去重,修复订单帧清理数量。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2c0aa81cab
commit
53956015bc
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue