From cfefa454e7d346d47e5f68b368c5595a0c38c7c0 Mon Sep 17 00:00:00 2001 From: eva li Date: Sat, 7 Feb 2026 13:39:02 -0500 Subject: [PATCH] runnin --- camera.py | 226 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 90 deletions(-) diff --git a/camera.py b/camera.py index 256f825..f6b7a4d 100644 --- a/camera.py +++ b/camera.py @@ -3,7 +3,6 @@ from mediapipe.tasks import python from mediapipe.tasks.python import vision import numpy as np -import math import time from key_emulator import set_key, release_all from pynput.mouse import Button, Controller @@ -18,25 +17,25 @@ "inventory_key": "e", # --- ZONES --- - "run_start_offset": 0.15, # Chest Level - "run_stop_offset": -0.10, # Waist Level - "click_trigger_offset": 0.02, + "run_start_offset": 0.15, # Chest Level + + # CHANGE: Lowered to 0.30 (Mid-Thigh) to give more running room + "run_stop_offset": 0.00, + + "click_trigger_offset": 0.02, # Eye Level "hold_threshold_s": 0.5, - # --- Sensitivity --- - "jump_threshold": 0.06, - "tpose_extension": 0.12, - "tpose_vertical_tol": 0.20, + # --- STRICT ALIGNMENT SETTINGS --- + "click_vertical_align": 0.15, + "scroll_horizontal_align": 0.15, + "tpose_extension": 0.15, + "scroll_cooldown_s": 0.5, - # --- RUNNING TIMING (TIGHTENED) --- + # --- RUNNING TIMING --- "switches_required": 1, - - # CHANGED: You must pump both hands within 0.3s to start. - # This prevents accidental running when just reaching for clicks. "start_switch_window_s": 0.3, - - # CHANGED: Reset counter faster so old movements don't linger "switch_count_reset_s": 0.5, + "jump_threshold": 0.06, } # ==================================================================== @@ -55,6 +54,9 @@ def force_up(button='left'): btn = Button.left if button == 'left' else Button.right mouse.release(btn) +def force_scroll(dy): + mouse.scroll(0, dy) + # Mediapipe Setup BaseOptions = python.BaseOptions PoseLandmarker = vision.PoseLandmarker @@ -109,6 +111,9 @@ def force_up(button='left'): _right_feedback_text = "" _right_feedback_timer = 0.0 +# Scroll State +_last_scroll_time = 0.0 + # Feedback Overlay State _overlay_msg = "" _overlay_timer = 0.0 @@ -138,8 +143,7 @@ def _safe_set_key(key, pressed, now): HandLandmarker.create_from_options(hand_options) as hand_landmarker: print("SYSTEM READY.") - print(" - START RUN: SNAP both hands up (Chest) quickly!") - print(" - STOP RUN: Drop hands below Waist.") + print(" - STOP LINE LOWERED (Mid-Thigh).") while cap.isOpened(): ret, frame = cap.read() @@ -151,69 +155,93 @@ def _safe_set_key(key, pressed, now): timestamp_ms = int(time.time() * 1000) pose_res = pose_landmarker.detect_for_video(mp_image, timestamp_ms) - hand_res = hand_landmarker.detect_for_video(mp_image, timestamp_ms) now = time.time() if pose_res.pose_landmarks: lm = pose_res.pose_landmarks[0] # Key Landmarks - left_eye, right_eye = lm[5], lm[2] - ls, rs = lm[11], lm[12] - lw, rw = lm[15], lm[16] + ls, rs = lm[11], lm[12] # Shoulders + lw, rw = lm[15], lm[16] # Wrists lh, rh = lm[23], lm[24] # Hips - # --- CALCULATE THRESHOLDS --- + # --- ZONES --- start_thresh_y = ((ls.y + rs.y) / 2.0) + CONFIG["run_start_offset"] stop_thresh_y = ((lh.y + rh.y) / 2.0) + CONFIG["run_stop_offset"] - eye_level_y = (left_eye.y + right_eye.y) / 2.0 - click_thresh_y = eye_level_y - CONFIG["click_trigger_offset"] - - # --- DRAW LINES --- - y_start_px = int(start_thresh_y * h) - cv2.line(frame, (0, y_start_px), (w_px, y_start_px), (255, 200, 0), 2) - cv2.putText(frame, "START (SNAP UP)", (10, y_start_px - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 200, 0), 1) - - y_stop_px = int(stop_thresh_y * h) - cv2.line(frame, (0, y_stop_px), (w_px, y_stop_px), (0, 255, 0), 2) - cv2.putText(frame, "STOP (DROP)", (10, y_stop_px + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) + + # Click Height Limit + click_y_limit = ((lm[5].y + lm[2].y)/2.0) - CONFIG["click_trigger_offset"] - y_click_px = int(click_thresh_y * h) - cv2.line(frame, (0, y_click_px), (w_px, y_click_px), (0, 0, 255), 2) + # --- DRAW VISUAL GUIDES (RESTORED) --- + # 1. Start/Stop Lines + y_start = int(start_thresh_y * h) + y_stop = int(stop_thresh_y * h) + cv2.line(frame, (0, y_start), (w_px, y_start), (255, 200, 0), 1) + cv2.line(frame, (0, y_stop), (w_px, y_stop), (0, 0, 255), 2) + cv2.putText(frame, "STOP LINE (THIGHS)", (10, y_stop - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) - # --- RUNNING LOGIC --- - l_above_start = lw.y < start_thresh_y - r_above_start = rw.y < start_thresh_y - l_below_stop = lw.y > stop_thresh_y - r_below_stop = rw.y > stop_thresh_y - - _last_left_above = _update_switch(l_above_start, _last_left_above, "left", now) - _last_right_above = _update_switch(r_above_start, _last_right_above, "right", now) + # 2. CLICK ZONES (Vertical Boxes above shoulders) + v_tol_px = int(CONFIG["click_vertical_align"] * w_px) + ls_px = int(ls.x * w_px) + rs_px = int(rs.x * w_px) - # Reset counters if too slow (now 0.5s) - if _last_any_switch_time and (now - _last_any_switch_time) > CONFIG["switch_count_reset_s"]: - _left_switch_count = _right_switch_count = 0 - - # START Condition - req = CONFIG["switches_required"] - if _left_switch_count >= req and _right_switch_count >= req: - t_max = max(_last_left_switch_time, _last_right_switch_time) - t_min = min(_last_left_switch_time, _last_right_switch_time) - # STRICT WINDOW (0.3s) - if (t_max - t_min) <= CONFIG["start_switch_window_s"] and (now - t_max) <= CONFIG["start_switch_window_s"]: - if now >= _insta_stop_until and not _running_holding: - _running_holding = True - _left_switch_count = _right_switch_count = 0 + cv2.rectangle(frame, (ls_px - v_tol_px, 0), (ls_px + v_tol_px, int(click_y_limit*h)), (50, 50, 255), 1) + cv2.rectangle(frame, (rs_px - v_tol_px, 0), (rs_px + v_tol_px, int(click_y_limit*h)), (50, 50, 255), 1) + + # 3. SCROLL ZONES (Horizontal Boxes beside shoulders) + h_tol_px = int(CONFIG["scroll_horizontal_align"] * h) + ls_y_px = int(ls.y * h) + rs_y_px = int(rs.y * h) - # STOP Condition - if l_below_stop and r_below_stop: - _running_holding = False + cv2.rectangle(frame, (0, ls_y_px - h_tol_px), (ls_px - 100, ls_y_px + h_tol_px), (255, 0, 255), 1) + cv2.rectangle(frame, (rs_px + 100, rs_y_px - h_tol_px), (w_px, rs_y_px + h_tol_px), (255, 0, 255), 1) + + + # --- GEOMETRY CHECKS (STRICT) --- + # 1. Click Check + l_is_vertical = abs(lw.x - ls.x) < CONFIG["click_vertical_align"] + r_is_vertical = abs(rw.x - rs.x) < CONFIG["click_vertical_align"] + l_click_active = l_is_vertical and (lw.y < click_y_limit) + r_click_active = r_is_vertical and (rw.y < click_y_limit) + + # 2. Scroll Check + l_is_horizontal = abs(lw.y - ls.y) < CONFIG["scroll_horizontal_align"] + r_is_horizontal = abs(rw.y - rs.y) < CONFIG["scroll_horizontal_align"] + l_is_extended = abs(lw.x - ls.x) > CONFIG["tpose_extension"] + r_is_extended = abs(rw.x - rs.x) > CONFIG["tpose_extension"] + + is_left_scroll = l_is_horizontal and l_is_extended and not l_click_active + is_right_scroll = r_is_horizontal and r_is_extended and not r_click_active + + # --- SCROLL & INVENTORY --- + if is_left_scroll and is_right_scroll: + cv2.putText(frame, "INVENTORY (E)", (int(w_px/2)-60, 150), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) + if not prev_tpose: + _safe_set_key(CONFIG["inventory_key"], True, now) + time.sleep(0.05) + _safe_set_key(CONFIG["inventory_key"], False, now) + _overlay_msg = "INVENTORY" + _overlay_timer = now + 1.5 + prev_tpose = True + elif is_left_scroll: + prev_tpose = False + if now - _last_scroll_time > CONFIG["scroll_cooldown_s"]: + force_scroll(-1) + _last_scroll_time = now + _overlay_msg = "SCROLL RIGHT -->" + _overlay_timer = now + 0.5 + elif is_right_scroll: + prev_tpose = False + if now - _last_scroll_time > CONFIG["scroll_cooldown_s"]: + force_scroll(1) + _last_scroll_time = now + _overlay_msg = "<-- SCROLL LEFT" + _overlay_timer = now + 0.5 + else: + prev_tpose = False # --- CLICK LOGIC --- - l_click_active = lw.y < click_thresh_y - r_click_active = rw.y < click_thresh_y hold_time = CONFIG["hold_threshold_s"] - if l_click_active: if not _prev_left_raised: _left_raise_start_time = now dur = now - _left_raise_start_time @@ -228,7 +256,6 @@ def _safe_set_key(key, pressed, now): force_up('left') _left_is_dragging = False _left_feedback_text = "RELEASED" - _left_feedback_timer = now + 1.0 else: force_click('left') _left_feedback_text = "CLICK!" @@ -248,7 +275,6 @@ def _safe_set_key(key, pressed, now): force_up('right') _right_is_dragging = False _right_feedback_text = "RELEASED" - _right_feedback_timer = now + 1.0 else: force_click('right') _right_feedback_text = "CLICK!" @@ -257,26 +283,33 @@ def _safe_set_key(key, pressed, now): _prev_left_raised = l_click_active _prev_right_raised = r_click_active - # --- T-POSE (PRESS E) --- - ext_thresh = CONFIG["tpose_extension"] - vert_thresh = CONFIG["tpose_vertical_tol"] - arms_wide = (abs(lw.x - ls.x) > ext_thresh) and (abs(rw.x - rs.x) > ext_thresh) - arms_level = (abs(lw.y - ls.y) < vert_thresh) and (abs(rw.y - rs.y) < vert_thresh) - is_tpose = arms_wide and arms_level - - if is_tpose: - cv2.rectangle(frame, (100, 100), (w_px-100, h-100), (0, 255, 0), 4) - cv2.putText(frame, "T-POSE", (int(w_px/2)-60, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) - - if not prev_tpose: - _safe_set_key(CONFIG["inventory_key"], True, now) - time.sleep(0.05) - _safe_set_key(CONFIG["inventory_key"], False, now) - _overlay_msg = "INVENTORY (E)" - _overlay_timer = now + 1.5 - - prev_tpose = is_tpose + # --- RUNNING LOGIC (UPDATED) --- + l_below_stop = lw.y > stop_thresh_y + r_below_stop = rw.y > stop_thresh_y + if l_below_stop or r_below_stop: + _running_holding = False + else: + is_clicking_any = l_click_active or r_click_active + if not is_clicking_any: + l_above_start = lw.y < start_thresh_y + r_above_start = rw.y < start_thresh_y + + _last_left_above = _update_switch(l_above_start, _last_left_above, "left", now) + _last_right_above = _update_switch(r_above_start, _last_right_above, "right", now) + + if _last_any_switch_time and (now - _last_any_switch_time) > CONFIG["switch_count_reset_s"]: + _left_switch_count = _right_switch_count = 0 + + req = CONFIG["switches_required"] + if _left_switch_count >= req and _right_switch_count >= req: + t_max = max(_last_left_switch_time, _last_right_switch_time) + t_min = min(_last_left_switch_time, _last_right_switch_time) + if (t_max - t_min) <= CONFIG["start_switch_window_s"] and (now - t_max) <= CONFIG["start_switch_window_s"]: + if now >= _insta_stop_until and not _running_holding: + _running_holding = True + _left_switch_count = _right_switch_count = 0 + # --- JUMP LOGIC --- avg_hip_y = (lm[23].y + lm[24].y) / 2 if _is_jumping: @@ -295,7 +328,25 @@ def _safe_set_key(key, pressed, now): prev_hip_y = avg_hip_y - # --- VISUALS --- + # --- VISUAL FEEDBACK --- + _safe_set_key(CONFIG["running_key"], _running_holding, now) + + # Status Bar + status_text = "RUNNING (W)" if _running_holding else "STOPPED" + status_color = (0, 255, 0) if _running_holding else (0, 0, 255) + + box_width = 300 + box_height = 60 + top_x = int(w_px/2 - box_width/2) + cv2.rectangle(frame, (top_x, 10), (top_x + box_width, 10 + box_height), (50, 50, 50), -1) + cv2.rectangle(frame, (top_x, 10), (top_x + box_width, 10 + box_height), status_color, 3) + + text_size = cv2.getTextSize(status_text, cv2.FONT_HERSHEY_SIMPLEX, 1, 2)[0] + text_x = int(w_px/2 - text_size[0]/2) + text_y = int(10 + box_height/2 + text_size[1]/2) + cv2.putText(frame, status_text, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 1, status_color, 2) + + # Other Feedback if now < _left_feedback_timer: cv2.putText(frame, _left_feedback_text, (50, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2) if now < _right_feedback_timer: @@ -304,6 +355,7 @@ def _safe_set_key(key, pressed, now): if now < _overlay_timer: cv2.putText(frame, _overlay_msg, (int(w_px/2)-100, int(h/2)), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 255), 4) + # Progress Bars if l_click_active and not _left_is_dragging: pct = min(1.0, (now - _left_raise_start_time)/hold_time) cv2.rectangle(frame, (50, 200), (50+int(100*pct), 210), (0,255,255), -1) @@ -311,12 +363,6 @@ def _safe_set_key(key, pressed, now): pct = min(1.0, (now - _right_raise_start_time)/hold_time) cv2.rectangle(frame, (w_px-150, 200), (w_px-150+int(100*pct), 210), (0,255,255), -1) - _safe_set_key(CONFIG["running_key"], _running_holding, now) - - status = "RUNNING" if _running_holding else "STOPPED" - col = (0, 255, 0) if _running_holding else (0, 0, 255) - cv2.putText(frame, f"Mode: {status}", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, col, 2) - cv2.imshow('Action Recognition', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break