From 621bec63fb6db75d1078f850184b53e27101b44a Mon Sep 17 00:00:00 2001 From: wanghanbo <1411232085@qq.com> Date: Wed, 14 Oct 2026 17:48:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=A6=E6=88=AA=E8=8F=9C=E5=93=81=E4=B8=80?= =?UTF-8?q?=E6=A0=B7=EF=BC=8C=E8=AF=86=E5=88=AB=E7=9A=84=E6=9C=AA=E7=9F=A5?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E4=B8=8D=E4=B8=80=E6=A0=B7=E7=9A=84=EF=BC=8C?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=AE=A2=E5=8D=95=E9=98=B2=E6=8A=96=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dao/settings.py | 6 +++--- ui/classifier_ui.py | 44 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/dao/settings.py b/dao/settings.py index ad1ea3d..9e5c40b 100644 --- a/dao/settings.py +++ b/dao/settings.py @@ -118,9 +118,9 @@ DISH_LIVE_MOTION_SIM_WITH_ORDER = float(os.environ.get('DISH_LIVE_MOTION_SIM_WIT DISH_LIVE_ORDER_DEBOUNCE_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_DEBOUNCE_SECONDS', '2')) # 无餐盘持续该秒数后清场解锁,允许下一盘(含相同菜名)重新存图/上报 DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS', '1.5')) -# 同场景去重:按预览帧计数(约 25fps,默认 40 帧 ≈ 1.6s)。 -# 窗口内即使个别菜名不同,只要检测框布局相近也视为同一单(不重复上报) -DISH_LIVE_ORDER_SAME_SCENE_FRAMES = int(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_FRAMES', '40')) +# 同场景去重:按预览帧计数(约 25fps,默认 60 帧 ≈ 2.4s)。 +# 窗口内即使个别已知菜名不同,只要检测框布局相近也视为同一单(不重复上报) +DISH_LIVE_ORDER_SAME_SCENE_FRAMES = int(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_FRAMES', '60')) # 框匹配 IoU 阈值;匹配对数占比阈值(0~1) DISH_LIVE_ORDER_LAYOUT_IOU = float(os.environ.get('DISH_LIVE_ORDER_LAYOUT_IOU', '0.40')) DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO = float(os.environ.get('DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO', '0.70')) diff --git a/ui/classifier_ui.py b/ui/classifier_ui.py index 8d3b6a4..4d960e0 100644 --- a/ui/classifier_ui.py +++ b/ui/classifier_ui.py @@ -1992,18 +1992,23 @@ class BaseDishApp(PendingLearningUIMixin): self._live_stable_snapshot = None # committed_sig 保留到场景变化时再清,避免同一场景反复写入 + @staticmethod + def _quantize_box(box, step=40): + if isinstance(box, (list, tuple)) and len(box) >= 4: + try: + return tuple(int(round(float(x) / float(step))) * int(step) for x in box[:4]) + except Exception: + return (0, 0, 0, 0) + return (0, 0, 0, 0) + def _live_result_signature(self, dishes): - """粗粒度签名:盘数 + 已知/未知名 + 量化框(忽略细微分数抖动)。""" + """粗粒度签名:盘数 + 已知名/未知框(未知不用建议菜名,避免红薯↔土豆抖动)。""" parts = [] for d in dishes or []: - 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) + qb = self._quantize_box(d.get("box")) if d.get("is_unknown"): - sug = (d.get("suggested_category") or "").strip()[:24] - parts.append(("U", sug, qb)) + # 未知只按框位置,建议名(红薯/土豆等)不参与 + parts.append(("U", "", qb)) else: name = (d.get("label") or d.get("name") or "").strip()[:24] parts.append(("K", name, qb)) @@ -2011,16 +2016,12 @@ class BaseDishApp(PendingLearningUIMixin): return (len(parts), tuple(parts)) def _report_signature(self, dishes): - """上报去重签名:已知 + 未知(is_unknown)菜名多重集合(不含框)。""" + """上报去重签名:已知菜名 + 未知按量化框(建议名不参与)。""" keys = [] for d in dishes or []: if d.get("is_unknown"): - sug = ( - d.get("suggested_name") - or d.get("suggested_category") - or "未知" - ) - keys.append(("U", str(sug).strip()[:24] or "未知")) + qb = self._quantize_box(d.get("box")) + keys.append(("U", qb)) else: name = (d.get("label") or d.get("name") or "").strip() if name: @@ -2030,16 +2031,15 @@ class BaseDishApp(PendingLearningUIMixin): return tuple(sorted(Counter(keys).items())) def _order_dish_signature(self, dishes): - """订单菜品签名:已知 + 未知组合(忽略框位置),手晃框抖不算新单。""" + """订单菜品签名:已知按菜名;未知按框位置(忽略建议名抖动)。 + + 例如同一未知框建议从「红薯」变成「土豆」仍视为同一单,避免 1~2 秒内双报。 + """ keys = [] for d in dishes or []: if d.get("is_unknown"): - sug = ( - d.get("suggested_name") - or d.get("suggested_category") - or "未知" - ) - keys.append(f"U:{str(sug).strip()[:24] or '未知'}") + qb = self._quantize_box(d.get("box")) + keys.append(f"U:{qb}") else: name = (d.get("label") or d.get("name") or "").strip() if name: