学习 MediaPipe 手部检测和手势识别
前文链接:学习 MediaPipe 手部检测和手势识别(1)
所有图片均来自互联网。
3 手势识别
3.0 识别内容讨论
个人认为,在手势识别中,应该对:手掌朝向、手指弯曲、指间距离,这 3 个方面进行识别。
3.1 手掌朝向
通过计算向量 0 —> 5 和 0 —> 17 的叉乘,确定手掌朝向。规定代表手掌朝向的向量由手背指向掌心。所以,识别的左手(label == Left)为 v0_5 × v0_17,识别的右手(label == Right)为 v0_17 × v0_5。将叉乘结果归一化,并将向量在xoy平面的映射向量画出来。用绿点表示朝外,红色表示朝内。
计算手掌朝向向量部分的代码:
@staticmethod
def _calculate_cross_product(vector_a, vector_b, normalized:bool=True):
if vector_a.shape == (3,) and vector_b.shape == (3,):
cross_product = np.cross(vector_a, vector_b)
if normalized:
norm = np.linalg.norm(cross_product)
if norm != 0:
cross_product = cross_product / norm
return cross_product
return None
def _get_palm_facing(self):
for result in self.results_list:
# 绘制手掌方向
if "palm_facing" not in result.keys():
label = result["label"]
coord_w = result["coord_w"]
p0 = coord_w[:, 0]
v0_5 = coord_w[:, 5] - p0
v0_17 = coord_w[:, 17] - p0
if label == "Left":
result["plam_facing"] = self._calculate_cross_product(
v0_5, v0_17)
else:
result["plam_facing"] = self._calculate_cross_product(
v0_17, v0_5)
绘制手掌朝向向量部分的代码:
def check(self, image):
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = self.hands_model.process(img_rgb)
# 解析结果
self._analysis_results(results, image.shape)
# 绘制手部关节和骨骼
if results.multi_hand_landmarks:
for handLms in results.multi_hand_landmarks:
draw_landmarks(image, handLms, HAND_CONNECTIONS)
self.draw(image)
def draw(self, image):
if len(self.results_list) == 0:
return
for result in self.results_list:
# 手掌朝向
self._get_palm_facing()
vector_palm = result["plam_facing"]
p0 = result["coord"][:, 0]
pv = (p0 + vector_palm * 100)
cv2.line(image, tuple(map(int, p0[:2])), tuple(map(int, pv[:2])),
(0, 0, 255), 2)
if vector_palm[2] < 0:
cv2.circle(image, tuple(map(int, p0[:2])), 3, (0, 255, 0), -1)
3.2 手指弯曲程度
这里笼统的称作手指弯曲程度,其实应该包含:手指弯曲程度、手指根部弯曲程度
考虑手指弯曲程度,有3种方案:
1.手指夹角;
2.指尖-指根距离占比;
3.指尖-中间关节距离与手腕到指根距离的大小比(优快云 徐岸轩: Mediapipe实现手势识别教程)
手指根部弯曲程度使用角度计算比较靠谱。
3.2.1 手指夹角
可以通过 v1_2 和 v3_4(大拇指的也可以改为v2_3 和 v3_4),v5_6 和 v7_8(以此类推)的夹角评估手指弯曲程度。通过 v0_1 和 v1_2,v0_5 和 v5_6(以此类推)的夹角评估手指根部的弯曲程度。
这里可以使用 三维世界坐标 计算手指方向向量。从图片可以看出,计算出来的角度与目测的手指弯曲程度并不完全匹配。计算出来的角度可以一定程度上反应手指弯曲程度,但是也有笔直的手指计算出来的角度是50°。
使用计算手指弯曲程度的代码:
# 手指弯曲程度
@staticmethod
def _angle_between_vectors(vector1, vector2):
# 计算向量的模长
magnitude1 = np.linalg.norm(vector1)
magnitude2 = np.linalg.norm(vector2)
# 计算向量的点积
dot_product = np.dot(vector1, vector2)
# 计算夹角
theta = np.arccos(dot_product / (magnitude1 * magnitude2))
return np.degrees(theta)
def _get_finger_bending_degree(self):
""" 使用世界三维坐标计算手指夹角,评价手指弯曲程度。 """
for result in self.results_list:
# 计算手指弯曲程度
if "finger_bending" not in result.keys():
coord_w = result["coord_w"]
list_bending = []
p0 = coord_w[:, 0]
for i in range(5):
p1 = coord_w[:, 1+4*i]
p2 = coord_w[:, 2+4*i]
p3 = coord_w[:, 3+4*i]
p4 = coord_w[:, 4+4*i]
# 手指弯曲
list_bending.append(int(self._angle_between_vectors(p1-p0, p2-p1)))
# 手指的弯曲程度
list_bending.append(int(self._angle_between_vectors(p4-p3, p2-p1)))
result["finger_bending"] = list_bending
显示手指弯曲程度的部分代码:
for result in self.results_list:
# 手指弯曲程度
self._get_finger_bending_degree()
finger_bending = result["finger_bending"]
coord = result["coord"]
list_key = [1, 4, 5, 8, 9, 12, 13, 16, 17, 20]
for index, value in enumerate(list_key):
cx, cy, cz = coord[:, value]
text = f'{finger_bending[index]}'
self._put_text_within_image(image, text, (cx, cy), color=(0, 0, 255),
font_scale=0.25, thickness=1)
明天开始黑猴,今天学一下。
========== 2024/08/19 学习中 ==========