一、前言
??本文基于Google的Mediapipe框架,利用其自身回傳的坐標,計算手指的彎曲角度,效果圖如下,

二、實作程序
??我在我的文章Mediapipe入門——搭建姿態檢測模型并實時輸出人體關節點3d坐標里講述了如何搭建mediapipe姿態檢測模型以及如何獲得坐標,這里不多贅述,
??依據官網的說明,mediapipe處理視頻流的函式能夠回傳人體標注節點的三維坐標,如下圖所示,一共33個,

再看看手部的節點,每只手有21個,

??讓我舉個例子,上面“手掌”的部分節點序號標在手上是這樣的,

??現在知道了坐標,也知道了點在哪,開始計算手指運動程序中的夾角,以食指為例,計算∠876,推導程序比較簡單,因為知道三個點的xy坐標,借助三角函式弧度rad=arctan(y/x)分別求出兩條直線的正切角,再相減即可,

??Python里該如何實作呢,以右手掌為例,首先,視頻流處理函式回傳右手地標right_hand_landmarks,該地標包含坐標xyz與可見度visibility兩個資訊,只需提取坐標landmark,而landmark就像是陣列,只要知道對應節點的序號(索引)就能提取到坐標,
#以提取右手食指指尖坐標為例,由前述可知指尖序號為8
results = holistic.process(image)
if results.right_hand_landmarks:
RHL = results.right_hand_landmarks
coord_8=[RHL.landmark[8].x, RHL.landmark[8].y]
#coord_8便是指尖的xy坐標
??接下來借助for回圈來批量操作,
#食指、中指、無名指、小手指
joint_list = [[8, 7, 6], [12, 11, 10], [16, 15, 14], [20, 19, 18]] # 手指關節序列
if results.right_hand_landmarks:
RHL = results.right_hand_landmarks
for joint in joint_list:
a = np.array([RHL.landmark[joint[0]].x, RHL.landmark[joint[0]].y])
b = np.array([RHL.landmark[joint[1]].x, RHL.landmark[joint[1]].y])
c = np.array([RHL.landmark[joint[2]].x, RHL.landmark[joint[2]].y])
??這樣,a, b, c就是三個點的xy坐標,再借助np.arctan2函式計算反正切,然后將計算出來的弧度轉為角度,
# 計算弧度
radians_fingers = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
angle = np.abs(radians_fingers * 180.0 / np.pi) # 弧度轉角度
??有必要說一下,arctan2函式和arctan函式有所不同,arctan的值域是[-π/2, π/2],arctan2的值域是[-π, π],畢竟手指伸直可以達到180°,還是用arctan2函式好,
??有了角度,再利用cv2.putText()函式把角度資料實時渲染在手指旁邊,
cv2.putText(image, str(round(angle, 2)), tuple(np.multiply(b, [640, 480]).astype(int)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)
#渲染的位置在三個點的中間也就是坐標b的位置
三、演示
??直接運行


??檢測速度很快,其實基于此,還可以計算手肘角度、腋下角度、膝蓋彎曲角度等,原理相同,不多說,
四、完整代碼
import cv2
import numpy as np
import mediapipe as mp
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_holistic = mp.solutions.holistic
joint_list = [[8, 7, 6], [12, 11, 10], [16, 15, 14], [20, 19, 18]] # 手指關節序列
cap = cv2.VideoCapture(0)
with mp_holistic.Holistic(
min_detection_confidence=0.5,
min_tracking_confidence=0.5) as holistic:
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
break
image.flags.writeable = False
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = holistic.process(image)
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# 渲染
mp_drawing.draw_landmarks(
image,
results.face_landmarks,
mp_holistic.FACEMESH_CONTOURS,
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles
.get_default_face_mesh_tesselation_style())
mp_drawing.draw_landmarks(
image,
results.pose_landmarks,
mp_holistic.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
mp_drawing.draw_landmarks(image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS,
landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style())
mp_drawing.draw_landmarks(image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS,
landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style())
# 監測到右手,執行
if results.right_hand_landmarks:
RHL = results.right_hand_landmarks
# 計算角度
for joint in joint_list:
a = np.array([RHL.landmark[joint[0]].x, RHL.landmark[joint[0]].y])
b = np.array([RHL.landmark[joint[1]].x, RHL.landmark[joint[1]].y])
c = np.array([RHL.landmark[joint[2]].x, RHL.landmark[joint[2]].y])
# 計算弧度
radians_fingers = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
angle = np.abs(radians_fingers * 180.0 / np.pi) # 弧度轉角度
if angle > 180.0:
angle = 360 - angle
cv2.putText(image, str(round(angle, 2)), tuple(np.multiply(b, [640, 480]).astype(int)),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)
# cv2.imshow('MediaPipe Holistic', cv2.flip(image, 1))
cv2.imshow('Mediapipe Holistic', image) # 取消鏡面翻轉
if cv2.waitKey(5) == ord('q'):
break
cap.release()
五、若有錯誤請指正,歡迎討論賜教,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/414020.html
標籤:其他
上一篇:(五)比賽中的CV演算法(下4)損失函式的改進(2)
下一篇:學習記錄:-)
