Ai detec มือ เพื่อใช้ในการเล่น game
Tags: ai, gamedev, python
Published: 2024-04-17 15:18:46

**Dino Game** เป็นเกมที่เล่นใน Google Chrome เมื่อไม่มีอินเทอร์เน็ต โดยควบคุมไดโนเสาร์ให้กระโดดเพื่อหลีกเลี่ยงอุปสรรค คะแนนเพิ่มขึ้นเรื่อยๆ โดยเกมจะเริ่มช้าและเร่งขึ้นตามเวลา ซึ่งเป็นเกมที่นิยมในเว็บ
โดยใช้ Computer vision ในการเล่นโดยใช้ Mediapipe และ OpenCV เพื่อติดตามมือผู้เล่น เลือกจุดสำคัญบนมือแต่ละข้อ เพื่อกดปุ่ม Spacebar ในเกม แทนการกดปุ่มด้วยมือ ทำให้เล่นเกมสะดวกสบายและสนุกมากขึ้น
**มาดูขั้นตอนการรันโค้ดเลยค้าบ**
```
import cv2
import pyautogui
import mediapipe as mp
import time
```
1. เป็นการ import opencv ใช้สำหรับการประมวลผลภาพและวิดีโอใน Python
2. เป็นไลบรารีที่ใช้สำหรับควบคุมคีย์บอร์ดและเมาส์ผ่าน Python
3. เป็นไลบรารีที่ใช้สำหรับการประมวลผลภาพ
4. เป็นไลบรารีสำหรับการจัดการเวลาใน Python

```
cap = cv2.VideoCapture(0)

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(static_image_mode=False, max_num_hands=1,
                       min_detection_confidence=0.5, min_tracking_confidence=0.5)

mp_drawing = mp.solutions.drawing_utils
```
1. เปิดการใช้งานกล้องหรือวิดีโอจากอุปกรณ์ที่เปิดตัวเป็นกล้อง โดยเลข 0 หมายถึงการใช้กล้องของคอมพิวเตอร์
2. เป็นการกำหนดตัวแปร mp_hand 
3. hands โดยกำหนดเป็นพารามิเตอร์ เป็น 
static_image_mode=False คือ การกำหนดให้โมเดลมือทำงานในโหมดวิดีโอแทนโหมดภาพเคลื่อนไหว
max_num_hands=1 คือ การกำหนดให้ตรวจจับมือได้มือเพียงหนึ่งมือเท่านั้น
min_detection_confidence=0.5 คือ การกำหนดค่าคงที่ขั้นต่ำในการตรวจจับมือ
min_tracking_confidence=0.5 คือ การกำหนดค่าคงที่ขั้นต่ำในการติดตามการเคลื่อนไหวของมือ
4. mp_drawing = mp.solutions.drawing_utils เป็นการเพิ่มความสามารถในการแสดงผล โดยการวาดเส้นเชื่อมจุดต่างๆของมือบนภาพ

```
while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    
    results = hands.process(image_rgb)
```
1. สร้างลูป while True เพื่ออ่านภาพจากกล้อง โดยใช้คำสั่ง cap.read และเก็บภาพที่ได้ไว้ในตัวแปร frame และตรวจสอบในการอ่านภาพว่าสำเร็จไหม ด้วยตัวแปร ret ถ้าอ่านไม่ได้ ให้หยุดลูปนั้น ด้วยคำสั่ง break
2. ทำการแปลงสีของภาพจาก BGR ให้เป็น RGB ด้วยคำสั่ง cv2.cvtColor()
3. เรียกใช้ตัวแปร hands ที่กำหนดไว้ มาทำการประมวลผลภาพของมือที่ได้รับจากกล้อง โดยใช้ภาพที่แปลงสีแล้ว ด้วยคำสั่ง hands.process(image_rgb) ซึ่งผลการตรวจจับมือจะเก็บมาไว้ในตัวแปร results

```
if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)
            
            thumb_tip = hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP]
            index_finger_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
            middle_finger_tip = hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP]
            ring_finger_tip = hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP]
            pinky_tip = hand_landmarks.landmark[mp_hands.HandLandmark.PINKY_TIP]

            index_finger_mcp = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP]
```
1. เป็นการตรวจสอบว่ามือที่ตรวจจับได้ในภาพหรือไม่ หากมีการตรวจจับได้ จะทำการลูปผ่านพิกัดของมือทั้งหมด ด้วยคำสั่ง for hand_landmarks in results.multi_hand_landmarks
2. ทำการวาดเส้นเชื่อมต่อระหว่างจุดบนมือด้วยคำสั่ง mp_drawing.draw_landmarks() โดยแสดงเส้นเชื่อม ตามข้อมูลการเชื่อมที่ได้จากคำสั่ง mp_hands.HAND_CONNECTIONS
3. เก็บพิกัดของจุดที่สำคัญบนมือ เช่น ปลายนิ้วมือต่างๆ ในตัวแปรต่างๆ thumb_tip , index_finger_tip , middle_finger_tip , ring_finger_tip , pinky_tip , index_finger_mcp
4. เป็นการเข้าถึงพิกัดจุดที่สำคัญบนมือ ด้วยการใช้ mp_hands.HandLandmark.INDEX_FINGER_MCP ซึ่ง จะเป็นผลลัพธ์ของการตรวจจับมือที่ได้จาก Mediapipe ในภาพ และกำหนดค่าไว้ในตัวแปร index_finger_mcp

