在各种技术领域和平台,感知手的形状和运动的能力是改善用户体验的重要组成部分。例如,它可以构成手语理解和手势控制的基础,还可以在增强现实中将数字内容和信息叠加在物理世界之上。虽然对人们来说很自然,但强大的实时手部感知绝对是一项具有挑战性的计算机视觉任务,因为手经常遮挡自己或彼此(例如手指/手掌遮挡和握手)并且缺乏高对比度模式。
MediaPipe Hands 是一种高保真手和手指跟踪解决方案。它采用机器学习 (ML) 从单个帧中推断出手的 21 个 3D 地标。当前最先进的方法主要依赖于强大的桌面环境进行推理,而我们的方法在手机上实现了实时性能,甚至可以扩展到多只手。我们希望向更广泛的研究和开发社区提供这种手部感知功能,以将导致创造性用例的出现,刺激新的应用程序和新的研究途径。
MediaPipe Hands 使用由多个协同工作的模型组成的 ML 管道:一个手掌检测模型,对整个图像进行操作并返回一个定向的手部边界框。一种手部地标模型,该模型对手掌检测器定义的裁剪图像区域进行操作,并返回高保真 3D 手部关键点。此策略类似于我们的 MediaPipe Face Mesh 解决方案中采用的策略,后者使用面部检测器和面部地标模型。
为手部地标模型提供精确裁剪的手部图像大大减少了对数据增强(例如旋转、平移和缩放)的需求,而是允许网络将其大部分能力用于坐标预测精度。此外,在我们的管道中,手区域也可以根据在前一帧中识别的手地标生成,并且只有当地标模型无法再识别手部存在时,才会调用手掌检测来重新定位手部。
手掌检测模型:
为了检测初始手部位置,我们设计了一个单次检测器模型,该模型针对移动实时使用进行了优化,其方式类似于 MediaPipe Face Mesh 中的人脸检测模型。检测手部绝对是一项复杂的任务:我们的模型必须处理各种手部尺寸,并且相对于图像帧具有较大的跨度(~20 倍),并且能够检测被遮挡和自遮挡的手。尽管面部具有高对比度图案,例如在眼睛和嘴巴区域,但由于手部缺乏此类特征,因此仅从视觉特征中可靠地检测它们相对困难。相反,提供额外的上下文,如手臂、身体或人物特征,有助于准确定位手部。
我们的方法使用不同的策略解决了上述挑战。首先,我们训练手掌检测器而不是手部检测器,因为估计手掌和拳头等刚性物体的边界框比检测带有关节的手指的手要简单得多。此外,由于手掌是较小的对象,因此非极大值抑制算法即使对于握手等双手自遮挡情况也能很好地工作。此外,手掌可以使用方形边界框(ML 术语中的锚点)进行建模,而忽略其他纵横比,因此将锚点的数量减少了 3-5 倍。其次,编码器-解码器特征提取器用于更大的场景上下文感知,即使是小对象(类似于 RetinaNet 方法)。最后,我们在训练期间最小化 focal loss以支持由高尺度方差导致的大量锚点。
采用上述技术,我们在手掌检测中达到了95.7%的平均精度。使用常规的交叉熵损失和没有解码器的基线仅为 86.22%。
手地标模型:
在对整个图像进行手掌检测后,我们的手部地标模型通过回归对被检测手部区域内的21个三维手部关节坐标进行精确的关键点定位,即直接进行坐标预测。该模型学习一致的内部手部姿势表示,即使是部分可见的手和自我遮挡,也具有鲁棒性。
为了获得地面真实数据,我们手工标注了~30K张真实世界的图像,包含21个三维坐标,如下图所示(如果对应坐标上存在z值,我们从图像深度图中获取)。为了更好地覆盖可能的手部姿态,并提供对手部几何性质的额外监督,我们还在各种背景上渲染高质量的合成手部模型,并将其映射到相应的3D坐标。
max_num_hands
手并定位了相应的手的地标,它就会简单地跟踪这些地标,而不会调用另一个检测,直到它失去对任何一只手的跟踪。这减少了延迟,非常适合处理视频帧。如果设置为 true,则在每个输入图像上运行手部检测,非常适合处理一批静态的、可能不相关的图像。默认为false。static_image_mode
为真,则忽这个参数略,手部检测将在每个图像上运行。默认为 0.5。支持配置选项:
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
import cv2
import mediapipe as mp
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_utils.DrawingSpec
mp_hands = mp.solutions.hands
# For static images:
IMAGE_FILES = ["3.jpg"]
with mp_hands.Hands(
static_image_mode=True,
max_num_hands=2,
min_detection_confidence=0.5) as hands:
for idx, file in enumerate(IMAGE_FILES):
# Read an image, flip it around y-axis for correct handedness output (see
# above).
image = cv2.flip(cv2.imread(file), 1)
# Convert the BGR image to RGB before processing.
results = hands.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
# Print handedness and draw hand landmarks on the image.
print('Handedness:', results.multi_handedness)
if not results.multi_hand_landmarks:
continue
image_height, image_width, _ = image.shape
annotated_image = image.copy()
for hand_landmarks in results.multi_hand_landmarks:
# print('hand_landmarks:', hand_landmarks)
print(
f'Index finger tip coordinates: (',
f'{
hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].x * image_width}, '
f'{
hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y * image_height})'
)
mp_drawing.draw_landmarks(
annotated_image,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles(),
mp_drawing_styles())
cv2.imwrite(
'annotated_image' + str(idx) + '.png', cv2.flip(annotated_image, 1))
# For webcam input:
cap = cv2.VideoCapture("2.gif")
with mp_hands.Hands(
min_detection_confidence=0.5,
min_tracking_confidence=0.5) as hands:
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
# If loading a camera, use 'continue' instead of 'break'.
break
# Flip the image horizontally for a later selfie-view display, and convert
# the BGR image to RGB.
image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
# To improve performance, optionally mark the image as not writeable to
# pass by reference.
image.flags.writeable = False
results = hands.process(image)
# Draw the hand annotations on the image.
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(
image,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles(),
mp_drawing_styles())
cv2.imshow('MediaPipe Hands', image)
if cv2.waitKey(5) & 0xFF == 27:
break
cap.release()
# Hand Tracing Module
import cv2
import mediapipe as mp
import time
class handDetector():
def __init__(self, mode=False, maxHands=2, detectionCon=0.5, trackCon=0.5):
self.mode = mode
self.maxHands = maxHands
self.detectionCon = detectionCon
self.trackCon = trackCon
self.mpHands = mp.solutions.hands
self.hands = self.mpHands.Hands(self.mode, self.maxHands,
self.detectionCon, self.trackCon)
self.mpDraw = mp.solutions.drawing_utils
def findHands(self, img, draw=True):
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
self.results = self.hands.process(imgRGB)
# print(results.multi_hand_landmarks)
if self.results.multi_hand_landmarks:
for handLms in self.results.multi_hand_landmarks:
if draw:
self.mpDraw.draw_landmarks(img, handLms,
self.mpHands.HAND_CONNECTIONS)
return img
def findPosition(self, img, handNo=0, draw=True):
lmList = []
if self.results.multi_hand_landmarks:
myHand = self.results.multi_hand_landmarks[handNo]
for id, lm in enumerate(myHand.landmark):
# print(id, lm)
h, w, c = img.shape
cx, cy = int(lm.x * w), int(lm.y * h)
# print(id, cx, cy)
lmList.append([id, cx, cy])
if draw:
cv2.circle(img, (cx, cy), 15, (255, 0, 255), cv2.FILLED)
return lmList
def main():
pTime = 0
cTime = 0
cap = cv2.VideoCapture(0)
detector = handDetector()
while True:
success, img = cap.read()
img = detector.findHands(img)
lmList = detector.findPosition(img)
if len(lmList) != 0:
print(lmList[4])
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
cv2.putText(img, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3,
(255, 0, 255), 3)
cv2.imshow("Image", img)
cv2.waitKey(1)
if __name__ == "__main__":
main()
https://google.github.io/mediapipe/solutions/hands.html