溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

發(fā)布時(shí)間:2022-03-11 11:44:38 來(lái)源:億速云 閱讀:213 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

介紹

規(guī)則:食指指尖控制蛇頭,指尖每接觸到黃色方塊,計(jì)數(shù)加一,蛇身變長(zhǎng),方塊隨機(jī)切換位置。如果指尖停止移動(dòng),或者移動(dòng)過(guò)程中蛇頭撞到蛇身,那么游戲結(jié)束。點(diǎn)擊鍵盤上的R鍵重新開始游戲。

游戲進(jìn)行時(shí):

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

游戲結(jié)束界面:

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

1. 安裝工具包

pip install opencv_python==4.2.0.34  # 安裝opencv
pip install mediapipe  # 安裝mediapipe
# pip install mediapipe --user  #有user報(bào)錯(cuò)的話試試這個(gè)
pip install cvzone  # 安裝cvzone
 
# 導(dǎo)入工具包
import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 導(dǎo)入手部檢測(cè)模塊
import math
import random

21個(gè)手部關(guān)鍵點(diǎn)信息如下,本節(jié)我們主要研究食指指尖'8'的坐標(biāo)(x,y)信息。

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

2. 檢測(cè)手部關(guān)鍵點(diǎn)

(1)cvzone.HandTrackingModule.HandDetector()是手部關(guān)鍵點(diǎn)檢測(cè)方法

參數(shù):

mode: 默認(rèn)為 False,將輸入圖像視為視頻流。它將嘗試在第一個(gè)輸入圖像中檢測(cè)手,并在成功檢測(cè)后進(jìn)一步定位手的坐標(biāo)。在隨后的圖像中,一旦檢測(cè)到所有 maxHands 手并定位了相應(yīng)的手的坐標(biāo),它就會(huì)跟蹤這些坐標(biāo),而不會(huì)調(diào)用另一個(gè)檢測(cè),直到它失去對(duì)任何一只手的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設(shè)置為 True,則在每個(gè)輸入圖像上運(yùn)行手部檢測(cè),用于處理一批靜態(tài)的、可能不相關(guān)的圖像。

maxHands: 最多檢測(cè)幾只手,默認(rèn)為 2

detectionCon: 手部檢測(cè)模型的最小置信值(0-1之間),超過(guò)閾值則檢測(cè)成功。默認(rèn)為 0.5

minTrackingCon: 坐標(biāo)跟蹤模型的最小置信值 (0-1之間),用于將手部坐標(biāo)視為成功跟蹤,不成功則在下一個(gè)輸入圖像上自動(dòng)調(diào)用手部檢測(cè)。將其設(shè)置為更高的值可以提高解決方案的穩(wěn)健性,但代價(jià)是更高的延遲。如果 mode 為 True,則忽略這個(gè)參數(shù),手部檢測(cè)將在每個(gè)圖像上運(yùn)行。默認(rèn)為 0.5

它的參數(shù)和返回值類似于官方函數(shù) mediapipe.solutions.hands.Hands()

MULTI_HAND_LANDMARKS: 被檢測(cè)/跟蹤的手的集合,其中每只手被表示為21個(gè)手部地標(biāo)的列表,每個(gè)地標(biāo)由x, y, z組成。x和y分別由圖像的寬度和高度歸一化為[0,1]。Z表示地標(biāo)深度。

MULTI_HANDEDNESS: 被檢測(cè)/追蹤的手是左手還是右手的集合。每只手由label(標(biāo)簽)和score(分?jǐn)?shù))組成。 label 是 'Left' 或 'Right' 值的字符串。 score 是預(yù)測(cè)左右手的估計(jì)概率。

(2)cvzone.HandTrackingModule.HandDetector.findHands()找到手部關(guān)鍵點(diǎn)并繪圖

參數(shù):

img: 需要檢測(cè)關(guān)鍵點(diǎn)的幀圖像,格式為BGR

draw: 是否需要在原圖像上繪制關(guān)鍵點(diǎn)及識(shí)別框

