Python+OpenCV自制AI視覺版貪吃蛇游戲
介紹
各位同學(xué)好,今天和大家分享一下如何使用 mediapipe+opencv 自制貪吃蛇小游戲。先放張圖看效果。
規(guī)則:食指指尖控制蛇頭,指尖每接觸到黃色方塊,計數(shù)加一,蛇身變長,方塊隨機切換位置。如果指尖停止移動,或者移動過程中蛇頭撞到蛇身,那么游戲結(jié)束。點擊鍵盤上的R鍵重新開始游戲。
游戲進行時:
游戲結(jié)束界面:
1. 安裝工具包
pip install opencv_python==4.2.0.34 # 安裝opencv pip install mediapipe # 安裝mediapipe # pip install mediapipe --user #有user報錯的話試試這個 pip install cvzone # 安裝cvzone # 導(dǎo)入工具包 import cv2 import cvzone import numpy as np from cvzone.HandTrackingModule import HandDetector # 導(dǎo)入手部檢測模塊 import math import random
21個手部關(guān)鍵點信息如下,本節(jié)我們主要研究食指指尖'8'的坐標(biāo)(x,y)信息。
2. 檢測手部關(guān)鍵點
(1)cvzone.HandTrackingModule.HandDetector()是手部關(guān)鍵點檢測方法
參數(shù):
mode: 默認(rèn)為 False,將輸入圖像視為視頻流。它將嘗試在第一個輸入圖像中檢測手,并在成功檢測后進一步定位手的坐標(biāo)。在隨后的圖像中,一旦檢測到所有 maxHands 手并定位了相應(yīng)的手的坐標(biāo),它就會跟蹤這些坐標(biāo),而不會調(diào)用另一個檢測,直到它失去對任何一只手的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設(shè)置為 True,則在每個輸入圖像上運行手部檢測,用于處理一批靜態(tài)的、可能不相關(guān)的圖像。
maxHands: 最多檢測幾只手,默認(rèn)為 2
detectionCon: 手部檢測模型的最小置信值(0-1之間),超過閾值則檢測成功。默認(rèn)為 0.5
minTrackingCon: 坐標(biāo)跟蹤模型的最小置信值 (0-1之間),用于將手部坐標(biāo)視為成功跟蹤,不成功則在下一個輸入圖像上自動調(diào)用手部檢測。將其設(shè)置為更高的值可以提高解決方案的穩(wěn)健性,但代價是更高的延遲。如果 mode 為 True,則忽略這個參數(shù),手部檢測將在每個圖像上運行。默認(rèn)為 0.5
它的參數(shù)和返回值類似于官方函數(shù) mediapipe.solutions.hands.Hands()
MULTI_HAND_LANDMARKS: 被檢測/跟蹤的手的集合,其中每只手被表示為21個手部地標(biāo)的列表,每個地標(biāo)由x, y, z組成。x和y分別由圖像的寬度和高度歸一化為[0,1]。Z表示地標(biāo)深度。
MULTI_HANDEDNESS: 被檢測/追蹤的手是左手還是右手的集合。每只手由label(標(biāo)簽)和score(分?jǐn)?shù))組成。 label 是 'Left' 或 'Right' 值的字符串。 score 是預(yù)測左右手的估計概率。
(2)cvzone.HandTrackingModule.HandDetector.findHands()找到手部關(guān)鍵點并繪圖
參數(shù):
img: 需要檢測關(guān)鍵點的幀圖像,格式為BGR
draw: 是否需要在原圖像上繪制關(guān)鍵點及識別框
flipType: 圖像是否需要翻轉(zhuǎn),當(dāng)視頻圖像和我們自己不是鏡像關(guān)系時,設(shè)為True就可以了
返回值:
hands: 檢測到的手部信息,由0或1或2個字典組成的列表。如果檢測到兩只手就是由兩個字典組成的列表。字典中包含:21個關(guān)鍵點坐標(biāo)(x,y,z),檢測框左上坐標(biāo)及其寬高,檢測框中心點坐標(biāo),檢測出是哪一只手。
img: 返回繪制了關(guān)鍵點及連線后的圖像
代碼如下:
import cv2 import cvzone import numpy as np from cvzone.HandTrackingModule import HandDetector # 導(dǎo)入手部檢測模塊 #(1)獲取攝像頭 cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭 # 設(shè)置顯示窗口的size cap.set(3, 1280) # 窗口寬1280 cap.set(4, 720) # 窗口高720 #(2)模型配置 detector = HandDetector(maxHands=1, # 最多檢測1只手 detectionCon=0.8) # 最小檢測置信度0.8 #(3)圖像處理 while True: # 每次讀取一幀相機圖像,返回是否讀取成功success,讀取的幀圖像img success, img = cap.read() # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系 img = cv2.flip(img, 1) # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn) # 檢測手部關(guān)鍵點。返回手部信息hands,繪制關(guān)鍵點后的圖像img hands, img = detector.findHands(img, flipType=False) # 由于上一行翻轉(zhuǎn)過圖像了,這里就不用翻轉(zhuǎn)了 # 查看關(guān)鍵點信息 print(hands) #(4)關(guān)鍵點處理 if hands: # 如果檢測到手了,那就處理關(guān)鍵點 # 獲得食指指尖坐標(biāo)(x,y) hand = hands[0] # 獲取一只手的全部信息 lmList = hand['lmList'] # 獲得這只手的21個關(guān)鍵點的坐標(biāo)(x,y,z) pointIndex = lmList[8][0:2] # 只獲取食指指尖關(guān)鍵點的(x,y)坐標(biāo) # 以食指指尖為圓心畫圈(圓心坐標(biāo)是元組類型),半徑為15,青色填充 cv2.circle(img, tuple(pointIndex), 15, (255,0,0), cv2.FILLED) #(5)顯示圖像 cv2.imshow('img', img) # 輸入圖像顯示窗口的名稱及圖像 # 每幀滯留1毫秒后消失,并且按下ESC鍵退出 if cv2.waitKey(1) & 0xFF == 27: break # 釋放視頻資源 cap.release() cv2.destroyAllWindows()
效果圖如下:
打印手部關(guān)鍵點信息如下:
[{'lmList': [[1152, 675, 0], [1085, 693, -37], [1030, 698, -68], [1003, 698, -97], [1003, 679, -122], [1001, 511, -48], [1041, 546, -81], [1093, 608, -102], [1134, 652, -110], [1075, 484, -46], [1119, 534, -84], [1171, 605, -101], [1217, 659, -103], [1141, 481, -45], [1177, 529, -83], [1219, 590, -84], [1253, 642, -73], [1195, 494, -47], [1221, 521, -73], [1245, 566, -65], [1267, 602, -49]], 'bbox': (1001, 481, 266, 217), 'center': (1134, 589), 'type': 'Right'}]
3. 蛇身移動
構(gòu)造一個處理蛇身移動的類,要求在沒吃食物時,蛇身保持固定的長度跟隨食指指尖移動。
舉個例子,如果當(dāng)前的蛇身節(jié)點列表 self.points 包含 [a, b, c, d] 這四個節(jié)點,a節(jié)點代表蛇尾,d節(jié)點代表蛇頭。在下一幀,食指指尖移動到 e 點,將 e 節(jié)點追加到蛇身節(jié)點列表中,那么現(xiàn)在的列表包含 [a, b, c, d, e] 節(jié)點,其中 e 節(jié)點為新的蛇頭。
此時判斷當(dāng)前蛇身總長度 self.currentLength(列表中所有節(jié)點之間的長度之和)是否大于蛇身固定長度 self.allowedLength,保證在移動過程中蛇身長度不變。
如果當(dāng)前蛇身總長度 self.currentLength 大于固定長度 self.allowedLength,那么在節(jié)點列表中從尾到頭依次刪除節(jié)點,列表 [a, b, c, d, e] 中 a 表示蛇尾節(jié)點,先刪除,判斷列表 [b, c, d, e] 的節(jié)點之間的總長度是否滿足要求。若仍大于固定長度,那么就再刪除 b 節(jié)點,再判斷。
如果當(dāng)前蛇身總長度 self.currentLength 小于固定長度 self.allowedLength,那么就不做任何處理。
在上述代碼中補充:
import cv2 import cvzone import numpy as np from cvzone.HandTrackingModule import HandDetector # 導(dǎo)入手部檢測模塊 import math # 構(gòu)造一個貪吃蛇移動的類 class SnakeGameClass: #(一)初始化 def __init__(self): self.points = [] # 蛇的身體的節(jié)點坐標(biāo) self.lengths = [] # 蛇身各個節(jié)點之間的坐標(biāo) self.currentLength = 0 # 當(dāng)前蛇身長度 self.allowedLength = 150 # 沒吃東西時,蛇的總長度 self.previousHead = (0,0) # 前一個蛇頭節(jié)點的坐標(biāo) #(二)更新增加蛇身長度 def update(self, imgMain, currentHead): # 輸入圖像,當(dāng)前蛇頭的坐標(biāo) px, py = self.previousHead # 獲得前一個蛇頭的x和y坐標(biāo) cx, cy = currentHead # 當(dāng)前蛇頭節(jié)點的x和y坐標(biāo) # 添加當(dāng)前蛇頭的坐標(biāo)到蛇身節(jié)點坐標(biāo)列表中 self.points.append([cx,cy]) # 計算兩個節(jié)點之間的距離 distance = math.hypot(cx-px, cy-py) # 計算平方和開根 # 將節(jié)點之間的距離添加到蛇身節(jié)點距離列表中 self.lengths.append(distance) # 增加當(dāng)前蛇身長度 self.currentLength += distance # 更新蛇頭坐標(biāo) self.previousHead = (cx,cy) #(三)減少蛇尾長度,即移動過程中蛇頭到蛇尾的長度不大于150 if self.currentLength > self.allowedLength: # 遍歷所有的節(jié)點線段長度。新更新的蛇頭索引在列表后面,蛇尾的索引在列表前面 for i, length in enumerate(self.lengths): # 從蛇尾到蛇頭依次減線段長度,得到的長度是否滿足要求 self.currentLength -= length # 從列表中刪除蛇尾端的線段長度,以及蛇尾節(jié)點 self.lengths.pop(i) self.points.pop(i) # 如果當(dāng)前蛇身長度小于規(guī)定長度,滿足要求,退出循環(huán) if self.currentLength < self.allowedLength: break #(四)繪制蛇 # 當(dāng)節(jié)點列表中有值了,才能繪制 if self.points: # 遍歷蛇身節(jié)點坐標(biāo) for i, point in enumerate(self.points): # 繪制前后兩個節(jié)點之間的連線 if i != 0: cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20) # 在蛇頭的位置畫個圓 cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,0,0), cv2.FILLED) # 返回更新后的圖像 return imgMain #(1)獲取攝像頭 cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭 # 設(shè)置顯示窗口的size cap.set(3, 1280) # 窗口寬1280 cap.set(4, 720) # 窗口高720 #(2)模型配置 detector = HandDetector(maxHands=1, # 最多檢測1只手 detectionCon=0.8) # 最小檢測置信度0.8 # 接收創(chuàng)建貪吃蛇的類 game = SnakeGameClass() #(3)圖像處理 while True: # 每次讀取一幀相機圖像,返回是否讀取成功success,讀取的幀圖像img success, img = cap.read() # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系 img = cv2.flip(img, 1) # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn) # 檢測手部關(guān)鍵點。返回手部信息hands,繪制關(guān)鍵點后的圖像img hands, img = detector.findHands(img, flipType=False) # 由于上一行翻轉(zhuǎn)過圖像了,這里就不用翻轉(zhuǎn)了 # 查看關(guān)鍵點信息 print(hands) #(4)關(guān)鍵點處理 if hands: # 如果檢測到手了,那就處理關(guān)鍵點 # 獲得食指指尖坐標(biāo)(x,y) hand = hands[0] # 獲取一只手的全部信息 lmList = hand['lmList'] # 獲得這只手的21個關(guān)鍵點的坐標(biāo)(x,y,z) pointIndex = lmList[8][0:2] # 只獲取食指指尖關(guān)鍵點的(x,y)坐標(biāo) # 更新貪吃蛇的節(jié)點,給出蛇頭節(jié)點坐標(biāo)。返回更新后的圖像 img = game.update(img, pointIndex) #(5)顯示圖像 cv2.imshow('img', img) # 輸入圖像顯示窗口的名稱及圖像 # 每幀滯留1毫秒后消失,并且按下ESC鍵退出 if cv2.waitKey(1) & 0xFF == 27: break # 釋放視頻資源 cap.release() cv2.destroyAllWindows()
效果圖如下,蛇身保持默認(rèn)固定長度隨著指尖而移動。
4. 蛇進食增加身體長度
先看下面代碼 SnakeGameClass 類中的第(五)步。給食物(即繪制的矩形)隨機給出一個中心點坐標(biāo),自定義的類方法 randomFoodLocation(),執(zhí)行該方法則食物的中心點坐標(biāo)的x在[100,1000]中隨機取一個數(shù),y在[100,600]中隨機取一個數(shù)。
下面代碼定義的類中的第(七)步。判斷食指指尖(即蛇頭節(jié)點坐標(biāo))是否在矩形內(nèi)部,如果在內(nèi)部,那么蛇身移動過程中的固定長度 self.allowedLength 增加50個像素值。得分 self.score 加一。并在下一幀隨機改變食物的位置。
在上述代碼中補充:
import cv2 import cvzone import numpy as np from cvzone.HandTrackingModule import HandDetector # 導(dǎo)入手部檢測模塊 import math import random # 構(gòu)造一個貪吃蛇移動的類 class SnakeGameClass: #(一)初始化 def __init__(self): self.score = 0 # 積分器 self.points = [] # 蛇的身體的節(jié)點坐標(biāo) self.lengths = [] # 蛇身各個節(jié)點之間的坐標(biāo) self.currentLength = 0 # 當(dāng)前蛇身長度 self.allowedLength = 150 # 沒吃東西時,蛇的總長度 self.previousHead = (0,0) # 前一個蛇頭節(jié)點的坐標(biāo) self.foodPoint = (0,0) # 食物的起始位置 self.randomFoodLocation() # 隨機改變食物的位置 #(五)食物隨機出現(xiàn)的位置 def randomFoodLocation(self): # x在100至1000之間,y在100至600之間,隨機取一個整數(shù) self.foodPoint = random.randint(100, 1000), random.randint(100, 600) #(二)更新增加蛇身長度 def update(self, imgMain, currentHead): # 輸入圖像,當(dāng)前蛇頭的坐標(biāo) px, py = self.previousHead # 獲得前一個蛇頭的x和y坐標(biāo) cx, cy = currentHead # 當(dāng)前蛇頭節(jié)點的x和y坐標(biāo) # 添加當(dāng)前蛇頭的坐標(biāo)到蛇身節(jié)點坐標(biāo)列表中 self.points.append([cx,cy]) # 計算兩個節(jié)點之間的距離 distance = math.hypot(cx-px, cy-py) # 計算平方和開根 # 將節(jié)點之間的距離添加到蛇身節(jié)點距離列表中 self.lengths.append(distance) # 增加當(dāng)前蛇身長度 self.currentLength += distance # 更新蛇頭坐標(biāo) self.previousHead = (cx,cy) #(三)減少蛇尾長度,即移動過程中蛇頭到蛇尾的長度不大于150 if self.currentLength > self.allowedLength: # 遍歷所有的節(jié)點線段長度。新更新的蛇頭索引在列表后面,蛇尾的索引在列表前面 for i, length in enumerate(self.lengths): # 從蛇尾到蛇頭依次減線段長度,得到的長度是否滿足要求 self.currentLength -= length # 從列表中刪除蛇尾端的線段長度,以及蛇尾節(jié)點 self.lengths.pop(i) self.points.pop(i) # 如果當(dāng)前蛇身長度小于規(guī)定長度,滿足要求,退出循環(huán) if self.currentLength < self.allowedLength: break #(七)檢查蛇是否吃了食物 rx, ry = self.foodPoint # 得到食物的中心點坐標(biāo)位置 # 繪制矩形作為蛇的食物 cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (255,255,0), cv2.FILLED) cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (0,255,255), 5) cv2.rectangle(imgMain, (rx-5, ry-5), (rx+5, ry+5), (0,0,255), cv2.FILLED) # 檢查指尖(即蛇頭cx,cy)是否在矩形內(nèi)部 if rx-20 < cx < rx+20 and ry-20< cy < ry+20: # 隨機更換食物的位置 self.randomFoodLocation() # 增加蛇身的限制長度,每吃1個食物就能變長50 self.allowedLength += 50 # 吃食物的計數(shù)加一 self.score += 1 print('eat!', f'score:{self.score}') #(四)繪制蛇 # 當(dāng)節(jié)點列表中有值了,才能繪制 if self.points: # 遍歷蛇身節(jié)點坐標(biāo) for i, point in enumerate(self.points): # 繪制前后兩個節(jié)點之間的連線 if i != 0: cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20) cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,0,255), 15) # 在蛇頭的位置畫個圓 cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,255,0), cv2.FILLED) cv2.circle(imgMain, tuple(self.points[-1]), 18, (255,0,0), 3) cv2.circle(imgMain, tuple(self.points[-1]), 5, (0,0,0), cv2.FILLED) # 返回更新后的圖像 return imgMain #(1)獲取攝像頭 cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭 # 設(shè)置顯示窗口的size cap.set(3, 1280) # 窗口寬1280 cap.set(4, 720) # 窗口高720 #(2)模型配置 detector = HandDetector(maxHands=1, # 最多檢測1只手 detectionCon=0.8) # 最小檢測置信度0.8 # 接收創(chuàng)建貪吃蛇的類 game = SnakeGameClass() #(3)圖像處理 while True: # 每次讀取一幀相機圖像,返回是否讀取成功success,讀取的幀圖像img success, img = cap.read() # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系 img = cv2.flip(img, 1) # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn) # 檢測手部關(guān)鍵點。返回手部信息hands,繪制關(guān)鍵點后的圖像img hands, img = detector.findHands(img, flipType=False) # 由于上一行翻轉(zhuǎn)過圖像了,這里就不用翻轉(zhuǎn)了 #(4)關(guān)鍵點處理 if hands: # 如果檢測到手了,那就處理關(guān)鍵點 # 獲得食指指尖坐標(biāo)(x,y) hand = hands[0] # 獲取一只手的全部信息 lmList = hand['lmList'] # 獲得這只手的21個關(guān)鍵點的坐標(biāo)(x,y,z) pointIndex = lmList[8][0:2] # 只獲取食指指尖關(guān)鍵點的(x,y)坐標(biāo) # 更新貪吃蛇的節(jié)點,給出蛇頭節(jié)點坐標(biāo)。返回更新后的圖像 img = game.update(img, pointIndex) #(5)顯示圖像 cv2.imshow('img', img) # 輸入圖像顯示窗口的名稱及圖像 # 每幀滯留1毫秒后消失,并且按下ESC鍵退出 if cv2.waitKey(1) & 0xFF == 27: break # 釋放視頻資源 cap.release() cv2.destroyAllWindows()
效果圖如下:
5. 自身碰撞及界面的處理
先看到自定義類 SnakeGameClass 中的第(八)步,蛇身節(jié)點列表 self.points 中包含從蛇頭到蛇尾的所有節(jié)點。如 [a, b, c, d, e] 節(jié)點,a 節(jié)點代表蛇尾,e 節(jié)點代表蛇頭。這里我就粗糙地判斷一下是否碰撞,如果大家有更好的判斷方法可以改動這第(八)步。計算蛇頭 e 節(jié)點到所有節(jié)點之間的距離,如果小于某個值就代表碰撞了,游戲結(jié)束 self.gameover = True
再看到自定義類中的第(三)步。如果游戲結(jié)束 self.gameover = True,那就在下一幀中繪制結(jié)算界面,不再執(zhí)行蛇身移動程序。
再看到主程序中的第(5)步。其中 k == ord('r'),按下鍵盤上的 r 鍵來重新游戲。將所有蛇身變量初始化,并將 self.gameover = False,退出結(jié)算界面,使得下一幀能執(zhí)行蛇身移動操作。
在上述代碼中補充:
import cv2 import cvzone from matplotlib.cbook import pts_to_midstep import numpy as np from cvzone.HandTrackingModule import HandDetector # 導(dǎo)入手部檢測模塊 import math import random # 構(gòu)造一個貪吃蛇移動的類 class SnakeGameClass: #(一)初始化 def __init__(self): self.score = 0 # 積分器 self.points = [] # 蛇的身體的節(jié)點坐標(biāo) self.lengths = [] # 蛇身各個節(jié)點之間的坐標(biāo) self.currentLength = 0 # 當(dāng)前蛇身長度 self.allowedLength = 150 # 沒吃東西時,蛇的總長度 self.previousHead = (0,0) # 前一個蛇頭節(jié)點的坐標(biāo) self.foodPoint = (0,0) # 食物的起始位置 self.randomFoodLocation() # 隨機改變食物的位置 self.gameover = False # 蛇頭撞到蛇身,變成True,游戲結(jié)束 #(二)食物隨機出現(xiàn)的位置 def randomFoodLocation(self): # x在100至1000之間,y在100至600之間,隨機取一個整數(shù) self.foodPoint = random.randint(100, 1000), random.randint(100, 600) #(三)更新增加蛇身長度 def update(self, imgMain, currentHead): # 輸入圖像,當(dāng)前蛇頭的坐標(biāo) # 游戲結(jié)束,顯示文本 if self.gameover: cvzone.putTextRect(imgMain, 'GameOver', [400,300], 5, 3, colorR=(0,255,255), colorT=(0,0,255)) cvzone.putTextRect(imgMain, f'Score:{self.score}', [450,400], 5, 3, colorR=(255,255,0)) cvzone.putTextRect(imgMain, f"Press Key 'R' to Restart", [230,500], 4, 3, colorR=(0,255,0), colorT=(255,0,0)) else: px, py = self.previousHead # 獲得前一個蛇頭的x和y坐標(biāo) cx, cy = currentHead # 當(dāng)前蛇頭節(jié)點的x和y坐標(biāo) # 添加當(dāng)前蛇頭的坐標(biāo)到蛇身節(jié)點坐標(biāo)列表中 self.points.append([cx,cy]) # 計算兩個節(jié)點之間的距離 distance = math.hypot(cx-px, cy-py) # 計算平方和開根 # 將節(jié)點之間的距離添加到蛇身節(jié)點距離列表中 self.lengths.append(distance) # 增加當(dāng)前蛇身長度 self.currentLength += distance # 更新蛇頭坐標(biāo) self.previousHead = (cx,cy) #(四)減少蛇尾長度,即移動過程中蛇頭到蛇尾的長度不大于150 if self.currentLength > self.allowedLength: # 遍歷所有的節(jié)點線段長度。新更新的蛇頭索引在列表后面,蛇尾的索引在列表前面 for i, length in enumerate(self.lengths): # 從蛇尾到蛇頭依次減線段長度,得到的長度是否滿足要求 self.currentLength -= length # 從列表中刪除蛇尾端的線段長度,以及蛇尾節(jié)點 self.lengths.pop(i) self.points.pop(i) # 如果當(dāng)前蛇身長度小于規(guī)定長度,滿足要求,退出循環(huán) if self.currentLength < self.allowedLength: break #(五)繪制得分板 cvzone.putTextRect(imgMain, f'Score:{self.score}', [50,80], 4, 3, colorR=(255,255,0)) #(六)檢查蛇是否吃了食物 rx, ry = self.foodPoint # 得到食物的中心點坐標(biāo)位置 # 繪制矩形作為蛇的食物 cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (255,255,0), cv2.FILLED) cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (0,255,255), 5) cv2.rectangle(imgMain, (rx-5, ry-5), (rx+5, ry+5), (0,0,255), cv2.FILLED) # 檢查指尖(即蛇頭cx,cy)是否在矩形內(nèi)部 if rx-20 < cx < rx+20 and ry-20< cy < ry+20: # 隨機更換食物的位置 self.randomFoodLocation() # 增加蛇身的限制長度,每吃1個食物就能變長50 self.allowedLength += 50 # 吃食物的計數(shù)加一 self.score += 1 print('eat!', f'score:{self.score}') #(七)繪制蛇 # 當(dāng)節(jié)點列表中有值了,才能繪制 if self.points: # 遍歷蛇身節(jié)點坐標(biāo) for i, point in enumerate(self.points): # 繪制前后兩個節(jié)點之間的連線 if i != 0: cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20) cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,0,255), 15) # 在蛇頭的位置畫個圓 cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,255,0), cv2.FILLED) cv2.circle(imgMain, tuple(self.points[-1]), 18, (255,0,0), 3) cv2.circle(imgMain, tuple(self.points[-1]), 5, (0,0,0), cv2.FILLED) #(八)檢查蛇頭碰撞到自身 for point in self.points[:-2]: # 不算蛇頭到自身的距離 # 計算蛇頭和每個節(jié)點之間的距離 dist = math.hypot(cx-point[0], cy-point[1]) # 如果距離小于1.8,那么就證明碰撞了 if dist < 1.8: # 游戲結(jié)束 self.gameover = True # 返回更新后的圖像 return imgMain #(1)獲取攝像頭 cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭 # 設(shè)置顯示窗口的size cap.set(3, 1280) # 窗口寬1280 cap.set(4, 720) # 窗口高720 #(2)模型配置 detector = HandDetector(maxHands=1, # 最多檢測1只手 detectionCon=0.8) # 最小檢測置信度0.8 # 接收創(chuàng)建貪吃蛇的類 game = SnakeGameClass() #(3)圖像處理 while True: # 每次讀取一幀相機圖像,返回是否讀取成功success,讀取的幀圖像img success, img = cap.read() # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系 img = cv2.flip(img, 1) # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn) # 檢測手部關(guān)鍵點。返回手部信息hands,繪制關(guān)鍵點后的圖像img hands, img = detector.findHands(img, flipType=False) # 由于上一行翻轉(zhuǎn)過圖像了,這里就不用翻轉(zhuǎn)了 #(4)關(guān)鍵點處理 if hands: # 如果檢測到手了,那就處理關(guān)鍵點 # 獲得食指指尖坐標(biāo)(x,y) hand = hands[0] # 獲取一只手的全部信息 lmList = hand['lmList'] # 獲得這只手的21個關(guān)鍵點的坐標(biāo)(x,y,z) pointIndex = lmList[8][0:2] # 只獲取食指指尖關(guān)鍵點的(x,y)坐標(biāo) # 更新貪吃蛇的節(jié)點,給出蛇頭節(jié)點坐標(biāo)。返回更新后的圖像 img = game.update(img, pointIndex) #(5)顯示圖像 cv2.imshow('img', img) # 輸入圖像顯示窗口的名稱及圖像 # 重新開始游戲 k = cv2.waitKey(1) # 每幀滯留1毫秒后消失 if k == ord('r'): # 鍵盤'r'鍵代表重新開始游戲 game.gameover = False game.score = 0 # 積分器 game.points = [] # 蛇的身體的節(jié)點坐標(biāo) game.lengths = [] # 蛇身各個節(jié)點之間的坐標(biāo) game.currentLength = 0 # 當(dāng)前蛇身長度 game.allowedLength = 150 # 沒吃東西時,蛇的總長度 game.previousHead = (0,0) # 前一個蛇頭節(jié)點的坐標(biāo) game.randomFoodLocation() # 隨機改變食物的位置 if k & 0xFF == 27: # 鍵盤ESC鍵退出程序 break # 釋放視頻資源 cap.release() cv2.destroyAllWindows()
效果圖如下,在移動過程中,蛇頭每碰到一個食物,蛇身就會變長,如果 停止移動 或 蛇頭節(jié)點距離蛇身節(jié)點過近 就會結(jié)束游戲。
以上就是Python+OpenCV自制AI視覺版貪吃蛇游戲的詳細內(nèi)容,更多關(guān)于Python OpenCV貪吃蛇的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
pytorch模型訓(xùn)練的時候GPU使用率不高的問題
這篇文章主要介紹了pytorch模型訓(xùn)練的時候GPU使用率不高的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09python按順序重命名文件并分類轉(zhuǎn)移到各個文件夾中的實現(xiàn)代碼
這篇文章主要介紹了python按順序重命名文件并分類轉(zhuǎn)移到各個文件夾中,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07教你用python提取txt文件中的特定信息并寫入Excel
這篇文章主要給大家介紹了如何利用python提取txt文件中的特定信息并寫入Excel的相關(guān)資料,Python是一個強大的語言,解決這點問題非常簡單,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-11-11Python網(wǎng)絡(luò)編程中urllib2模塊的用法總結(jié)
使用urllib2模塊進行基于url的HTTP請求等操作大家也許都比較熟悉,這里我們再深入來了解一下urllib2針對HTTP的異常處理相關(guān)功能,一起來看一下Python網(wǎng)絡(luò)編程中urllib2模塊的用法總結(jié):2016-07-07Python基于Faker假數(shù)據(jù)構(gòu)造庫
這篇文章主要介紹了Python基于Faker假數(shù)據(jù)構(gòu)造庫,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11pycharm中:OSError:[WinError?1455]頁面文件太小無法完成操作問題的多種解決方法
這篇文章主要給大家介紹了關(guān)于pycharm中:OSError:[WinError?1455]頁面文件太小無法完成操作問題的多種徹底解決方法,文中通過圖文介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-02-02對dataframe數(shù)據(jù)之間求補集的實例詳解
今天小編就為大家分享一篇對dataframe數(shù)據(jù)之間求補集的實例詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-01-01