OpenCV實(shí)現(xiàn)手勢(shì)虛擬拖拽的使用示例(附demo)
一、主要步驟及庫(kù)的功能介紹
1.主要步驟
要實(shí)現(xiàn)本次實(shí)驗(yàn),主要步驟如下:
- 導(dǎo)入OpenCV庫(kù)。
- 通過(guò)OpenCV讀取攝像頭的視頻流。
- 使用膚色檢測(cè)算法(如色彩空間轉(zhuǎn)換和閾值分割)來(lái)識(shí)別手部區(qū)域。
- 對(duì)手部區(qū)域進(jìn)行輪廓檢測(cè),找到手的輪廓。
- 根據(jù)手的輪廓,獲取手指關(guān)鍵點(diǎn)的像素坐標(biāo)。對(duì)于拖拽手勢(shì),可以關(guān)注食指和中指的位置。
- 計(jì)算食指和中指指尖之間的距離并判斷是否滿足條件觸發(fā)拖拽動(dòng)作。
- 如果滿足條件,可以使用勾股定理計(jì)算距離,并將矩形區(qū)域變色以示觸發(fā)拖拽。
- 根據(jù)手指的位置更新矩形的坐標(biāo),使矩形跟隨手指運(yùn)動(dòng)。
- 當(dāng)手指放開(kāi)時(shí)停止矩形的移動(dòng)。
2.需要的庫(kù)介紹
導(dǎo)入的庫(kù)在實(shí)現(xiàn)手勢(shì)虛擬拖拽的代碼中起著重要的作用,下面是對(duì)每個(gè)庫(kù)的簡(jiǎn)要介紹:
OpenCV (cv2): OpenCV是一個(gè)開(kāi)源的計(jì)算機(jī)視覺(jué)庫(kù),提供了豐富的圖像和視頻處理功能,使用OpenCV來(lái)讀取攝像頭視頻流、進(jìn)行圖像處理和計(jì)算。
mediapipe (mp): mediapipe提供一系列預(yù)訓(xùn)練的機(jī)器學(xué)習(xí)模型和工具,用于實(shí)現(xiàn)計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)任務(wù)。我們使用mediapipe來(lái)進(jìn)行手部關(guān)鍵點(diǎn)檢測(cè)和姿勢(shì)估計(jì)。
time: 我們使用time庫(kù)來(lái)計(jì)時(shí)和進(jìn)行時(shí)間相關(guān)的操作。
math: 在本次實(shí)驗(yàn)我們使用math庫(kù)來(lái)計(jì)算距離和角度。
二、導(dǎo)入所需要的模塊
# 導(dǎo)入OpenCV import cv2 # 導(dǎo)入mediapipe import mediapipe as mp # 導(dǎo)入其他依賴包 import time import math
三、方塊管理類
(SquareManager)是一個(gè)方塊管理器,用于創(chuàng)建、顯示、更新和處理方塊的相關(guān)操作。
1.初始化方塊管理器
初始化方塊管理器,傳入方塊的長(zhǎng)度(rect_width)
作為參數(shù),并初始化方塊列表、距離、激活狀態(tài)和激活的方塊ID等屬性。
class SquareManager: def __init__(self, rect_width): # 方框長(zhǎng)度 self.rect_width = rect_width # 方塊列表 self.square_count = 0 self.rect_left_x_list = [] self.rect_left_y_list = [] self.alpha_list = [] # 中指與矩形左上角點(diǎn)的距離 self.L1 = 0 self.L2 = 0 # 激活移動(dòng)模式 self.drag_active = False # 激活的方塊ID self.active_index = -1
2.創(chuàng)建一個(gè)方塊
創(chuàng)建一個(gè)方塊,將方塊的左上角坐標(biāo)和透明度添加到相應(yīng)的列表中。
# 創(chuàng)建一個(gè)方塊,但是沒(méi)有顯示 def create(self, rect_left_x, rect_left_y, alpha=0.4): # 將方塊的左上角坐標(biāo)和透明度添加到相應(yīng)的列表中 self.rect_left_x_list.append(rect_left_x) self.rect_left_y_list.append(rect_left_y) self.alpha_list.append(alpha) self.square_count += 1
3.更新顯示方塊的位置
根據(jù)方塊的狀態(tài),在圖像上繪制方塊,并使用透明度將疊加圖像疊加到原始圖像上。
# 更新顯示方塊的位置 def display(self, class_obj): # 遍歷方塊列表 for i in range(0, self.square_count): x = self.rect_left_x_list[i] y = self.rect_left_y_list[i] alpha = self.alpha_list[i] overlay = class_obj.image.copy() # 如果方塊處于激活狀態(tài),繪制紫色方塊;否則繪制藍(lán)色方塊 if (i == self.active_index): cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 255), -1) else: cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 0), -1) # 使用透明度將疊加圖像疊加到原始圖像上 class_obj.image = cv2.addWeighted(overlay, alpha, class_obj.image, 1 - alpha, 0)
4.判斷落點(diǎn)方塊
判斷給定的坐標(biāo)是否在方塊內(nèi),并返回方塊的ID。
# 判斷落在哪個(gè)方塊上,返回方塊的ID def checkOverlay(self, check_x, check_y): # 遍歷方塊列表 for i in range(0, self.square_count): x = self.rect_left_x_list[i] y = self.rect_left_y_list[i] # 檢查指定點(diǎn)是否在方塊內(nèi) if (x < check_x < (x + self.rect_width)) and (y < check_y < (y + self.rect_width)): # 保存被激活的方塊ID self.active_index = i return i return -1
5.計(jì)算距離、更新位置
??setLen? 方法:計(jì)算激活方塊與指尖的距離。
??updateSquare? 方法:根據(jù)給定的新坐標(biāo)更新激活方塊的位置。
# 計(jì)算與指尖的距離 def setLen(self, check_x, check_y): # 計(jì)算距離 self.L1 = check_x - self.rect_left_x_list[self.active_index] self.L2 = check_y - self.rect_left_y_list[self.active_index] # 更新方塊位置 def updateSquare(self, new_x, new_y): self.rect_left_x_list[self.active_index] = new_x - self.L1 self.rect_left_y_list[self.active_index] = new_y - self.L2
三、識(shí)別控制類
1.初始化識(shí)別控制類
class HandControlVolume: def __init__(self): # 初始化mediapipe self.mp_drawing = mp.solutions.drawing_utils self.mp_drawing_styles = mp.solutions.drawing_styles self.mp_hands = mp.solutions.hands # 中指與矩形左上角點(diǎn)的距離 self.L1 = 0 self.L2 = 0 # image實(shí)例,以便另一個(gè)類調(diào)用 self.image = None
HandControlVolume
用于初始化mediapipe
以及存儲(chǔ)中指與矩形左上角點(diǎn)的距離和image
實(shí)例。
__init__
方法:在初始化對(duì)象時(shí),初始化mediapipe,包括drawing_utils、drawing_styles和hands。此外,還初始化了中指與矩形左上角點(diǎn)的距離和image實(shí)例。
通過(guò)mediapipe,可以進(jìn)行手部關(guān)鍵點(diǎn)檢測(cè)和姿勢(shì)估計(jì),進(jìn)而進(jìn)行手勢(shì)識(shí)別和處理。為了使其他類能夠調(diào)用image實(shí)例,將其作為該類的屬性進(jìn)行存儲(chǔ),方便地處理手勢(shì)識(shí)別和控制操作。
2.主函數(shù)
這部分代碼主要用于初始化和準(zhǔn)備處理視頻流以進(jìn)行手勢(shì)識(shí)別和交互。
def recognize(self): # 計(jì)算刷新率 fpsTime = time.time() # OpenCV讀取視頻流 cap = cv2.VideoCapture(0) # 視頻分辨率 resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 畫(huà)面顯示初始化參數(shù) rect_percent_text = 0 # 初始化方塊管理器 squareManager = SquareManager(150) # 創(chuàng)建多個(gè)方塊 for i in range(0, 5): squareManager.create(200 * i + 20, 200, 0.6) with self.mp_hands.Hands(min_detection_confidence=0.7, min_tracking_confidence=0.5, max_num_hands=2) as hands: while cap.isOpened(): # 初始化矩形 success, self.image = cap.read() self.image = cv2.resize(self.image, (resize_w, resize_h)) if not success: print("空幀.") continue
resize_w
和resize_h
:根據(jù)攝像頭分辨率獲取的視頻幀的寬度和高度,并作為后續(xù)處理的圖像尺寸進(jìn)行縮放。rect_percent_text
:畫(huà)面顯示初始化參數(shù),可能被用于屏幕上的文本顯示。squareManager
:初始化了方塊管理器類的實(shí)例,并設(shè)置方塊的長(zhǎng)度為150。
使用一個(gè)循環(huán),創(chuàng)建了五個(gè)方塊,并通過(guò)create
方法將其添加到方塊管理器中。進(jìn)入循環(huán),從視頻流中讀取幀圖像,并將其調(diào)整為指定的尺寸。如果成功讀取幀圖像,則會(huì)進(jìn)一步處理,否則將輸出錯(cuò)誤消息。
3.提高性能和處理圖像
self.image.flags.writeable = False # 轉(zhuǎn)為RGB self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) # 鏡像 self.image = cv2.flip(self.image, 1) # mediapipe模型處理 results = hands.process(self.image) self.image.flags.writeable = True self.image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR)
self.image.flags.writeable = False
:設(shè)置圖像為不可寫(xiě),以提高性能并避免數(shù)據(jù)拷貝。self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
:將BGR格式的圖像轉(zhuǎn)換為RGB格式,因?yàn)閙ediapipe模型處理的輸入圖像需要是RGB格式。self.image = cv2.flip(self.image, 1)
:將圖像進(jìn)行鏡像翻轉(zhuǎn),以與mediapipe模型期望的手部位置對(duì)應(yīng)。results = hands.process(self.image)
:將處理后的圖像傳遞給mediapipe的hands模型,進(jìn)行手勢(shì)識(shí)別和處理。self.image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR)
:將圖像從RGB格式轉(zhuǎn)換回BGR格式,以便后續(xù)的顯示和處理。
4.檢測(cè)手掌,標(biāo)記關(guān)鍵點(diǎn)和連接關(guān)系
if results.multi_hand_landmarks: # 遍歷每個(gè)手掌 for hand_landmarks in results.multi_hand_landmarks: # 在畫(huà)面標(biāo)注手指 self.mp_drawing.draw_landmarks( self.image, hand_landmarks, self.mp_hands.HAND_CONNECTIONS, self.mp_drawing_styles.get_default_hand_landmarks_style(), self.mp_drawing_styles.get_default_hand_connections_style())
if results.multi_hand_landmarks:
:檢查是否檢測(cè)到手掌。如果有檢測(cè)到手掌,則進(jìn)入下一步處理;否則跳過(guò)。for hand_landmarks in results.multi_hand_landmarks:
:遍歷檢測(cè)到的每個(gè)手掌。self.mp_drawing.draw_landmarks
:使用mediapipe的draw_landmarks
方法,在圖像上標(biāo)記手指的關(guān)鍵點(diǎn)和連接關(guān)系。
self.image
:輸入的圖像。hand_landmarks
:手掌的關(guān)鍵點(diǎn)。
self.mp_hands.HAND_CONNECTIONS
:手指之間的連接關(guān)系。
5.解析檢測(cè)到的手掌并提取手指的關(guān)鍵點(diǎn)
檢測(cè)到的手掌并提取手指的關(guān)鍵點(diǎn),然后將手指的坐標(biāo)存儲(chǔ)起來(lái)。
landmark_list = [] # 用來(lái)存儲(chǔ)手掌范圍的矩形坐標(biāo) paw_x_list = [] paw_y_list = [] for landmark_id, finger_axis in enumerate( hand_landmarks.landmark): landmark_list.append([ landmark_id, finger_axis.x, finger_axis.y, finger_axis.z ]) paw_x_list.append(finger_axis.x) paw_y_list.append(finger_axis.y) if landmark_list: # 比例縮放到像素 ratio_x_to_pixel = lambda x: math.ceil(x * resize_w) ratio_y_to_pixel = lambda y: math.ceil(y * resize_h) # 設(shè)計(jì)手掌左上角、右下角坐標(biāo) paw_left_top_x, paw_right_bottom_x = map(ratio_x_to_pixel, [min(paw_x_list), max(paw_x_list)]) paw_left_top_y, paw_right_bottom_y = map(ratio_y_to_pixel, [min(paw_y_list), max(paw_y_list)]) # 給手掌畫(huà)框框 cv2.rectangle(self.image, (paw_left_top_x - 30, paw_left_top_y - 30), (paw_right_bottom_x + 30, paw_right_bottom_y + 30), (0, 255, 0), 2) # 獲取中指指尖坐標(biāo) middle_finger_tip = landmark_list[12] middle_finger_tip_x = ratio_x_to_pixel(middle_finger_tip[1]) middle_finger_tip_y = ratio_y_to_pixel(middle_finger_tip[2]) # 獲取食指指尖坐標(biāo) index_finger_tip = landmark_list[8] index_finger_tip_x = ratio_x_to_pixel(index_finger_tip[1]) index_finger_tip_y = ratio_y_to_pixel(index_finger_tip[2]) # 中間點(diǎn) between_finger_tip = (middle_finger_tip_x + index_finger_tip_x) // 2, ( middle_finger_tip_y + index_finger_tip_y) // 2 thumb_finger_point = (middle_finger_tip_x, middle_finger_tip_y) index_finger_point = (index_finger_tip_x, index_finger_tip_y)
landmark_list
:一個(gè)列表,用于存儲(chǔ)手指的關(guān)鍵點(diǎn)信息。paw_x_list
和paw_y_list
:用于存儲(chǔ)手掌范圍的矩形框的橫縱坐標(biāo)。在循環(huán)中,將每個(gè)手指的關(guān)鍵點(diǎn)的索引、x坐標(biāo)、y坐標(biāo)和z坐標(biāo)存儲(chǔ)在
landmark_list
中,同時(shí)將手掌范圍的橫縱坐標(biāo)存儲(chǔ)在paw_x_list
和paw_y_list
中。
如果landmark_list
不為空,即有手指的關(guān)鍵點(diǎn)被檢測(cè)到ratio_x_to_pixel
和 ratio_y_to_pixel
:兩個(gè)lambda函數(shù),用于將相對(duì)比例轉(zhuǎn)換為像素坐標(biāo)的函數(shù)。根據(jù)手掌范圍的矩形坐標(biāo),計(jì)算手掌區(qū)域的左上角和右下角坐標(biāo),并畫(huà)出方框。使用landmark_list
中的信息獲取中指指尖坐標(biāo)和食指指尖坐標(biāo),并將它們轉(zhuǎn)換為像素坐標(biāo)。計(jì)算中指指尖坐標(biāo)和食指指尖坐標(biāo)的中間點(diǎn)。將中指指尖的坐標(biāo)和食指指尖的坐標(biāo)存儲(chǔ)在thumb_finger_point
和index_finger_point
中。
解析檢測(cè)到的手掌信息,并提取手指的關(guān)鍵點(diǎn)坐標(biāo),將手指坐標(biāo)轉(zhuǎn)換為像素坐標(biāo),并將中指指尖和食指指尖的位置標(biāo)記在圖像上。
6.繪制指尖圓圈和連接線,計(jì)算距離
circle_func = lambda point: cv2.circle(self.image, point, 10, (255, 0, 255), -1) self.image = circle_func(thumb_finger_point) self.image = circle_func(index_finger_point) self.image = circle_func(between_finger_tip) # 畫(huà)2點(diǎn)連線 self.image = cv2.line(self.image, thumb_finger_point, index_finger_point, (255, 0, 255), 5) # 勾股定理計(jì)算長(zhǎng)度 line_len = math.hypot((index_finger_tip_x - middle_finger_tip_x), (index_finger_tip_y - middle_finger_tip_y)) # 將指尖距離映射到文字 rect_percent_text = math.ceil(line_len)
cv2.line
函數(shù),在圖像上繪制中指指尖和食指指尖之間的連接線。math.hypot
函數(shù)計(jì)算直角三角形斜邊的長(zhǎng)度。將指尖之間的距離映射到
rect_percent_text
變量中,用作后續(xù)文本顯示的參數(shù)。
7.跟蹤手指之間的距離
if squareManager.drag_active: # 更新方塊 squareManager.updateSquare(between_finger_tip[0], between_finger_tip[1]) if (line_len > 100): # 取消激活 squareManager.drag_active = False squareManager.active_index = -1 elif (line_len < 100) and (squareManager.checkOverlay(between_finger_tip[0], between_finger_tip[1]) != -1) and ( squareManager.drag_active == False): # 激活 squareManager.drag_active = True # 計(jì)算距離 squareManager.setLen(between_finger_tip[0], between_finger_tip[1])
如果squareManager
的drag_active
屬性為True,即矩形的移動(dòng)模式已經(jīng)激活,使用squareManager.updateSquare
方法更新矩形的位置。如果兩個(gè)手指之間的距離大于100,即手指之間的距離超過(guò)了閾值,取消矩形的激活模式,將drag_active
設(shè)置為False,將active_index
設(shè)置為-1。
否則,如果兩個(gè)手指之間的距離小于100,且手指之間存在重疊的矩形,并且矩形的移動(dòng)模式未激活。激活矩形的移動(dòng)模式,將drag_active
設(shè)置為True。根據(jù)手指之間的距離,計(jì)算并設(shè)置矩形的長(zhǎng)度,使用squareManager.setLen
方法。
8.顯示圖像
squareManager.display(self) # 顯示距離 cv2.putText(self.image, "Distance:" + str(rect_percent_text), (10, 120), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3) # 顯示當(dāng)前激活 cv2.putText(self.image, "Active:" + ( "None" if squareManager.active_index == -1 else str(squareManager.active_index)), (10, 170), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3) # 顯示刷新率FPS cTime = time.time() fps_text = 1 / (cTime - fpsTime) fpsTime = cTime cv2.putText(self.image, "FPS: " + str(int(fps_text)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3) # 顯示畫(huà)面 cv2.imshow('virtual drag and drop', self.image) if cv2.waitKey(5) & 0xFF == 27: break cap.release() control = HandControlVolume() control.recognize()
主函數(shù)(recognize
)的結(jié)尾部分,用于顯示圖像、矩形的狀態(tài)和刷新率,并等待按鍵響應(yīng)。使用squareManager.display
方法顯示矩形。cv2.waitKey
函數(shù)等待按鍵輸入,如果按下的鍵是ESC鍵(對(duì)應(yīng)的ASCII碼為27),則退出循環(huán)。
在屏幕上顯示處理后的圖像、矩形的狀態(tài)和刷新率,并等待按鍵響應(yīng)。這樣可以實(shí)現(xiàn)交互式的虛擬拖放功能。接下來(lái)我們看一下實(shí)際的操作效果。
四、實(shí)戰(zhàn)演示
通過(guò)演示我們可以實(shí)現(xiàn)通過(guò)手部對(duì)方塊進(jìn)行拖拽,效果可以達(dá)到良好的狀態(tài)。
五、源碼分享
import cv2 import mediapipe as mp import time import math class SquareManager: def __init__(self, rect_width): # 方框長(zhǎng)度 self.rect_width = rect_width # 方塊list self.square_count = 0 self.rect_left_x_list = [] self.rect_left_y_list = [] self.alpha_list = [] # 中指與矩形左上角點(diǎn)的距離 self.L1 = 0 self.L2 = 0 # 激活移動(dòng)模式 self.drag_active = False # 激活的方塊ID self.active_index = -1 # 創(chuàng)建一個(gè)方塊,但是沒(méi)有顯示 def create(self, rect_left_x, rect_left_y, alpha=0.4): self.rect_left_x_list.append(rect_left_x) self.rect_left_y_list.append(rect_left_y) self.alpha_list.append(alpha) self.square_count += 1 # 更新位置 def display(self, class_obj): for i in range(0, self.square_count): x = self.rect_left_x_list[i] y = self.rect_left_y_list[i] alpha = self.alpha_list[i] overlay = class_obj.image.copy() if (i == self.active_index): cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 255), -1) else: cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 0), -1) # Following line overlays transparent rectangle over the self.image class_obj.image = cv2.addWeighted(overlay, alpha, class_obj.image, 1 - alpha, 0) # 判斷落在哪個(gè)方塊上,返回方塊的ID def checkOverlay(self, check_x, check_y): for i in range(0, self.square_count): x = self.rect_left_x_list[i] y = self.rect_left_y_list[i] if (x < check_x < (x + self.rect_width)) and (y < check_y < (y + self.rect_width)): # 保存被激活的方塊ID self.active_index = i return i return -1 # 計(jì)算與指尖的距離 def setLen(self, check_x, check_y): # 計(jì)算距離 self.L1 = check_x - self.rect_left_x_list[self.active_index] self.L2 = check_y - self.rect_left_y_list[self.active_index] # 更新方塊 def updateSquare(self, new_x, new_y): # print(self.rect_left_x_list[self.active_index]) self.rect_left_x_list[self.active_index] = new_x - self.L1 self.rect_left_y_list[self.active_index] = new_y - self.L2 # 識(shí)別控制類 class HandControlVolume: def __init__(self): # 初始化medialpipe self.mp_drawing = mp.solutions.drawing_utils self.mp_drawing_styles = mp.solutions.drawing_styles self.mp_hands = mp.solutions.hands # 中指與矩形左上角點(diǎn)的距離 self.L1 = 0 self.L2 = 0 # image實(shí)例,以便另一個(gè)類調(diào)用 self.image = None # 主函數(shù) def recognize(self): # 計(jì)算刷新率 fpsTime = time.time() # OpenCV讀取視頻流 cap = cv2.VideoCapture(0) # 視頻分辨率 resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 畫(huà)面顯示初始化參數(shù) rect_percent_text = 0 # 初始化方塊管理器 squareManager = SquareManager(150) # 創(chuàng)建多個(gè)方塊 for i in range(0, 5): squareManager.create(200 * i + 20, 200, 0.6) with self.mp_hands.Hands(min_detection_confidence=0.7, min_tracking_confidence=0.5, max_num_hands=2) as hands: while cap.isOpened(): # 初始化矩形 success, self.image = cap.read() self.image = cv2.resize(self.image, (resize_w, resize_h)) if not success: print("空幀.") continue # 提高性能 self.image.flags.writeable = False # 轉(zhuǎn)為RGB self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) # 鏡像 self.image = cv2.flip(self.image, 1) # mediapipe模型處理 results = hands.process(self.image) self.image.flags.writeable = True self.image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR) # 判斷是否有手掌 if results.multi_hand_landmarks: # 遍歷每個(gè)手掌 for hand_landmarks in results.multi_hand_landmarks: # 在畫(huà)面標(biāo)注手指 self.mp_drawing.draw_landmarks( self.image, hand_landmarks, self.mp_hands.HAND_CONNECTIONS, self.mp_drawing_styles.get_default_hand_landmarks_style(), self.mp_drawing_styles.get_default_hand_connections_style()) # 解析手指,存入各個(gè)手指坐標(biāo) landmark_list = [] # 用來(lái)存儲(chǔ)手掌范圍的矩形坐標(biāo) paw_x_list = [] paw_y_list = [] for landmark_id, finger_axis in enumerate( hand_landmarks.landmark): landmark_list.append([ landmark_id, finger_axis.x, finger_axis.y, finger_axis.z ]) paw_x_list.append(finger_axis.x) paw_y_list.append(finger_axis.y) if landmark_list: # 比例縮放到像素 ratio_x_to_pixel = lambda x: math.ceil(x * resize_w) ratio_y_to_pixel = lambda y: math.ceil(y * resize_h) # 設(shè)計(jì)手掌左上角、右下角坐標(biāo) paw_left_top_x, paw_right_bottom_x = map(ratio_x_to_pixel, [min(paw_x_list), max(paw_x_list)]) paw_left_top_y, paw_right_bottom_y = map(ratio_y_to_pixel, [min(paw_y_list), max(paw_y_list)]) # 給手掌畫(huà)框框 cv2.rectangle(self.image, (paw_left_top_x - 30, paw_left_top_y - 30), (paw_right_bottom_x + 30, paw_right_bottom_y + 30), (0, 255, 0), 2) # 獲取中指指尖坐標(biāo) middle_finger_tip = landmark_list[12] middle_finger_tip_x = ratio_x_to_pixel(middle_finger_tip[1]) middle_finger_tip_y = ratio_y_to_pixel(middle_finger_tip[2]) # 獲取食指指尖坐標(biāo) index_finger_tip = landmark_list[8] index_finger_tip_x = ratio_x_to_pixel(index_finger_tip[1]) index_finger_tip_y = ratio_y_to_pixel(index_finger_tip[2]) # 中間點(diǎn) between_finger_tip = (middle_finger_tip_x + index_finger_tip_x) // 2, ( middle_finger_tip_y + index_finger_tip_y) // 2 # print(middle_finger_tip_x) thumb_finger_point = (middle_finger_tip_x, middle_finger_tip_y) index_finger_point = (index_finger_tip_x, index_finger_tip_y) # 畫(huà)指尖2點(diǎn) circle_func = lambda point: cv2.circle(self.image, point, 10, (255, 0, 255), -1) self.image = circle_func(thumb_finger_point) self.image = circle_func(index_finger_point) self.image = circle_func(between_finger_tip) # 畫(huà)2點(diǎn)連線 self.image = cv2.line(self.image, thumb_finger_point, index_finger_point, (255, 0, 255), 5) # 勾股定理計(jì)算長(zhǎng)度 line_len = math.hypot((index_finger_tip_x - middle_finger_tip_x), (index_finger_tip_y - middle_finger_tip_y)) # 將指尖距離映射到文字 rect_percent_text = math.ceil(line_len) # 激活模式,需要讓矩形跟隨移動(dòng) if squareManager.drag_active: # 更新方塊 squareManager.updateSquare(between_finger_tip[0], between_finger_tip[1]) if (line_len > 100): # 取消激活 squareManager.drag_active = False squareManager.active_index = -1 elif (line_len < 100) and (squareManager.checkOverlay(between_finger_tip[0], between_finger_tip[1]) != -1) and ( squareManager.drag_active == False): # 激活 squareManager.drag_active = True # 計(jì)算距離 squareManager.setLen(between_finger_tip[0], between_finger_tip[1]) # 顯示方塊,傳入本實(shí)例,主要為了半透明的處理 squareManager.display(self) # 顯示距離 cv2.putText(self.image, "Distance:" + str(rect_percent_text), (10, 120), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3) # 顯示當(dāng)前激活 cv2.putText(self.image, "Active:" + ( "None" if squareManager.active_index == -1 else str(squareManager.active_index)), (10, 170), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3) # 顯示刷新率FPS cTime = time.time() fps_text = 1 / (cTime - fpsTime) fpsTime = cTime cv2.putText(self.image, "FPS: " + str(int(fps_text)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3) # 顯示畫(huà)面 cv2.imshow('virtual drag and drop', self.image) if cv2.waitKey(5) & 0xFF == 27: break cap.release() control = HandControlVolume() control.recognize()
到此這篇關(guān)于OpenCV實(shí)現(xiàn)手勢(shì)虛擬拖拽的使用示例的文章就介紹到這了,更多相關(guān)OpenCV 手勢(shì)虛擬拖拽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python腳本破解壓縮文件口令實(shí)例教程(zipfile)
這篇文章主要給大家介紹了關(guān)于Python腳本破解壓縮文件口令(zipfile)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06在Python 字典中一鍵對(duì)應(yīng)多個(gè)值的實(shí)例
今天小編就為大家分享一篇在Python 字典中一鍵對(duì)應(yīng)多個(gè)值的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-02-02python+opencv實(shí)現(xiàn)堆疊圖片
這篇文章主要為大家詳細(xì)介紹了python+opencv實(shí)現(xiàn)堆疊圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Python使用sort和class實(shí)現(xiàn)的多級(jí)排序功能示例
這篇文章主要介紹了Python使用sort和class實(shí)現(xiàn)的多級(jí)排序功能,涉及Python基于面向?qū)ο蟮脑乇闅v、列表排序、添加等相關(guān)操作技巧,需要的朋友可以參考下2018-08-08Python?numpy之線性代數(shù)與隨機(jī)漫步
這篇文章主要介紹了Python?numpy之線性代數(shù)與隨機(jī)漫步,線性代數(shù),矩陣計(jì)算,優(yōu)化與內(nèi)存;比如矩陣乘法,分解,行列式等數(shù)學(xué)知識(shí),是所有數(shù)組類庫(kù)的重要組成部分2022-07-07