【机器视觉案例】(8) AI视觉,手势控制电脑鼠标,附python完整代码
各位同學好,今天和大家分享一下如何使用?MediaPipe+Opencv 通過手勢識別來控制電腦鼠標的移動和點擊,如果有興趣的話,可以代替鼠標去打游戲。先放圖看效果。用畫圖板來測試
黃框代表電腦屏幕的范圍,將黃框的寬高映射到電腦屏幕的寬高。食指豎起并且中指彎下時,移動鼠標。食指和中指都豎起,并且兩個指尖距離小于50時,認為是點擊鼠標。左上角30代表FPS值
移動鼠標:移動時,食指指尖有淡藍色圓點,表明鼠標在移動,如右圖的綠色線條是鼠標移動軌跡
點擊鼠標:當食指和中指間的距離小于50,食指指尖圓點變成綠色,點擊鼠標,如畫圖板上的兩個點,就是點擊兩下實現的。
1. 導入工具包
# 安裝工具包
pip install opencv-contrib-python # 安裝opencv
pip install mediapipe # 安裝mediapipe
# pip install mediapipe --user #有user報錯的話試試這個
pip install cvzone # 安裝cvzone
pip install autopy # 鼠標控制單元# 導入工具包
import numpy as np
import cv2
from cvzone.HandTrackingModule import HandDetector # 手部追蹤方法
import mediapipe as mp
import time
import autopy
21個手部關鍵點信息如下,本節我們主要研究食指指尖"8"和中指指尖"12"的坐標信息。
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: 檢測到的手部信息,包含:21個關鍵點坐標,檢測框坐標及寬高,檢測框中心坐標,檢測出是哪一只手。
img: 返回繪制了關鍵點及連線后的圖像
手部檢測的代碼如下:
import cv2
from cvzone.HandTrackingModule import HandDetector # 手部檢測方法
import time#(1)導數視頻數據
cap = cv2.VideoCapture(0) # 0代表自己電腦的攝像頭
cap.set(3, 1280) # 設置顯示框的寬度1280
cap.set(4, 720) # 設置顯示框的高度720pTime = 0 # 設置第一幀開始處理的起始時間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流圖像 maxHands=1, # 最多檢測一只手detectionCon=0.8, # 最小檢測置信度 minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 圖片是否成功接收、img幀圖像success, img = cap.read()# 翻轉圖像,使自身和攝像頭中的自己呈鏡像關系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉,0代表豎直翻轉#(4)手部檢測方法# 傳入每幀圖像, 返回手部關鍵點的坐標信息(字典構成的列表hands),繪制關鍵點后的圖像imghands, img = detector.findHands(img, flipType=False) # 上面反轉過了,這里就不用再翻轉了# print(hands)#(5)顯示圖像# 查看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(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
打印每幀圖像檢測到的手部信息hands列表,由字典組成。lmList 代表21個手部關鍵點的像素坐標;bbox 代表檢測框的左上角坐標和框的寬高;center 代表檢測框的中心點的像素坐標;type 代表檢測出的是左手還是右手。
----------------------------------------------------------------------------
[{'lmList': [[522, 755], [621, 761], [709, 724], [765, 675], [794, 615], [705, 629], [761, 588], [749, 643], [715, 686], [676, 599], [743, 565], [713, 637], [664, 684], [634, 565], [710, 543], [668, 622], [613, 666], [576, 533], [657, 519], [640, 580], [597, 620]],'bbox': (522, 519, 272, 242), 'center': (658, 640), 'type': 'Left'}]
[{'lmList': [[520, 763], [620, 774], [716, 753], [779, 707], [816, 650], [716, 655], [781, 619], [767, 677], [727, 721], [689, 627], [759, 595], [731, 667], [683, 710], [649, 594], [727, 579], [680, 653], [620, 689], [593, 558], [674, 549], [655, 608], [608, 642]],'bbox': (520, 549, 296, 225),'center': (668, 661),'type': 'Left'}]
----------------------------------------------------------------------------
檢測結果如圖所示:
2. 移動鼠標
移動鼠標的思路是:如果檢測到食指豎起,并且中指彎下,那么就認為是移動鼠標,鼠標的位置坐標是食指指尖所在的位置坐標。
檢測哪個手指是豎起的方法是?detector.fingersUp() ,傳入檢測到的某只手的手部信息hands[0];返回值是由5個元素構成的列表,元素為1代表該手指豎起,0代表手指彎下,例如:[0,1,1,0,0] 就代表食指和中指豎起,其他手指彎下。
當手指在攝像頭畫面的下半部分移動時,由于攝像頭界限范圍問題,手掌部分會在攝像頭畫面中消失,導致檢測不到手部關鍵點,因此,在屏幕畫面的偏上半部分繪制一個黃色的矩形框,手指只能在矩形框中移動,避免手部關鍵點的消失。
由于我們設置的矩形框大小明顯要小于電腦屏幕的大小,導致手控的鼠標無法在整個電腦屏幕上移動。因此,需要將矩形框的寬和高映射到電腦屏幕的寬和高。使用線性插值方法 np.interp(x, xp, fp)??簡單來說就是將變量x的范圍從原來的xp映射到fp。如:np.interp(x1, (pt1[0], pt2[0]), (0, wScr)),就是將x坐標的范圍從原來的 pt1[0] 到 pt1[0]+w,映射到整個電腦屏幕?0?到 wScr。
返回電腦屏幕的寬和高:?autopy.screen.size()
移動鼠標的位置到坐標(x,y): autopy.mouse.move(x, y)
autopy具體使用方法見下文:https://blog.csdn.net/qq_30462003/article/details/100130472
因此,我們在上述代碼中補充:
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 手部檢測方法
import time
# pip install autopy #鼠標控制單元
import autopy#(1)導數視頻數據
wScr, hScr = autopy.screen.size() # 返回電腦屏幕的寬和高(1920.0, 1080.0)
wCam, hCam = 1280, 720 # 視頻顯示窗口的寬和高
pt1, pt2 = (100,100), (1100, 500) # 虛擬鼠標的移動范圍,左上坐標pt1,右下坐標pt2cap = cv2.VideoCapture(0) # 0代表自己電腦的攝像頭
cap.set(3, wCam) # 設置顯示框的寬度1280
cap.set(4, hCam) # 設置顯示框的高度720pTime = 0 # 設置第一幀開始處理的起始時間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流圖像 maxHands=1, # 最多檢測一只手detectionCon=0.8, # 最小檢測置信度 minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 圖片是否成功接收、img幀圖像success, img = cap.read()# 翻轉圖像,使自身和攝像頭中的自己呈鏡像關系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉,0代表豎直翻轉# 在圖像窗口上創建一個矩形框,在該區域內移動鼠標cv2.rectangle(img, pt1, pt2, (0,255,255), 5)#(4)手部關鍵點檢測# 傳入每幀圖像, 返回手部關鍵點的坐標信息(字典),繪制關鍵點后的圖像hands, img = detector.findHands(img, flipType=False) # 上面反轉過了,這里就不用再翻轉了# print(hands)# 如果能檢測到手那么就進行下一步if hands:# 獲取手部信息hands中的21個關鍵點信息lmList = hands[0]['lmList'] # hands是由N個字典組成的列表,字典包每只手的關鍵點信息# 獲取食指指尖坐標,和中指指尖坐標x1, y1 = lmList[8] # 食指尖的關鍵點索引號為8x2, y2 = lmList[12] # 中指指尖索引12#(5)檢查哪個手指是朝上的fingers = detector.fingersUp(hands[0]) # 傳入# print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指豎起# 如果食指豎起且中指彎下,就認為是移動鼠標if fingers[1] == 1 and fingers[2] == 0:# 開始移動時,在食指指尖畫一個圓圈,看得更清晰一些cv2.circle(img, (x1,y1), 15, (255,255,0), cv2.FILLED) # 顏色填充整個圓#(6)確定鼠標移動的范圍# 將食指的移動范圍從預制的窗口范圍,映射到電腦屏幕范圍x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))#(7)移動鼠標autopy.mouse.move(x3, y3) # 給出鼠標移動位置坐標#(8)顯示圖像# 查看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(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
效果圖如下:
3. 點擊鼠標
點擊鼠標的思路是:如果食指和中指同時豎起,并且食指指尖和中指指尖之間的像素距離小于50時,那么就認為是點擊鼠標。
檢測哪個手指是豎起的方法是上面已經解釋過的?detector.fingersUp() 方法,檢測指尖距離的方法是: detector.findDistance(pt1, pt2, img), pt1 和 pt2 是兩個點的坐標,傳入img來繪制指尖連線圖。
點擊鼠標的函數,autopy.mouse.click()
由于用手指控制鼠標時,每一幀的坐標位置的變化幅度較大,導致電腦鼠標在手指控制下很容易晃動,很難準確定位到一個目標。因此需要平滑每一幀的坐標變化,使坐標變化更緩慢一些。
例如:cLocx = pLocx + (x3 - pLocx) / smooth,式中:前幀的鼠標位置的x坐標 cLocx;前一幀的鼠標位置的x坐標?pLocx;當前鼠標位置的x坐標?x3;自定義平滑系數smooth,值越大鼠標移動就越慢,平穩性就越高。
因此,在上述代碼中補充:
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 手部檢測方法
import time
import autopy#(1)導數視頻數據
wScr, hScr = autopy.screen.size() # 返回電腦屏幕的寬和高(1920.0, 1080.0)
wCam, hCam = 1280, 720 # 視頻顯示窗口的寬和高
pt1, pt2 = (100,100), (1100, 500) # 虛擬鼠標的移動范圍,左上坐標pt1,右下坐標pt2cap = cv2.VideoCapture(0) # 0代表自己電腦的攝像頭
cap.set(3, wCam) # 設置顯示框的寬度1280
cap.set(4, hCam) # 設置顯示框的高度720pTime = 0 # 設置第一幀開始處理的起始時間pLocx, pLocy = 0, 0 # 上一幀時的鼠標所在位置smooth = 4 # 自定義平滑系數,讓鼠標移動平緩一些#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流圖像 maxHands=1, # 最多檢測一只手detectionCon=0.8, # 最小檢測置信度 minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 圖片是否成功接收、img幀圖像success, img = cap.read()# 翻轉圖像,使自身和攝像頭中的自己呈鏡像關系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉,0代表豎直翻轉# 在圖像窗口上創建一個矩形框,在該區域內移動鼠標cv2.rectangle(img, pt1, pt2, (0,255,255), 5)#(4)手部關鍵點檢測# 傳入每幀圖像, 返回手部關鍵點的坐標信息(字典),繪制關鍵點后的圖像hands, img = detector.findHands(img, flipType=False) # 上面反轉過了,這里就不用再翻轉了# print(hands)# 如果能檢測到手那么就進行下一步if hands:# 獲取手部信息hands中的21個關鍵點信息lmList = hands[0]['lmList'] # hands是由N個字典組成的列表,字典包每只手的關鍵點信息# 獲取食指指尖坐標,和中指指尖坐標x1, y1 = lmList[8] # 食指尖的關鍵點索引號為8x2, y2 = lmList[12] # 中指指尖索引12#(5)檢查哪個手指是朝上的fingers = detector.fingersUp(hands[0]) # 傳入# print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指豎起# 如果食指豎起且中指彎下,就認為是移動鼠標if fingers[1] == 1 and fingers[2] == 0:# 開始移動時,在食指指尖畫一個圓圈,看得更清晰一些cv2.circle(img, (x1,y1), 15, (255,255,0), cv2.FILLED) # 顏色填充整個圓#(6)確定鼠標移動的范圍# 將食指的移動范圍從預制的窗口范圍,映射到電腦屏幕范圍x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))#(7)平滑,使手指在移動鼠標時,鼠標箭頭不會一直晃動cLocx = pLocx + (x3 - pLocx) / smooth # 當前的鼠標所在位置坐標cLocy = pLocy + (y3 - pLocy) / smooth #(8)移動鼠標autopy.mouse.move(cLocx, cLocy) # 給出鼠標移動位置坐標# 更新前一幀的鼠標所在位置坐標,將當前幀鼠標所在位置,變成下一幀的鼠標前一幀所在位置pLocx, pLocy = cLocx, cLocy#(9)如果食指和中指都豎起,指尖距離小于某個值認為是點擊鼠標if fingers[1] == 1 and fingers[2] == 1: # 食指和中指都豎起# 計算食指尖和中指尖之間的距離distance,繪制好了的圖像img,指尖連線的信息infodistance, info, img = detector.findDistance((x1, y1), (x2, y2), img)# print(distance)# 當指間距離小于50(像素距離)就認為是點擊鼠標if distance < 50:# 在食指尖畫個綠色的圓,表示點擊鼠標cv2.circle(img, (x1,y1), 15, (0,255,0), cv2.FILLED)# 點擊鼠標autopy.mouse.click()#(10)顯示圖像# 查看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(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
結果圖像展示,在繪圖板中每點擊一次就繪制一個圓圈。
總結
以上是生活随笔為你收集整理的【机器视觉案例】(8) AI视觉,手势控制电脑鼠标,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【opencv】(13) 案例:停车场空
- 下一篇: 【面向对象编程】(1) 类实例化的基本方