判断运动更新,
This commit is contained in:
parent
2bf6d2c47f
commit
2c0aa81cab
|
|
@ -92,6 +92,10 @@ DISH_LIVE_PENDING_STABLE_SECONDS = float(os.environ.get('DISH_LIVE_PENDING_STABL
|
||||||
# 运动判定:从动到静后需保持静止的秒数,达到后 YOLO 识别一次;持续静止不再识别,
|
# 运动判定:从动到静后需保持静止的秒数,达到后 YOLO 识别一次;持续静止不再识别,
|
||||||
# 须再经历「静→动→静」才触发下一次
|
# 须再经历「静→动→静」才触发下一次
|
||||||
DISH_LIVE_STILL_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_STILL_HOLD_SECONDS', '0.1'))
|
DISH_LIVE_STILL_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_STILL_HOLD_SECONDS', '0.1'))
|
||||||
|
# 已有订单时,菜品区域相似度阈值(更低=更宽松,手在中间晃仍算静止,减少误触发)
|
||||||
|
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'))
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ from dao.settings import (
|
||||||
DISH_MGMT_MEAL_ON_START,
|
DISH_MGMT_MEAL_ON_START,
|
||||||
DISH_LIVE_PENDING_STABLE_SECONDS,
|
DISH_LIVE_PENDING_STABLE_SECONDS,
|
||||||
DISH_LIVE_STILL_HOLD_SECONDS,
|
DISH_LIVE_STILL_HOLD_SECONDS,
|
||||||
|
DISH_LIVE_MOTION_SIM_WITH_ORDER,
|
||||||
|
DISH_LIVE_ORDER_DEBOUNCE_SECONDS,
|
||||||
)
|
)
|
||||||
from dao.base_registry import bootstrap_default_base, get_active_base, set_default_base
|
from dao.base_registry import bootstrap_default_base, get_active_base, set_default_base
|
||||||
from models.App_settings import AppSetting
|
from models.App_settings import AppSetting
|
||||||
|
|
@ -131,10 +133,14 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._live_stable_committed_sig = None
|
self._live_stable_committed_sig = None
|
||||||
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_at = 0.0
|
||||||
self._last_detect_fp = None # 识别后锚点指纹(静止等待运动;仅餐盘区域)
|
self._last_detect_fp = None # 识别后锚点指纹(静止等待运动;仅餐盘区域)
|
||||||
self._last_live_dishes = None # 上一轮识别结果(跳过检测时推进稳定计时)
|
self._last_live_dishes = None # 上一轮识别结果(跳过检测时推进稳定计时)
|
||||||
self._last_motion_boxes = None # 上一轮餐盘框,运动判定只看菜品区域
|
self._last_motion_boxes = None # 上一轮餐盘框,运动判定只看菜品区域
|
||||||
self._frame_sim_threshold = 0.992 # 帧相似度阈值:判定静止 / 运动
|
self._frame_sim_threshold = 0.992 # 帧相似度阈值:判定静止 / 运动
|
||||||
|
self._motion_sim_with_order = float(DISH_LIVE_MOTION_SIM_WITH_ORDER)
|
||||||
|
self._order_debounce_seconds = float(DISH_LIVE_ORDER_DEBOUNCE_SECONDS)
|
||||||
# 运动状态机:动→静保持 still_hold 秒 → YOLO 一次 → 静待运动 → 再静…
|
# 运动状态机:动→静保持 still_hold 秒 → YOLO 一次 → 静待运动 → 再静…
|
||||||
self._live_still_hold_seconds = float(DISH_LIVE_STILL_HOLD_SECONDS)
|
self._live_still_hold_seconds = float(DISH_LIVE_STILL_HOLD_SECONDS)
|
||||||
self._motion_state = "moving" # moving | settling | wait_motion
|
self._motion_state = "moving" # moving | settling | wait_motion
|
||||||
|
|
@ -1762,7 +1768,42 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
qb = (0, 0, 0, 0)
|
qb = (0, 0, 0, 0)
|
||||||
parts.append((name, qb))
|
parts.append((name, qb))
|
||||||
parts.sort(key=lambda x: (x[1], x[0]))
|
parts.sort(key=lambda x: (x[1], x[0]))
|
||||||
return tuple(parts)
|
return tuple(parts) if parts else None
|
||||||
|
|
||||||
|
def _order_dish_signature(self, dishes):
|
||||||
|
"""订单菜品签名:只看菜名组合(忽略框位置),手晃框抖不算新单。"""
|
||||||
|
names = []
|
||||||
|
for d in dishes or []:
|
||||||
|
if d.get("is_unknown"):
|
||||||
|
continue
|
||||||
|
name = (d.get("label") or d.get("name") or "").strip()
|
||||||
|
if name:
|
||||||
|
names.append(name)
|
||||||
|
if not names:
|
||||||
|
return None
|
||||||
|
names.sort()
|
||||||
|
return tuple(names)
|
||||||
|
|
||||||
|
def _should_record_new_order(self, known_dishes):
|
||||||
|
"""菜品未变 → 不存图、不重新统计订单。
|
||||||
|
|
||||||
|
例:0.5s 内手在中间晃动再次触发识别,但菜名组合未变 → 保持当前单。
|
||||||
|
"""
|
||||||
|
sig = self._order_dish_signature(known_dishes)
|
||||||
|
if not sig:
|
||||||
|
return False, sig
|
||||||
|
last_sig = getattr(self, "_last_order_dish_sig", None)
|
||||||
|
if sig == last_sig:
|
||||||
|
debounce = float(
|
||||||
|
getattr(self, "_order_debounce_seconds", DISH_LIVE_ORDER_DEBOUNCE_SECONDS) or 0.5
|
||||||
|
)
|
||||||
|
last_at = float(getattr(self, "_last_order_at", 0) or 0)
|
||||||
|
if last_at and (time.time() - last_at) < debounce:
|
||||||
|
print(f"[live] {debounce:g}s 内菜品未变,跳过存图/订单统计")
|
||||||
|
else:
|
||||||
|
print("[live] 菜品未变,跳过存图/订单统计")
|
||||||
|
return False, sig
|
||||||
|
return True, sig
|
||||||
|
|
||||||
def _update_live_stability(self, dishes):
|
def _update_live_stability(self, dishes):
|
||||||
"""跟踪稳定性。仅当同一结果稳定满 hold 秒且有未知时,返回 (snapshot, commit_sig)。
|
"""跟踪稳定性。仅当同一结果稳定满 hold 秒且有未知时,返回 (snapshot, commit_sig)。
|
||||||
|
|
@ -1886,7 +1927,13 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if state == "wait_motion":
|
if state == "wait_motion":
|
||||||
similar, sim = self._frames_very_similar(fp, self._last_detect_fp)
|
# 已有订单时放宽「静止」判定,手在中间区域晃动尽量不触发再识别
|
||||||
|
thr = None
|
||||||
|
if getattr(self, "_last_order_dish_sig", None) or getattr(self, "_cart", None):
|
||||||
|
thr = float(
|
||||||
|
getattr(self, "_motion_sim_with_order", DISH_LIVE_MOTION_SIM_WITH_ORDER) or 0.97
|
||||||
|
)
|
||||||
|
similar, sim = self._frames_very_similar(fp, self._last_detect_fp, threshold=thr)
|
||||||
if not similar:
|
if not similar:
|
||||||
self._motion_state = "moving"
|
self._motion_state = "moving"
|
||||||
self._motion_prev_fp = fp
|
self._motion_prev_fp = fp
|
||||||
|
|
@ -1933,6 +1980,8 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
"""暂停识别;预览继续(仍显示上一轮框)。"""
|
"""暂停识别;预览继续(仍显示上一轮框)。"""
|
||||||
self._live_enabled = False
|
self._live_enabled = False
|
||||||
self._last_reported_sig = None
|
self._last_reported_sig = None
|
||||||
|
self._last_order_dish_sig = None
|
||||||
|
self._last_order_at = 0.0
|
||||||
self._last_detect_fp = None
|
self._last_detect_fp = None
|
||||||
self._last_live_dishes = None
|
self._last_live_dishes = None
|
||||||
self._last_motion_boxes = None
|
self._last_motion_boxes = None
|
||||||
|
|
@ -2007,6 +2056,8 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._last_detect_fp = None
|
self._last_detect_fp = None
|
||||||
self._last_live_dishes = None
|
self._last_live_dishes = None
|
||||||
self._last_motion_boxes = None
|
self._last_motion_boxes = None
|
||||||
|
self._last_order_dish_sig = None
|
||||||
|
self._last_order_at = 0.0
|
||||||
self._reset_motion_gate(initial_state="moving")
|
self._reset_motion_gate(initial_state="moving")
|
||||||
if hasattr(self, "camera_btn"):
|
if hasattr(self, "camera_btn"):
|
||||||
try:
|
try:
|
||||||
|
|
@ -2414,19 +2465,30 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
# 用识别后的餐盘框再锚一次,后续 wait_motion 更稳
|
# 用识别后的餐盘框再锚一次,后续 wait_motion 更稳
|
||||||
self._last_detect_fp = self._motion_fingerprint(frame)
|
self._last_detect_fp = self._motion_fingerprint(frame)
|
||||||
known_dishes = [d for d in dishes if not d.get("is_unknown")]
|
known_dishes = [d for d in dishes if not d.get("is_unknown")]
|
||||||
# 有一单(至少 1 个已识别菜,且与上次不同)才存 images/ 并上报;无单不落盘
|
# 菜名未变 / 防抖期内:不存 images、不重新统计订单(防手在中间晃误触发)
|
||||||
save_path = None
|
save_path = None
|
||||||
|
order_unchanged = False
|
||||||
if known_dishes:
|
if known_dishes:
|
||||||
sig = self._report_signature(known_dishes)
|
should_record, order_sig = self._should_record_new_order(known_dishes)
|
||||||
if sig and sig != self._last_reported_sig:
|
if should_record:
|
||||||
save_path = self._save_order_image(frame)
|
save_path = self._save_order_image(frame)
|
||||||
if save_path:
|
if save_path:
|
||||||
self.report_recognition(
|
reported = self.report_recognition(
|
||||||
save_path, known_dishes, labeled_path or visualize_save_path or save_path,
|
save_path, known_dishes,
|
||||||
|
labeled_path or visualize_save_path or save_path,
|
||||||
schedule_check=False, dedupe=True,
|
schedule_check=False, dedupe=True,
|
||||||
)
|
)
|
||||||
else:
|
if reported:
|
||||||
self._last_reported_sig = None
|
self._last_order_dish_sig = order_sig
|
||||||
|
self._last_order_at = time.time()
|
||||||
|
elif order_sig:
|
||||||
|
# dedupe 跳过也记为当前单,避免反复落盘
|
||||||
|
self._last_order_dish_sig = order_sig
|
||||||
|
self._last_order_at = time.time()
|
||||||
|
else:
|
||||||
|
order_unchanged = True
|
||||||
|
print(f"[live] 菜品未变,跳过存图与订单统计 sig={order_sig}")
|
||||||
|
# 临时未识别到菜(手遮挡等):不清除上一单签名,左侧仍保持上次已点
|
||||||
preview = labeled_path or visualize_save_path or save_path
|
preview = labeled_path or visualize_save_path or save_path
|
||||||
|
|
||||||
# 同一结果稳定满 hold 秒 → 未知菜写入待学习(同场景一次;盘走不刷入)
|
# 同一结果稳定满 hold 秒 → 未知菜写入待学习(同场景一次;盘走不刷入)
|
||||||
|
|
@ -2451,9 +2513,11 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
path=preview, txt=final_result, ds=dishes,
|
path=preview, txt=final_result, ds=dishes,
|
||||||
written=pending_written, elapsed=stable_elapsed,
|
written=pending_written, elapsed=stable_elapsed,
|
||||||
committed=already_committed, hold_s=hold,
|
committed=already_committed, hold_s=hold,
|
||||||
|
same_order=order_unchanged,
|
||||||
):
|
):
|
||||||
# 只更新叠加框 + 左侧清单;预览继续刷实时帧
|
# 只更新叠加框 + 左侧清单;预览继续刷实时帧
|
||||||
self._live_overlay_dishes = list(ds or [])
|
self._live_overlay_dishes = list(ds or [])
|
||||||
|
# 菜品未变:仍刷新预览框,但左侧保持/同步同一批菜,不视为新单
|
||||||
self.render_result_dock(ds, path)
|
self.render_result_dock(ds, path)
|
||||||
try:
|
try:
|
||||||
self.update_pending_btn()
|
self.update_pending_btn()
|
||||||
|
|
@ -2466,7 +2530,10 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
if known:
|
if known:
|
||||||
top = max(known, key=lambda d: d.get("score") or 0)
|
top = max(known, key=lambda d: d.get("score") or 0)
|
||||||
name = top.get("label") or "菜品"
|
name = top.get("label") or "菜品"
|
||||||
msg = f"扫描中|已识别 {len(known)}|{name}"
|
if same_order:
|
||||||
|
msg = f"扫描中|菜品未变|{len(known)}|{name}|不重复存图/统计"
|
||||||
|
else:
|
||||||
|
msg = f"扫描中|已识别 {len(known)}|{name}"
|
||||||
if unknown:
|
if unknown:
|
||||||
msg += f"|未知 {len(unknown)}(不入清单)"
|
msg += f"|未知 {len(unknown)}(不入清单)"
|
||||||
self._set_result_text(msg, "ok" if not unknown else "warn")
|
self._set_result_text(msg, "ok" if not unknown else "warn")
|
||||||
|
|
@ -2526,6 +2593,9 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._cart = []
|
self._cart = []
|
||||||
self._last_dishes = []
|
self._last_dishes = []
|
||||||
self._live_overlay_dishes = []
|
self._live_overlay_dishes = []
|
||||||
|
self._last_order_dish_sig = None
|
||||||
|
self._last_order_at = 0.0
|
||||||
|
self._last_reported_sig = None
|
||||||
self.render_cart()
|
self.render_cart()
|
||||||
if not self._preview_enabled and not self._live_enabled and hasattr(self, "img_label"):
|
if not self._preview_enabled and not self._live_enabled and hasattr(self, "img_label"):
|
||||||
self.img_label.config(
|
self.img_label.config(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue