【机器视觉案例】(5) AI视觉,远程手势控制虚拟计算器,附python完整代码
各位同學好,今天和大家分享一下如何使用MediaPipe+Opencv完成虛擬計算器,先放張圖看效果。FPS值為29,食指和中指距離小于規定閾值則認為點擊按鍵,為避免重復數字出現,規定每20幀可點擊一次。
手部關鍵點檢測的方法我之前已經詳細寫過,這里就直接使用,有不明白的可看我的這篇文章:【MediaPipe】(1) AI視覺,手部關鍵點實時跟蹤,附python完整代碼
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 mediapipe as mp
import time
21個手部關鍵點信息如下,本節我們主要研究食指指尖"8"和中指指尖"12"的坐標信息。
2. 相關函數介紹
(1)cvzone.HandTrackingModule.HandDetector()? ??手部關鍵點檢測方法
是已經編寫好的檢測方法,它的具體原理和我寫的第一篇手部關鍵點檢測的方法相同,想知道具體實現的方法可以去看一下。鏈接在文章開頭。
參數:
mode: 默認為 False,將輸入圖像視為視頻流。它將嘗試在第一個輸入圖像中檢測手,并在成功檢測后進一步定位手的坐標。在隨后的圖像中,一旦檢測到所有 max_num_hands 手并定位了相應的手的坐標,它就會跟蹤這些坐標,而不會調用另一個檢測,直到它失去對任何一只手的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設置為 True,則在每個輸入圖像上運行手部檢測,用于處理一批靜態的、可能不相關的圖像。
maxHands: 最多檢測幾只手,默認為2
detectionCon: 手部檢測模型的最小置信值(0-1之間),超過閾值則檢測成功。默認為 0.5
minTrackingCon: 坐標跟蹤模型的最小置信值 (0-1之間),用于將手部坐標視為成功跟蹤,不成功則在下一個輸入圖像上自動調用手部檢測。將其設置為更高的值可以提高解決方案的穩健性,但代價是更高的延遲。如果 static_image_mode 為 True,則忽略這個參數,手部檢測將在每個圖像上運行。默認為 0.5
它的參數和返回值類似于官方函數?mediapipe.solutions.hands.Hands()
(2)cvzone.HandTrackingModule.HandDetector.findHands()? ??找到手部關鍵點并繪圖
參數:
img: 需要檢測關鍵點的幀圖像,格式為BGR
draw: 是否需要在原圖像上繪制關鍵點及識別框
flipType: 圖像是否需要翻轉,當視頻圖像和我們自己不是鏡像關系時,設為True就可以了
返回值:繪制關鍵點后的img幀圖像;以及21個關鍵點的坐標信息(列表形式)
3. 檢測手部信息
這里設置 maxHand=1 最多檢測一只手,我們只需要一只手點計算器就可以了。
使用 cv2.flip() 函數翻轉讀取的攝像機圖像,因為我們自己的右邊相當于攝像機的左邊,需要統一一下,變成鏡像關系。指定參數 flipCode=0 豎向翻轉,flipCode=1 水平翻轉。
#(1)捕獲攝像頭
cap = cv2.VideoCapture(0)
cap.set(3, 1080) # 顯示框的寬1080
cap.set(4, 720) # 顯示框的高720pTime = 0 # 設置第一幀開始處理的起始時間# 手部檢測方法,置信度為0.8,最多檢測一只手
detector = HandDetector(detectionCon=0.8, maxHands=1) #(2)處理每一幀圖像
while True:# 接收圖片是否導入成功、幀圖像success, img = cap.read()# 翻轉圖像,保證攝像機畫面和人的動作是鏡像img = cv2.flip(img, flipCode=1) #0豎直翻轉,1水平翻轉#(3)檢測手部關鍵點,返回所有關鍵點的坐標和繪制后的圖像hands, img = detector.findHands(img, flipType=False)# 查看FPScTime = time.time() #處理完一幀圖像的時間fps = 1/(cTime-pTime)pTime = cTime #重置起始時間# 在視頻上顯示fps信息,先轉換成整數再變成字符串形式,文本顯示坐標,文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數據cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
效果圖如下:
4. 創建虛擬計算器
接下來我們需要在屏幕上創建一個計算器界面,定義一個類方法Button。計算器一共有16個按鍵,我們在 buttonListvalues 中存放每個按鍵的文本。利用兩個for循環,讓16個按鍵都經過button類初始化,暫時不繪圖,把按鍵信息存放到 buttonList 中。初始化方法是在while循環開始之前就已經進行了,button類方法中的繪圖方法draw()是在讀取每一幀圖像時進行,通過for循環繪制buttonList列表中每個按鍵元素。
因此我們在上述代碼中補充。
# 創建按鍵類
class Button:# 初始化,傳入pos按鍵位置,每個矩形框的寬高,矩形框上的數字valuedef __init__(self, pos, width, height, value): # 初始化在while循環之前完成self.pos = posself.width = widthself.height = heightself.value = value# 繪圖方法在while循環之后完成def draw(self, img):# 繪制計算器輪廓,img畫板,起點坐標,終點坐標,顏色填充cv2.rectangle(img, self.pos, (self.pos[0]+self.width, self.pos[1]+self.height), (225,225,225), cv2.FILLED)# 給計算器添加邊框cv2.rectangle(img, self.pos, (self.pos[0]+self.width, self.pos[1]+self.height),(50,50,50), 3)# 按鍵添加文本,img畫板,文本內容,坐標,字體,字體大小,字體顏色,線條寬度cv2.putText(img, self.value, (self.pos[0]+30,self.pos[1]+70),cv2.FONT_HERSHEY_COMPLEX, 2, (50,50,50), 2)#(1)捕獲攝像頭
cap = cv2.VideoCapture(0)
cap.set(3, 1280) # 顯示框的寬1280
cap.set(4, 720) # 顯示框的高720pTime = 0 # 設置第一幀開始處理的起始時間# ==1== 手部檢測方法,置信度為0.8,最多檢測一只手
detector = HandDetector(detectionCon=0.8, maxHands=1) # ==2== 創建計算器按鍵
# 創建按鈕內容列表
buttonListvalues = [['7', '8', '9', '*'],['4', '5', '6', '-'],['1', '2', '3', '+'],['0', '/', '.', '=']]buttonList = [] #存放每個按鍵的信息# 創建4*4個按鍵
for x in range(4): # 四列for y in range(4): # 四行xpos = x * 100 + 800 #得到四塊寬為100的矩形的起點x坐標,從x=800開始ypos = y * 100 + 150 #起點y坐標# 傳入起點坐標及寬高button1 = Button((xpos,ypos), 100, 100, buttonListvalues[y][x])buttonList.append(button1) # 將確定坐標的矩形框信息存入列表中#(2)處理每一幀圖像
while True:# 接收圖片是否導入成功、幀圖像success, img = cap.read()# 翻轉圖像,保證攝像機畫面和人的動作是鏡像img = cv2.flip(img, flipCode=1) #0豎直翻轉,1水平翻轉#(3)檢測手部關鍵點,返回所有繪制后的圖像hands, img = detector.findHands(img, flipType=False)#(4)繪制計算器# 繪制計算器顯示結果的部分,四個按鍵的寬合起來是400cv2.rectangle(img, (800, 50), (800+400, 70+100), (225,225,225), cv2.FILLED)# 結果框輪廓cv2.rectangle(img, (800, 50), (800+400, 70+100), (50,50,50), 3)# 遍歷列表,調用類中的draw方法,繪制每個按鍵for button in buttonList: button.draw(img)# 查看FPScTime = time.time() #處理完一幀圖像的時間fps = 1/(cTime-pTime)pTime = cTime #重置起始時間# 在視頻上顯示fps信息,先轉換成整數再變成字符串形式,文本顯示坐標,文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數據cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
結果如下:
?5. 激活按鈕完成數學計算
補充上述代碼,從第(5)部分開始,detector.findDistance() 函數傳入兩個關鍵點的xy坐標和img畫板,返回兩點之間的長度length,繪制后的圖像img。在這里主要研究食指,如果食指在某個按鍵框內,并且食指和中指的距離小于50,那么就認為是點擊按鍵。
因此我們需要新建一個類方法 checkClick?來判斷,食指和中指接觸時,食指在哪個位置。依次遍歷 buttonList 中的16個按鍵框信息,和當前食指所在位置(x,y),如果 (x, y) 在某個按鍵框內,即 x1 < x < x1 + width 且?y1 < y < y1 + height,那么就改變這個按鍵的顏色,表明已經點擊,類方法 checkClick?返回True。因此,通過按鍵 buttonListvalues 的索引,我們就找到了我們點下去的是哪一個字符。
我們每幀圖像點擊按鍵,得到的字符串的各種組合,存放在 myEquation 變量中,如 '5 * 6 - 2'。當我們點擊 '=' 號時,得到的應該是一個長的像數值的字符串,而不是數值表達式組成的字符串。這時,使用 eval() 函數,它會將長得像數值運算的字符串當作數值來計算,返回一個數值。再把它變成字符串文本顯示出來就可以了。
由于圖像每一幀的刷新的很快,可能我們點了一次2,就顯示出來十幾個2。因此我們需要設置一個延時器 delayCounter,只有當它為0時我們才能再點擊,避免出現每一幀我們都點擊按鍵,這里設置為50幀,每50幀點擊一次。if delayCounter > 50: delayCounter = 0
如果想清空結果框怎么辦呢,方法如下,只要在英文模式下點擊鍵盤上的c鍵就可以了,key = cv2.waitKey(1); if key == ord('c'): myEquation = ' '
import cv2
from cvzone.HandTrackingModule import HandDetector
import mediapipe as mp
import time# 創建按鍵類
class Button:# 初始化,傳入pos按鍵位置,每個矩形框的寬高,矩形框上的數字valuedef __init__(self, pos, width, height, value): # 初始化在while循環之前完成self.pos = posself.width = widthself.height = heightself.value = value# 繪圖方法在while循環之后完成def draw(self, img):# 繪制計算器輪廓,img畫板,起點坐標,終點坐標,顏色填充cv2.rectangle(img, self.pos, (self.pos[0]+self.width, self.pos[1]+self.height), (225,225,225), cv2.FILLED)# 給計算器添加邊框cv2.rectangle(img, self.pos, (self.pos[0]+self.width, self.pos[1]+self.height),(50,50,50), 3)# 按鍵添加文本,img畫板,文本內容,坐標,字體,字體大小,字體顏色,線條寬度cv2.putText(img, self.value, (self.pos[0]+30,self.pos[1]+70),cv2.FONT_HERSHEY_COMPLEX, 2, (50,50,50), 2)# 點擊按鈕def checkClick(self, x, y): #傳入食指尖坐標# 檢查食指x坐標在哪一個按鈕框內,x1 < x < x1 + width ,控制一列# 檢查食指y坐標在哪一個按鈕框內,y1 < y < y1 + height ,控制一行if self.pos[0] < x < self.pos[0] + self.width and \self.pos[1] < y < self.pos[1] + self.height: # '\'用來換行# 如果點擊按鈕就改變按鈕顏色cv2.rectangle(img, self.pos, (self.pos[0]+self.width, self.pos[1]+self.height), (0,255,0), cv2.FILLED)# 邊框還是原來的不變cv2.rectangle(img, self.pos, (self.pos[0]+self.width, self.pos[1]+self.height),(50,50,50), 3)# 按鍵文本變顏色,面積變化cv2.putText(img, self.value, (self.pos[0]+30, self.pos[1]+70),cv2.FONT_HERSHEY_COMPLEX, 2, (0,0,255), 5)# 如果成功點擊按鈕就返回Truereturn Trueelse:return False#(1)捕獲攝像頭
cap = cv2.VideoCapture(0)
cap.set(3, 1280) # 顯示框的寬1280
cap.set(4, 720) # 顯示框的高720pTime = 0 # 設置第一幀開始處理的起始時間# ==1== 手部檢測方法,置信度為0.8,最多檢測一只手
detector = HandDetector(detectionCon=0.8, maxHands=1) # ==2== 創建計算器按鍵
# 創建按鈕內容列表
buttonListvalues = [['7', '8', '9', '*'],['4', '5', '6', '-'],['1', '2', '3', '+'],['0', '/', '.', '=']]buttonList = [] #存放每個按鍵的信息# 創建4*4個按鍵
for x in range(4): # 四列for y in range(4): # 四行xpos = x * 100 + 800 #得到四塊寬為100的矩形的起點x坐標,從x=800開始ypos = y * 100 + 150 #起點y坐標# 傳入起點坐標及寬高button1 = Button((xpos,ypos), 100, 100, buttonListvalues[y][x])buttonList.append(button1) # 將確定坐標的矩形框信息存入列表中# ==3== 初始化結果顯示框
myEquation = ''
# eval('5'+'5') ==> 10,eval()函數將數字字符串轉換成數字計算
delayCounter = 0 #添加計數器,一次點擊觸發一次按鈕,避免重復#(2)處理每一幀圖像
while True:# 接收圖片是否導入成功、幀圖像success, img = cap.read()# 翻轉圖像,保證攝像機畫面和人的動作是鏡像img = cv2.flip(img, flipCode=1) #0豎直翻轉,1水平翻轉#(3)檢測手部關鍵點,返回所有繪制后的圖像hands, img = detector.findHands(img, flipType=False)#(4)繪制計算器# 繪制計算器顯示結果的部分,四個按鍵的寬合起來是400cv2.rectangle(img, (800, 50), (800+400, 70+100), (225,225,225), cv2.FILLED)# 結果框輪廓cv2.rectangle(img, (800, 50), (800+400, 70+100), (50,50,50), 3)# 遍歷列表,調用類中的draw方法,繪制每個按鍵for button in buttonList: button.draw(img)#(5)檢測手按了哪個鍵if hands: #如果手部關鍵點返回的列表不為空,證明檢測到了手# 0代表第一只手,由于我們設置了只檢測一只手,所以0就代表檢測到的那只lmlist = hands[0]['lmList'] # 獲取食指和中指的指尖距離并繪制連線# 返回指尖連線長度,線條信息,繪制后的圖像length, _, img = detector.findDistance(lmlist[8], lmlist[12], img)# print(length)x, y = lmlist[8] # 獲取食指坐標# 如果指尖距離小于50,找到按下了哪個鍵if length < 50: for i, button in enumerate(buttonList): # 遍歷所有按鍵,找到食指尖在哪個按鍵內# 點擊按鍵,按鍵顏色面積發生變化,返回True。并且延時器為0才能運行if button.checkClick(x,y) and delayCounter==0: #(6)數值計算# 找到點擊的按鈕的編號i,i是0-15,# 如"4",索引為4,位置[1][0],等同于[i%4][i//4]# print(buttonListvalues[i%4][i//4])myValue = buttonListvalues[i%4][i//4]# 如果點的是'='號if myValue == '=':# eval()使字符串數字和符號直接做計算, eval('5 * 6 - 2')myEquation = str(eval(myEquation)) #eval返回一個數值else:# 第一次點擊"5",第二次點擊"6",需要顯示的是56myEquation += myValue # 字符串直接相加# 避免重復,方法一,不推薦: # time.sleep(0.2)delayCounter = 1 # 啟動計數器,一次運行點擊了一個鍵#(7)避免點一次出現多個相同數,方法二:# 點擊一個按鈕之后,delayCounter=1,20幀后才能點擊下一個if delayCounter != 0:delayCounter += 1 # 延遲一幀if delayCounter > 50: # 10幀過去了才能再點擊delayCounter = 0#(8)繪制顯示的計算表達式cv2.putText(img, myEquation, (800+10,100+20), cv2.FONT_HERSHEY_PLAIN,3, (50,50,50), 3)# 查看FPScTime = time.time() #處理完一幀圖像的時間fps = 1/(cTime-pTime)pTime = cTime #重置起始時間# 在視頻上顯示fps信息,先轉換成整數再變成字符串形式,文本顯示坐標,文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數據cv2.imshow('image', img)# 每幀滯留時間key = cv2.waitKey(1)# 清空計算器框if key == ord('c'):myEquation = ''# 退出顯示if key & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
沒點擊按鈕時:
點擊按鈕時:
總結
以上是生活随笔為你收集整理的【机器视觉案例】(5) AI视觉,远程手势控制虚拟计算器,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【MediaPipe】(4) AI视觉,
- 下一篇: 【网络爬虫】(1) 网络请求,urlli