Virtual Keyboard - کیبورد مجازی با هوش مصنوعی

پروژه‌های هوش مصنوعی - بینایی کامپیوتر

آموزش ۶ دقیقه مطالعه
فهرست مطالب
این مطلب از سایت tool.hamidvalad.ir گرفته شده است

معرفی

این پروژه یک کیبورد مجازی پیشرفته است که با استفاده از هوش مصنوعی و بینایی کامپیوتر، حرکات دست شما را تشخیص داده و امکان تایپ بدون لمس فیزیکی صفحه‌کلید را فراهم می‌کند.

ویژگی‌های اصلی:

  • تشخیص حرکات دست با استفاده از کتابخانه MediaPipe
  • طراحی رابط کاربری زیبا و واکنش‌گرا
  • قابلیت جستجوی مستقیم در گوگل و یوتیوب
  • بهینه‌سازی شده برای عملکرد بالا (FPS)
  • پشتیبانی از حرکات لمسی (انگشت شست و اشاره)
  • نمایش زنده دوربین با overlay گرافیکی

پیش‌نیازها (کتابخانه‌ها و ابزارها)

  • Python 3.8+ (توصیه‌شده 3.10/3.11) کتابخانه MediaPipe روی پایتون 3.13 پشتیبانی نمیشود
  • pip (مدیر بستهٔ پایتون)
  • OpenCV (cv2) پردازش تصویر و ویدئو
  • cvzone کتابخانه کمکی برای OpenCV
  • MediaPipe تشخیص دست و حرکات
  • pynput کنترل کیبورد سیستم
  • numpy محاسبات عددی
  • webbrowser باز کردن مرورگر
Terminal
pip install opencv-python cvzone mediapipe pynput numpy

در ویندوز اگر چند نسخهٔ پایتون نصب دارید، ممکن است لازم باشد از py -3 -m pip install استفاده کنید.

راه‌اندازی گام‌به‌گام

1. ایجاد محیط مجازی (Virtual Environment)

Terminal
# ایجاد محیط مجازی
python -m venv virtual_keyboard_env

# فعال کردن محیط در ویندوز
virtual_keyboard_env\Scripts\activate

# فعال کردن محیط در لینوکس/مک
source virtual_keyboard_env/bin/activate

2. نصب کتابخانه‌های مورد نیاز

Terminal
pip install opencv-python cvzone mediapipe pynput numpy

3. تست دوربین

قبل از اجرای برنامه اصلی، مطمئن شوید دوربین شما به درستی کار می‌کند:

Python
import cv2
cap = cv2.VideoCapture(0)
if cap.isOpened():
    print("دوربین فعال است")
    cap.release()
else:
    print("مشکل در اتصال به دوربین")

چگونه برنامه را اجرا کنیم

  1. کد کامل را در یک فایل به نام virtual_keyboard.py ذخیره کنید
  2. محیط مجازی را فعال کنید
  3. در ترمینال دستور زیر را اجرا کنید:
Terminal
python virtual_keyboard.py

راهنمای استفاده:

  • از انگشت اشاره برای اشاره به دکمه‌ها استفاده کنید
  • برای کلیک، انگشت شست و اشاره را به هم نزدیک کنید
  • دکمه Google: متن را در گوگل جستجو می‌کند
  • دکمه YouTube: متن را در یوتیوب جستجو می‌کند
  • دکمه Space: فاصله ایجاد می‌کند
  • دکمه Clear: متن را پاک می‌کند
  • دکمه <: کاراکتر آخر را حذف می‌کند
  • کلید q: خروج از برنامه

کد کامل پروژه (قابل کپی)

کد کامل کیبورد مجازی در ادامه آمده است — می‌توانید آن را کپی کرده و در فایل virtual_keyboard.py ذخیره کنید.

Python
import cv2
from cvzone.HandTrackingModule import HandDetector
from time import sleep, time
import numpy as np
import cvzone
from pynput.keyboard import Controller
import webbrowser
import urllib.parse

class Button:
    def __init__(self, pos, text, size=[60, 60]):
        self.pos = pos
        self.size = size
        self.text = text

