基于Python的手势控制贪吃蛇游戏


前言

   随着计算机视觉技术的不断发展,手势识别成为了一种新兴的人机交互方式。通过Python编程语言结合OpenCV库,我们可以开发出基于手势识别的游戏应用。本文将详细介绍如何使用Python实现一个简单的手势控制版贪吃蛇游戏,让玩家可以通过手势来控制游戏中蛇的移动方向。


一、pygame和mediapipe是什么?

尽管本文的重点在于手势识别与贪吃蛇游戏的结合,但为了保持结构的完整性,这里简要说明pygamemediapipe的作用:

  • pygame是一个用于制作视频游戏的Python库,它提供了访问计算机上的音频、图像等功能的接口,并简化了许多常见游戏功能的实现。
  • mediapipe是一个跨平台的框架,用于构建多模态应用级机器感知流水线,特别适用于实时处理人体姿态、面部表情等数据。

二、使用步骤

1.引入库

在开始之前,我们需要导入一些必要的库,包括pygame用于游戏逻辑和界面显示,cv2(OpenCV)、mediapipe用于手势识别,以及numpy进行数值计算。

import pygame
import sys
import random
import cv2
import mediapipe as mp
import numpy as np

2.实现游戏逻辑

首先,初始化pygame并设置游戏的基本参数,如屏幕大小、蛇的大小等。

pygame.init()

# 游戏常量
SCREEN_WIDTH, SCREEN_HEIGHT = 640, 480
SNAKE_SIZE = 20
FOOD_SIZE = 20

# 方向定义
DIRECTIONS = {'UP': 0, 'RIGHT': 1, 'DOWN': 2, 'LEFT': 3}

# 颜色定义
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()

 定义SnakeFood类来管理游戏中的蛇和食物:

class Snake:
    def __init__(self):
        self.body = [(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)]
        self.direction = DIRECTIONS['RIGHT']

    def move(self):
        head_x, head_y = self.body[0]
        if self.direction == DIRECTIONS['RIGHT']:
            head_x += SNAKE_SIZE
        elif self.direction == DIRECTIONS['LEFT']:
            head_x -= SNAKE_SIZE
        elif self.direction == DIRECTIONS['UP']:
            head_y -= SNAKE_SIZE
        elif self.direction == DIRECTIONS['DOWN']:
            head_y += SNAKE_SIZE

        self.body.insert(0, (head_x, head_y))
        if len(self.body) > len(self.body) - 1:
            self.body.pop()

    def draw(self, surface):
        for segment in self.body:
            pygame.draw.rect(surface, GREEN, pygame.Rect(segment[0], segment[1], SNAKE_SIZE, SNAKE_SIZE))