flipType: 圖像是否需要翻轉(zhuǎn),當(dāng)視頻圖像和我們自己不是鏡像關(guān)系時(shí),設(shè)為True就可以了

返回值:

hands: 檢測(cè)到的手部信息,由0或1或2個(gè)字典組成的列表。如果檢測(cè)到兩只手就是由兩個(gè)字典組成的列表。字典中包含:21個(gè)關(guān)鍵點(diǎn)坐標(biāo)(x,y,z),檢測(cè)框左上坐標(biāo)及其寬高,檢測(cè)框中心點(diǎn)坐標(biāo),檢測(cè)出是哪一只手。

img: 返回繪制了關(guān)鍵點(diǎn)及連線后的圖像

代碼如下:

import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 導(dǎo)入手部檢測(cè)模塊
 
#(1)獲取攝像頭
cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭
# 設(shè)置顯示窗口的size
cap.set(3, 1280)  # 窗口寬1280
cap.set(4, 720)   # 窗口高720
 
#(2)模型配置
detector = HandDetector(maxHands=1,  # 最多檢測(cè)1只手
                        detectionCon=0.8)  # 最小檢測(cè)置信度0.8
 
#(3)圖像處理
while True:
 
    # 每次讀取一幀相機(jī)圖像,返回是否讀取成功success,讀取的幀圖像img
    success, img = cap.read()
 
    # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系
    img = cv2.flip(img, 1)  # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn)
 
    # 檢測(cè)手部關(guān)鍵點(diǎn)。返回手部信息hands,繪制關(guān)鍵點(diǎn)后的圖像img
    hands, img = detector.findHands(img, flipType=False)  # 由于上一行翻轉(zhuǎn)過(guò)圖像了,這里就不用翻轉(zhuǎn)了
 
    # 查看關(guān)鍵點(diǎn)信息
    print(hands)
 
    #(4)關(guān)鍵點(diǎn)處理
    if hands:  # 如果檢測(cè)到手了,那就處理關(guān)鍵點(diǎn)
 
        # 獲得食指指尖坐標(biāo)(x,y)
        hand = hands[0]  # 獲取一只手的全部信息
        lmList = hand['lmList']  # 獲得這只手的21個(gè)關(guān)鍵點(diǎn)的坐標(biāo)(x,y,z)
        pointIndex = lmList[8][0:2]  # 只獲取食指指尖關(guān)鍵點(diǎ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()

效果圖如下:

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