```
cv2.putText(frame, f"Thumb tip y: {round(thumb_tip.y, 2)}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.putText(frame, f"Index tip y: {round(index_finger_tip.y, 2)}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.putText(frame, f"Middle tip y: {round(middle_finger_tip.y, 2)}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.putText(frame, f"Ring tip y: {round(ring_finger_tip.y, 2)}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.putText(frame, f"Pinky tip y: {round(pinky_tip.y, 2)}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            
```
1. cv2.putText() เป็นการแสดงข้อความบนภาพวิดีโอที่มาจากกล้อง และแสดงค่าพิกัด y ของปลายนิ้วทั้ง 5 ซึ่งมี 
Thumb tip y : พร้อมกับค่าพิกัด y ของปลายนิ้วโป้ง และ แสดงบนภาพที่ตำแหน่ง (10,30)
Index tip y : พร้อมกับค่าพิกัด y ของปลายนิ้วชี้ และ แสดงบนภาพที่ตำแหน่ง (10,60)
Middle tip y : พร้อมกับค่าพิกัด y ของปลายนิ้วกลาง และ แสดงบนภาพที่ตำแหน่ง (10,90)
Ring tip y : พร้อมกับค่าพิกัด y ของปลายนิ้วนาง และ แสดงบนภาพที่ตำแหน่ง (10,120)
Pinky tip y : พร้อมกับค่าพิกัด y ของปลายนิ้วก้อย และ แสดงบนภาพที่ตำแหน่ง (10,150)
2. cv2.FONT_HERSHEY_SIMPLEX แต่ละข้อความจะใช้ฟอนต์ และขนาดข้อความ 1 และสีข้อความเป็นสีเขียว (0,255,0) และความหนาของตัวอักษรเป็น 2 

```
 is_hand_closed = (
                index_finger_tip.y > thumb_tip.y and
                middle_finger_tip.y > thumb_tip.y and
                ring_finger_tip.y > thumb_tip.y and
                pinky_tip.y > thumb_tip.y
            )
            is_hand_jump = (
                index_finger_tip.y < index_finger_mcp.y and
                middle_finger_tip.y < index_finger_mcp.y and
                ring_finger_tip.y > thumb_tip.y and
                pinky_tip.y > thumb_tip.y
            )

```
1. กำหนดเงื่อนไขสำหรับการตรวจสถานะของมือ โดย กำหนดขึ้นมา 2 ตัวแปร is_hand_closed และ is_hand_jump ที่จะเก็บค่า (true,false)ตามเงื่อนไข
2. is_hand_closed เป็นการตรวจสอบว่ามือปิดหรือไม่โดยการดูจากตำแหน่ง y ของปลายนิ้วทั้ง 5 นิ้ว ว่า ตำแหน่งนิ้วก้อยอยู่ต่ำกว่าต่ำแหน่งนิ้วโป้งหรือไม่
3. ถ้าปลายนิ้วทั้ง 4 อยู่ต่ำกว่าปลายนิ้วโป้ง แสดงว่ามือกำลังจะปิด และเก็บค่าเป็นค่า true 
4. is_hand_jump เป็นการตรวจสอบว่าปลายนิ้วชี้และนิ้วกลางอยู่ต่ำกว่าจุด MCP ของนิ้วชี้หรือไม่

```
if is_hand_closed:
                pass
            if is_hand_jump:
                pyautogui.press('space')
                time.sleep(0.1)
            
            
    cv2.imshow('CV', frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    
cap.release()
cv2.destroyAllWindows()
```
1. คำสั่ง if is_hand_closed ตรวจสอบว่า มือปิดหรือไม่ ถ้าใช่ จะเป็น pass 
2. คำสั่ง if is_hand_jump ตรวจสอบว่ามือกำลังมีการเคลื่อนไหวหรือกระโดด ถ้าใช่ จะเป็นการใช้ pyautogui.press('space') เพื่อจำลองการกดปุ่ม space บนแป้นพิมพ์ การหน่วงเวลา 0.1 วินาทีหลังการกดปุ่มเพื่อป้องกันการกดซ้ำที่รวดเร็วเกินไป
3. cv2.imshow('CV', frame) เป็นการแสดงผลภาพวิดีโอที่ได้จากกล้อง
4. คำสั่ง if cv2.waitKey(1) & 0xFF == ord('q'): เป็นการใช้สำหรับการออกจาก loop และปิดหน้าต่างโปรแกรมเมื่อผู้ใช้กดปุ่ม 'q'
5. คำสั่ง cap.release() และ cv2.destroyAllWindows() เป็นการใช้สำหรับปิดการจับภาพวิดีโอและปิดหน้าต่าง GUI ทั้งหมดตามลำดับ

**สรุปผล**
Dino Game การเล่นจะมีความท้าทายเพิ่มมากขึ้นตามความเร็วที่เพิ่มขึ้น Computer vision ในการเล่นเกมโดยใช้ Mediapipe และ OpenCV เพื่อทำให้ผู้เล่นสามารถควบคุมไดโนเสาร์ด้วยการขยับมือหรือนิ้วแทนการกดปุ่ม Spacebar ในคีย์บอร์ด การนำไปต่อยอดในการเพิ่มความสะดวกสบายและความสนุกในการเล่นเกมที่มีความหลากหลายมากขึ้นจากวิธีการเล่นแบบเดิมๆ ด้วยการใช้ Computer vision เพื่อแทนการกดปุ่ม Spacebar ในกระโดดของเกม Dino Game

**ตัวอย่างการ detec มือ**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz32gs3xtc3xtoz860dw.jpg)

**Reference**

- https://www.youtube.com/watch?v=QekxFU0YDqM

- https://github.com/rocketingdatascience/Gaming_with_Hand_Gestures_Recognition/blob/main/OpenCV%20Play%20Dino%20Game.py