class VirtualKeyboard:
    def __init__(self):
        # Camera setup with optimized settings
        self.cap = cv2.VideoCapture(0)
        if not self.cap.isOpened():
            print("Error: Could not open camera")
            return
        
        # Set lower resolution for better FPS
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        self.cap.set(cv2.CAP_PROP_FPS, 60)
        self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
        
        # Hand detector with optimized settings
        self.detector = HandDetector(detectionCon=0.7, maxHands=1)
        self.keyboard = Controller()
        self.final_text = ""
        
        # Keyboard layout with Google and YouTube buttons
        self.keys = [
            ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "<"],
            ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
            ["A", "S", "D", "F", "G", "H", "J", "K", "L", ";"],
            ["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/"],
            ["Space", "Clear", "Enter", "Google", "YouTube"]
        ]
        
        # Button properties
        self.button_width = 50
        self.button_height = 50
        self.button_spacing = 6
        self.cooldown_time = 0.2
        
        self.button_list = self.create_buttons()
        self.last_press_time = 0
        self.frame_count = 0
        self.fps = 0
        self.last_fps_time = time()

    def create_buttons(self):
        """Create button objects with proper positioning"""
        button_list = []
        start_x = 30
        start_y = 30
        
        for i, row in enumerate(self.keys):
            if i == 0:
                for j, key in enumerate(row):
                    x = start_x + j * (self.button_width + self.button_spacing)
                    y = start_y + i * (self.button_height + self.button_spacing)
                    button_list.append(Button([x, y], key))
                    
            elif i == 4:
                special_widths = [
                    self.button_width * 2,
                    self.button_width * 1.5,
                    self.button_width * 1.5,
                    self.button_width * 2,
                    self.button_width * 2
                ]
                x_offset = start_x
                for j, key in enumerate(row):
                    x = x_offset
                    y = start_y + i * (self.button_height + self.button_spacing)
                    button_list.append(Button([x, y], key, [int(special_widths[j]), self.button_height]))
                    x_offset += special_widths[j] + self.button_spacing
            else:
                for j, key in enumerate(row):
                    x = start_x + j * (self.button_width + self.button_spacing)
                    y = start_y + i * (self.button_height + self.button_spacing)
                    button_list.append(Button([x, y], key))
        
        return button_list

    def draw_keyboard(self, img):
        """Draw the virtual keyboard in the top section"""
        if img is None:
            return None
            
        img_with_keyboard = img.copy()
        
        overlay = img.copy()
        cv2.rectangle(overlay, (0, 0), (img.shape[1], 350), (0, 0, 0), -1)
        cv2.addWeighted(overlay, 0.5, img_with_keyboard, 0.5, 0, img_with_keyboard)
        
        button_overlay = np.zeros_like(img_with_keyboard)
        
        for button in self.button_list:
            x, y = button.pos
            w_btn, h_btn = button.size

            x, y, w_btn, h_btn = int(x), int(y), int(w_btn), int(h_btn)
            
            if button.text in ["Space", "Clear", "Enter"]:
                color = (100, 0, 200)
            elif button.text == "<":
                color = (200, 100, 0)
            elif button.text == "Google":
                color = (66, 133, 244)
            elif button.text == "YouTube":
                color = (255, 0, 0)
            else:
                color = (200, 0, 200)
            
            cv2.rectangle(button_overlay, (x, y), (x + w_btn, y + h_btn), color, cv2.FILLED)
            cv2.rectangle(button_overlay, (x, y), (x + w_btn, y + h_btn), (255, 255, 255), 2)
            
            text_scale = 0.5 if len(button.text) > 5 else 0.6
            text_thickness = 2
            text_size = cv2.getTextSize(button.text, cv2.FONT_HERSHEY_SIMPLEX, text_scale, text_thickness)[0]
            text_x = x + (w_btn - text_size[0]) // 2
            text_y = y + (h_btn + text_size[1]) // 2
            
            cv2.putText(button_overlay, button.text, (text_x, text_y),
                       cv2.FONT_HERSHEY_SIMPLEX, text_scale, (255, 255, 255), text_thickness)
        
        mask = button_overlay.astype(bool)
        img_with_keyboard[mask] = cv2.addWeighted(img_with_keyboard, 0.2, button_overlay, 0.8, 0)[mask]
        
        return img_with_keyboard

    def draw_text_display(self, img):
        """Draw the text display below the keyboard"""
        if img is None:
            return
            
        h, w = img.shape[:2]
        
        display_y = 350
        cv2.rectangle(img, (30, display_y), (w - 30, display_y + 50), (50, 50, 80), cv2.FILLED)
        cv2.rectangle(img, (30, display_y), (w - 30, display_y + 50), (255, 255, 255), 2)
        
        display_text = self.final_text
        if len(display_text) > 35:
            display_text = "..." + display_text[-35:]
        
        cv2.putText(img, display_text, (40, display_y + 35),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
        
        cv2.putText(img, f"Chars: {len(self.final_text)}", (w - 100, display_y + 20),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)

    def open_google_search(self, search_text):
        """Open Google with the search text"""
        try:
            if search_text.strip():
                encoded_search = urllib.parse.quote_plus(search_text)
                url = f"https://www.google.com/search?q={encoded_search}"
                webbrowser.open(url)
                print(f"Opening Google search: {search_text}")
            else:
                webbrowser.open("https://www.google.com")
                print("Opening Google homepage")
        except Exception as e:
            print(f"Error opening Google: {e}")

    def open_youtube_search(self, search_text):
        """Open YouTube with the search text"""
        try:
            if search_text.strip():
                encoded_search = urllib.parse.quote_plus(search_text)
                url = f"https://www.youtube.com/results?search_query={encoded_search}"
                webbrowser.open(url)
                print(f"Opening YouTube search: {search_text}")
            else:
                webbrowser.open("https://www.youtube.com")
                print("Opening YouTube homepage")
        except Exception as e:
            print(f"Error opening YouTube: {e}")

    def handle_special_keys(self, key):
        """Handle special key functions"""
        try:
            if key == "Space":
                self.final_text += " "
                self.keyboard.press(' ')
                self.keyboard.release(' ')
                return True
            elif key == "Clear":
                self.final_text = ""
                return True
            elif key == "Enter":
                self.final_text += "\n"
                self.keyboard.press('\n')
                self.keyboard.release('\n')
                return True
            elif key == "<":
                if self.final_text:
                    self.final_text = self.final_text[:-1]
                    self.keyboard.press('\x08')
                    self.keyboard.release('\x08')
                return True
            elif key == "Google":
                self.open_google_search(self.final_text)
                return True
            elif key == "YouTube":
                self.open_youtube_search(self.final_text)
                return True
            return False
        except Exception as e:
            print(f"Error handling key {key}: {e}")
            return False

    def process_hand_interaction(self, img, lmList):
        """Process hand interactions with keyboard"""
        if not lmList or len(lmList) < 21:
            return
        
        current_time = time()
        
        for button in self.button_list:
            x, y = button.pos
            w_btn, h_btn = button.size

            x, y, w_btn, h_btn = int(x), int(y), int(w_btn), int(h_btn)

            if x < lmList[8][0] < x + w_btn and y < lmList[8][1] < y + h_btn:
                cv2.rectangle(img, (x-3, y-3), (x + w_btn + 3, y + h_btn + 3), (100, 200, 255), 3)
                
                try:
                    length, info, img = self.detector.findDistance(lmList[8][:2], lmList[4][:2], img)
                    
                    cv2.line(img, 
                            (int(lmList[8][0]), int(lmList[8][1])), 
                            (int(lmList[4][0]), int(lmList[4][1])), 
                            (0, 255, 255), 3)
                    
                    if length < 35 and (current_time - self.last_press_time) > self.cooldown_time:
                        cv2.rectangle(img, (x-5, y-5), (x + w_btn + 5, y + h_btn + 5), (0, 255, 0), cv2.FILLED)
                        
                        if button.text in ["Google", "YouTube"]:
                            cv2.rectangle(img, (x-7, y-7), (x + w_btn + 7, y + h_btn + 7), (255, 255, 0), 3)
                        
                        if not self.handle_special_keys(button.text):
                            self.final_text += button.text
                            self.keyboard.press(button.text)
                            self.keyboard.release(button.text)
                        
                        self.last_press_time = current_time
                except Exception as e:
                    print(f"Error in hand interaction: {e}")

    def calculate_fps(self):
        """Calculate actual FPS"""
        self.frame_count += 1
        current_time = time()
        if current_time - self.last_fps_time >= 1.0:
            self.fps = self.frame_count
            self.frame_count = 0
            self.last_fps_time = current_time

    def run(self):
        """Main application loop"""
        print("Virtual Keyboard Started! Press 'q' to quit.")
        print("Features:")
        print("- Type text using hand gestures")
        print("- Click 'Google' to open Google search with your text")
        print("- Click 'YouTube' to open YouTube search with your text")
        print("- Use INDEX FINGER and THUMB to click (bring them together)")
        print("- High opacity buttons for better visibility")
        print("- Optimized for high FPS performance")
        
        if not self.cap.isOpened():
            print("Error: Camera not available")
            return
        
        cv2.namedWindow("Virtual Keyboard", cv2.WINDOW_NORMAL)
        cv2.resizeWindow("Virtual Keyboard", 800, 600)
        
        print("Optimizing camera...")
        for _ in range(10):
            self.cap.read()
        
        print("Starting main loop...")
        
        while True:
            try:
                success, img = self.cap.read()
                if not success:
                    print("Failed to capture image")
                    break
                
                self.calculate_fps()
                
                img = cv2.flip(img, 1)
                
                hands, img_with_hands = self.detector.findHands(img, flipType=False, draw=True)
                lmList = hands[0]["lmList"] if hands else []
                
                img_with_keyboard = self.draw_keyboard(img_with_hands)
                if img_with_keyboard is None:
                    continue
                
                self.draw_text_display(img_with_keyboard)
                
                if lmList:
                    self.process_hand_interaction(img_with_keyboard, lmList)
                
                h, w = img_with_keyboard.shape[:2]
                cv2.line(img_with_keyboard, (0, 350), (w, 350), (255, 255, 255), 2)
                
                cv2.putText(img_with_keyboard, f"FPS: {self.fps}", (w - 80, 20),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
                cv2.putText(img_with_keyboard, "VIRTUAL KEYBOARD", (30, 20),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
                cv2.putText(img_with_keyboard, "Use INDEX + THUMB to click", (200, 20),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)
                cv2.putText(img_with_keyboard, "CAMERA FEED", (30, 370),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
                
                cv2.putText(img_with_keyboard, "Gesture: Bring thumb and index finger together to click", 
                           (30, h - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 2)
                
                cv2.imshow("Virtual Keyboard", img_with_keyboard)
                
                key = cv2.waitKey(1) & 0xFF
                if key == ord('q'):
                    break
                elif key == ord('f'):
                    cv2.setWindowProperty("Virtual Keyboard", cv2.WND_PROP_FULLSCREEN, 
                                        cv2.WINDOW_FULLSCREEN ^ cv2.WINDOW_FULLSCREEN)
            
            except Exception as e:
                print(f"Error in main loop: {e}")
                break
        
        self.cap.release()
        cv2.destroyAllWindows()
        print("Virtual Keyboard closed.")

if __name__ == "__main__":
    try:
        vk = VirtualKeyboard()
        vk.run()
    except Exception as e:
        print(f"Fatal error: {e}")
        input("Press Enter to exit...")

شرح قسمت‌های مهم کد

کلاس Button

این کلاس برای ایجاد دکمه‌های مجازی استفاده می‌شود:

  • pos: موقعیت دکمه در صفحه
  • text: متن روی دکمه
  • size: اندازه دکمه

کلاس VirtualKeyboard

کلاس اصلی که تمام منطق برنامه را مدیریت می‌کند:

متد __init__

  • تنظیمات دوربین و تشخیص دست
  • تعریف layout کیبورد
  • مشخصات دکمه‌ها و فاصله‌ها

متد create_buttons

دکمه‌ها را بر اساس layout تعریف شده ایجاد می‌کند.

متد draw_keyboard

کیبورد مجازی را روی تصویر دوربین رسم می‌کند.

متد process_hand_interaction

حرکات دست را پردازش کرده و برهم‌کنش با دکمه‌ها را مدیریت می‌کند.

متد handle_special_keys

دکمه‌های خاص مانند Space, Clear, Google, YouTube را مدیریت می‌کند.

عیب‌یابی و نکات

  • دوربین کار نمی‌کند: مطمئن شوید دوربین در برنامه‌های دیگر قفل نیست
  • تشخیص دست ضعیف است: نور محیط را افزایش دهید
  • FPS پایین: رزولوشن دوربین را کاهش دهید
  • دکمه‌ها پاسخ نمی‌دهند: فاصله دست از دوربین را تنظیم کنید
  • خطای کتابخانه: مطمئن شوید همه کتابخانه‌ها نصب شده‌اند

نکات بهینه‌سازی:

  • از نور کافی برای تشخیص بهتر دست استفاده کنید
  • دست خود را در فاصله مناسب از دوربین نگه دارید (30-60 سانتی‌متر)
  • حرکات را آرام و دقیق انجام دهید
  • برای عملکرد بهتر، برنامه‌های دیگر را ببندید

منابع

منبع اصلی کد گرفته شده

منبع :https://github.com/vinit714/AI-Virtual-Keyboard