打印手部關(guān)鍵點(diǎ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. 蛇身移動(dòng)

構(gòu)造一個(gè)處理蛇身移動(dòng)的類,要求在沒吃食物時(shí),蛇身保持固定的長(zhǎng)度跟隨食指指尖移動(dòng)。

舉個(gè)例子,如果當(dāng)前的蛇身節(jié)點(diǎn)列表 self.points 包含 [a, b, c, d] 這四個(gè)節(jié)點(diǎn),a節(jié)點(diǎn)代表蛇尾,d節(jié)點(diǎn)代表蛇頭。在下一幀,食指指尖移動(dòng)到 e 點(diǎn),將 e 節(jié)點(diǎn)追加到蛇身節(jié)點(diǎn)列表中,那么現(xiàn)在的列表包含 [a, b, c, d, e] 節(jié)點(diǎn),其中 e 節(jié)點(diǎn)為新的蛇頭。

此時(shí)判斷當(dāng)前蛇身總長(zhǎng)度 self.currentLength(列表中所有節(jié)點(diǎn)之間的長(zhǎng)度之和)是否大于蛇身固定長(zhǎng)度 self.allowedLength,保證在移動(dòng)過(guò)程中蛇身長(zhǎng)度不變。

如果當(dāng)前蛇身總長(zhǎng)度 self.currentLength 大于固定長(zhǎng)度 self.allowedLength,那么在節(jié)點(diǎn)列表中從尾到頭依次刪除節(jié)點(diǎn),列表 [a, b, c, d, e] 中 a 表示蛇尾節(jié)點(diǎn),先刪除,判斷列表 [b, c, d, e] 的節(jié)點(diǎn)之間的總長(zhǎng)度是否滿足要求。若仍大于固定長(zhǎng)度,那么就再刪除 b 節(jié)點(diǎn),再判斷。

如果當(dāng)前蛇身總長(zhǎng)度 self.currentLength 小于固定長(zhǎng)度 self.allowedLength,那么就不做任何處理。

在上述代碼中補(bǔ)充:

import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 導(dǎo)入手部檢測(cè)模塊
import math
 
# 構(gòu)造一個(gè)貪吃蛇移動(dòng)的類
class SnakeGameClass:
 
    #(一)初始化
    def __init__(self):
 
        self.points = []  # 蛇的身體的節(jié)點(diǎn)坐標(biāo)
        self.lengths = []  # 蛇身各個(gè)節(jié)點(diǎn)之間的坐標(biāo)
        self.currentLength = 0  # 當(dāng)前蛇身長(zhǎng)度
        self.allowedLength = 150  # 沒吃東西時(shí),蛇的總長(zhǎng)度
        self.previousHead = (0,0)  # 前一個(gè)蛇頭節(jié)點(diǎn)的坐標(biāo)
 
    #(二)更新增加蛇身長(zhǎng)度
    def update(self, imgMain, currentHead): # 輸入圖像,當(dāng)前蛇頭的坐標(biāo)
 
        px, py = self.previousHead  # 獲得前一個(gè)蛇頭的x和y坐標(biāo)
        cx, cy = currentHead  # 當(dāng)前蛇頭節(jié)點(diǎn)的x和y坐標(biāo)
        
        # 添加當(dāng)前蛇頭的坐標(biāo)到蛇身節(jié)點(diǎn)坐標(biāo)列表中
        self.points.append([cx,cy])
 
        # 計(jì)算兩個(gè)節(jié)點(diǎn)之間的距離
        distance = math.hypot(cx-px, cy-py)  # 計(jì)算平方和開根
        # 將節(jié)點(diǎn)之間的距離添加到蛇身節(jié)點(diǎn)距離列表中
        self.lengths.append(distance)
        # 增加當(dāng)前蛇身長(zhǎng)度
        self.currentLength += distance
 
        # 更新蛇頭坐標(biāo)
        self.previousHead = (cx,cy)
 
        #(三)減少蛇尾長(zhǎng)度,即移動(dòng)過(guò)程中蛇頭到蛇尾的長(zhǎng)度不大于150
        if self.currentLength > self.allowedLength:
 
            # 遍歷所有的節(jié)點(diǎn)線段長(zhǎng)度。新更新的蛇頭索引在列表后面,蛇尾的索引在列表前面
            for i, length in enumerate(self.lengths):
 
                # 從蛇尾到蛇頭依次減線段長(zhǎng)度,得到的長(zhǎng)度是否滿足要求
                self.currentLength -= length
 
                # 從列表中刪除蛇尾端的線段長(zhǎng)度,以及蛇尾節(jié)點(diǎn)
                self.lengths.pop(i)
                self.points.pop(i)
 
                # 如果當(dāng)前蛇身長(zhǎng)度小于規(guī)定長(zhǎng)度,滿足要求,退出循環(huán)
                if self.currentLength < self.allowedLength:
                    break
 
        #(四)繪制蛇
        # 當(dāng)節(jié)點(diǎn)列表中有值了,才能繪制
        if self.points:
 
            # 遍歷蛇身節(jié)點(diǎn)坐標(biāo)
            for i, point in enumerate(self.points):  
                # 繪制前后兩個(gè)節(jié)點(diǎn)之間的連線
                if i != 0:
                    cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20)
 
            # 在蛇頭的位置畫個(gè)圓
            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,  # 最多檢測(cè)1只手
                        detectionCon=0.8)  # 最小檢測(cè)置信度0.8
 
# 接收創(chuàng)建貪吃蛇的類
game = SnakeGameClass()
 
