判定运动,不用时间去判断,改为帧判断
This commit is contained in:
parent
e19e462837
commit
ff6da8fcfe
|
|
@ -103,16 +103,17 @@ DISH_MEAL_REFRESH_MINUTE = int(os.environ.get('DISH_MEAL_REFRESH_MINUTE', '0'))
|
||||||
# 实时扫描:未知菜写入待学习前的最短稳定秒数(客人放盘短,默认 1 秒)
|
# 实时扫描:未知菜写入待学习前的最短稳定秒数(客人放盘短,默认 1 秒)
|
||||||
# 盘子移走时不刷入,避免移盘过程中的误检未知
|
# 盘子移走时不刷入,避免移盘过程中的误检未知
|
||||||
DISH_LIVE_PENDING_STABLE_SECONDS = float(os.environ.get('DISH_LIVE_PENDING_STABLE_SECONDS', '1.0'))
|
DISH_LIVE_PENDING_STABLE_SECONDS = float(os.environ.get('DISH_LIVE_PENDING_STABLE_SECONDS', '1.0'))
|
||||||
# 运动判定:从动到静后需保持静止的秒数,达到后 YOLO 识别一次;持续静止不再识别,
|
# 运动判定:从动到静后,连续多少帧与静止锚点相似,才触发 YOLO 一次(按相机帧,不按秒)。
|
||||||
# 须再经历「静→动→静」才触发下一次
|
# 持续静止不再识别,须再经历「静→动→静」。默认 4 帧(约对应原 0.2s@20Hz 轮询)。
|
||||||
DISH_LIVE_STILL_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_STILL_HOLD_SECONDS', '0.2'))
|
DISH_LIVE_STILL_HOLD_FRAMES = int(os.environ.get('DISH_LIVE_STILL_HOLD_FRAMES', '4'))
|
||||||
# 已有订单时,菜品区域相似度阈值(更低=更宽松,手在中间晃仍算静止,减少误触发)
|
# 已有订单时,菜品区域相似度阈值(更低=更宽松,手在中间晃仍算静止,减少误触发)
|
||||||
DISH_LIVE_MOTION_SIM_WITH_ORDER = float(os.environ.get('DISH_LIVE_MOTION_SIM_WITH_ORDER', '0.970'))
|
DISH_LIVE_MOTION_SIM_WITH_ORDER = float(os.environ.get('DISH_LIVE_MOTION_SIM_WITH_ORDER', '0.970'))
|
||||||
# 订单防抖:菜品名称集合去重 + 时间间隔(秒),避免同一次晃动重复上传
|
# 订单防抖:菜品名称集合去重 + 时间间隔(秒),避免同一次晃动重复上传
|
||||||
# 默认 2s(建议 2~3s)
|
# 默认 2s(建议 2~3s)
|
||||||
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', '0.5'))
|
DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS', '1.5'))
|
||||||
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ from dao.settings import (
|
||||||
DISH_BASE_NAME,
|
DISH_BASE_NAME,
|
||||||
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_FRAMES,
|
||||||
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,
|
||||||
|
|
@ -147,12 +147,12 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
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._motion_sim_with_order = float(DISH_LIVE_MOTION_SIM_WITH_ORDER)
|
||||||
self._order_debounce_seconds = float(DISH_LIVE_ORDER_DEBOUNCE_SECONDS)
|
self._order_debounce_seconds = float(DISH_LIVE_ORDER_DEBOUNCE_SECONDS)
|
||||||
# 运动状态机:动→静保持 still_hold 秒 → YOLO 一次 → 静待运动 → 再静…
|
# 运动状态机:动→静连续 N 帧相似 → YOLO 一次 → 静待运动 → 再静…
|
||||||
self._live_still_hold_seconds = float(DISH_LIVE_STILL_HOLD_SECONDS)
|
self._live_still_hold_frames = max(1, int(DISH_LIVE_STILL_HOLD_FRAMES))
|
||||||
self._motion_state = "moving" # moving | settling | wait_motion
|
self._motion_state = "moving" # moving | settling | wait_motion
|
||||||
self._motion_prev_fp = None
|
self._motion_prev_fp = None
|
||||||
self._motion_settle_fp = None
|
self._motion_settle_fp = None
|
||||||
self._motion_still_since = None
|
self._motion_still_count = 0 # settling 期间已连续相似的帧数
|
||||||
self._yolo_trigger_pending = False
|
self._yolo_trigger_pending = False
|
||||||
self.cfg = Config()
|
self.cfg = Config()
|
||||||
self.app = YOLOv11PlateExtractor(self.cfg)
|
self.app = YOLOv11PlateExtractor(self.cfg)
|
||||||
|
|
@ -240,7 +240,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
print(f"保存订单帧失败: {e}")
|
print(f"保存订单帧失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _cleanup_old_live_frames(self, keep=3000):
|
def _cleanup_old_live_frames(self, keep=300000):
|
||||||
"""清理过期订单帧,防止 images/ 无限增长。"""
|
"""清理过期订单帧,防止 images/ 无限增长。"""
|
||||||
try:
|
try:
|
||||||
folder = os.path.abspath("images")
|
folder = os.path.abspath("images")
|
||||||
|
|
@ -1939,11 +1939,11 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self.start_live_scan()
|
self.start_live_scan()
|
||||||
|
|
||||||
def _reset_motion_gate(self, initial_state="moving"):
|
def _reset_motion_gate(self, initial_state="moving"):
|
||||||
"""重置动→静触发状态机。默认 moving:开扫后首段静止满 hold 也会识别一次。"""
|
"""重置动→静触发状态机。默认 moving:开扫后连续静止帧满也会识别一次。"""
|
||||||
self._motion_state = initial_state
|
self._motion_state = initial_state
|
||||||
self._motion_prev_fp = None
|
self._motion_prev_fp = None
|
||||||
self._motion_settle_fp = None
|
self._motion_settle_fp = None
|
||||||
self._motion_still_since = None
|
self._motion_still_count = 0
|
||||||
self._yolo_trigger_pending = False
|
self._yolo_trigger_pending = False
|
||||||
|
|
||||||
def _motion_fingerprint(self, frame_bgr):
|
def _motion_fingerprint(self, frame_bgr):
|
||||||
|
|
@ -1953,14 +1953,18 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
boxes=getattr(self, "_last_motion_boxes", None),
|
boxes=getattr(self, "_last_motion_boxes", None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _still_hold_frames(self):
|
||||||
|
return max(1, int(getattr(self, "_live_still_hold_frames", DISH_LIVE_STILL_HOLD_FRAMES) or 4))
|
||||||
|
|
||||||
def _update_motion_gate(self, frame_bgr):
|
def _update_motion_gate(self, frame_bgr):
|
||||||
"""根据当前帧更新运动状态;从动到静保持 hold 秒时置位 YOLO 触发。
|
"""根据当前帧更新运动状态;连续 N 帧与静止锚点相似时触发 YOLO 一次。
|
||||||
|
|
||||||
运动只看餐盘区域(有上次检测框用框,否则画面中央),避免旁人晃动误触发。
|
运动只看餐盘区域(有上次检测框用框,否则画面中央),避免旁人晃动误触发。
|
||||||
|
用帧计数而非秒数,避免轮询抖动 / 帧率波动导致触发不稳。
|
||||||
|
|
||||||
状态:
|
状态:
|
||||||
moving — 菜品区域在动(或刚开扫)
|
moving — 菜品区域在动(或刚开扫)
|
||||||
settling — 菜品刚停住,累计静止时间
|
settling — 菜品刚停住,累计连续相似帧
|
||||||
wait_motion — 已识别过,菜品静止中忽略,直到菜品再出现运动
|
wait_motion — 已识别过,菜品静止中忽略,直到菜品再出现运动
|
||||||
"""
|
"""
|
||||||
if frame_bgr is None:
|
if frame_bgr is None:
|
||||||
|
|
@ -1969,9 +1973,8 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
if fp is None:
|
if fp is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
hold = float(getattr(self, "_live_still_hold_seconds", DISH_LIVE_STILL_HOLD_SECONDS) or 0.1)
|
need = self._still_hold_frames()
|
||||||
state = getattr(self, "_motion_state", "moving")
|
state = getattr(self, "_motion_state", "moving")
|
||||||
now = time.time()
|
|
||||||
|
|
||||||
if state == "wait_motion":
|
if state == "wait_motion":
|
||||||
# 已有订单时放宽「静止」判定,手在中间区域晃动尽量不触发再识别
|
# 已有订单时放宽「静止」判定,手在中间区域晃动尽量不触发再识别
|
||||||
|
|
@ -1985,7 +1988,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._motion_state = "moving"
|
self._motion_state = "moving"
|
||||||
self._motion_prev_fp = fp
|
self._motion_prev_fp = fp
|
||||||
self._motion_settle_fp = None
|
self._motion_settle_fp = None
|
||||||
self._motion_still_since = None
|
self._motion_still_count = 0
|
||||||
if sim is not None:
|
if sim is not None:
|
||||||
print(f"[live] 菜品区域运动 sim={sim:.4f},等待静止后识别")
|
print(f"[live] 菜品区域运动 sim={sim:.4f},等待静止后识别")
|
||||||
return False
|
return False
|
||||||
|
|
@ -1999,7 +2002,7 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
if similar:
|
if similar:
|
||||||
self._motion_state = "settling"
|
self._motion_state = "settling"
|
||||||
self._motion_settle_fp = fp
|
self._motion_settle_fp = fp
|
||||||
self._motion_still_since = now
|
self._motion_still_count = 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if state == "settling":
|
if state == "settling":
|
||||||
|
|
@ -2008,16 +2011,18 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self._motion_state = "moving"
|
self._motion_state = "moving"
|
||||||
self._motion_prev_fp = fp
|
self._motion_prev_fp = fp
|
||||||
self._motion_settle_fp = None
|
self._motion_settle_fp = None
|
||||||
self._motion_still_since = None
|
self._motion_still_count = 0
|
||||||
return False
|
return False
|
||||||
since = self._motion_still_since or now
|
self._motion_still_count = int(getattr(self, "_motion_still_count", 0) or 0) + 1
|
||||||
if (now - since) >= hold:
|
# 锚点用最新静止帧,减轻缓慢漂移
|
||||||
|
self._motion_settle_fp = fp
|
||||||
|
if self._motion_still_count >= need:
|
||||||
self._yolo_trigger_pending = True
|
self._yolo_trigger_pending = True
|
||||||
self._motion_state = "wait_motion"
|
self._motion_state = "wait_motion"
|
||||||
self._motion_settle_fp = None
|
self._motion_settle_fp = None
|
||||||
self._motion_still_since = None
|
self._motion_still_count = 0
|
||||||
self._motion_prev_fp = fp
|
self._motion_prev_fp = fp
|
||||||
print(f"[live] 菜品静止满 {hold:g}s,触发 YOLO 识别一次")
|
print(f"[live] 菜品连续静止 {need} 帧,触发 YOLO 识别一次")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -2108,18 +2113,18 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
self.camera_btn.config(text="暂停扫描")
|
self.camera_btn.config(text="暂停扫描")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
still_hold = float(getattr(self, "_live_still_hold_seconds", DISH_LIVE_STILL_HOLD_SECONDS) or 0.1)
|
still_n = self._still_hold_frames()
|
||||||
hold_s = float(getattr(self, "_live_stable_seconds", DISH_LIVE_PENDING_STABLE_SECONDS) or 1.0)
|
hold_s = float(getattr(self, "_live_stable_seconds", DISH_LIVE_PENDING_STABLE_SECONDS) or 1.0)
|
||||||
hold_txt = f"{hold_s:g}"
|
hold_txt = f"{hold_s:g}"
|
||||||
if hasattr(self, "scan_tip_label"):
|
if hasattr(self, "scan_tip_label"):
|
||||||
try:
|
try:
|
||||||
self.scan_tip_label.config(
|
self.scan_tip_label.config(
|
||||||
text=f"实时扫描中 · 动→静 {still_hold:g}s 识别一次 · 未知约 {hold_txt}s 记待学习"
|
text=f"实时扫描中 · 动→静连续 {still_n} 帧识别一次 · 未知约 {hold_txt}s 记待学习"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self._set_result_text(
|
self._set_result_text(
|
||||||
f"实时扫描已开启(动→静保持 {still_hold:g}s 识别一次;未知约 {hold_txt}s 写入待学习)",
|
f"实时扫描已开启(动→静连续 {still_n} 帧识别一次;未知约 {hold_txt}s 写入待学习)",
|
||||||
"ok",
|
"ok",
|
||||||
)
|
)
|
||||||
self._schedule_live_tick(immediate=True)
|
self._schedule_live_tick(immediate=True)
|
||||||
|
|
@ -2418,19 +2423,15 @@ class BaseDishApp(PendingLearningUIMixin):
|
||||||
if getattr(self, "_motion_state", None) == "wait_motion":
|
if getattr(self, "_motion_state", None) == "wait_motion":
|
||||||
self._live_on_similar_frame_skip()
|
self._live_on_similar_frame_skip()
|
||||||
elif getattr(self, "_motion_state", None) == "settling":
|
elif getattr(self, "_motion_state", None) == "settling":
|
||||||
hold = float(
|
need = self._still_hold_frames()
|
||||||
getattr(self, "_live_still_hold_seconds", DISH_LIVE_STILL_HOLD_SECONDS) or 0.1
|
got = int(getattr(self, "_motion_still_count", 0) or 0)
|
||||||
)
|
left = max(0, need - got)
|
||||||
since = self._motion_still_since
|
|
||||||
left = hold
|
|
||||||
if since:
|
|
||||||
left = max(0.0, hold - (time.time() - since))
|
|
||||||
|
|
||||||
def _settling_tip(remain=left):
|
def _settling_tip(remain=left, total=need, cur=got):
|
||||||
if hasattr(self, "scan_tip_label"):
|
if hasattr(self, "scan_tip_label"):
|
||||||
try:
|
try:
|
||||||
self.scan_tip_label.config(
|
self.scan_tip_label.config(
|
||||||
text=f"扫描中 · 已静止,约 {remain:.1f}s 后识别"
|
text=f"扫描中 · 已静止 {cur}/{total} 帧,还需 {remain} 帧后识别"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue