From e0a4f85f592a104c162ae73975018890b5eeba38 Mon Sep 17 00:00:00 2001 From: wanghanbo <1411232085@qq.com> Date: Wed, 14 Oct 2026 15:12:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E7=94=A8=E7=A7=92=E5=88=A4?= =?UTF-8?q?=E6=96=AD=EF=BC=8C=E9=80=9A=E8=BF=87=E5=B8=A7=E7=8E=87=E5=88=A4?= =?UTF-8?q?=E6=96=AD=EF=BC=8C=E8=8F=9C=E5=90=8D=E4=B8=8D=E4=B8=80=E6=A0=B7?= =?UTF-8?q?=EF=BC=8C=E4=BD=86=E5=83=8F=E5=90=8C=E7=9B=98=EF=BC=8C=E5=B0=B1?= =?UTF-8?q?=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dao/settings.py | 5 +++-- ui/classifier_ui.py | 33 ++++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/dao/settings.py b/dao/settings.py index e944256..7516b52 100644 --- a/dao/settings.py +++ b/dao/settings.py @@ -118,8 +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')) -# 短时同场景去重:窗口内即使个别菜名识别不同,只要检测框布局相近也视为同一单(不重复上报) -DISH_LIVE_ORDER_SAME_SCENE_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_SECONDS', '1.5')) +# 同场景去重:按预览帧计数(约 25fps,默认 40 帧 ≈ 1.6s)。 +# 窗口内即使个别菜名不同,只要检测框布局相近也视为同一单(不重复上报) +DISH_LIVE_ORDER_SAME_SCENE_FRAMES = int(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_FRAMES', '40')) # 框匹配 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 03a4c4d..8d3b6a4 100644 --- a/ui/classifier_ui.py +++ b/ui/classifier_ui.py @@ -51,7 +51,7 @@ from dao.settings import ( DISH_LIVE_MOTION_SIM_WITH_ORDER, DISH_LIVE_ORDER_DEBOUNCE_SECONDS, DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS, - DISH_LIVE_ORDER_SAME_SCENE_SECONDS, + DISH_LIVE_ORDER_SAME_SCENE_FRAMES, DISH_LIVE_ORDER_LAYOUT_IOU, DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO, DISH_STREAM_RECORD_ENABLED, @@ -157,11 +157,13 @@ class BaseDishApp(PendingLearningUIMixin): self._live_stable_snapshot = None # 稳定期间保留的 dishes,供写入 self._last_reported_sig = None # 实时上报去重:相同菜名集合只报一次 self._last_order_dish_sig = None # 仅菜名签名:菜未变不重复存图/统计 - self._last_order_boxes = None # 上一单检测框,用于短时同场景去重(忽略菜名抖动) + self._last_order_boxes = None # 上一单检测框,用于同场景去重(忽略菜名抖动) self._last_order_at = 0.0 + self._last_order_preview_seq = None # 上一单对应的预览帧序号 + self._preview_frame_seq = 0 # 预览成功取帧累计(用于按帧去重) self._order_clear_since = None # 无盘连续计时起点 self._order_clear_hold = float(DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS) - self._order_same_scene_seconds = float(DISH_LIVE_ORDER_SAME_SCENE_SECONDS) + self._order_same_scene_frames = max(0, int(DISH_LIVE_ORDER_SAME_SCENE_FRAMES)) self._order_layout_iou = float(DISH_LIVE_ORDER_LAYOUT_IOU) self._order_layout_match_ratio = float(DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO) self._last_detect_fp = None # 识别后锚点指纹(静止等待运动;仅餐盘区域) @@ -2053,6 +2055,7 @@ class BaseDishApp(PendingLearningUIMixin): self._last_order_dish_sig = None self._last_order_boxes = None self._last_order_at = 0.0 + self._last_order_preview_seq = None self._last_reported_sig = None def _note_order_scene_busy(self): @@ -2140,12 +2143,13 @@ class BaseDishApp(PendingLearningUIMixin): self._last_order_dish_sig = order_sig self._last_order_boxes = self._extract_order_boxes(dishes) self._last_order_at = time.time() + self._last_order_preview_seq = int(getattr(self, "_preview_frame_seq", 0) or 0) def _should_record_new_order(self, dishes): - """菜品未变 / 短时同场景 → 不存图、不重新统计订单。 + """菜品未变 / 同场景(按预览帧) → 不存图、不重新统计订单。 - 菜名组合相同:始终视为同一单(直到清场) - - 短时窗口内菜名略有不同,但检测框布局相近:仍视为同一盘(剔除重复上报) + - 上报后 N 个预览帧内,菜名略有不同但检测框布局相近:仍视为同一盘 清场解锁后 `_last_order_dish_sig` 已被清空,相同菜名可再报一单。 """ sig = self._order_dish_signature(dishes) @@ -2165,16 +2169,22 @@ class BaseDishApp(PendingLearningUIMixin): print("[live] 菜品未变,跳过存图/订单统计") return False, sig - # 菜名不同:短时内若框布局仍像同一盘,也不重复上报 - same_scene_s = float( - getattr(self, "_order_same_scene_seconds", DISH_LIVE_ORDER_SAME_SCENE_SECONDS) or 1.5 + # 菜名不同:按预览帧窗口 + 框布局判断是否同一盘 + same_frames = int( + getattr(self, "_order_same_scene_frames", DISH_LIVE_ORDER_SAME_SCENE_FRAMES) or 0 ) - if last_at and same_scene_s > 0 and (now - last_at) <= same_scene_s: + last_seq = getattr(self, "_last_order_preview_seq", None) + cur_seq = int(getattr(self, "_preview_frame_seq", 0) or 0) + if ( + same_frames > 0 + and last_seq is not None + and (cur_seq - int(last_seq)) <= same_frames + ): last_boxes = getattr(self, "_last_order_boxes", None) if self._order_layouts_similar(dishes, last_boxes): print( - f"[live] {same_scene_s:g}s 内同场景(框布局相近,忽略菜名差异)," - f"跳过重复上报" + f"[live] {same_frames} 帧内同场景(框布局相近,忽略菜名差异)," + f"跳过重复上报 seq={cur_seq - int(last_seq)}/{same_frames}" ) return False, sig return True, sig @@ -2532,6 +2542,7 @@ class BaseDishApp(PendingLearningUIMixin): try: frame = self.grab_preview_frame() if frame is not None: + self._preview_frame_seq = int(getattr(self, "_preview_frame_seq", 0) or 0) + 1 with self._live_lock: self._latest_frame = frame self.show_preview_frame(frame)