PiscCode构建Mediapipe 手势识别“剪刀石头布”小游戏

在计算机视觉与人机交互领域,手势识别是一个非常有趣的应用场景。本文将带你用 MediapipePython 实现一个基于摄像头的手势识别“剪刀石头布”小游戏,并展示实时手势与游戏结果。


1. 项目概述

本项目实现了一个基于摄像头的人机互动小游戏 —— 石头剪刀布 + OK 手势控制。玩家只需对着摄像头做出相应手势,系统便会自动识别并判定游戏结果,整个过程无需鼠标键盘操作,完全通过手势完成。

具体功能包括:

  • 实时手势检测
    借助 MediaPipe Hand Landmarker 模型,系统能够实时检测玩家的手部关键点,并通过几何计算判断玩家当前的手势类型。支持的手势包括:

    • 石头(Rock)

    • 剪刀(Scissors)

    • 布(Paper)

    • OK 手势(用于游戏重置)

  • 游戏逻辑

    • 当玩家展示 石头/剪刀/布 时,系统会让电脑随机选择一种出拳方式,并即时对比胜负。

    • 当玩家展示 OK 手势 时,系统会自动重置游戏,清除上一轮的结果,等待下一局。

  • 可视化效果

    • 在摄像头画面上实时绘制手部关键点与连线,帮助玩家直观了解系统的检测效果。

    • 在手部附近叠加文字标注,显示系统识别到的手势。

    • 在屏幕左上角动态显示:

      • 电脑的出拳选择

      • 本轮的胜负结果(玩家胜利、电脑胜利或平局)


2. MediaPipe 简介

MediaPipe 是由 Google Research 推出的一个开源跨平台机器学习框架,专注于实时的多模态应用(如视频、音频和传感器数据)的处理。它为开发者提供了一系列 预训练模型高性能计算管道,在移动端、桌面端甚至 Web 环境中都能高效运行。

在本项目中,我们使用了 MediaPipe 的 Hand Landmarker 模型,它可以在单帧图像或视频流中检测到 21 个手部关键点(包括手指关节和指尖)。这些关键点的位置数据为手势识别提供了坚实的基础。

MediaPipe 的优势主要体现在:

  1. 高性能:底层针对 CPU/GPU 进行了优化,能在实时视频流中稳定运行。

  2. 跨平台:支持 Android、iOS、Windows、Linux 和 Web,适合快速开发跨平台应用。

  3. 模块化:提供了丰富的组件(如 Face Mesh、Pose、Hand Tracking 等),可以灵活组合使用。

  4. 易用性:Python API 简洁直观,适合科研、教学和快速原型开发。

因此,MediaPipe 不仅是手势识别领域的常用工具,也广泛应用于 人脸识别、姿态检测、表情分析、增强现实(AR)和虚拟试衣等场景


3. 核心类:SimpleHandGestureGame

class SimpleHandGestureGame:
    def __init__(self, model_path="hand_landmarker.task", num_hands=1):
        # 初始化 Mediapipe HandLandmarker
        base_options = python.BaseOptions(model_asset_path=model_path)
        options = vision.HandLandmarkerOptions(base_options=base_options, num_hands=num_hands)
        self.detector = vision.HandLandmarker.create_from_options(options)
        self.computer_choice = None
        self.round_result = ""
        self.round_played = False

3.1 手势绘制 _draw_landmarks

def _draw_landmarks(self, rgb_image, detection_result):
    annotated_image = np.copy(rgb_image)
    if detection_result.hand_landmarks:
        for hand_landmarks in detection_result.hand_landmarks:
            proto_landmarks = landmark_pb2.NormalizedLandmarkList()
            proto_landmarks.landmark.extend([landmark_pb2.NormalizedLandmark(x=lm.x, y=lm.y, z=lm.z) for lm in hand_landmarks])
            solutions.drawing_utils.draw_landmarks(
                image=annotated_image,
                landmark_list=proto_landmarks,
                connections=mp.solutions.hands.HAND_CONNECTIONS,
                landmark_drawing_spec=solutions.drawing_styles.get_default_hand_landmarks_style(),
                connection_drawing_spec=solutions.drawing_styles.get_default_hand_connections_style()
            )
    return annotated_image

3.2 手势识别 _judge_gesture

我们通过手指关键点的伸直状态判断手势:

  • 石头(Rock):全部手指弯曲

  • 剪刀(Scissors):食指和中指伸直,其他弯曲

  • 布(Paper):五指全部伸直

  • OK:拇指与食指形成圆圈,其余三指伸直

def _judge_gesture(self, hand_landmarks):
    def is_straight(tip, pip, mcp=None):
        if mcp:
            a, b, c = np.array([tip.x, tip.y]), np.array([pip.x, pip.y]), np.array([mcp.x, mcp.y])
            ba, bc = a - b, c - b
            cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) + 1e-6)
            return np.arccos(np.clip(cos_angle, -1, 1)) * 180 / np.pi > 160
        else:
            return tip.y < pip.y

    thumb_straight = is_straight(hand_landmarks[4], hand_landmarks[2], hand_landmarks[1])
    index_straight = is_straight(hand_landmarks[8], hand_landmarks[6])
    middle_straight = is_straight(hand_landmarks[12], hand_landmarks[10])
    ring_straight = is_straight(hand_landmarks[16], hand_landmarks[14])
    pinky_straight = is_straight(hand_landmarks[20], hand_landmarks[18])
    total = sum([thumb_straight, index_straight, middle_straight, ring_straight, pinky_straight])

    # OK gesture
    thumb_tip, index_tip = np.array([hand_landmarks[4].x, hand_landmarks[4].y]), np.array([hand_landmarks[8].x, hand_landmarks[8].y])
    if np.linalg.norm(thumb_tip - index_tip) < 0.05 and middle_straight and ring_straight and pinky_straight:
        return "OK"

    if total == 0: return "Rock"
    if total == 2 and index_straight and middle_straight: return "Scissors"
    if total == 5: return "Paper"
    return "Undefined"

3.3 游戏逻辑 _play_game

def _play_game(self, player_choice):
    choices = ["Rock", "Scissors", "Paper"]
    if self.computer_choice is None:
        self.computer_choice = random.choice(choices)
    if player_choice == self.computer_choice:
        self.round_result = "Draw"
    elif (player_choice == "Rock" and self.computer_choice == "Scissors") or \
         (player_choice == "Scissors" and self.computer_choice == "Paper") or \
         (player_choice == "Paper" and self.computer_choice == "Rock"):
        self.round_result = "You Win"
    else:
        self.round_result = "Computer Wins"
    self.round_played = True

3.4 图像处理 do

最终的 do 方法负责:

  1. 读取摄像头帧

  2. 调用 Mediapipe 检测手势

  3. 绘制手部关键点和手势文字

  4. 显示电脑出拳及胜负结果

def do(self, frame, device=None):
    if frame is None: return None
    mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    detection_result = self.detector.detect(mp_image)
    annotated = self._draw_landmarks(mp_image.numpy_view(), detection_result)
    # ...绘制手势文字和游戏结果...
    return cv2.cvtColor(annotated, cv2.COLOR_RGB2BGR)

4. 快速体验

import cv2
import numpy as np
import mediapipe as mp
from mediapipe import solutions
from mediapipe.framework.formats import landmark_pb2
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import random

