【机器视觉案例】(10) AI视觉搭积木,手势移动虚拟物体,附python完整代码
各位同學好,今天和大家分享一下如何使用 opencv+mediapipe 完成手勢移動虛擬物體,可自定義各種形狀的物體,通過手勢搭積木。先放張圖看效果。
規(guī)則:當食指在某個物體內部,并且中指指尖和食指指尖的距離小于規(guī)定值,指尖連線的中點變成綠色,認為是選中物體,物體變成紅色。可以移動物體。物體中點隨著食指的位置移動,物體移動到指定位置后,指尖距離大于規(guī)定值,物體停下,變成淡藍色。
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個手部關鍵點信息如下,本節(jié)我們主要研究食指根部"5"和小指根部'17'的坐標信息。
2. 檢測手部關鍵點
(1) cvzone.HandTrackingModule.HandDetector() ?是手部關鍵點檢測方法
參數:
mode: 默認為 False,將輸入圖像視為視頻流。它將嘗試在第一個輸入圖像中檢測手,并在成功檢測后進一步定位手的坐標。在隨后的圖像中,一旦檢測到所有 maxHands 手并定位了相應的手的坐標,它就會跟蹤這些坐標,而不會調用另一個檢測,直到它失去對任何一只手的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設置為 True,則在每個輸入圖像上運行手部檢測,用于處理一批靜態(tài)的、可能不相關的圖像。
maxHands: 最多檢測幾只手,默認為 2
detectionCon: 手部檢測模型的最小置信值(0-1之間),超過閾值則檢測成功。默認為 0.5
minTrackingCon: 坐標跟蹤模型的最小置信值 (0-1之間),用于將手部坐標視為成功跟蹤,不成功則在下一個輸入圖像上自動調用手部檢測。將其設置為更高的值可以提高解決方案的穩(wěn)健性,但代價是更高的延遲。如果 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, # 靜態(tài)圖模式,若為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(1) & 0xFF==27: #每幀滯留1毫秒后消失,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': 'Right'}]
[{'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': 'Right'}]
-----------------------------------------------------------------
圖像顯示結果如下:
3. 繪制虛擬物體
首先,在提取視頻幀圖像之前先設置好虛擬物體的初始位置,將每個矩形的左上坐標點[ptx, pty]保存在一個列表中?ptList.append([ptx, pty]),因為后續(xù)移動物體時,每次移動需要改變單個物體的位置,而其他物體的位置不變,保存在列表中易于后需更改坐標位置。
在讀取視頻幀圖像之后通過for循環(huán)遍歷每個矩形的左上坐標 pt,在圖像上繪制出來。為了顯示的清晰一些,采用半透明矩形,透明度 alphaReserve 等于0時全顏色填充,等于1時無填充。
import cv2
import time
from cvzone.HandTrackingModule import HandDetector # 導入手部檢測模塊#(1)視頻捕獲
cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭
cap.set(3, 1280) # 設置圖像顯示窗口的寬
cap.set(4, 720) # 設置圖像顯示窗口的高pTime = 0 # 處理一幀圖像的初始時間color = (255,255,0) # 可移動物體的默認顏色w, h = 150, 150 # 矩形寬和高#(2)在頻幕上構造物體的函數
def creObj(img, color, ptx, pty, w, h):# 透明矩形參數設置alphaReserve = 0.6 # 透明度BChannel, GChannel, RChannel = color # 設置矩形顏色 yMin, yMax = pty, pty+h # 矩形框的y坐標范圍xMin, xMax = ptx, ptx+w # 矩形框的y坐標范圍# 繪制透明矩形img[yMin:yMax, xMin:xMax, 0] = img[yMin:yMax, xMin:xMax, 0] * alphaReserve + BChannel * (1 - alphaReserve)img[yMin:yMax, xMin:xMax, 1] = img[yMin:yMax, xMin:xMax, 1] * alphaReserve + GChannel * (1 - alphaReserve)img[yMin:yMax, xMin:xMax, 2] = img[yMin:yMax, xMin:xMax, 2] * alphaReserve + RChannel * (1 - alphaReserve)# 美化邊界框line = 35 # 邊緣線段長度cv2.rectangle(img, (ptx,pty), (ptx+w,pty+h), (255,0,255), 2) # 邊框cv2.line(img, (ptx,pty), (ptx,pty+line), (0,255,255), 5) # 左上角cv2.line(img, (ptx,pty), (ptx+line,pty), (0,255,255), 5) cv2.line(img, (ptx+w,pty), (ptx+w-line,pty), (0,255,255), 5) # 右上角cv2.line(img, (ptx+w,pty), (ptx+w,pty+line), (0,255,255), 5) cv2.line(img, (ptx,pty+h), (ptx+line,pty+h), (0,255,255), 5) # 左下角cv2.line(img, (ptx,pty+h), (ptx,pty+h-line), (0,255,255), 5) cv2.line(img, (ptx+w,pty+h), (ptx+w-line,pty+h), (0,255,255), 5) # 右下角cv2.line(img, (ptx+w,pty+h), (ptx+w,pty+h-line), (0,255,255), 5) # 返回繪制后的圖像return img#(3)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流maxHands=1, # 最多檢測一只手detectionCon=0.8, # 手部檢測的最小置信度minTrackCon=0.5) # 手部跟蹤的最小置信度#(4)在屏幕上創(chuàng)建初始矩形
ptList = [] # 存放每個矩形的左上角坐標# 通過循環(huán)創(chuàng)建9個矩形,初始排列方式為3行3列
for i in range(3): # 3行for j in range(3): # 3列# 指定每個矩形的左上角坐標ptx = 200 * j + 100 # x坐標,起始位置為x=100,水平方向兩個矩形間隔200個像素pty = 200 * i + 100 # y坐標,起始位置為y=100,每次換行下移200個像素# 將每個矩形的左上角坐標保存起來ptList.append([ptx, pty])#(5)處理每一幀視頻圖像
while True:# 返回是否讀取成功和讀取的圖像success, img = cap.read()# 圖像翻轉,呈鏡像關系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉,0代表豎直翻轉#(6)手部關鍵點檢測# 返回檢測到的手部關鍵點信息,以及繪制關鍵點后的圖像hands, img = detector.findHands(img, flipType=False) # 由于上面翻轉過圖像了,這里就設置flipType不翻轉 #(7)繪制可移動物體for pt in ptList: # 遍歷所有矩形的左上角 img = creObj(img, color, pt[0], pt[1], w, h)#(8)顯示圖像# 記錄執(zhí)行時間 cTime = time.time() # 計算fpsfps = 1/(cTime-pTime)# 重置起始時間pTime = cTime# 把fps顯示在窗口上;img畫板;取整的fps值;顯示位置的坐標;設置字體;字體比例;顏色;厚度cv2.putText(img, str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)# 顯示圖像cv2.imshow('image', img) #窗口名,圖像變量if cv2.waitKey(1) & 0xFF==27: #每幀滯留1毫秒后消失break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
圖像顯示結果如下:
4. 移動物體
從下面代碼的第(7)步開始,計算食指指尖 lmList[8] 和中指指尖?lmList[12] 之間的距離,并繪制指尖連線cv2.line。使用計算平方和再開根的方法math.sqrt(),計算指尖距離。如果像素距離小于80就認為是選擇該物體,指尖連線中點變成綠色。
第(8)步確定食指在哪個矩形的內部。遍歷所有的矩形的左上角坐標ptList,如果食指關鍵點坐標在某個矩形框內部,就記錄下該矩形所在列表中的索引changed = index,接下來改變這個矩形的坐標位置。
找到了食指在哪個矩形內之后,如果指尖距離小于規(guī)定值,代表移動該物體,讓該矩形的中點落在食指關鍵點的位置?cx, cy = finTip,那么矩形就可以跟著食指一起移動了,并且,更改每一幀的矩形的左上坐標 ptList[index] = pt,時刻改變矩形在屏幕上的位置。
有時食指關鍵點會在幾個矩形的內部,這樣的話,這幾個矩形的中點都變成了食指指尖關鍵點,使這幾個矩形都重合在一起,為了避免這種情況,當我們選擇了一個矩形時,就?break?斷開當前循壞,不再判斷食指在哪個物體內部,這樣就可以每次只移動一個物體。
import cv2
import time
import math
from cvzone.HandTrackingModule import HandDetector # 導入手部檢測模塊#(1)視頻捕獲
cap = cv2.VideoCapture(0) # 0代表電腦自帶的攝像頭
cap.set(3, 1280) # 設置圖像顯示窗口的寬
cap.set(4, 720) # 設置圖像顯示窗口的高pTime = 0 # 處理一幀圖像的初始時間changed = None # 初始狀態(tài)不需要改變矩形顏色color = (255,255,0) # 可移動物體的默認顏色medColor = (255,0,0) # 中指和食指指尖中點的初始顏色w, h = 150, 150 # 矩形寬和高#(2)在頻幕上構造物體的函數
def creObj(img, color, ptx, pty, w, h):# 透明矩形參數設置alphaReserve = 0.6 # 透明度BChannel, GChannel, RChannel = color # 設置矩形顏色 yMin, yMax = pty, pty+h # 矩形框的y坐標范圍xMin, xMax = ptx, ptx+w # 矩形框的y坐標范圍# 繪制透明矩形img[yMin:yMax, xMin:xMax, 0] = img[yMin:yMax, xMin:xMax, 0] * alphaReserve + BChannel * (1 - alphaReserve)img[yMin:yMax, xMin:xMax, 1] = img[yMin:yMax, xMin:xMax, 1] * alphaReserve + GChannel * (1 - alphaReserve)img[yMin:yMax, xMin:xMax, 2] = img[yMin:yMax, xMin:xMax, 2] * alphaReserve + RChannel * (1 - alphaReserve)# 美化邊界框line = 35 # 邊緣線段長度cv2.rectangle(img, (ptx,pty), (ptx+w,pty+h), (255,0,255), 2) # 邊框cv2.line(img, (ptx,pty), (ptx,pty+line), (0,255,255), 5) # 左上角cv2.line(img, (ptx,pty), (ptx+line,pty), (0,255,255), 5) cv2.line(img, (ptx+w,pty), (ptx+w-line,pty), (0,255,255), 5) # 右上角cv2.line(img, (ptx+w,pty), (ptx+w,pty+line), (0,255,255), 5) cv2.line(img, (ptx,pty+h), (ptx+line,pty+h), (0,255,255), 5) # 左下角cv2.line(img, (ptx,pty+h), (ptx,pty+h-line), (0,255,255), 5) cv2.line(img, (ptx+w,pty+h), (ptx+w-line,pty+h), (0,255,255), 5) # 右下角cv2.line(img, (ptx+w,pty+h), (ptx+w,pty+h-line), (0,255,255), 5) # 返回繪制后的圖像return img#(3)接收手部檢測方法
detector = HandDetector(mode=False, # 視頻流maxHands=1, # 最多檢測一只手detectionCon=0.8, # 手部檢測的最小置信度minTrackCon=0.5) # 手部跟蹤的最小置信度#(4)在屏幕上創(chuàng)建初始矩形
ptList = [] # 存放每個矩形的左上角坐標# 通過循環(huán)創(chuàng)建9個矩形,初始排列方式為3行3列
for i in range(3): # 3行for j in range(3): # 3列# 指定每個矩形的左上角坐標ptx = 200 * j + 100 # x坐標,起始位置為x=100,水平方向兩個矩形間隔200個像素pty = 200 * i + 100 # y坐標,起始位置為y=100,每次換行下移200個像素# 將每個矩形的左上角坐標保存起來ptList.append([ptx, pty])#(4)處理每一幀視頻圖像
while True:# 返回是否讀取成功和讀取的圖像success, img = cap.read()# 圖像翻轉,呈鏡像關系img = cv2.flip(img, flipCode=1) # 1代表水平翻轉,0代表豎直翻轉#(5)手部關鍵點檢測# 返回檢測到的手部關鍵點信息,以及繪制關鍵點后的圖像hands, img = detector.findHands(img, flipType=False) # 由于上面翻轉過圖像了,這里就設置flipType不翻轉 #(6)繪制可移動物體for index, pt in enumerate(ptList): # 遍歷所有矩形的左上角# 如果索引等于需要改變的矩形索引,就改變該矩形的顏色,否則就不變if index == changed:color = (0,0,255)else:color = (255,255,0)img = creObj(img, color, pt[0], pt[1], w, h)#(7)計算食指和中指間的距離 if hands: # 如果檢測到手部信息才接下去執(zhí)行# 將該只手的21個關鍵點坐標提取出來,hands是字典存放手信息lmList = hands[0]['lmList']# 獲取食指指尖的坐標(像素坐標)finTip = lmList[8] # 存放x和y坐標# 獲取中指指尖坐標checkTip = lmList[12]# 繪制食指和中指指尖的連線cv2.line(img, finTip, checkTip, (255,0,0), 9)cv2.circle(img, finTip, 15, (255,0,0), cv2.FILLED) # 以食指尖為圓心畫圓cv2.circle(img, checkTip, 15, (255,0,0), cv2.FILLED) # 以中指尖為圓心畫圓# 以兩指尖的中點為圓心畫圓,如果距離小于規(guī)定值,顏色改變cv2.circle(img, ((finTip[0]+checkTip[0])//2, (finTip[1]+checkTip[1])//2), 15, medColor, cv2.FILLED) # 計算食指和中指間的距離distance = math.sqrt((finTip[0]-checkTip[0])**2 + (finTip[1]-checkTip[1])**2)# 如果距離小于80認為是選擇物體,指尖指尖中點的顏色改變if distance < 80:medColor = (0,255,0)else: # 如果大于80,就重置指尖中點顏色medColor = (255,0,0)#(8)判斷食指指尖在哪個矩形的內部for index, pt in enumerate(ptList): # 遍歷所有矩形的左上角坐標ptx, pty = pt # 提取每個矩形左上角的x和y坐標 # finTip保存食指指尖的x和y坐標if ptx<=finTip[0]<=ptx+w and pty<=finTip[1]<=pty+h: # 如果食指指尖在某個矩形框內部# 記錄下該矩形的索引changed = index# 如果食指和中指間的距離小于80,那就認為是移動物體if distance < 80:# 讓物體中心點位于指尖位置cx, cy = finTip# 改變左上角坐標pt = [cx-w//2, cy-h//2]# 改變列表中的該索引對應的左上角坐標ptList[index] = pt# 找到了就退出循環(huán),代表一次只移動一個矩形break# 如果物體移動到了指定位置,那就松手else: # 重置矩形的顏色color = (255,255,0) changed = None #(7)顯示圖像# 記錄執(zhí)行時間 cTime = time.time() # 計算fpsfps = 1/(cTime-pTime)# 重置起始時間pTime = cTime# 把fps顯示在窗口上;img畫板;取整的fps值;顯示位置的坐標;設置字體;字體比例;顏色;厚度cv2.putText(img, str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)# 顯示圖像cv2.imshow('image', img) #窗口名,圖像變量if cv2.waitKey(1) & 0xFF==27: #每幀滯留1毫秒后消失break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
沒選擇物體時:
移動物體時:
總結
以上是生活随笔為你收集整理的【机器视觉案例】(10) AI视觉搭积木,手势移动虚拟物体,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【面向对象编程】(2) 类属性的定义及使
- 下一篇: 【面向对象编程】(3) 类之间的交互,依