去除用秒判断,通过帧率判断,菜名不一样,但像同盘,就过滤

This commit is contained in:
wanghanbo 2026-10-14 15:12:28 +08:00
parent 678706454a
commit e0a4f85f59
2 changed files with 25 additions and 13 deletions

View File

@ -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_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_CLEAR_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS', '1.5'))
# 短时同场景去重:窗口内即使个别菜名识别不同,只要检测框布局相近也视为同一单(不重复上报) # 同场景去重:按预览帧计数(约 25fps,默认 40 帧 ≈ 1.6s)。
DISH_LIVE_ORDER_SAME_SCENE_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_SECONDS', '1.5')) # 窗口内即使个别菜名不同,只要检测框布局相近也视为同一单(不重复上报)
DISH_LIVE_ORDER_SAME_SCENE_FRAMES = int(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_FRAMES', '40'))
# 框匹配 IoU 阈值;匹配对数占比阈值(0~1) # 框匹配 IoU 阈值;匹配对数占比阈值(0~1)
DISH_LIVE_ORDER_LAYOUT_IOU = float(os.environ.get('DISH_LIVE_ORDER_LAYOUT_IOU', '0.40')) 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')) DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO = float(os.environ.get('DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO', '0.70'))

View File

@ -51,7 +51,7 @@ from dao.settings import (
DISH_LIVE_MOTION_SIM_WITH_ORDER, DISH_LIVE_MOTION_SIM_WITH_ORDER,
DISH_LIVE_ORDER_DEBOUNCE_SECONDS, DISH_LIVE_ORDER_DEBOUNCE_SECONDS,
DISH_LIVE_ORDER_CLEAR_HOLD_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_IOU,
DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO, DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO,
DISH_STREAM_RECORD_ENABLED, DISH_STREAM_RECORD_ENABLED,
@ -157,11 +157,13 @@ class BaseDishApp(PendingLearningUIMixin):
self._live_stable_snapshot = None # 稳定期间保留的 dishes,供写入 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_dish_sig = None # 仅菜名签名:菜未变不重复存图/统计
self._last_order_boxes = None # 上一单检测框,用于短时同场景去重(忽略菜名抖动) self._last_order_boxes = None # 上一单检测框,用于同场景去重(忽略菜名抖动)
self._last_order_at = 0.0 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_since = None # 无盘连续计时起点
self._order_clear_hold = float(DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS) 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_iou = float(DISH_LIVE_ORDER_LAYOUT_IOU)
self._order_layout_match_ratio = float(DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO) self._order_layout_match_ratio = float(DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO)
self._last_detect_fp = None # 识别后锚点指纹(静止等待运动;仅餐盘区域) self._last_detect_fp = None # 识别后锚点指纹(静止等待运动;仅餐盘区域)
@ -2053,6 +2055,7 @@ class BaseDishApp(PendingLearningUIMixin):
self._last_order_dish_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_at = 0.0
self._last_order_preview_seq = None
self._last_reported_sig = None self._last_reported_sig = None
def _note_order_scene_busy(self): def _note_order_scene_busy(self):
@ -2140,12 +2143,13 @@ class BaseDishApp(PendingLearningUIMixin):
self._last_order_dish_sig = order_sig self._last_order_dish_sig = order_sig
self._last_order_boxes = self._extract_order_boxes(dishes) self._last_order_boxes = self._extract_order_boxes(dishes)
self._last_order_at = time.time() 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): def _should_record_new_order(self, dishes):
"""菜品未变 / 短时同场景 → 不存图、不重新统计订单。 """菜品未变 / 同场景(按预览帧) → 不存图、不重新统计订单。
- 菜名组合相同:始终视为同一单(直到清场) - 菜名组合相同:始终视为同一单(直到清场)
- 短时窗口内菜名略有不同,但检测框布局相近:仍视为同一盘(剔除重复上报) - 上报后 N 个预览帧内,菜名略有不同但检测框布局相近:仍视为同一盘
清场解锁后 `_last_order_dish_sig` 已被清空,相同菜名可再报一单。 清场解锁后 `_last_order_dish_sig` 已被清空,相同菜名可再报一单。
""" """
sig = self._order_dish_signature(dishes) sig = self._order_dish_signature(dishes)
@ -2165,16 +2169,22 @@ class BaseDishApp(PendingLearningUIMixin):
print("[live] 菜品未变,跳过存图/订单统计") print("[live] 菜品未变,跳过存图/订单统计")
return False, sig return False, sig
# 菜名不同:短时内若框布局仍像同一盘,也不重复上报 # 菜名不同:按预览帧窗口 + 框布局判断是否同一盘
same_scene_s = float( same_frames = int(
getattr(self, "_order_same_scene_seconds", DISH_LIVE_ORDER_SAME_SCENE_SECONDS) or 1.5 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) last_boxes = getattr(self, "_last_order_boxes", None)
if self._order_layouts_similar(dishes, last_boxes): if self._order_layouts_similar(dishes, last_boxes):
print( print(
f"[live] {same_scene_s:g}s 内同场景(框布局相近,忽略菜名差异)," f"[live] {same_frames} 帧内同场景(框布局相近,忽略菜名差异),"
f"跳过重复上报" f"跳过重复上报 seq={cur_seq - int(last_seq)}/{same_frames}"
) )
return False, sig return False, sig
return True, sig return True, sig
@ -2532,6 +2542,7 @@ class BaseDishApp(PendingLearningUIMixin):
try: try:
frame = self.grab_preview_frame() frame = self.grab_preview_frame()
if frame is not None: if frame is not None:
self._preview_frame_seq = int(getattr(self, "_preview_frame_seq", 0) or 0) + 1
with self._live_lock: with self._live_lock:
self._latest_frame = frame self._latest_frame = frame
self.show_preview_frame(frame) self.show_preview_frame(frame)