class SimpleHandGestureGame:
    def __init__(self, model_path="文件地址/hand_landmarker.task", num_hands=1):
        """Initialize Mediapipe HandLandmarker and game state"""
        base_options = python.BaseOptions(model_asset_path=model_path)
        options = vision.HandLandmarkerOptions(base_options=base_options, num_hands=num_hands)
        self.detector = vision.HandLandmarker.create_from_options(options)
        self.computer_choice = None
        self.round_result = ""
        self.round_played = False

    def _draw_landmarks(self, rgb_image, detection_result):
        annotated_image = np.copy(rgb_image)
        if detection_result.hand_landmarks:
            for hand_landmarks in detection_result.hand_landmarks:
                proto_landmarks = landmark_pb2.NormalizedLandmarkList()
                proto_landmarks.landmark.extend([landmark_pb2.NormalizedLandmark(x=lm.x, y=lm.y, z=lm.z) for lm in hand_landmarks])
                solutions.drawing_utils.draw_landmarks(
                    image=annotated_image,
                    landmark_list=proto_landmarks,
                    connections=mp.solutions.hands.HAND_CONNECTIONS,
                    landmark_drawing_spec=solutions.drawing_styles.get_default_hand_landmarks_style(),
                    connection_drawing_spec=solutions.drawing_styles.get_default_hand_connections_style()
                )
        return annotated_image

    def _judge_gesture(self, hand_landmarks):
        """Determine hand gesture: Rock-Paper-Scissors + OK"""
        def is_straight(tip, pip, mcp=None):
            if mcp:
                a, b, c = np.array([tip.x, tip.y]), np.array([pip.x, pip.y]), np.array([mcp.x, mcp.y])
                ba, bc = a - b, c - b
                cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) + 1e-6)
                return np.arccos(np.clip(cos_angle, -1, 1)) * 180 / np.pi > 160
            else:
                return tip.y < pip.y

        thumb_straight = is_straight(hand_landmarks[4], hand_landmarks[2], hand_landmarks[1])
        index_straight = is_straight(hand_landmarks[8], hand_landmarks[6])
        middle_straight = is_straight(hand_landmarks[12], hand_landmarks[10])
        ring_straight = is_straight(hand_landmarks[16], hand_landmarks[14])
        pinky_straight = is_straight(hand_landmarks[20], hand_landmarks[18])
        thumb, index, middle, ring, pinky = thumb_straight, index_straight, middle_straight, ring_straight, pinky_straight
        total = sum([thumb, index, middle, ring, pinky])

        # OK gesture
        thumb_tip, index_tip = np.array([hand_landmarks[4].x, hand_landmarks[4].y]), np.array([hand_landmarks[8].x, hand_landmarks[8].y])
        if np.linalg.norm(thumb_tip - index_tip) < 0.05 and middle and ring and pinky:
            return "OK"

        # Rock-Paper-Scissors
        if total == 0:
            return "Rock"
        if total == 2 and index and middle:
            return "Scissors"
        if total == 5:
            return "Paper"
        return "Undefined"

    def _play_game(self, player_choice):
        """Determine the result of Rock-Paper-Scissors round"""
        choices = ["Rock", "Scissors", "Paper"]
        if self.computer_choice is None:
            self.computer_choice = random.choice(choices)
        if player_choice == self.computer_choice:
            self.round_result = "Draw"
        elif (player_choice == "Rock" and self.computer_choice == "Scissors") or \
             (player_choice == "Scissors" and self.computer_choice == "Paper") or \
             (player_choice == "Paper" and self.computer_choice == "Rock"):
            self.round_result = "You Win"
        else:
            self.round_result = "Computer Wins"
        self.round_played = True

    def do(self, frame, device=None):
        """Process a single frame, overlay hand gesture and game result (vertically)"""
        if frame is None:
            return None
        mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        detection_result = self.detector.detect(mp_image)
        annotated = self._draw_landmarks(mp_image.numpy_view(), detection_result)
        gesture_display = ""
        if detection_result.hand_landmarks:
            for hand_landmarks in detection_result.hand_landmarks:
                gesture = self._judge_gesture(hand_landmarks)
                if gesture == "OK":
                    self.computer_choice = random.choice(["Rock", "Scissors", "Paper"])
                    self.round_result = ""
                    self.round_played = False
                    gesture_display = "Game Ready..."
                elif gesture in ["Rock", "Scissors", "Paper"] and not self.round_played:
                    self._play_game(gesture)
                    gesture_display = f"{gesture}"
                else:
                    gesture_display = gesture
                h, w, _ = annotated.shape
                index_finger_tip = hand_landmarks[8]
                cx, cy = int(index_finger_tip.x * w), int(index_finger_tip.y * h)
                cv2.putText(annotated, gesture_display, (cx, cy - 20), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2)

        if self.round_result:
            start_x, start_y, line_height = 30, 50, 40
            lines = [f"Computer Choice: {self.computer_choice}", f"Result: {self.round_result}"]
            for i, line in enumerate(lines):
                cv2.putText(annotated, line, (start_x, start_y + i * line_height), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3)

        return cv2.cvtColor(annotated, cv2.COLOR_RGB2BGR)

5. 总结

本文展示了如何用 Mediapipe HandLandmarker 快速搭建一个实时手势识别小游戏。通过关键点计算与简单逻辑判断,实现了石头、剪刀、布和 OK 手势识别,并结合游戏逻辑输出结果。

