first commit

This commit is contained in:
tcsenpai 2024-11-21 19:25:29 +01:00
commit 2bfbaead48
6 changed files with 591 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
config.json
*.pyc
*.log
*.txt
*.csv
*.json
*.jsonl
__pycache__

99
README.md Normal file
View 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
View 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
View 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
View 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
View 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)