#(3)圖像處理
while True:
 
    # 每次讀取一幀相機(jī)圖像,返回是否讀取成功success,讀取的幀圖像img
    success, img = cap.read()
 
    # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系
    img = cv2.flip(img, 1)  # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn)
 
    # 檢測(cè)手部關(guān)鍵點(diǎn)。返回手部信息hands,繪制關(guān)鍵點(diǎn)后的圖像img
    hands, img = detector.findHands(img, flipType=False)  # 由于上一行翻轉(zhuǎn)過(guò)圖像了,這里就不用翻轉(zhuǎn)了
 
    # 查看關(guān)鍵點(diǎn)信息
    print(hands)
 
    #(4)關(guān)鍵點(diǎn)處理
    if hands:  # 如果檢測(cè)到手了,那就處理關(guān)鍵點(diǎn)
 
        # 獲得食指指尖坐標(biāo)(x,y)
        hand = hands[0]  # 獲取一只手的全部信息
        lmList = hand['lmList']  # 獲得這只手的21個(gè)關(guān)鍵點(diǎn)的坐標(biāo)(x,y,z)
        pointIndex = lmList[8][0:2]  # 只獲取食指指尖關(guān)鍵點(diǎn)的(x,y)坐標(biāo)
 
        # 更新貪吃蛇的節(jié)點(diǎn),給出蛇頭節(jié)點(diǎn)坐標(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)固定長(zhǎng)度隨著指尖而移動(dòng)。

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

4. 蛇進(jìn)食增加身體長(zhǎng)度

先看下面代碼 SnakeGameClass 類中的第(五)步。給食物(即繪制的矩形)隨機(jī)給出一個(gè)中心點(diǎn)坐標(biāo),自定義的類方法 randomFoodLocation(),執(zhí)行該方法則食物的中心點(diǎn)坐標(biāo)的x在[100,1000]中隨機(jī)取一個(gè)數(shù),y在[100,600]中隨機(jī)取一個(gè)數(shù)。

下面代碼定義的類中的第(七)步。判斷食指指尖(即蛇頭節(jié)點(diǎn)坐標(biāo))是否在矩形內(nèi)部,如果在內(nèi)部,那么蛇身移動(dòng)過(guò)程中的固定長(zhǎng)度 self.allowedLength 增加50個(gè)像素值。得分 self.score 加一。并在下一幀隨機(jī)改變食物的位置。

在上述代碼中補(bǔ)充:

import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 導(dǎo)入手部檢測(cè)模塊
import math
import random
 