对 PiscTrace or PiscCode感兴趣?更多精彩内容请移步官网看看~🔗 PiscTrace

<think>嗯,用户想找用OpenCV手势识别来制作剪刀石头小游戏的资料或教程。首先,我得回忆一下之前看过的相关引用内容。引用2提到使用OpenCV和VC++,而引用3和4是基于Python的,用户可能更倾向于Python,因为示例代码是Python的。需要确定用户的技术栈偏好,但如果没有明确说明,可能需要提供多种方法,但考虑到用户提供的引用中有Python的例子,可能优先推荐Python方案。 接下来,需要整理实现步骤。根据引用2和3,主要步骤包括摄像头捕获、图像预处理、手势识别(轮廓分析、凸包检测、特征提取)、游戏逻辑和界面设计。引用3还提到了PyQt5做GUI,而引用4可能参考了具体的代码结构。 用户需要教程或实现方法,所以应该分步骤说明,并给出代码示例。比如,先介绍如何读取摄像头数据,然后预处理图像,比如高斯滤波、颜色空间转换(CrCb中的Cr分量),然后形态学处理,边缘检测,轮廓查找,凸包分析,最后根据面积比或周长比判断手势类型。 可能遇到的问题包括光照条件影响手势识别,不同手势的特征提取是否准确,以及如何将识别结果集成到游戏逻辑中。需要确保代码示例简洁,并指出关键点,比如引用2中的算法步骤。 另外,引用4提到了参考GitHub的代码,可能需要建议用户查看该仓库获取完整代码。同时,引用3提到结合深度学习或Haar级联分类器,但传统方法可能更适合初学者,所以先介绍传统方法,再提到深度学习的可能性。 最后,生成相关问题时要围绕实现细节、优化方法、技术难点和应用扩展,帮助用户深入理解。</think>### OpenCV手势识别实现剪刀石头小游戏教程 #### **一、实现步骤** 1. **环境搭建** 安装PythonOpenCV、NumPy等库: ```bash pip install opencv-python numpy pyqt5 ``` 2. **摄像头捕获与预处理** ```python import cv2 cap = cv2.VideoCapture(0) # 打开摄像头 while True: ret, frame = cap.read() if not ret: break # 高斯滤波去噪 blurred = cv2.GaussianBlur(frame, (5,5), 0) # 转换到YCrCb颜色空间并提取Cr分量 ycrcb = cv2.cvtColor(blurred, cv2.COLOR_BGR2YCrCb) cr = ycrcb[:, :, 1] # Otsu阈值分割 _, skin_mask = cv2.threshold(cr, 0, 255, cv2.THRESH_OTSU) cv2.imshow("Skin Mask", skin_mask) if cv2.waitKey(1) == 27: break cap.release() cv2.destroyAllWindows() ``` 3. **手势轮廓分析** ```python # 形态学开运算去噪 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) opened = cv2.morphologyEx(skin_mask, cv2.MORPH_OPEN, kernel) # 查找最大轮廓 contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) max_contour = max(contours, key=cv2.contourArea) if contours else None ``` 4. **凸包检测与特征计算** ```python if max_contour is not None: hull = cv2.convexHull(max_contour) # 计算轮廓面积与凸包面积比 area_contour = cv2.contourArea(max_contour) area_hull = cv2.contourArea(hull) ratio = area_contour / area_hull if area_hull > 0 else 0 # 根据比值判断手势 if ratio < 0.7: gesture = "Rock" elif 0.7 <= ratio < 0.85: gesture = "Scissors" else: gesture = "Paper" ``` 5. **游戏逻辑与界面集成** 使用PyQt5设计界面(引用3): ```python from PyQt5.QtWidgets import QApplication, QLabel app = QApplication([]) label = QLabel("识别结果: " + gesture) label.show() app.exec_() ``` #### **二、关键算法说明** - **Cr分量+Otsu法**:利用肤色在Cr通道的分特性分割手部区域[^2] - **轮廓面积比**:通过手势轮廓与凸包面积的比例区分石头(闭合)、剪刀(V形)、(展开)[^3] - **形态学处理**:消除噪声干扰,优化分割效果[^2] #### **三、完整代码参考** GitHub开源项目(引用4): https://github.com/az666/opencv-game/blob/master/shitoujiandaobu.py ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值