【机器视觉案例】(8) AI视觉,手势控制电脑鼠标,附python完整代码
各位同學(xué)好,今天和大家分享一下如何使用?MediaPipe+Opencv 通過手勢識別來控制電腦鼠標(biāo)的移動(dòng)和點(diǎn)擊,如果有興趣的話,可以代替鼠標(biāo)去打游戲。先放圖看效果。用畫圖板來測試
黃框代表電腦屏幕的范圍,將黃框的寬高映射到電腦屏幕的寬高。食指豎起并且中指彎下時(shí),移動(dòng)鼠標(biāo)。食指和中指都豎起,并且兩個(gè)指尖距離小于50時(shí),認(rèn)為是點(diǎn)擊鼠標(biāo)。左上角30代表FPS值
移動(dòng)鼠標(biāo):移動(dòng)時(shí),食指指尖有淡藍(lán)色圓點(diǎn),表明鼠標(biāo)在移動(dòng),如右圖的綠色線條是鼠標(biāo)移動(dòng)軌跡
點(diǎn)擊鼠標(biāo):當(dāng)食指和中指間的距離小于50,食指指尖圓點(diǎn)變成綠色,點(diǎn)擊鼠標(biāo),如畫圖板上的兩個(gè)點(diǎn),就是點(diǎn)擊兩下實(shí)現(xiàn)的。
1. 導(dǎo)入工具包
# 安裝工具包
pip install opencv-contrib-python # 安裝opencv
pip install mediapipe # 安裝mediapipe
# pip install mediapipe --user #有user報(bào)錯(cuò)的話試試這個(gè)
pip install cvzone # 安裝cvzone
pip install autopy # 鼠標(biāo)控制單元# 導(dǎo)入工具包
import numpy as np
import cv2
from cvzone.HandTrackingModule import HandDetector # 手部追蹤方法
import mediapipe as mp
import time
import autopy
21個(gè)手部關(guān)鍵點(diǎn)信息如下,本節(jié)我們主要研究食指指尖"8"和中指指尖"12"的坐標(biāo)信息。
2. 手部關(guān)鍵點(diǎn)檢測
(1) cvzone.HandTrackingModule.HandDetector()? ?手部關(guān)鍵點(diǎn)檢測方法
參數(shù):
mode: 默認(rèn)為 False,將輸入圖像視為視頻流。它將嘗試在第一個(gè)輸入圖像中檢測手,并在成功檢測后進(jìn)一步定位手的坐標(biāo)。在隨后的圖像中,一旦檢測到所有 maxHands 手并定位了相應(yīng)的手的坐標(biāo),它就會(huì)跟蹤這些坐標(biāo),而不會(huì)調(diào)用另一個(gè)檢測,直到它失去對任何一只手的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設(shè)置為 True,則在每個(gè)輸入圖像上運(yùn)行手部檢測,用于處理一批靜態(tài)的、可能不相關(guān)的圖像。
maxHands: 最多檢測幾只手,默認(rèn)為 2
detectionCon: 手部檢測模型的最小置信值(0-1之間),超過閾值則檢測成功。默認(rèn)為 0.5
minTrackingCon: 坐標(biāo)跟蹤模型的最小置信值 (0-1之間),用于將手部坐標(biāo)視為成功跟蹤,不成功則在下一個(gè)輸入圖像上自動(dòng)調(diào)用手部檢測。將其設(shè)置為更高的值可以提高解決方案的穩(wěn)健性,但代價(jià)是更高的延遲。如果 mode 為 True,則忽略這個(gè)參數(shù),手部檢測將在每個(gè)圖像上運(yùn)行。默認(rèn)為 0.5
它的參數(shù)和返回值類似于官方函數(shù) mediapipe.solutions.hands.Hands()
(2)cvzone.HandTrackingModule.HandDetector.findHands() ? ?找到手部關(guān)鍵點(diǎn)并繪圖
參數(shù):
img: 需要檢測關(guān)鍵點(diǎn)的幀圖像,格式為BGR
draw: 是否需要在原圖像上繪制關(guān)鍵點(diǎn)及識別框
flipType: 圖像是否需要翻轉(zhuǎn),當(dāng)視頻圖像和我們自己不是鏡像關(guān)系時(shí),設(shè)為True就可以了
返回值:
hands: 檢測到的手部信息,包含:21個(gè)關(guān)鍵點(diǎn)坐標(biāo),檢測框坐標(biāo)及寬高,檢測框中心坐標(biāo),檢測出是哪一只手。
img: 返回繪制了關(guān)鍵點(diǎn)及連線后的圖像
手部檢測的代碼如下:
import cv2
from cvzone.HandTrackingModule import HandDetector # 手部檢測方法
import time#(1)導(dǎo)數(shù)視頻數(shù)據(jù)
cap = cv2.VideoCapture(0) # 0代表自己電腦的攝像頭
cap.set(3, 1280) # 設(shè)置顯示框的寬度1280
cap.set(4, 720) # 設(shè)置顯示框的高度720pTime = 0 # 設(shè)置第一幀開始處理的起始時(shí)間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流圖像 maxHands=1, # 最多檢測一只手detectionCon=0.8, # 最小檢測置信度 minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 圖片是否成功接收、img幀圖像success, img = cap.read()# 翻轉(zhuǎn)圖像,使自身和攝像頭中的自己呈鏡像關(guān)系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉(zhuǎn),0代表豎直翻轉(zhuǎn)#(4)手部檢測方法# 傳入每幀圖像, 返回手部關(guān)鍵點(diǎn)的坐標(biāo)信息(字典構(gòu)成的列表hands),繪制關(guān)鍵點(diǎn)后的圖像imghands, img = detector.findHands(img, flipType=False) # 上面反轉(zhuǎn)過了,這里就不用再翻轉(zhuǎn)了# print(hands)#(5)顯示圖像# 查看FPScTime = time.time() #處理完一幀圖像的時(shí)間fps = 1/(cTime-pTime)pTime = cTime #重置起始時(shí)間# 在視頻上顯示fps信息,先轉(zhuǎn)換成整數(shù)再變成字符串形式,文本顯示坐標(biāo),文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數(shù)據(jù)cv2.imshow('image', img) if cv2.waitKey(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
打印每幀圖像檢測到的手部信息hands列表,由字典組成。lmList 代表21個(gè)手部關(guān)鍵點(diǎn)的像素坐標(biāo);bbox 代表檢測框的左上角坐標(biāo)和框的寬高;center 代表檢測框的中心點(diǎn)的像素坐標(biāo);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'}]
----------------------------------------------------------------------------
檢測結(jié)果如圖所示:
2. 移動(dòng)鼠標(biāo)
移動(dòng)鼠標(biāo)的思路是:如果檢測到食指豎起,并且中指彎下,那么就認(rèn)為是移動(dòng)鼠標(biāo),鼠標(biāo)的位置坐標(biāo)是食指指尖所在的位置坐標(biāo)。
檢測哪個(gè)手指是豎起的方法是?detector.fingersUp() ,傳入檢測到的某只手的手部信息hands[0];返回值是由5個(gè)元素構(gòu)成的列表,元素為1代表該手指豎起,0代表手指彎下,例如:[0,1,1,0,0] 就代表食指和中指豎起,其他手指彎下。
當(dāng)手指在攝像頭畫面的下半部分移動(dòng)時(shí),由于攝像頭界限范圍問題,手掌部分會(huì)在攝像頭畫面中消失,導(dǎo)致檢測不到手部關(guān)鍵點(diǎn),因此,在屏幕畫面的偏上半部分繪制一個(gè)黃色的矩形框,手指只能在矩形框中移動(dòng),避免手部關(guān)鍵點(diǎn)的消失。
由于我們設(shè)置的矩形框大小明顯要小于電腦屏幕的大小,導(dǎo)致手控的鼠標(biāo)無法在整個(gè)電腦屏幕上移動(dòng)。因此,需要將矩形框的寬和高映射到電腦屏幕的寬和高。使用線性插值方法 np.interp(x, xp, fp)??簡單來說就是將變量x的范圍從原來的xp映射到fp。如:np.interp(x1, (pt1[0], pt2[0]), (0, wScr)),就是將x坐標(biāo)的范圍從原來的 pt1[0] 到 pt1[0]+w,映射到整個(gè)電腦屏幕?0?到 wScr。
返回電腦屏幕的寬和高:?autopy.screen.size()
移動(dòng)鼠標(biāo)的位置到坐標(biāo)(x,y): autopy.mouse.move(x, y)
autopy具體使用方法見下文:https://blog.csdn.net/qq_30462003/article/details/100130472
因此,我們在上述代碼中補(bǔ)充:
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 手部檢測方法
import time
# pip install autopy #鼠標(biāo)控制單元
import autopy#(1)導(dǎo)數(shù)視頻數(shù)據(jù)
wScr, hScr = autopy.screen.size() # 返回電腦屏幕的寬和高(1920.0, 1080.0)
wCam, hCam = 1280, 720 # 視頻顯示窗口的寬和高
pt1, pt2 = (100,100), (1100, 500) # 虛擬鼠標(biāo)的移動(dòng)范圍,左上坐標(biāo)pt1,右下坐標(biāo)pt2cap = cv2.VideoCapture(0) # 0代表自己電腦的攝像頭
cap.set(3, wCam) # 設(shè)置顯示框的寬度1280
cap.set(4, hCam) # 設(shè)置顯示框的高度720pTime = 0 # 設(shè)置第一幀開始處理的起始時(shí)間#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流圖像 maxHands=1, # 最多檢測一只手detectionCon=0.8, # 最小檢測置信度 minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 圖片是否成功接收、img幀圖像success, img = cap.read()# 翻轉(zhuǎn)圖像,使自身和攝像頭中的自己呈鏡像關(guān)系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉(zhuǎn),0代表豎直翻轉(zhuǎn)# 在圖像窗口上創(chuàng)建一個(gè)矩形框,在該區(qū)域內(nèi)移動(dòng)鼠標(biāo)cv2.rectangle(img, pt1, pt2, (0,255,255), 5)#(4)手部關(guān)鍵點(diǎn)檢測# 傳入每幀圖像, 返回手部關(guān)鍵點(diǎn)的坐標(biāo)信息(字典),繪制關(guān)鍵點(diǎn)后的圖像hands, img = detector.findHands(img, flipType=False) # 上面反轉(zhuǎn)過了,這里就不用再翻轉(zhuǎn)了# print(hands)# 如果能檢測到手那么就進(jìn)行下一步if hands:# 獲取手部信息hands中的21個(gè)關(guān)鍵點(diǎn)信息lmList = hands[0]['lmList'] # hands是由N個(gè)字典組成的列表,字典包每只手的關(guān)鍵點(diǎn)信息# 獲取食指指尖坐標(biāo),和中指指尖坐標(biāo)x1, y1 = lmList[8] # 食指尖的關(guān)鍵點(diǎn)索引號為8x2, y2 = lmList[12] # 中指指尖索引12#(5)檢查哪個(gè)手指是朝上的fingers = detector.fingersUp(hands[0]) # 傳入# print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指豎起# 如果食指豎起且中指彎下,就認(rèn)為是移動(dòng)鼠標(biāo)if fingers[1] == 1 and fingers[2] == 0:# 開始移動(dòng)時(shí),在食指指尖畫一個(gè)圓圈,看得更清晰一些cv2.circle(img, (x1,y1), 15, (255,255,0), cv2.FILLED) # 顏色填充整個(gè)圓#(6)確定鼠標(biāo)移動(dòng)的范圍# 將食指的移動(dòng)范圍從預(yù)制的窗口范圍,映射到電腦屏幕范圍x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))#(7)移動(dòng)鼠標(biāo)autopy.mouse.move(x3, y3) # 給出鼠標(biāo)移動(dòng)位置坐標(biāo)#(8)顯示圖像# 查看FPScTime = time.time() #處理完一幀圖像的時(shí)間fps = 1/(cTime-pTime)pTime = cTime #重置起始時(shí)間# 在視頻上顯示fps信息,先轉(zhuǎn)換成整數(shù)再變成字符串形式,文本顯示坐標(biāo),文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數(shù)據(jù)cv2.imshow('image', img) if cv2.waitKey(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
效果圖如下:
3. 點(diǎn)擊鼠標(biāo)
點(diǎn)擊鼠標(biāo)的思路是:如果食指和中指同時(shí)豎起,并且食指指尖和中指指尖之間的像素距離小于50時(shí),那么就認(rèn)為是點(diǎn)擊鼠標(biāo)。
檢測哪個(gè)手指是豎起的方法是上面已經(jīng)解釋過的?detector.fingersUp() 方法,檢測指尖距離的方法是: detector.findDistance(pt1, pt2, img), pt1 和 pt2 是兩個(gè)點(diǎn)的坐標(biāo),傳入img來繪制指尖連線圖。
點(diǎn)擊鼠標(biāo)的函數(shù),autopy.mouse.click()
由于用手指控制鼠標(biāo)時(shí),每一幀的坐標(biāo)位置的變化幅度較大,導(dǎo)致電腦鼠標(biāo)在手指控制下很容易晃動(dòng),很難準(zhǔn)確定位到一個(gè)目標(biāo)。因此需要平滑每一幀的坐標(biāo)變化,使坐標(biāo)變化更緩慢一些。
例如:cLocx = pLocx + (x3 - pLocx) / smooth,式中:前幀的鼠標(biāo)位置的x坐標(biāo) cLocx;前一幀的鼠標(biāo)位置的x坐標(biāo)?pLocx;當(dāng)前鼠標(biāo)位置的x坐標(biāo)?x3;自定義平滑系數(shù)smooth,值越大鼠標(biāo)移動(dòng)就越慢,平穩(wěn)性就越高。
因此,在上述代碼中補(bǔ)充:
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 手部檢測方法
import time
import autopy#(1)導(dǎo)數(shù)視頻數(shù)據(jù)
wScr, hScr = autopy.screen.size() # 返回電腦屏幕的寬和高(1920.0, 1080.0)
wCam, hCam = 1280, 720 # 視頻顯示窗口的寬和高
pt1, pt2 = (100,100), (1100, 500) # 虛擬鼠標(biāo)的移動(dòng)范圍,左上坐標(biāo)pt1,右下坐標(biāo)pt2cap = cv2.VideoCapture(0) # 0代表自己電腦的攝像頭
cap.set(3, wCam) # 設(shè)置顯示框的寬度1280
cap.set(4, hCam) # 設(shè)置顯示框的高度720pTime = 0 # 設(shè)置第一幀開始處理的起始時(shí)間pLocx, pLocy = 0, 0 # 上一幀時(shí)的鼠標(biāo)所在位置smooth = 4 # 自定義平滑系數(shù),讓鼠標(biāo)移動(dòng)平緩一些#(2)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流圖像 maxHands=1, # 最多檢測一只手detectionCon=0.8, # 最小檢測置信度 minTrackCon=0.5) # 最小跟蹤置信度#(3)處理每一幀圖像
while True:# 圖片是否成功接收、img幀圖像success, img = cap.read()# 翻轉(zhuǎn)圖像,使自身和攝像頭中的自己呈鏡像關(guān)系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉(zhuǎn),0代表豎直翻轉(zhuǎn)# 在圖像窗口上創(chuàng)建一個(gè)矩形框,在該區(qū)域內(nèi)移動(dòng)鼠標(biāo)cv2.rectangle(img, pt1, pt2, (0,255,255), 5)#(4)手部關(guān)鍵點(diǎn)檢測# 傳入每幀圖像, 返回手部關(guān)鍵點(diǎn)的坐標(biāo)信息(字典),繪制關(guān)鍵點(diǎn)后的圖像hands, img = detector.findHands(img, flipType=False) # 上面反轉(zhuǎn)過了,這里就不用再翻轉(zhuǎn)了# print(hands)# 如果能檢測到手那么就進(jìn)行下一步if hands:# 獲取手部信息hands中的21個(gè)關(guān)鍵點(diǎn)信息lmList = hands[0]['lmList'] # hands是由N個(gè)字典組成的列表,字典包每只手的關(guān)鍵點(diǎn)信息# 獲取食指指尖坐標(biāo),和中指指尖坐標(biāo)x1, y1 = lmList[8] # 食指尖的關(guān)鍵點(diǎn)索引號為8x2, y2 = lmList[12] # 中指指尖索引12#(5)檢查哪個(gè)手指是朝上的fingers = detector.fingersUp(hands[0]) # 傳入# print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指豎起# 如果食指豎起且中指彎下,就認(rèn)為是移動(dòng)鼠標(biāo)if fingers[1] == 1 and fingers[2] == 0:# 開始移動(dòng)時(shí),在食指指尖畫一個(gè)圓圈,看得更清晰一些cv2.circle(img, (x1,y1), 15, (255,255,0), cv2.FILLED) # 顏色填充整個(gè)圓#(6)確定鼠標(biāo)移動(dòng)的范圍# 將食指的移動(dòng)范圍從預(yù)制的窗口范圍,映射到電腦屏幕范圍x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))#(7)平滑,使手指在移動(dòng)鼠標(biāo)時(shí),鼠標(biāo)箭頭不會(huì)一直晃動(dòng)cLocx = pLocx + (x3 - pLocx) / smooth # 當(dāng)前的鼠標(biāo)所在位置坐標(biāo)cLocy = pLocy + (y3 - pLocy) / smooth #(8)移動(dòng)鼠標(biāo)autopy.mouse.move(cLocx, cLocy) # 給出鼠標(biāo)移動(dòng)位置坐標(biāo)# 更新前一幀的鼠標(biāo)所在位置坐標(biāo),將當(dāng)前幀鼠標(biāo)所在位置,變成下一幀的鼠標(biāo)前一幀所在位置pLocx, pLocy = cLocx, cLocy#(9)如果食指和中指都豎起,指尖距離小于某個(gè)值認(rèn)為是點(diǎn)擊鼠標(biāo)if fingers[1] == 1 and fingers[2] == 1: # 食指和中指都豎起# 計(jì)算食指尖和中指尖之間的距離distance,繪制好了的圖像img,指尖連線的信息infodistance, info, img = detector.findDistance((x1, y1), (x2, y2), img)# print(distance)# 當(dāng)指間距離小于50(像素距離)就認(rèn)為是點(diǎn)擊鼠標(biāo)if distance < 50:# 在食指尖畫個(gè)綠色的圓,表示點(diǎn)擊鼠標(biāo)cv2.circle(img, (x1,y1), 15, (0,255,0), cv2.FILLED)# 點(diǎn)擊鼠標(biāo)autopy.mouse.click()#(10)顯示圖像# 查看FPScTime = time.time() #處理完一幀圖像的時(shí)間fps = 1/(cTime-pTime)pTime = cTime #重置起始時(shí)間# 在視頻上顯示fps信息,先轉(zhuǎn)換成整數(shù)再變成字符串形式,文本顯示坐標(biāo),文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數(shù)據(jù)cv2.imshow('image', img) if cv2.waitKey(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
結(jié)果圖像展示,在繪圖板中每點(diǎn)擊一次就繪制一個(gè)圓圈。
總結(jié)
以上是生活随笔為你收集整理的【机器视觉案例】(8) AI视觉,手势控制电脑鼠标,附python完整代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【opencv】(13) 案例:停车场空
- 下一篇: 【面向对象编程】(1) 类实例化的基本方