# 構(gòu)造一個(gè)貪吃蛇移動(dòng)的類
class SnakeGameClass:
 
    #(一)初始化
    def __init__(self):
 
        self.score = 0  # 積分器
        self.points = []  # 蛇的身體的節(jié)點(diǎn)坐標(biāo)
        self.lengths = []  # 蛇身各個(gè)節(jié)點(diǎn)之間的坐標(biāo)
        self.currentLength = 0  # 當(dāng)前蛇身長(zhǎng)度
        self.allowedLength = 150  # 沒吃東西時(shí),蛇的總長(zhǎng)度
        self.previousHead = (0,0)  # 前一個(gè)蛇頭節(jié)點(diǎn)的坐標(biāo)
 
        self.foodPoint = (0,0)  # 食物的起始位置
        self.randomFoodLocation()  # 隨機(jī)改變食物的位置
 
    #(五)食物隨機(jī)出現(xiàn)的位置
    def randomFoodLocation(self):
        # x在100至1000之間,y在100至600之間,隨機(jī)取一個(gè)整數(shù)
        self.foodPoint = random.randint(100, 1000),  random.randint(100, 600)
 
    #(二)更新增加蛇身長(zhǎng)度
    def update(self, imgMain, currentHead): # 輸入圖像,當(dāng)前蛇頭的坐標(biāo)
 
        px, py = self.previousHead  # 獲得前一個(gè)蛇頭的x和y坐標(biāo)
        cx, cy = currentHead  # 當(dāng)前蛇頭節(jié)點(diǎn)的x和y坐標(biāo)
        
        # 添加當(dāng)前蛇頭的坐標(biāo)到蛇身節(jié)點(diǎn)坐標(biāo)列表中
        self.points.append([cx,cy])
 
        # 計(jì)算兩個(gè)節(jié)點(diǎn)之間的距離
        distance = math.hypot(cx-px, cy-py)  # 計(jì)算平方和開根
        # 將節(jié)點(diǎn)之間的距離添加到蛇身節(jié)點(diǎn)距離列表中
        self.lengths.append(distance)
        # 增加當(dāng)前蛇身長(zhǎng)度
        self.currentLength += distance
 
        # 更新蛇頭坐標(biāo)
        self.previousHead = (cx,cy)
 
        #(三)減少蛇尾長(zhǎng)度,即移動(dòng)過(guò)程中蛇頭到蛇尾的長(zhǎng)度不大于150
        if self.currentLength > self.allowedLength:
 
            # 遍歷所有的節(jié)點(diǎn)線段長(zhǎng)度。新更新的蛇頭索引在列表后面,蛇尾的索引在列表前面
            for i, length in enumerate(self.lengths):
 
                # 從蛇尾到蛇頭依次減線段長(zhǎng)度,得到的長(zhǎng)度是否滿足要求
                self.currentLength -= length
 
                # 從列表中刪除蛇尾端的線段長(zhǎng)度,以及蛇尾節(jié)點(diǎn)
                self.lengths.pop(i)
                self.points.pop(i)
 
                # 如果當(dāng)前蛇身長(zhǎng)度小于規(guī)定長(zhǎng)度,滿足要求,退出循環(huán)
                if self.currentLength < self.allowedLength:
                    break
        
        #(七)檢查蛇是否吃了食物
        rx, ry = self.foodPoint  # 得到食物的中心點(diǎn)坐標(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:
 
            # 隨機(jī)更換食物的位置
            self.randomFoodLocation()
 
            # 增加蛇身的限制長(zhǎng)度,每吃1個(gè)食物就能變長(zhǎng)50
            self.allowedLength += 50
 
            # 吃食物的計(jì)數(shù)加一
            self.score += 1
 
            print('eat!', f'score:{self.score}')
 
        #(四)繪制蛇
        # 當(dāng)節(jié)點(diǎn)列表中有值了,才能繪制
        if self.points:
 
            # 遍歷蛇身節(jié)點(diǎn)坐標(biāo)
            for i, point in enumerate(self.points):  
                # 繪制前后兩個(gè)節(jié)點(diǎn)之間的連線
                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)
 
            # 在蛇頭的位置畫個(gè)圓
            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,  # 最多檢測(cè)1只手
                        detectionCon=0.8)  # 最小檢測(cè)置信度0.8
 
# 接收創(chuàng)建貪吃蛇的類
game = SnakeGameClass()
 
#(3)圖像處理
while True:
 
    # 每次讀取一幀相機(jī)圖像,返回是否讀取成功success,讀取的幀圖像img
    success, img = cap.read()
 
    # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系
    img = cv2.flip(img, 1)  # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn)
 
    # 檢測(cè)手部關(guān)鍵點(diǎn)。返回手部信息hands,繪制關(guān)鍵點(diǎn)后的圖像img
    hands, img = detector.findHands(img, flipType=False)  # 由于上一行翻轉(zhuǎn)過(guò)圖像了,這里就不用翻轉(zhuǎn)了
 
    #(4)關(guān)鍵點(diǎn)處理
    if hands:  # 如果檢測(cè)到手了,那就處理關(guān)鍵點(diǎn)
 
        # 獲得食指指尖坐標(biāo)(x,y)
        hand = hands[0]  # 獲取一只手的全部信息
        lmList = hand['lmList']  # 獲得這只手的21個(gè)關(guān)鍵點(diǎn)的坐標(biāo)(x,y,z)
        pointIndex = lmList[8][0:2]  # 只獲取食指指尖關(guān)鍵點(diǎn)的(x,y)坐標(biāo)
 
        # 更新貪吃蛇的節(jié)點(diǎn),給出蛇頭節(jié)點(diǎn)坐標(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()

效果圖如下:

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

5. 自身碰撞及界面的處理

