>  기사  >  백엔드 개발  >  Python이 제스처 인식을 사용하여 Snake 게임을 구현하는 방법

Python이 제스처 인식을 사용하여 Snake 게임을 구현하는 방법

WBOY
WBOY앞으로
2023-05-11 20:19:041379검색

    프로젝트 소개

    1. 게임 운영 방법

    스네이크 게임은 누구나 알고 있지만 컴퓨터 비전은 잘 알려지지 않았습니다. 컴퓨터 비전 + 스네이크 게임은 사람들의 참여도와 신선도를 높여줄 것입니다. 간단한 뱀 게임을 완성해보세요. 이 게임에서는 컴퓨터가 카메라를 통해 우리의 동작을 포착하고 움직일지 여부를 결정합니다. 플레이어는 손을 움직여 뱀을 제어하여 화면에 무작위로 나타나는 음식을 얻을 때마다 음식을 1개로 계산합니다. 점수는 1이 추가되어 화면에 표시됩니다. 플레이어가 작동 중에 실수로 뱀의 머리와 몸에 충돌하면 게임오버(GameOver) 버튼을 눌러 게임을 다시 시작하세요.

    Python이 제스처 인식을 사용하여 Snake 게임을 구현하는 방법

    2. 개발 과정에서 주의할 점

    (1) 이미지의 좌우 문제

    뱀의 움직임을 제어하기 위해 제스처를 사용하지만 카메라 사진은 타인의 관점을 보여주기 때문입니다. , 그래서 이것은 플레이어의 왼쪽 및 오른쪽 인식과 정확히 반대이므로 카메라가 읽은 이미지를 왼쪽 및 오른쪽으로 뒤집어야 합니다. 원칙적으로 왼쪽과 오른쪽 픽셀의 위치는 교환되지만 Python에서는 cv2.flip() 함수를 사용하여 거울 뒤집기를 수행할 수 있습니다.

    (2) 카메라 화면 크기 문제
    카메라를 통해 얻은 이미지로 게임을 해야 하기 때문에 화면이 너무 작으면 처음에는 게임 공간이 부족해집니다. 화면 크기를 좀 더 적당하게 설정해 놓으면 게임을 할 때 최종 화면이 좁아 보이지 않습니다. 화면의 너비와 높이는 cap.set(3, m) cap.set(4, n) 함수를 통해 설정할 수 있습니다.

    이 프로젝트에는 충돌 판단, 식량 획득 판단 등 다른 예방 조치도 있을 예정입니다. 이에 대해서는 나중에 프로젝트 과정에서 소개하겠습니다.

    3. 게임 구현의 핵심 포인트

    1. 타사 라이브러리 선택

    사용된 일부 타사 라이브러리:

    import math
    import random
    import cvzone
    import cv2
    import numpy as np
    from cvzone.HandTrackingModule import HandDetector

    이 프로젝트에서는 주로 위의 라이브러리를 사용하며 그중 무작위 라이브러리가 사용됩니다. 음식 도넛을 배치하기 위해 픽셀을 선택하고, cvzone에서 손 인식을 사용하여 플레이어 제스처를 감지하고, cv2를 사용하여 일부 기본 이미지 작업을 수행하며, 다른 라이브러리에도 자체 용도가 있습니다. 이에 대해서는 나중에 하나씩 소개하겠습니다.

    2 핵심 포인트를 찾아 표시하세요

    이 게임에서는 손을 대상 노드로 선택했기 때문에 화면에 나타나는 손을 감지하면 핵심 포인트를 표시해야 하는데 이 키 포인트가 발생합니다. 우리 탐욕스러운 뱀의 머리가 되려면 타사 라이브러리를 호출하고 이 라이브러리는 손을 3D로 표시할 수 있으므로 x와 y의 두 좌표 값만 필요합니다. 손의 핵심 노드를 표시하는 함수:

    #检测到第一个手,并标记手部位置
        if hands:
            lmList = hands[0]['lmList']
            pointIndex = lmList[8][0:2] #第八个坐标点的 x, y值,其中 z 值不被包括在里面
            cv2.circle(img, pointIndex, 20, (200, 0, 200), cv2.FILLED) #在关键点处绘制一个圆点并进行填充(此处只是示范,后面会更改)

    3. 게임의 모든 기능을 저장하는 클래스를 만듭니다.

    우리가 구현해야 할 게임은 이러한 기능을 구현하기 위해 함수를 사용하려는 경우입니다. 함수를 사용하면 매우 번거로울 것입니다. 클래스를 사용하여 완료하면 동일한 클래스에 많은 항목이 저장되므로 난이도가 줄어듭니다. 이 수업에서는 뱀 몸의 모든 점, 뱀의 길이, 뱀의 전체 거리, 음식 배치, 점수 등과 같이 우리가 사용하는 일부 핵심 사항을 저장하기 위해 많은 중요한 목록을 만듭니다. :

    class SnakeGameClass:
        def __init__(self, pathFood):
            self.points = []  #贪吃蛇身上所有点
            self.lengths = []  #点与点之间的距离
            self.currentLength = 0  #当下蛇的长度
            self.allowedLength = 50  #最大允许长度(阈值)
            self.previousHead = 0, 0  #手部关键点之后的第一个点
     
            self.imgFood = cv2.imread(pathFood, cv2.IMREAD_UNCHANGED) #定义食物
            self.hFood, self.wFood, _ = self.imgFood.shape
            self.foodPoint = 0, 0
            self.randomFoodLocation()
     
            self.score = 0
            self.gameOver = False

    4. 지속적으로 업데이트하는 기능 정의

    손이 움직이면 뱀의 길이와 위치가 바뀌기 때문에 변화하는 요구에 맞게 지속적으로 업데이트하는 기능을 만들어야 합니다. 이전에 생성된 빅 클래스):

        def update(self, imgMain, currentHead):
            #游戏结束,打印文本
            if self.gameOver:
                cvzone.putTextRect(imgMain, "Game Over", [300, 400],
                                   scale=7, thickness=5, offset=20)
                cvzone.putTextRect(imgMain, f'Your Score: {self.score}', [300, 550],
                                   scale=7, thickness=5, offset=20)
            else:
                px, py = self.previousHead
                cx, cy = currentHead
     
                self.points.append([cx, cy])
                distance = math.hypot(cx - px, cy - py)
                self.lengths.append(distance)
                self.currentLength += distance
                self.previousHead = cx, cy
     
                #长度缩小
                if self.currentLength > self.allowedLength:
                    for i, length in enumerate(self.lengths):
                        self.currentLength -= length
                        self.lengths.pop(i)
                        self.points.pop(i)
                        if self.currentLength < self.allowedLength:
                            break
     
                #检查贪吃蛇是否已经触碰到食物
                rx, ry = self.foodPoint
                if rx - self.wFood // 2 < cx < rx + self.wFood // 2 and \
                        ry - self.hFood // 2 < cy < ry + self.hFood // 2:
                    self.randomFoodLocation()
                    self.allowedLength += 50
                    self.score += 1
                    print(self.score)
     
                #使用线条绘制贪吃蛇
                if self.points:
                    for i, point in enumerate(self.points):
                        if i != 0:
                            cv2.line(imgMain, self.points[i - 1], self.points[i], (0, 0, 255), 20)
                    cv2.circle(imgMain, self.points[-1], 20, (0, 255, 0), cv2.FILLED)
     
                #显示食物
                imgMain = cvzone.overlayPNG(imgMain, self.imgFood,
                                            (rx - self.wFood // 2, ry - self.hFood // 2))
     
                cvzone.putTextRect(imgMain, f&#39;Score: {self.score}&#39;, [50, 80],
                                   scale=3, thickness=3, offset=10)
     
                #检测是否碰撞
                pts = np.array(self.points[:-2], np.int32)
                pts = pts.reshape((-1, 1, 2))
                cv2.polylines(imgMain, [pts], False, (0, 255, 0), 3)
                minDist = cv2.pointPolygonTest(pts, (cx, cy), True)
     
                if -1 <= minDist <= 1:
                    print("Hit")
                    self.gameOver = True
                    self.points = []  #蛇身上所有的点
                    self.lengths = []  #不同点之间的距离
                    self.currentLength = 0  #当前蛇的长度
                    self.allowedLength = 50  #最大允许长度
                    self.previousHead = 0, 0  #先前的蛇的头部
                    self.randomFoodLocation()
     
            return imgMain

    이 업데이트된 함수에서는 욕심 많은 뱀이 음식에 닿았는지 여부(음식에 닿으면 뱀의 길이를 늘려 점수를 축적해야 함), 현재 길이가 허용된 최대 길이를 초과하는지 여부(최대 길이보다 작으면 현재 길이를 변경할 필요가 없지만, 현재 길이가 최대 길이보다 크면 줄여야 함), 뱀이 있는지 여부 충돌(키 노드 사이의 거리에 따라 뱀이 충돌했는지 여부가 결정됩니다. 충돌이 발생하면 게임 오버 모듈로 들어가고 충돌이 없으면 게임을 계속합니다) 등은 위 코드에서 모두 설명됩니다.

    주로 위에서 정의한 클래스를 통해서 현재의 Snake 게임을 구현할 수 있습니다.

    4. 전체 코드

    스테이션 b에서 튜토리얼을 보고 이 미니 게임에 대해 단계별로 재현해 보았습니다. 물론, 평소와 같이 전체 코드는 아래에 게시됩니다.

    """
    Author:XiaoMa
    CSDN Address:一马归一码
    """
    import math
    import random
    import cvzone
    import cv2
    import numpy as np
    from cvzone.HandTrackingModule import HandDetector
     
    cap = cv2.VideoCapture(0)
     
    #设置画面的尺寸大小,过小的话导致贪吃蛇活动不开
    cap.set(3, 1280)
    cap.set(4, 720)
     
    detector = HandDetector(detectionCon=0.8, maxHands=1)
     
     
    class SnakeGameClass:
        def __init__(self, pathFood):
            self.points = []  #贪吃蛇身上所有点
            self.lengths = []  #每一个点之间的距离
            self.currentLength = 0  #当下蛇的长度
            self.allowedLength = 50  #最大允许长度(阈值)
            self.previousHead = 0, 0  #手部关键点之后的第一个点
     
            self.imgFood = cv2.imread(pathFood, cv2.IMREAD_UNCHANGED) #定义食物
            self.hFood, self.wFood, _ = self.imgFood.shape
            self.foodPoint = 0, 0
            self.randomFoodLocation()
     
            self.score = 0
            self.gameOver = False
     
        def randomFoodLocation(self):
            self.foodPoint = random.randint(100, 1000), random.randint(100, 600)
     
        def update(self, imgMain, currentHead):
            #游戏结束,打印文本
            if self.gameOver:
                cvzone.putTextRect(imgMain, "Game Over", [300, 400],
                                   scale=7, thickness=5, offset=20)
                cvzone.putTextRect(imgMain, f&#39;Your Score: {self.score}&#39;, [300, 550],
                                   scale=7, thickness=5, offset=20)
            else:
                px, py = self.previousHead
                cx, cy = currentHead
     
                self.points.append([cx, cy])
                distance = math.hypot(cx - px, cy - py)
                self.lengths.append(distance)
                self.currentLength += distance
                self.previousHead = cx, cy
     
                #长度缩小
                if self.currentLength > self.allowedLength:
                    for i, length in enumerate(self.lengths):
                        self.currentLength -= length
                        self.lengths.pop(i)
                        self.points.pop(i)
                        if self.currentLength < self.allowedLength:
                            break
     
                #检查贪吃蛇是否已经触碰到食物
                rx, ry = self.foodPoint
                if rx - self.wFood // 2 < cx < rx + self.wFood // 2 and \
                        ry - self.hFood // 2 < cy < ry + self.hFood // 2:
                    self.randomFoodLocation()
                    self.allowedLength += 50
                    self.score += 1
                    print(self.score)
     
                #使用线条绘制贪吃蛇
                if self.points:
                    for i, point in enumerate(self.points):
                        if i != 0:
                            cv2.line(imgMain, self.points[i - 1], self.points[i], (0, 0, 255), 20)
                    cv2.circle(imgMain, self.points[-1], 20, (0, 255, 0), cv2.FILLED)
     
                #显示食物
                imgMain = cvzone.overlayPNG(imgMain, self.imgFood,
                                            (rx - self.wFood // 2, ry - self.hFood // 2))
     
                cvzone.putTextRect(imgMain, f&#39;Score: {self.score}&#39;, [50, 80],
                                   scale=3, thickness=3, offset=10)
     
                #检测是否碰撞
                pts = np.array(self.points[:-2], np.int32)
                pts = pts.reshape((-1, 1, 2))
                cv2.polylines(imgMain, [pts], False, (0, 255, 0), 3)
                minDist = cv2.pointPolygonTest(pts, (cx, cy), True)
     
                if -1 <= minDist <= 1:
                    print("Hit")
                    self.gameOver = True
                    self.points = []  #蛇身上所有的点
                    self.lengths = []  #不同点之间的距离
                    self.currentLength = 0  #当前蛇的长度
                    self.allowedLength = 50  #最大允许长度
                    self.previousHead = 0, 0  #先前的蛇的头部
                    self.randomFoodLocation()
     
            return imgMain
     
     
    game = SnakeGameClass("Donut.png")
     
    while True:
        success, img = cap.read()
        img = cv2.flip(img, 1) #镜像翻转
        hands, img = detector.findHands(img, flipType=False)
        #检测到第一个手,并标记手部位置
        if hands:
            lmList = hands[0]['lmList']
            pointIndex = lmList[8][0:2] #第八个坐标点的 x, y值,其中 z 值不被包括在里面
            #cv2.circle(img, pointIndex, 20, (200, 0, 200), cv2.FILLED) #在关键点处绘制一个圆点并进行填充(此处只是示范,后面会更改)
            img = game.update(img, pointIndex)
     
        cv2.imshow("Image", img)
        key = cv2.waitKey(1)
        #按下‘r'重新开始游戏
        if key == ord('r'):
            game.gameOver = False

    필요한 사항은 사용된 도넛 패턴은 온라인에서 적당한 사이즈 패턴을 찾아 교체하시면 됩니다.

    위 내용은 Python이 제스처 인식을 사용하여 Snake 게임을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제