mirror of
https://github.com/tcsenpai/mienmouse.git
synced 2025-06-02 17:20:03 +00:00
first commit
This commit is contained in:
commit
2bfbaead48
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
config.json
|
||||
*.pyc
|
||||
*.log
|
||||
*.txt
|
||||
*.csv
|
||||
*.json
|
||||
*.jsonl
|
||||
__pycache__
|
||||
|
99
README.md
Normal file
99
README.md
Normal file
@ -0,0 +1,99 @@
|
||||
# MienMouse 🎯
|
||||
|
||||
MienMouse is a hands-free mouse control system that enables users to control their computer using facial gestures. Built with Python and powered by MediaPipe's facial landmark detection, it provides an intuitive and accessible way to interact with your computer.
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
- **Intuitive Mouse Control**: Control cursor movement using head position
|
||||
- **Natural Gestures**:
|
||||
- Left Click: Open mouth
|
||||
- Right Click: Raise eyebrows
|
||||
- Double Click: Combine both gestures
|
||||
- Drag & Drop: Keep mouth open while moving
|
||||
- **Smooth Tracking**: Advanced smoothing algorithms for precise cursor control
|
||||
- **Real-time Feedback**: Visual indicators for gesture recognition
|
||||
- **Customizable Settings**: Adjust sensitivity and response to your preferences
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.8+
|
||||
- Webcam
|
||||
- Windows/Linux/MacOS
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository
|
||||
```bash
|
||||
git clone https://github.com/yourusername/mienmouse.git
|
||||
cd mienmouse
|
||||
```
|
||||
|
||||
2. Install dependencies
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Run the application
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
## 🎮 Controls
|
||||
|
||||
| Action | Gesture |
|
||||
|--------|---------|
|
||||
| Move Cursor | Move head |
|
||||
| Left Click | Open mouth |
|
||||
| Right Click | Raise eyebrows |
|
||||
| Double Click | Open mouth + raise eyebrows |
|
||||
| Drag & Drop | Keep mouth open while moving |
|
||||
| Recenter | Press 'R' |
|
||||
| Toggle Precision | Press 'P' |
|
||||
| Toggle Audio | Press 'A' |
|
||||
| Toggle Tracking | Press 'T' |
|
||||
| Hide Controls | Press 'H' |
|
||||
| Exit | Press 'ESC' |
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
Adjust settings in `config.json` by copying `config.json.example` to `config.json`:
|
||||
```json
|
||||
{
|
||||
"webcam_index": 0,
|
||||
"smoothing": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Advanced Settings
|
||||
|
||||
Fine-tune the system by adjusting these parameters in `mouse_controller.py`:
|
||||
- Movement sensitivity
|
||||
- Gesture recognition thresholds
|
||||
- Smoothing intensity
|
||||
- Response time
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- [MediaPipe](https://mediapipe.dev/) for facial landmark detection
|
||||
- [OpenCV](https://opencv.org/) for image processing
|
||||
- [PyAutoGUI](https://pyautogui.readthedocs.io/) for mouse control
|
||||
|
||||
## 📞 Support
|
||||
|
||||
If you encounter any issues or have questions, please [open an issue](https://github.com/yourusername/mienmouse/issues).
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
Made with ❤️ for accessibility
|
||||
</p>
|
16
config.json.example
Normal file
16
config.json.example
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"webcam_index": 0,
|
||||
"smoothing": 0.5,
|
||||
"thresholds": {
|
||||
"mouth_open": 0.017,
|
||||
"eyebrow_raise": 0.023,
|
||||
"eye_closed": 0.02,
|
||||
"nose_movement": 0.015
|
||||
},
|
||||
"mouse": {
|
||||
"velocity_scale": 45.0,
|
||||
"max_velocity": 60,
|
||||
"deadzone": 0.1,
|
||||
"click_cooldown": 0.3
|
||||
}
|
||||
}
|
33
config_manager.py
Normal file
33
config_manager.py
Normal file
@ -0,0 +1,33 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(self, config_file="config.json"):
|
||||
self.config_file = config_file
|
||||
self.default_config = {
|
||||
"webcam_index": 0,
|
||||
"smoothing": 0.5,
|
||||
"thresholds": {
|
||||
"mouth_open": 0.017,
|
||||
"eyebrow_raise": 0.023,
|
||||
"eye_closed": 0.02,
|
||||
"nose_movement": 0.015
|
||||
},
|
||||
"mouse": {
|
||||
"velocity_scale": 55.0,
|
||||
"max_velocity": 110,
|
||||
"deadzone": 0.07,
|
||||
"click_cooldown": 0.3
|
||||
}
|
||||
}
|
||||
self.config = self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
if os.path.exists(self.config_file):
|
||||
with open(self.config_file, 'r') as f:
|
||||
return json.load(f)
|
||||
return self.default_config.copy()
|
||||
|
||||
def save_config(self):
|
||||
with open(self.config_file, 'w') as f:
|
||||
json.dump(self.config, f, indent=4)
|
245
main.py
Normal file
245
main.py
Normal file
@ -0,0 +1,245 @@
|
||||
import cv2
|
||||
import mediapipe as mp
|
||||
import numpy as np
|
||||
from mouse_controller import MouseController
|
||||
from config_manager import ConfigManager
|
||||
import time
|
||||
|
||||
class FaceTracker:
|
||||
def __init__(self, config_manager):
|
||||
self.mp_face_mesh = mp.solutions.face_mesh
|
||||
self.face_mesh = self.mp_face_mesh.FaceMesh(
|
||||
max_num_faces=1,
|
||||
refine_landmarks=True,
|
||||
min_detection_confidence=0.5,
|
||||
min_tracking_confidence=0.5
|
||||
)
|
||||
self.mp_draw = mp.solutions.drawing_utils
|
||||
self.drawing_spec = self.mp_draw.DrawingSpec(thickness=1, circle_radius=1)
|
||||
|
||||
self.mouse_controller = MouseController(config_manager)
|
||||
|
||||
# UI state
|
||||
self.show_controls = True
|
||||
self.show_confidence = True
|
||||
self.show_prediction = True
|
||||
self.last_fps_time = time.time()
|
||||
self.frame_count = 0
|
||||
self.fps = 0
|
||||
|
||||
def process_frame(self, frame):
|
||||
self.frame_count += 1
|
||||
current_time = time.time()
|
||||
if current_time - self.last_fps_time > 1:
|
||||
self.fps = self.frame_count
|
||||
self.frame_count = 0
|
||||
self.last_fps_time = current_time
|
||||
|
||||
frame = self._enhance_frame(frame)
|
||||
|
||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
results = self.face_mesh.process(frame_rgb)
|
||||
|
||||
self._draw_ui(frame, results)
|
||||
|
||||
if results.multi_face_landmarks:
|
||||
face_landmarks = results.multi_face_landmarks[0]
|
||||
|
||||
# Draw minimal face mesh for feedback
|
||||
self._draw_tracking_points(frame, face_landmarks)
|
||||
|
||||
# Process mouse control
|
||||
self.mouse_controller.update_mouse(results)
|
||||
|
||||
# Show gesture feedback
|
||||
self._draw_gesture_feedback(frame)
|
||||
|
||||
return frame
|
||||
|
||||
def _enhance_frame(self, frame):
|
||||
"""Apply image processing to improve face detection"""
|
||||
# Convert to LAB color space
|
||||
lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
|
||||
l, a, b = cv2.split(lab)
|
||||
|
||||
# Apply CLAHE to L channel
|
||||
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
|
||||
cl = clahe.apply(l)
|
||||
|
||||
# Merge channels
|
||||
limg = cv2.merge((cl,a,b))
|
||||
|
||||
# Convert back to BGR
|
||||
enhanced = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
|
||||
|
||||
return enhanced
|
||||
|
||||
def _draw_ui(self, frame, results):
|
||||
height, width = frame.shape[:2]
|
||||
|
||||
# Draw FPS
|
||||
cv2.putText(frame, f"FPS: {self.fps}", (width-120, 30),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||
|
||||
# Draw confidence if face detected
|
||||
if self.show_confidence and results.multi_face_landmarks:
|
||||
cv2.putText(frame, "Face Detected", (width-200, 60),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||
|
||||
# Draw mode indicators
|
||||
self._draw_mode_indicators(frame)
|
||||
|
||||
# Draw controls if enabled
|
||||
if self.show_controls:
|
||||
self._draw_controls(frame)
|
||||
|
||||
def _draw_mode_indicators(self, frame):
|
||||
modes = [
|
||||
("Precision Mode", self.mouse_controller.precision_mode),
|
||||
("Audio Feedback", self.mouse_controller.audio_feedback),
|
||||
("Tracking", self.mouse_controller.tracking_enabled)
|
||||
]
|
||||
|
||||
for i, (mode, active) in enumerate(modes):
|
||||
color = (0, 255, 0) if active else (128, 128, 128)
|
||||
cv2.putText(frame, f"{mode}: {'ON' if active else 'OFF'}",
|
||||
(10, 30 + i*25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
|
||||
|
||||
def _draw_tracking_points(self, frame, face_landmarks):
|
||||
"""Draw facial tracking points"""
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# Key points to track
|
||||
points = {
|
||||
'nose': 4,
|
||||
'upper_lip': 13,
|
||||
'lower_lip': 14,
|
||||
'left_eyebrow': 282,
|
||||
'right_eyebrow': 52,
|
||||
'left_eye': 386,
|
||||
'right_eye': 159
|
||||
}
|
||||
|
||||
# Draw points
|
||||
for name, idx in points.items():
|
||||
point = face_landmarks.landmark[idx]
|
||||
x = int(point.x * w)
|
||||
y = int(point.y * h)
|
||||
|
||||
# Different colors for different features
|
||||
if 'nose' in name:
|
||||
color = (0, 255, 255) # Yellow
|
||||
size = 5
|
||||
elif 'lip' in name:
|
||||
color = (0, 0, 255) # Red
|
||||
size = 3
|
||||
else:
|
||||
color = (0, 255, 0) # Green
|
||||
size = 3
|
||||
|
||||
cv2.circle(frame, (x, y), size, color, -1)
|
||||
|
||||
# Draw mouth and eyebrow lines
|
||||
self._draw_feature_lines(frame, face_landmarks, points, w, h)
|
||||
|
||||
def _draw_feature_lines(self, frame, face_landmarks, points, w, h):
|
||||
# Draw mouth line
|
||||
upper_lip = face_landmarks.landmark[points['upper_lip']]
|
||||
lower_lip = face_landmarks.landmark[points['lower_lip']]
|
||||
cv2.line(frame,
|
||||
(int(upper_lip.x * w), int(upper_lip.y * h)),
|
||||
(int(lower_lip.x * w), int(lower_lip.y * h)),
|
||||
(0, 0, 255), 2)
|
||||
|
||||
# Draw eyebrow lines
|
||||
for side in ['left', 'right']:
|
||||
eyebrow = face_landmarks.landmark[points[f'{side}_eyebrow']]
|
||||
eye = face_landmarks.landmark[points[f'{side}_eye']]
|
||||
cv2.line(frame,
|
||||
(int(eyebrow.x * w), int(eyebrow.y * h)),
|
||||
(int(eye.x * w), int(eye.y * h)),
|
||||
(0, 255, 0), 2)
|
||||
|
||||
def _draw_gesture_feedback(self, frame):
|
||||
height = frame.shape[0]
|
||||
|
||||
if self.mouse_controller.is_left_clicking:
|
||||
status = "Dragging" if self.mouse_controller.is_dragging else "Left Click"
|
||||
cv2.putText(frame, status, (10, height-160),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
|
||||
if self.mouse_controller.is_right_clicking:
|
||||
cv2.putText(frame, "Right Click", (10, height-120),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
|
||||
|
||||
def _draw_controls(self, frame):
|
||||
height = frame.shape[0]
|
||||
controls = [
|
||||
"Controls:",
|
||||
"Move: Move head from center position",
|
||||
"Left click: Open mouth",
|
||||
"Right click: Raise eyebrows",
|
||||
"Double click: Open mouth + raise eyebrows",
|
||||
"Drag: Keep mouth open while moving",
|
||||
"'R': Recenter head position",
|
||||
"'P': Toggle precision mode",
|
||||
"'A': Toggle audio feedback",
|
||||
"'T': Toggle tracking",
|
||||
"'H': Hide/show controls",
|
||||
"ESC: Quit"
|
||||
]
|
||||
|
||||
# Draw semi-transparent background
|
||||
overlay = frame.copy()
|
||||
cv2.rectangle(overlay, (0, height-240), (400, height), (0, 0, 0), -1)
|
||||
cv2.addWeighted(overlay, 0.5, frame, 0.5, 0, frame)
|
||||
|
||||
# Draw controls text
|
||||
for i, text in enumerate(controls):
|
||||
cv2.putText(frame, text, (10, height-220+i*20),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
||||
|
||||
def main():
|
||||
config_manager = ConfigManager()
|
||||
|
||||
# Initialize camera with saved index
|
||||
cap = cv2.VideoCapture(config_manager.config["webcam_index"])
|
||||
if not cap.isOpened():
|
||||
print(f"Error: Could not open camera {config_manager.config['webcam_index']}")
|
||||
return
|
||||
|
||||
tracker = FaceTracker(config_manager)
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
print("Error: Could not read frame")
|
||||
break
|
||||
|
||||
# Flip frame horizontally for mirror effect
|
||||
frame = cv2.flip(frame, 1)
|
||||
|
||||
# Process the frame
|
||||
frame = tracker.process_frame(frame)
|
||||
|
||||
# Handle keyboard input
|
||||
key = cv2.waitKey(1) & 0xFF
|
||||
if key == 27: # ESC
|
||||
break
|
||||
elif key == ord('p'):
|
||||
tracker.mouse_controller.precision_mode = not tracker.mouse_controller.precision_mode
|
||||
elif key == ord('a'):
|
||||
tracker.mouse_controller.audio_feedback = not tracker.mouse_controller.audio_feedback
|
||||
elif key == ord('t'):
|
||||
tracker.mouse_controller.tracking_enabled = not tracker.mouse_controller.tracking_enabled
|
||||
elif key == ord('r'):
|
||||
tracker.mouse_controller.recenter()
|
||||
elif key == ord('h'):
|
||||
tracker.show_controls = not tracker.show_controls
|
||||
|
||||
cv2.imshow('Face Tracking', frame)
|
||||
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
189
mouse_controller.py
Normal file
189
mouse_controller.py
Normal file
@ -0,0 +1,189 @@
|
||||
import pyautogui
|
||||
import numpy as np
|
||||
from screeninfo import get_monitors
|
||||
import time
|
||||
import winsound
|
||||
|
||||
class MouseController:
|
||||
def __init__(self, config_manager):
|
||||
# Get primary monitor resolution
|
||||
self.monitors = get_monitors()
|
||||
self.primary_monitor = self.monitors[0]
|
||||
self.screen_width = self.primary_monitor.width
|
||||
self.screen_height = self.primary_monitor.height
|
||||
|
||||
# Configure PyAutoGUI
|
||||
pyautogui.FAILSAFE = False
|
||||
pyautogui.PAUSE = 0.0
|
||||
|
||||
# Get config values
|
||||
self.velocity_scale = config_manager.config["mouse"]["velocity_scale"]
|
||||
self.max_velocity = config_manager.config["mouse"]["max_velocity"]
|
||||
self.deadzone = config_manager.config["mouse"]["deadzone"]
|
||||
self.click_cooldown = config_manager.config["mouse"]["click_cooldown"]
|
||||
|
||||
# Get thresholds
|
||||
self.MOUTH_OPEN_THRESHOLD = config_manager.config["thresholds"]["mouth_open"]
|
||||
self.EYEBROW_RAISE_THRESHOLD = config_manager.config["thresholds"]["eyebrow_raise"]
|
||||
self.EYE_CLOSED_THRESHOLD = config_manager.config["thresholds"]["eye_closed"]
|
||||
self.NOSE_MOVEMENT_THRESHOLD = config_manager.config["thresholds"]["nose_movement"]
|
||||
|
||||
# Smoothing
|
||||
self.velocity_buffer = [(0, 0)] * 5
|
||||
self.smoothing = config_manager.config["smoothing"]
|
||||
|
||||
# Gesture state tracking
|
||||
self.is_left_clicking = False
|
||||
self.is_right_clicking = False
|
||||
self.is_dragging = False
|
||||
self.tracking_enabled = True
|
||||
self.last_click_time = 0
|
||||
|
||||
# Mode settings
|
||||
self.precision_mode = False
|
||||
self.audio_feedback = True
|
||||
|
||||
# Screen mapping
|
||||
self.x_scale = 2.0
|
||||
self.y_scale = 2.0
|
||||
|
||||
def update_mouse(self, results):
|
||||
if not self.tracking_enabled or not results.multi_face_landmarks:
|
||||
return
|
||||
|
||||
landmarks = results.multi_face_landmarks[0].landmark
|
||||
current_time = time.time()
|
||||
|
||||
# Get key facial features for tracking
|
||||
left_eye = landmarks[33] # Left eye outer corner
|
||||
right_eye = landmarks[263] # Right eye outer corner
|
||||
nose_bridge = landmarks[6] # Nose bridge
|
||||
|
||||
# Calculate head pose
|
||||
face_center_x = (left_eye.x + right_eye.x) / 2
|
||||
face_center_y = (left_eye.y + right_eye.y + nose_bridge.y) / 3
|
||||
|
||||
# Calculate offset from center (0.5, 0.5)
|
||||
offset_x = face_center_x - 0.5
|
||||
offset_y = face_center_y - 0.5
|
||||
|
||||
# Apply deadzone
|
||||
offset_x = 0 if abs(offset_x) < self.deadzone else offset_x
|
||||
offset_y = 0 if abs(offset_y) < self.deadzone else offset_y
|
||||
|
||||
# Convert offset to velocity
|
||||
velocity_x = offset_x * self.velocity_scale
|
||||
velocity_y = offset_y * self.velocity_scale
|
||||
|
||||
# Clamp velocity
|
||||
velocity_x = max(min(velocity_x, self.max_velocity), -self.max_velocity)
|
||||
velocity_y = max(min(velocity_y, self.max_velocity), -self.max_velocity)
|
||||
|
||||
# Update velocity buffer
|
||||
self.velocity_buffer.pop(0)
|
||||
self.velocity_buffer.append((velocity_x, velocity_y))
|
||||
|
||||
# Calculate smoothed velocity
|
||||
smooth_vx = sum(v[0] for v in self.velocity_buffer) / len(self.velocity_buffer)
|
||||
smooth_vy = sum(v[1] for v in self.velocity_buffer) / len(self.velocity_buffer)
|
||||
|
||||
# Get current mouse position
|
||||
current_x, current_y = pyautogui.position()
|
||||
|
||||
# Update position
|
||||
new_x = current_x + smooth_vx
|
||||
new_y = current_y + smooth_vy
|
||||
|
||||
# Ensure within screen bounds
|
||||
new_x = max(0, min(new_x, self.screen_width - 1))
|
||||
new_y = max(0, min(new_y, self.screen_height - 1))
|
||||
|
||||
# Move mouse if there's significant movement
|
||||
if abs(smooth_vx) > 0 or abs(smooth_vy) > 0:
|
||||
pyautogui.moveTo(int(new_x), int(new_y))
|
||||
|
||||
# Handle gestures (keep your existing gesture code)
|
||||
upper_lip = landmarks[13]
|
||||
lower_lip = landmarks[14]
|
||||
left_eyebrow = landmarks[282]
|
||||
right_eyebrow = landmarks[52]
|
||||
left_eye_top = landmarks[386]
|
||||
left_eye_bottom = landmarks[374]
|
||||
right_eye_top = landmarks[159]
|
||||
right_eye_bottom = landmarks[145]
|
||||
|
||||
# Calculate gestures
|
||||
mouth_open = (lower_lip.y - upper_lip.y) > self.MOUTH_OPEN_THRESHOLD
|
||||
left_eye_open = abs(left_eye_top.y - left_eye_bottom.y)
|
||||
right_eye_open = abs(right_eye_top.y - right_eye_bottom.y)
|
||||
eyes_closed = (left_eye_open < self.EYE_CLOSED_THRESHOLD or
|
||||
right_eye_open < self.EYE_CLOSED_THRESHOLD)
|
||||
|
||||
eyebrows_raised = False
|
||||
if not eyes_closed:
|
||||
eyebrows_raised = (
|
||||
(left_eye_top.y - left_eyebrow.y) > self.EYEBROW_RAISE_THRESHOLD and
|
||||
(right_eye_top.y - right_eyebrow.y) > self.EYEBROW_RAISE_THRESHOLD
|
||||
)
|
||||
|
||||
self._handle_gestures(mouth_open, eyebrows_raised, current_time)
|
||||
|
||||
def _handle_gestures(self, mouth_open, eyebrows_raised, current_time):
|
||||
# Double click (both gestures)
|
||||
if mouth_open and eyebrows_raised:
|
||||
if current_time - self.last_click_time > self.click_cooldown:
|
||||
pyautogui.doubleClick()
|
||||
if self.audio_feedback:
|
||||
winsound.Beep(900, 100)
|
||||
self.last_click_time = current_time
|
||||
return
|
||||
|
||||
# Left click/drag (mouth open)
|
||||
if mouth_open and not self.is_left_clicking:
|
||||
self.is_left_clicking = True
|
||||
pyautogui.mouseDown()
|
||||
if self.audio_feedback:
|
||||
winsound.Beep(800, 100)
|
||||
self.is_dragging = True
|
||||
|
||||
# Right click (eyebrows)
|
||||
elif eyebrows_raised and not mouth_open:
|
||||
if not self.is_right_clicking and current_time - self.last_click_time > self.click_cooldown:
|
||||
pyautogui.rightClick()
|
||||
if self.audio_feedback:
|
||||
winsound.Beep(1200, 100)
|
||||
self.is_right_clicking = True
|
||||
self.last_click_time = current_time
|
||||
|
||||
# Release
|
||||
elif not mouth_open and not eyebrows_raised:
|
||||
if self.is_left_clicking:
|
||||
pyautogui.mouseUp()
|
||||
self.is_dragging = False
|
||||
self.is_left_clicking = False
|
||||
self.is_right_clicking = False
|
||||
|
||||
def _significant_movement(self, x, y):
|
||||
"""Check if movement is significant enough"""
|
||||
if not hasattr(self, 'last_pos'):
|
||||
self.last_pos = (x, y)
|
||||
return False
|
||||
|
||||
dx = abs(x - self.last_pos[0])
|
||||
dy = abs(y - self.last_pos[1])
|
||||
|
||||
if dx > self.NOSE_MOVEMENT_THRESHOLD or dy > self.NOSE_MOVEMENT_THRESHOLD:
|
||||
self.last_pos = (x, y)
|
||||
return True
|
||||
return False
|
||||
|
||||
def recenter(self):
|
||||
"""Recenter the neutral position for head tracking"""
|
||||
self.center_x = 0.5
|
||||
self.center_y = 0.5
|
||||
self.current_x = self.screen_width // 2
|
||||
self.current_y = self.screen_height // 2
|
||||
self.movement_buffer = [] # Clear movement buffer
|
||||
|
||||
# Reset mouse to center of screen
|
||||
pyautogui.moveTo(self.current_x, self.current_y)
|
Loading…
x
Reference in New Issue
Block a user