先看到自定義類 SnakeGameClass 中的第(八)步,蛇身節(jié)點(diǎn)列表 self.points 中包含從蛇頭到蛇尾的所有節(jié)點(diǎn)。如 [a, b, c, d, e] 節(jié)點(diǎn),a 節(jié)點(diǎn)代表蛇尾,e 節(jié)點(diǎn)代表蛇頭。這里我就粗糙地判斷一下是否碰撞,如果大家有更好的判斷方法可以改動(dòng)這第(八)步。計(jì)算蛇頭 e 節(jié)點(diǎn)到所有節(jié)點(diǎn)之間的距離,如果小于某個(gè)值就代表碰撞了,游戲結(jié)束 self.gameover = True

再看到自定義類中的第(三)步。如果游戲結(jié)束 self.gameover = True,那就在下一幀中繪制結(jié)算界面,不再執(zhí)行蛇身移動(dòng)程序。

再看到主程序中的第(5)步。其中 k == ord('r'),按下鍵盤上的 r 鍵來(lái)重新游戲。將所有蛇身變量初始化,并將 self.gameover = False,退出結(jié)算界面,使得下一幀能執(zhí)行蛇身移動(dòng)操作。

在上述代碼中補(bǔ)充:

import cv2
import cvzone
from matplotlib.cbook import pts_to_midstep
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 導(dǎo)入手部檢測(cè)模塊
import math
import random
 
# 構(gòu)造一個(gè)貪吃蛇移動(dòng)的類
class SnakeGameClass:
 
    #(一)初始化
    def __init__(self):
 
        self.score = 0  # 積分器
        self.points = []  # 蛇的身體的節(jié)點(diǎn)坐標(biāo)
        self.lengths = []  # 蛇身各個(gè)節(jié)點(diǎn)之間的坐標(biāo)
        self.currentLength = 0  # 當(dāng)前蛇身長(zhǎng)度
        self.allowedLength = 150  # 沒吃東西時(shí),蛇的總長(zhǎng)度
        self.previousHead = (0,0)  # 前一個(gè)蛇頭節(jié)點(diǎn)的坐標(biāo)
 
        self.foodPoint = (0,0)  # 食物的起始位置
        self.randomFoodLocation()  # 隨機(jī)改變食物的位置
 
        self.gameover = False  # 蛇頭撞到蛇身,變成True,游戲結(jié)束
 
    #(二)食物隨機(jī)出現(xiàn)的位置
    def randomFoodLocation(self):
        # x在100至1000之間,y在100至600之間,隨機(jī)取一個(gè)整數(shù)
        self.foodPoint = random.randint(100, 1000),  random.randint(100, 600)
 
    #(三)更新增加蛇身長(zhǎng)度
    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  # 獲得前一個(gè)蛇頭的x和y坐標(biāo)
            cx, cy = currentHead  # 當(dāng)前蛇頭節(jié)點(diǎn)的x和y坐標(biāo)
            
            # 添加當(dāng)前蛇頭的坐標(biāo)到蛇身節(jié)點(diǎn)坐標(biāo)列表中
            self.points.append([cx,cy])
 
            # 計(jì)算兩個(gè)節(jié)點(diǎn)之間的距離
            distance = math.hypot(cx-px, cy-py)  # 計(jì)算平方和開根
            # 將節(jié)點(diǎn)之間的距離添加到蛇身節(jié)點(diǎn)距離列表中
            self.lengths.append(distance)
            # 增加當(dāng)前蛇身長(zhǎng)度
            self.currentLength += distance
 
            # 更新蛇頭坐標(biāo)
            self.previousHead = (cx,cy)
 
            #(四)減少蛇尾長(zhǎng)度,即移動(dòng)過(guò)程中蛇頭到蛇尾的長(zhǎng)度不大于150
            if self.currentLength > self.allowedLength:
 
                # 遍歷所有的節(jié)點(diǎn)線段長(zhǎng)度。新更新的蛇頭索引在列表后面,蛇尾的索引在列表前面
                for i, length in enumerate(self.lengths):
 
                    # 從蛇尾到蛇頭依次減線段長(zhǎng)度,得到的長(zhǎng)度是否滿足要求
                    self.currentLength -= length
 
                    # 從列表中刪除蛇尾端的線段長(zhǎng)度,以及蛇尾節(jié)點(diǎn)
                    self.lengths.pop(i)
                    self.points.pop(i)
 
                    # 如果當(dāng)前蛇身長(zhǎng)度小于規(guī)定長(zhǎng)度,滿足要求,退出循環(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  # 得到食物的中心點(diǎn)坐標(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:
 
                # 隨機(jī)更換食物的位置
                self.randomFoodLocation()
 
                # 增加蛇身的限制長(zhǎng)度,每吃1個(gè)食物就能變長(zhǎng)50
                self.allowedLength += 50
 
                # 吃食物的計(jì)數(shù)加一
                self.score += 1
 
                print('eat!', f'score:{self.score}')
 
            #(七)繪制蛇
            # 當(dāng)節(jié)點(diǎn)列表中有值了,才能繪制
            if self.points:
 
                # 遍歷蛇身節(jié)點(diǎn)坐標(biāo)
                for i, point in enumerate(self.points):  
                    # 繪制前后兩個(gè)節(jié)點(diǎn)之間的連線
                    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)
 
                # 在蛇頭的位置畫個(gè)圓
                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]:  # 不算蛇頭到自身的距離
 
                # 計(jì)算蛇頭和每個(gè)節(jié)點(diǎn)之間的距離
                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,  # 最多檢測(cè)1只手
                        detectionCon=0.8)  # 最小檢測(cè)置信度0.8
 