class Food:
    def __init__(self):
        self.position = (random.randint(0, (SCREEN_WIDTH - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE,
                         random.randint(0, (SCREEN_HEIGHT - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)

    def draw(self, surface):
        pygame.draw.rect(surface, RED, pygame.Rect(self.position[0], self.position[1], FOOD_SIZE, FOOD_SIZE))

接着,实现手势识别功能:

class HandDetector:
    # 手势识别类
    # ...(省略部分代码,见原代码段)

    def findHands(self, img, draw=True):
        # ...(省略部分代码,见原代码段)
        return img

    def findPosition(self, img, handNo=0, draw=True):
        # ...(省略部分代码,见原代码段)
        return self.lmList, bboxInfo

    def fingcurved(self):
        # ...(省略部分代码,见原代码段)
        return finger

    def okgesture(self):
        # ...(省略部分代码,见原代码段)
        return True

    def handType(self):
        # ...(省略部分代码,见原代码段)
        return "Right"

class Main:
    # 主控类
    # ...(省略部分代码,见原代码段)

    def Gesture_recognition(self):
        # ...(省略部分代码,见原代码段)
        return gesture

 最后,编写游戏主循环,将手势识别与游戏逻辑结合起来:

def get_new_direction():
    MAIN = Main()
    gesture = MAIN.Gesture_recognition()
    if gesture == "UP":
        return DIRECTIONS['UP']
    elif gesture == "RIGHT":
        return DIRECTIONS['RIGHT']
    elif gesture == "DOWN":
        return DIRECTIONS['DOWN']
    elif gesture == "LEFT":
        return DIRECTIONS['LEFT']
    return None

def main():
    snake = Snake()
    food = Food()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        new_direction = get_new_direction()
        if new_direction is not None and new_direction != (snake.direction + 2) % 4:
            snake.direction = new_direction

        snake.move()
        if snake.body[0] == food.position:
            food.position = (random.randint(0, (SCREEN_WIDTH - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE,
                             random.randint(0, (SCREEN_HEIGHT - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)
            snake.body.append(snake.body[-1])

        screen.fill(WHITE)
        snake.draw(screen)
        food.draw(screen)
        pygame.display.flip()
        clock.tick(10)

if __name__ == "__main__":
    main()

 完整代码如下:

# 比心       前进
# 张开手掌    后退
# ok手势     右转
# 剪刀手势    左转
import pygame
import sys
import random
import cv2
import mediapipe as mp
import numpy as np

pygame.init()

# 游戏常量
SCREEN_WIDTH, SCREEN_HEIGHT = 640, 480
SNAKE_SIZE = 20
FOOD_SIZE = 20

# 方向定义
DIRECTIONS = {'UP': 0, 'RIGHT': 1, 'DOWN': 2, 'LEFT': 3}

# 颜色定义
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()


class Snake:
    def __init__(self):
        self.body = [(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)]
        self.direction = DIRECTIONS['RIGHT']

    def move(self):
        head_x, head_y = self.body[0]
        if self.direction == DIRECTIONS['RIGHT']:
            head_x += SNAKE_SIZE
        elif self.direction == DIRECTIONS['LEFT']:
            head_x -= SNAKE_SIZE
        elif self.direction == DIRECTIONS['UP']:
            head_y -= SNAKE_SIZE
        elif self.direction == DIRECTIONS['DOWN']:
            head_y += SNAKE_SIZE

        self.body.insert(0, (head_x, head_y))
        if len(self.body) > len(self.body) - 1:
            self.body.pop()

    def draw(self, surface):
        for segment in self.body:
            pygame.draw.rect(surface, GREEN, pygame.Rect(segment[0], segment[1], SNAKE_SIZE, SNAKE_SIZE))


class Food:
    def __init__(self):
        self.position = (random.randint(0, (SCREEN_WIDTH - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE,
                         random.randint(0, (SCREEN_HEIGHT - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)

    def draw(self, surface):
        pygame.draw.rect(surface, RED, pygame.Rect(self.position[0], self.position[1], FOOD_SIZE, FOOD_SIZE))


class HandDetector:
    """
    使用mediapipe库查找手。导出地标像素格式。添加了额外的功能。
    如查找方式,许多手指向上或两个手指之间的距离。而且提供找到的手的边界框信息。
    """

    def __init__(self, mode=False, maxHands=2, detectionCon=0.5, minTrackCon=0.5):
        """
        :param mode: 在静态模式下,对每个图像进行检测
        :param maxHands: 要检测的最大手数
        :param detectionCon: 最小检测置信度
        :param minTrackCon: 最小跟踪置信度
        """
        self.results = None
        self.mode = mode
        self.maxHands = maxHands
        self.modelComplex = False
        self.detectionCon = detectionCon
        self.minTrackCon = minTrackCon

        # 初始化手部识别模型
        self.mpHands = mp.solutions.hands
        self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.modelComplex,
                                        self.detectionCon, self.minTrackCon)
        self.mpDraw = mp.solutions.drawing_utils  # 初始化绘图器
        self.tipIds = [4, 8, 12, 16, 20]  # 指尖列表
        self.fingers = []  # 存储手的状态
        self.lmList = []  # 储检测到的手部的每个关键点的坐标

    def findHands(self, img, draw=True):
        """
        从图像(BRG)中找到手部。
        :param img: 用于查找手的图像。
        :param draw: 在图像上绘制输出的标志。
        :return: 带或不带图形的图像
        """
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 将传入的图像由BGR模式转标准的Opencv模式——RGB模式,
        self.results = self.hands.process(imgRGB)  # 处理图像,返回包含检测到的手部信息的结果。这个结果通常包含了手部的关键点坐标

        # 画出手的关键点和线条
        if self.results.multi_hand_landmarks:
            for handLms in self.results.multi_hand_landmarks:
                if draw:
                    self.mpDraw.draw_landmarks(img, handLms,
                                               self.mpHands.HAND_CONNECTIONS)
        return img

    def findPosition(self, img, handNo=0, draw=True):
        """
        查找单手的地标并将其放入列表中像素格式。还可以返回手部周围的边界框。
        :param img: 要查找的主图像
        :param handNo: 如果检测到多只手,则为手部id
        :param draw: 在图像上绘制输出的标志。(默认绘制矩形框)
        :return: 像素格式的手部关节位置列表;手部边界框
        """
        # 保存关键点的像素坐标
        xList = []
        yList = []
        bbox = []
        bboxInfo = []  # 保存首部检测框信息
        self.lmList = []
        if self.results.multi_hand_landmarks:
            myHand = self.results.multi_hand_landmarks[handNo]
            for id, lm in enumerate(myHand.landmark):  # 遍历手部关键点,id表示关键点下标,lm表示关键点对象
                h, w, c = img.shape
                #  lm是存储的是关键点归一化(0~1)的相对位置,
                px, py = int(lm.x * w), int(lm.y * h)  # 转换为图像中的像素坐标
                xList.append(px)
                yList.append(py)
                self.lmList.append([px, py])
                if draw:
                    cv2.circle(img, (px, py), 5, (255, 0, 255), cv2.FILLED)  # 用红点标记关键点
            # 获取手关键点的左上点和右下点
            xmin, xmax = min(xList), max(xList)
            ymin, ymax = min(yList), max(yList)
            # 边界框信息存储
            boxW, boxH = xmax - xmin, ymax - ymin
            bbox = xmin, ymin, boxW, boxH
            # 边界框中心坐标
            cx, cy = bbox[0] + (bbox[2] // 2), \
                     bbox[1] + (bbox[3] // 2)
            # id含义是指的手部最后一个关键点的下标
            bboxInfo = {"id": id, "bbox": bbox, "center": (cx, cy)}

            if draw:
                cv2.rectangle(img, (bbox[0] - 20, bbox[1] - 20),
                              (bbox[0] + bbox[2] + 20, bbox[1] + bbox[3] + 20),
                              (0, 255, 0), 2)

        return self.lmList, bboxInfo

    def fingcurved(self):
        """
        查找除拇指外的四个手指弯曲状态
        计算方式:
            取出除了大拇指以外的四个手指指尖坐标a1、a2、a3、a4(对应地标8,12,16,20),
            然后取出地标为6,10,14,18的坐标b1、b2、b3、b4(即每个指尖以下第二个关节),
            通过比较指尖(a1、a2、a3、a4)到手腕地标(0)和指关节(b1、b2、b3、b4)到地标0的欧几里得距离,
            即可区分手指是否弯曲
        :return: 弯曲手指的列表
        """
        finger = []
        for id in range(1, 5):
            point1 = np.array((self.lmList[self.tipIds[id]][0], self.lmList[self.tipIds[id]][1]))
            point2 = np.array((self.lmList[self.tipIds[id] - 2][0], self.lmList[self.tipIds[id] - 2][1]))
            point3 = np.array((self.lmList[0][0], self.lmList[0][1]))
            if np.linalg.norm(point1 - point3) < np.linalg.norm(point2 - point3):  # 计算两点之间的距离
                finger.append(1)
            else:
                finger.append(0)

        return finger

    def okgesture(self):
        """
        特殊手势处理:判断是否手势为ok
        判断方式:
            ok手势,其拇指指尖地标a0和食指指尖地标a1十分接近,于是我们这样处理:如果中指、无名
            指、小拇指伸直并且食指指尖到大拇指指尖的距离小于食指指尖到中指指尖距离则断定为ok手
            势。
        """
        f1, f2, f3, f4 = self.fingcurved()
        if (f2 == 0 and f3 == 0 and f4 == 0):
            point1 = np.array((self.lmList[8][0], self.lmList[8][1]))
            point2 = np.array((self.lmList[4][0], self.lmList[4][1]))
            point3 = np.array((self.lmList[12][0], self.lmList[12][1]))
            if np.linalg.norm(point1 - point2) < np.linalg.norm(point1 - point3):
                return True

    def handType(self):
        """
        检查传入的手部是左还是右
        :return: "Right" 或 "Left"
        """
        if self.results.multi_hand_landmarks:
            if self.lmList[17][0] < self.lmList[5][0]:
                return "Right"
            else:
                return "Left"


class Main:

    def __init__(self):
        self.detector = None
        self.camera = cv2.VideoCapture(0, cv2.CAP_DSHOW)  # 以视频流传入
        self.camera.set(3, 1280)  # 设置分辨率
        self.camera.set(4, 720)

    def Gesture_recognition(self):
        self.detector = HandDetector()
        while True:
            ret, img = self.camera.read()
            if ret:
                img = self.detector.findHands(img)  # 获取你的手部的关键点信息
                cv2.imshow('hand', img)
                lmList, bbox = self.detector.findPosition(img)  # 获取你手部的关键点的像素坐标和边界框
                if lmList:
                    x_1, y_1 = bbox["bbox"][0], bbox["bbox"][1]
                    f1, f2, f3, f4 = self.detector.fingcurved()
                    # 根据手指弯曲状态识别手势并在图像上显示相应文本
                    if f1 == 0 and f2 == 1 and f3 == 1 and f4 == 1:
                        gesture = "UP"
                        print("手势为:", gesture)
                    elif self.detector.okgesture():
                        gesture = "RIGHT"
                        print("手势为:", gesture)
                    elif f1 == 0 and f2 == 0 and f3 == 0 and f4 == 0:
                        gesture = "DOWN"
                        print("手势为:", gesture)
                    elif f1 == 0 and f2 == 0 and f3 == 1 and f4 == 1:
                        gesture = "LEFT"
                        print("手势为:", gesture)
                    else:
                        gesture = None
                        print("手势为:", gesture)
                    return gesture
                    if gesture:
                        cv2.putText(img, gesture, (x_1, y_1), cv2.FONT_HERSHEY_PLAIN, 3, (0, 0, 255), 3)
                        gesture_buffer.insert(0, gesture)
                        gesture_buffer.pop()
                    return gesture
                cv2.imshow("camera", img)
            if cv2.getWindowProperty('camera', cv2.WND_PROP_VISIBLE) < 1:
                break
            # 通过关闭按钮退出程序
            cv2.waitKey(1)
            # if cv2.waitKey(1) & 0xFF == ord("q"):
            #     break # 按下q退出


def main():
    snake = Snake()
    food = Food()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        # 更新方向(这里应由手势控制)
        new_direction = get_new_direction()
        if new_direction is not None and new_direction != (snake.direction + 2) % 4:
            snake.direction = new_direction

        snake.move()
        if snake.body[0] == food.position:
            food.position = (random.randint(0, (SCREEN_WIDTH - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE,
                             random.randint(0, (SCREEN_HEIGHT - FOOD_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)
            snake.body.append(snake.body[-1])

        screen.fill(WHITE)
        snake.draw(screen)
        food.draw(screen)
        pygame.display.flip()
        clock.tick(10)


def get_new_direction():
    MAIN = Main()
    MAIN.Gesture_recognition()
    gesture = MAIN.Gesture_recognition()
    if gesture == "UP":
        return DIRECTIONS['UP']
    elif gesture == "DOWN":
        return DIRECTIONS['DOWN']
    elif gesture == "LEFT":
        return DIRECTIONS['LEFT']
    elif gesture == "RIGHT":
        return DIRECTIONS['RIGHT']
    else:
        return None


if __name__ == "__main__":
    main()

总结

以上就是今天我开发的手势大逃“蛇”游戏。本文通过详细的示例介绍了如何使用Python结合OpenCVmediapipe库来开发一个基于手势控制的贪吃蛇游戏。虽然这里提供的只是一个基础版本,但是通过进一步的开发,您可以逐步完善这个项目,实现更复杂的功能。

赋几张手势图和游戏画面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值