【机器视觉案例】(6) AI视觉,距离测量,自制AI小游戏,附python完整代码
各位同學好,今天和大家分享一下如何使用?opencv + mediapipe?創建一個AI視覺小游戲,先放圖看效果。
游戲規則,用手按下屏幕上的圓形按鈕,每按一次后松開,按鈕就隨機出現在屏幕上的一個位置,看規定時間內能準確按下多少次按鈕。根據手和攝像頭之間的距離,當距離小于30cm,并且按鈕在綠框內部,則認為是按下按鈕,按鈕變顏色,松開后,得分加一,并且按鈕隨機出現在另外一個位置。
游戲界面,左上角31代表FPS值,中間Score代表得分,Time代表游戲時間剩余幾秒,31cm代表手和攝像機的之間距離。
結算界面,顯示最終得分score,按下鍵盤上的 r 鍵重新開始游戲。
1. 導入工具包
# 安裝工具包
pip install opencv-contrib-python # 安裝opencv
pip install mediapipe # 安裝mediapipe
# pip install mediapipe --user #有user報錯的話試試這個
pip install cvzone # 安裝cvzone# 導入工具包
import cv2
from cvzone.HandTrackingModule import HandDetector # 手部追蹤方法
import time
import math
import random
21個手部關鍵點信息如下,本節我們主要研究食指根部"5"和小指根部'17'的坐標信息。
本節所用的手部關鍵點檢測的MediaPipe基本方法我參考我之前的文章:https://blog.csdn.net/dgvv4/article/details/122023047?spm=1001.2014.3001.5501,這里直接使用已經定義好的手部關鍵點檢測方法。
2. 檢測手部關鍵點
(1) cvzone.HandTrackingModule.HandDetector() ?是手部關鍵點檢測方法
參數:
mode: 默認為 False,將輸入圖像視為視頻流。它將嘗試在第一個輸入圖像中檢測手,并在成功檢測后進一步定位手的坐標。在隨后的圖像中,一旦檢測到所有 maxHands 手并定位了相應的手的坐標,它就會跟蹤這些坐標,而不會調用另一個檢測,直到它失去對任何一只手的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設置為 True,則在每個輸入圖像上運行手部檢測,用于處理一批靜態的、可能不相關的圖像。
maxHands: 最多檢測幾只手,默認為 2
detectionCon: 手部檢測模型的最小置信值(0-1之間),超過閾值則檢測成功。默認為 0.5
minTrackingCon: 坐標跟蹤模型的最小置信值 (0-1之間),用于將手部坐標視為成功跟蹤,不成功則在下一個輸入圖像上自動調用手部檢測。將其設置為更高的值可以提高解決方案的穩健性,但代價是更高的延遲。如果 mode 為 True,則忽略這個參數,手部檢測將在每個圖像上運行。默認為 0.5
它的參數和返回值類似于官方函數 mediapipe.solutions.hands.Hands()
(2)cvzone.HandTrackingModule.HandDetector.findHands() ? ?找到手部關鍵點并繪圖
參數:
img: 需要檢測關鍵點的幀圖像,格式為BGR
draw: 是否需要在原圖像上繪制關鍵點及識別框
flipType: 圖像是否需要翻轉,當視頻圖像和我們自己不是鏡像關系時,設為True就可以了
返回值:
hands: 檢測到的手部信息,由0或1或2個字典組成的列表。如果檢測到兩只手就是由兩個字典組成的列表。字典中包含:21個關鍵點坐標,檢測框坐標及寬高,檢測框中心坐標,檢測出是哪一只手。
img: 返回繪制了關鍵點及連線后的圖像
代碼如下
import cv2
from cvzone.HandTrackingModule import HandDetector
import time
import math#(1)捕獲攝像頭
cap = cv2.VideoCapture(0) # 捕獲電腦攝像頭
cap.set(3, 1280) # 設置顯示窗口寬度1280
cap.set(4, 720) # 顯示窗口高度720pTime = 0 # 處理第一幀圖像的起始時間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 靜態圖模式,若為True,每一幀都會調用檢測方法,導致檢測很慢maxHands=1, # 最多檢測幾只手detectionCon=0.8, # 最小檢測置信度minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 返回圖像是否讀取成功,以及讀取的幀圖像imgsuccess, img = cap.read()#(4)獲取手部關鍵點信息# 檢測手部信息,返回手部關鍵點信息hands字典,繪制關鍵點和連線后的圖像imghands, img = detector.findHands(img)print(hands)#(5)圖像顯示# 計算FPS值cTime = time.time() # 處理一幀圖像所需的時間fps = 1/(cTime-pTime) pTime = cTime # 更新處理下一幀的起始時間# 把fps值顯示在圖像上,img畫板,顯示字符串,顯示的坐標位置,字體,字體大小,顏色,線條粗細cv2.putText(img, str(int(fps)), (50,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)# 顯示圖像,輸入窗口名及圖像數據# cv2.namedWindow("img", 0) # 窗口大小可手動調整cv2.imshow('img', img) if cv2.waitKey(20) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
打印檢測到的手部關鍵點信息hands列表,lmList中存放21個手部關鍵點的像素坐標,bbox中存放檢測框的左上角坐標和框的寬高,center存放檢測框的中心坐標,type檢測的是左手還是右手。
-----------------------------------------------------------------
[{'lmList': [[227, 607], [335, 585], [439, 515], [508, 440], [563, 384], [434, 384], [491, 292], [520, 231], [543, 176], [380, 349], [423, 241], [445, 169], [459, 106], [320, 336], [347, 228], [368, 156], [387, 94], [250, 339], [255, 245], [264, 183], [279, 126]],'bbox': (227, 94, 336, 513),
'center': (395, 350),
'type': 'Left'}]
[{'lmList': [[219, 628], [324, 605], [427, 532], [489, 451], [540, 390], [424, 401], [483, 310], [511, 250], [532, 195], [369, 366], [415, 263], [436, 192], [449, 129], [308, 353], [340, 250], [362, 181], [382, 120], [238, 358], [248, 268], [261, 209], [278, 154]],
'bbox': (219, 120, 321, 508),
'center': (379, 374),
'type': 'Left'}]
-----------------------------------------------------------------
圖像顯示結果如下:
3. 距離檢測,確定像素距離和實際厘米距離之間的映射關系
距離檢測的思路是,獲取手掌關鍵點信息中的食指根部"5"坐標lmList[5]?和小指根部'17'坐標lmList[17],計算這兩個關鍵點之間的像素距離distance。將像素距離映射到手掌距離屏幕的實際距離。
在確定映射公式之前我們得先看一下掌間距離和相機與手之間的距離的對應關系,如下面代碼中的第(3)步。x代表掌間距離,y代表相機和手之間的距離,舉個例子,手掌間的像素距離為300時,對應的相機和手之間的距離是20cm。繪圖查看對應關系。
這里就簡單的使用一個二次多項式去擬合這條曲線,得到手掌和攝像機之間的大致的距離。感興趣的可以用指數擬合,更準確一些。使用 np.polyfit(x, y, 2) 函數,指定 x 和 y 之間是 2 次多項式關系,即?。返回值是一個數組coff,存放多項式的系數A、B、C。
因此,在計算實際距離distanceCM時,就可以根據二次多項式公式計算每一幀圖像的手掌和攝像機之間的距離,distanceCM = A*distance**2 + B*distance + C。
我們在上述代碼中補充。
import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector
import time
import math#(1)捕獲攝像頭
cap = cv2.VideoCapture(0) # 捕獲電腦攝像頭
cap.set(3, 1280) # 設置顯示窗口寬度1280
cap.set(4, 720) # 顯示窗口高度720pTime = 0 # 處理第一幀圖像的起始時間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 靜態圖模式,若為True,每一幀都會調用檢測方法,導致檢測很慢maxHands=1, # 最多檢測幾只手detectionCon=0.8, # 最小檢測置信度minTrackCon=0.5) # 最小跟蹤置信度#(3)找到手掌間的距離和實際的手與攝像機之間的距離的映射關系
# x 代表手掌間的距離(像素距離),y 代表手和攝像機之間的距離(cm)
x = [300, 245, 200, 170, 145, 130, 112, 103, 93, 87, 80, 75, 70, 67, 62, 59, 57]
y = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]# 繪圖查看xy的對應關系
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('reflection')# 因此我們需要一個類似 y = AX^2 + BX + C 的方程來擬合
import numpy as np
coff = np.polyfit(x, y, 2) #構造二階多項式方程
# coff中存放的是二階多項式的系數 A,B,C#(4)處理每一幀圖像
while True:# 返回圖像是否讀取成功,以及讀取的幀圖像imgsuccess, img = cap.read()#(5)獲取手部關鍵點信息# 檢測手部信息,返回手部關鍵點信息hands字典,不繪制圖像hands = detector.findHands(img, draw=False)# 如果檢測到手的話hands字典就不為空if hands:# 獲取檢測框的信息(x,y,w,h)x, y, w, h = hands[0]['bbox'] # 獲取字典中的關鍵點信息,key為lmListlmList = hands[0]['lmList'] # hands[0]代表檢測到的這只手的字典信息,hands是一個列表print('hands_landmarks:', lmList)# 獲取食指根部'5'和小指根部'17'的坐標點x1, y1 = lmList[5] x2, y2 = lmList[17]# 勾股定理計算關鍵點'5'和'17'之間的距離,并變成整型distance = int(math.sqrt((x2-x1)**2 + (y2-y1)**2))print('distance between 5 and 17:', distance)# 擬合的二次多項式的系數保存在coff數組中,即掌間距離和手與相機間的距離的對應關系的系數A, B, C = coff# 得到像素距離轉為實際cm距離的公式 y = Ax^2 + Bx + CdistanceCM = A*distance**2 + B*distance + Cprint('distance CM:', distanceCM)# 把距離繪制在圖像上,簡化了cv2.putText(),cvzone.putTextRect(img, f'{(int(distanceCM))} cm', (x+10,y-10))# 繪制手部檢測框cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)#(6)圖像顯示# 計算FPS值cTime = time.time() # 處理一幀圖像所需的時間fps = 1/(cTime-pTime) pTime = cTime # 更新處理下一幀的起始時間# 把fps值顯示在圖像上,img畫板,顯示字符串,顯示的坐標位置,字體,字體大小,顏色,線條粗細cv2.putText(img, str(int(fps)), (50,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)# 顯示圖像,輸入窗口名及圖像數據# cv2.namedWindow("img", 0) # 窗口大小可手動調整cv2.imshow('img', img) if cv2.waitKey(20) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
打印每幀的21個關鍵點信息?hands_landmarks,掌間像素距離?distance between 5 and 17,手掌和相機間的厘米距離?distance CM
-----------------------------------------------------------------------
hands_landmarks: [[211, 581], [276, 570], [340, 530], [373, 468], [371, 413], [360, 465], [382, 403], [358, 423], [340, 458], [327, 443], [345, 384], [311, 424], [292, 466], [294, 428], [306, 374], [281, 414], [266, 457], [261, 419], [271, 378], [256, 407], [246, 443]]
distance between 5 and 17: 109
distance CM: 56.75208816895032
hands_landmarks: [[151, 608], [212, 607], [286, 557], [306, 486], [280, 436], [301, 483], [322, 418], [295, 473], [287, 505], [262, 466], [273, 409], [248, 478], [246, 502], [222, 457], [229, 409], [210, 478], [210, 503], [180, 451], [185, 417], [177, 467], [177, 491]]
distance between 5 and 17: 125
distance CM: 48.49262820874043
-----------------------------------------------------------------------
顯示結果如圖,23cm代表手掌距離攝像機有多遠。
4. 創建虛擬按鍵,建立游戲規則
從第(8)步開始,如果手掌距離攝像機小于30cm,并且按鈕的中心點坐標(cx, cy)在檢測框內部,那么就認為此時手掌已經按下按鈕,counter變成1,按鈕變成紅色,counter變成2。如果手掌一直按著按鈕,那么counter一直保持著 counter=2。如果松開那么此時的counter自動從2加1,變成counter=3。顏色置為初始值,得分加一,按鈕隨機出現在屏幕中的任意位置random.randint(),重置按鈕確認器counter=0。
key == ord('r') 表示當點擊鍵盤上的R鍵時,可以重新開始游戲。
import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector
import time
import math
import random#(1)捕獲攝像頭
cap = cv2.VideoCapture(0) # 捕獲電腦攝像頭
cap.set(3, 1280) # 設置顯示窗口寬度1280
cap.set(4, 720) # 顯示窗口高度720pTime = 0 # 處理第一幀圖像的起始時間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 靜態圖模式,若為True,每一幀都會調用檢測方法,導致檢測很慢maxHands=1, # 最多檢測幾只手detectionCon=0.8, # 最小檢測置信度minTrackCon=0.5) # 最小跟蹤置信度#(3)找到手掌間的距離和實際的手與攝像機之間的距離的映射關系
# x 代表手掌間的距離(像素距離),y 代表手和攝像機之間的距離(cm)
x = [300, 245, 200, 170, 145, 130, 112, 103, 93, 87, 80, 75, 70, 67, 62, 59, 57]
y = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]# 繪圖查看xy的對應關系
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('reflection')# 因此我們需要一個類似 y = AX^2 + BX + C 的方程來擬合
import numpy as np
coff = np.polyfit(x, y, 2) #構造二階多項式方程
# coff中存放的是二階多項式的系數 A,B,C# 創建初始的按鈕的位置
cx, cy = 255, 255
# 初始的按鈕顏色紅色,如果接下來手碰到了它就變顏色
color = (255,255,0)
# 設置計數器,有沒有碰到按鈕
counter = 0
# 設置初始得分
score = 0
# 設置游戲開始的起始時間
startTime = time.time()
# 設置游戲的總時間10s
totalTime = 20#(4)處理每一幀圖像
while True:# 返回圖像是否讀取成功,以及讀取的幀圖像imgsuccess, img = cap.read()# 水平翻轉圖像,呈鏡像關系img = cv2.flip(img, 1) # 0代表垂直方向翻轉,1代表水平方向# 如果當前幀時間減去起始時間小于預設的時間,那么游戲繼續進行if time.time() - startTime <= totalTime:#(5)獲取手部關鍵點信息# 檢測手部信息,返回手部關鍵點信息hands字典,不繪制圖像hands = detector.findHands(img, draw=False)# 如果檢測到手的話hands字典就不為空if hands:# 獲取檢測框的信息(x,y,w,h)x, y, w, h = hands[0]['bbox'] # 獲取字典中的關鍵點信息,key為lmListlmList = hands[0]['lmList'] # hands[0]代表檢測到的這只手的字典信息,hands是一個列表print('hands_landmarks:', lmList)# 獲取食指根部'5'和小指根部'17'的坐標點x1, y1 = lmList[5] x2, y2 = lmList[17]# 勾股定理計算關鍵點'5'和'17'之間的距離,并變成整型distance = int(math.sqrt((x2-x1)**2 + (y2-y1)**2))print('distance between 5 and 17:', distance)# 擬合的二次多項式的系數保存在coff數組中,即掌間距離和手與相機間的距離的對應關系的系數A, B, C = coff# 得到像素距離轉為實際cm距離的公式 y = Ax^2 + Bx + CdistanceCM = A*distance**2 + B*distance + Cprint('distance CM:', distanceCM)# 把距離繪制在圖像上,簡化了cv2.putText(),cvzone.putTextRect(img, f'{(int(distanceCM))} cm', (x+10,y-10))# 繪制手部檢測框cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 3)#(8)設置游戲規則if distanceCM <= 30: # 如果手距相機的距離小于40cm,并且按鈕在檢測框內部,就認為碰到了 if x <cx< x+w and y <cy< y+h: # 按鈕在檢測框內部counter = 1 # 計數器變成1證明碰到了# 如果手碰到了按鈕if counter:counter += 1 # 如果碰到counter就一直是2,接下去畫圖color = (0,0,255) # 按鈕變成紅色# 如果沒有碰到,那么程序執行到這里時counter等于3,將按鈕顏色重置if counter == 3: # 手一旦沒有碰到按鈕,按鈕就隨機換位置cx = random.randint(100,1100)cy = random.randint(100,620)# 得分加1分,因為是按下按鈕后松開才能得分score += 1# 重置按鈕顏色color = (255,255,0)counter = 0#(9)創建游戲界面# 創建按鈕,觸碰到了就變顏色# 按鈕出現在屏幕的隨機位置,img畫板,圓心位置,半徑,顏色color,填充cv2.circle(img, (cx,cy), 30, color, cv2.FILLED)cv2.circle(img, (cx,cy), 20, (0,255,255), 4) # 把按鈕做得好看一些 cv2.circle(img, (cx,cy), 10, (100,100,255), 4)# 創建計時器,img畫板,顯示文本,位置,大小,背景顏色,offset上下左右填充nowTime = totalTime - int(time.time()-startTime) # 顯示剩余時間cvzone.putTextRect(img, f'Time:{nowTime}', (900,80), scale=4, colorR=(255,0,0), offset=20)# 創建得分計數板,在規定時間內碰到了幾次按鈕score_get = str(score).zfill(2) # 字符串, 兩位數'01','02'cvzone.putTextRect(img, 'score:' + score_get, (400,80), scale=4, colorT=(0,0,255), colorR=(0,255,255), offset=20)# 如果時間到了,顯示總得分else:cvzone.putTextRect(img, 'Game Over', (400,250), scale=5, colorT=(0,0,255), colorR=(255,255,0), offset=20, thickness=8)cvzone.putTextRect(img, 'score:' + score_get, (490,350), scale=4, colorT=(0,0,255), colorR=(0,255,0), offset=20)cvzone.putTextRect(img, 'press r to restart', (350,450), scale=4, colorT=(255,255,255), colorR=(255,0,255), offset=20)#(10)圖像顯示# 計算FPS值cTime = time.time() # 處理一幀圖像所需的時間fps = 1/(cTime-pTime) pTime = cTime # 更新處理下一幀的起始時間# 把fps值顯示在圖像上,img畫板,顯示字符串,顯示的坐標位置,字體,字體大小,顏色,線條粗細cv2.putText(img, str(int(fps)), (50,70), cv2.FONT_HERSHEY_PLAIN, 4, (255,0,0), 3)# 顯示圖像,輸入窗口名及圖像數據cv2.imshow('img', img)key = cv2.waitKey(1)# 重置游戲if key == ord('r'):startTime = time.time() # 重置開始時間score = 0 # 重置得分# 退出游戲if key==27: # ESC鍵退出顯示break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
當手掌按下按鈕,按鈕顏色從青色變成紅色,手不松開按鈕的話按鈕的顏色保持是不變,位置也不變,并且得分板也不增加。只有松開后才會重置位置,計數加一。練習拍擊按鈕的快準狠。
總結
以上是生活随笔為你收集整理的【机器视觉案例】(6) AI视觉,距离测量,自制AI小游戏,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【机器视觉案例】(5) AI视觉,手势调
- 下一篇: 【opencv】(13) 案例:停车场空