# 接收創(chuàng)建貪吃蛇的類
game = SnakeGameClass()
 
#(3)圖像處理
while True:
 
    # 每次讀取一幀相機(jī)圖像,返回是否讀取成功success,讀取的幀圖像img
    success, img = cap.read()
 
    # 圖像翻轉(zhuǎn),使圖像和自己呈鏡像關(guān)系
    img = cv2.flip(img, 1)  # 0代表上下翻轉(zhuǎn),1代表左右翻轉(zhuǎn)
 
    # 檢測(cè)手部關(guān)鍵點(diǎn)。返回手部信息hands,繪制關(guān)鍵點(diǎn)后的圖像img
    hands, img = detector.findHands(img, flipType=False)  # 由于上一行翻轉(zhuǎn)過(guò)圖像了,這里就不用翻轉(zhuǎn)了
 
    #(4)關(guān)鍵點(diǎn)處理
    if hands:  # 如果檢測(cè)到手了,那就處理關(guān)鍵點(diǎn)
 
        # 獲得食指指尖坐標(biāo)(x,y)
        hand = hands[0]  # 獲取一只手的全部信息
        lmList = hand['lmList']  # 獲得這只手的21個(gè)關(guān)鍵點(diǎn)的坐標(biāo)(x,y,z)
        pointIndex = lmList[8][0:2]  # 只獲取食指指尖關(guān)鍵點(diǎn)的(x,y)坐標(biāo)
 
        # 更新貪吃蛇的節(jié)點(diǎn),給出蛇頭節(jié)點(diǎn)坐標(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é)點(diǎn)坐標(biāo)
        game.lengths = []  # 蛇身各個(gè)節(jié)點(diǎn)之間的坐標(biāo)
        game.currentLength = 0  # 當(dāng)前蛇身長(zhǎng)度
        game.allowedLength = 150  # 沒吃東西時(shí),蛇的總長(zhǎng)度
        game.previousHead = (0,0)  # 前一個(gè)蛇頭節(jié)點(diǎn)的坐標(biāo)                
        game.randomFoodLocation()  # 隨機(jī)改變食物的位置
    
    if k & 0xFF == 27:  # 鍵盤ESC鍵退出程序
        break
 
# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()

效果圖如下,在移動(dòng)過(guò)程中,蛇頭每碰到一個(gè)食物,蛇身就會(huì)變長(zhǎng),如果 停止移動(dòng) 或 蛇頭節(jié)點(diǎn)距離蛇身節(jié)點(diǎn)過(guò)近 就會(huì)結(jié)束游戲。

怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲

以上就是“怎么用Python+OpenCV自制AI視覺版貪吃蛇游戲”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI