貪吃蛇遊戲人盡皆知,電腦視覺鮮為人知,電腦視覺貪吃蛇遊戲會帶給人們更多的參與感以及新鮮度,這次這個項目就是主要使用手勢辨識來完成貪吃蛇這個簡單的遊戲。在這個遊戲中,電腦透過攝影機捕捉到我們的手勢並判別是否進行移動,玩家移動手去操縱貪吃蛇得到螢幕中隨機出現的食物,每得到一個食物,就會算作一分,Score 就會加1並顯示在畫面中,當玩家在操作的過程中不小心使得蛇的頭部和身體相撞,那麼就會顯示GameOver! 按下‘r’ 鍵可以重新開始遊戲。
(1) 圖像的左右問題
由於我們是使用手勢來進行控制蛇的移動的,但攝影機的畫面顯示的是別人的視角,所以這和玩家的左右意識剛好是相反的,因此我們要將攝影機讀取到的畫面進行一個左右的翻轉。原理上說就是將左右的像素點位置進行一個調換,但在 Python 中可以使用一個 cv2.flip( ) 函數就可以實現鏡像翻轉了。
(2) 相機的畫面尺寸問題
通
過攝影機得到的影像我們需要在上面進行遊戲,因此畫面過小會導致遊戲空間不足,在最開始可以將畫面的大小進行一個預處理,設定一個較為合理的大小,最後得到的畫面玩遊戲時才不會顯得局促。透過函數 cap.set(3, m) cap.set(4, n) 可以實現對畫面的寬和高的設定。
本專案中還會存在一些其他的注意事項,例如判斷碰撞,判斷獲得食物等,我會在後面的專案過程中再加以介紹。
一些使用到的第三方函式庫:
import math import random import cvzone import cv2 import numpy as np from cvzone.HandTrackingModule import HandDetector
在本次專案中,我們主要使用到以上的幾個庫,其中使用random 庫來隨機選擇像素點來放置食物甜甜圈,使用cvzone 中的手部識別來進行玩家手勢的檢測,使用cv2 來進行一些基礎的圖像操作,其他的一些庫也各有用處,後面一一介紹。
在本次遊戲中我們是選擇了一隻手作為目標節點,所以當我們偵測到畫面中出現手部時需要對其中的關鍵點進行標記,而這個關鍵點恰好是我們的貪吃蛇的頭部,由於我們是調用的第三方庫,而該庫可以對手部進行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) #在关键点处绘制一个圆点并进行填充(此处只是示范,后面会更改)
我們需要實現的遊戲是很多功能結合起來完成的,如果想要使用函數來實現這些功能,那麼將會非常麻煩,當我們使用class 來完成時,由於很多東西都保存在同一個類別中,將會降低難度。在這個class 中我們將會創建很多重要的清單來儲存我們用得到的一些關鍵點,例如貪吃蛇的身上的所有的點、貪吃蛇的長度、蛇的總體距離、食物的放置、得分等:
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 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'Score: {self.score}', [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
在這個更新的函數中,我們需要判斷很多東西,例如貪吃蛇是否觸碰到食物(如果觸碰到食物我們就要增加蛇的長度並累積得分)、當前長度是否超過所允許的最大長度(當前長度小於最大長度就不必要進行更改了,但如果當前長度大於最大長度,則需要進行縮短)、貪吃蛇是否發生碰撞(透過關鍵節點之間的距離判斷貪吃蛇是否發生了碰撞,如果發生了碰撞,則進入gameover 模組,如果沒有,繼續遊戲)等,都解釋在上面的代碼中了。
主要是透過上面定義的 class 我們就能實現目前的貪吃蛇遊戲了。
本次小遊戲我是在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'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'Score: {self.score}', [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怎麼利用手勢辨識實現貪吃蛇遊戲的詳細內容。更多資訊請關注PHP中文網其他相關文章!