目录
[一、Mediapipe Python的安装和应用](#一、Mediapipe Python的安装和应用)
[1. 导入必要的库](#1. 导入必要的库)
[2. 初始化 MediaPipe 组件](#2. 初始化 MediaPipe 组件)
[3. 配置并创建手部检测模型实例](#3. 配置并创建手部检测模型实例)
[4. 启动摄像头并进入主循环](#4. 启动摄像头并进入主循环)
[5. 绘制检测结果](#5. 绘制检测结果)
[1. 手势识别核心逻辑](#1. 手势识别核心逻辑)
[a. 计算基准距离 (Base Distance)](#a. 计算基准距离 (Base Distance))
[b. 计算各手指尖到手腕的距离](#b. 计算各手指尖到手腕的距离)
[c. 判断手指是否伸直并计数](#c. 判断手指是否伸直并计数)
[d. 处理双手情况](#d. 处理双手情况)
简介
Mediapipe是google的一个开源项目,可以提供开源的、跨平台的常用机器学习(machine learning)方案。Mediapipe实际上是一个集成的机器学习视觉算法的工具库,包含了人脸检测、人脸关键点、手势识别、头像分割和姿态识别等各种模型。
Mediapipe具备的优点有:
1)支持各种平台和语言,包括IOS,Android,C++,Python,JAVAScript,Coral等;
2)速度快,各种模型基本上可以做到实时运行。
Mediapipe在实际应用中的例子:
1)人脸检测;
2)FaceMesh:从图像/视频中重建出人脸的3D Mesh,可以用于AR渲染;
3)人像分割:从图像/视频中把人分割出来,可用于视频会议如Zoom、钉钉;
4)手势识别和跟踪:可以识别标出手部21个关键点的3D坐标;
5)人体姿态识别:可以识别标出人体33个关键点的3D坐标。
- Github开源项目地址:https://github.com/google/mediapipe
一、Mediapipe Python的安装和应用
- 安装python 3.7以上版本,下载地址:https://www.python.org/getit
- 安装Mediapipe
1)安装OpenCV,终端执行pip install opencv-contrib-python
2)安装Mediapipe,终端执行pip install mediapipe,或者使用国内镜像 pip install mediapipe -i https://pypi.tuna.tsinghua.edu.cn/simple/br
具体详细应用可以详细查看官网介绍
二、手部检测
这段代码实现了一个实时的手部关键点检测系统。它通过电脑的摄像头捕获视频流,然后使用 Google 的 MediaPipe 库来识别画面中的人手,并实时地在视频上绘制出 21 个手部关键点及其连接关系。
1. 导入必要的库
python
import cv2
import mediapipe as mp
cv2
: 这是 OpenCV 库,一个强大的计算机视觉库。在这里,它的主要作用是:- 从摄像头捕获视频帧 (
cv2.VideoCapture
)。 - 在视频帧上绘制文字和关键点 (
cv2.putText
,cv2.imshow
)。 - 处理图像颜色空间 (
cv2.cvtColor
)。 - 显示视频窗口并等待用户按键 (
cv2.imshow
,cv2.waitKey
)。
- 从摄像头捕获视频帧 (
mediapipe
: 这是 Google 开发的一个开源机器学习库,特别擅长处理实时感知任务,如手部、面部、姿势识别等。
2. 初始化 MediaPipe 组件
python
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
mp.solutions.drawing_utils
: 这是 MediaPipe 提供的一个绘图工具包。它包含了方便的函数,可以直接在图像上绘制出检测到的关键点(landmarks)和它们之间的连接(connections)。mp.solutions.hands
: 这是 MediaPipe 的手部检测解决方案。它封装了预训练好的模型,可以直接用于检测图像或视频中的人手
3. 配置并创建手部检测模型实例
python
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.75,
min_tracking_confidence=0.75)
'''
mp.solutions.drawing_utils是一个绘图模块,将识别到的手部关键点信息绘制道cv2图像中,mp.solutions.drawing_style定义了绘制的风格。
mp.solutions.hands是mediapipe中的手部识别模块,可以通过它调用手部识别的api,然后通过调用mp_hands.Hands初始化手部识别类。
mp_hands.Hands中的参数:
1)static_image_mode=True适用于静态图片的手势识别,Flase适用于视频等动态识别,比较明显的区别是,若识别的手的数量超过了最大值,
True时识别的手会在多个手之间不停闪烁,而False时,超出的手不会识别,系统会自动跟踪之前已经识别过的手。默认值为False;
2)max_num_hands用于指定识别手的最大数量。默认值为2;
3)min_detection_confidence 表示最小检测信度,取值为[0.0,1.0]这个值约小越容易识别出手,用时越短,但是识别的准确度就越差。越大识别的越精准,
但是响应的时间也会增加。默认值为0.5;
4)min_tracking_confience 表示最小的追踪可信度,越大手部追踪的越准确,相应的响应时间也就越长。默认值为0.5。
'''
这是代码中非常关键的一步,我们创建了一个 Hands
类的实例。这个实例就是我们的手部检测器。它接受几个重要的参数来配置其行为:
static_image_mode=False
:False
(动态模式): 适用于视频流。系统会首先尝试检测手,如果成功,就会切换到更快速的 "追踪" 模式。这大大提高了处理视频时的帧率。如果追踪失败,它会自动重新开始检测。True
(静态模式): 适用于单张图片。系统会对每一帧都进行完整的检测,而不会追踪。这在视频中会非常慢,但对于分析静态图片更准确。
max_num_hands=2
: 指定最多可以同时检测的手的数量。这里设置为 2,意味着可以同时识别两只手。min_detection_confidence=0.75
: 最小检测置信度。只有当模型对检测结果的置信度(概率)高于这个值(这里是 75%)时,才会认为成功检测到了一只手。这个值越高,误检越少,但可能会漏掉一些手势。min_tracking_confidence=0.75
: 最小追踪置信度。在追踪模式下,只有当模型对追踪结果的置信度高于这个值时,才会继续追踪。如果低于这个值,系统会认为追踪丢失,并重新启动检测。
4. 启动摄像头并进入主循环
python
cap = cv2.VideoCapture(0)
while True:
flag = 0
ret, frame = cap.read()
h,w=frame.shape[:2]
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 因为摄像头是镜像的,所以将摄像头水平翻转
# 不是镜像的可以不翻转
frame = cv2.flip(frame, 1)
results = hands.process(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
5. 绘制检测结果
python
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
# print('hand_landmarks:', hand_landmarks)
# 计算关键点的距离,用于判断手指是否伸直
for i in range(len(hand_landmarks.landmark)):
x = hand_landmarks.landmark[i].x
y = hand_landmarks.landmark[i].y
z = hand_landmarks.landmark[i].z
# print(x,y,z)
cv2.putText(frame, str(i), (int(x*w),int(y*h)), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0),2)
# 关键点可视化
mp_drawing.draw_landmarks(frame,
hand_landmarks,
mp_hands.HAND_CONNECTIONS)
cv2.imshow('MediaPipe Hands', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
最后我们实现了一个这样的功能

三、手势识别
这段代码实现了一个实时的手势识别系统,能够通过摄像头识别出0 到 10的数字手势(即伸出几个手指,就识别为相应的数字)。它通过分析手部关键点之间的距离来判断每个手指是否伸直,然后根据伸直手指的数量来确定手势。
python
import cv2
import mediapipe as mp
gesture = ["none", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
flag = 0
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.75,
min_tracking_confidence=0.75)
cap = cv2.VideoCapture(0)
while True:
flag = 0
ret, frame = cap.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 因为摄像头是镜像的,所以将摄像头水平翻转
# 不是镜像的可以不翻转
frame = cv2.flip(frame, 1)
results = hands.process(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
# if results.multi_handedness:
# for hand_label in results.multi_handedness:
# print(hand_label)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
# print('hand_landmarks:', hand_landmarks)
# 计算关键点的距离,用于判断手指是否伸直
p0_x = hand_landmarks.landmark[0].x
p0_y = hand_landmarks.landmark[0].y
p5_x = hand_landmarks.landmark[5].x
p5_y = hand_landmarks.landmark[5].y
distance_0_5 = pow(p0_x - p5_x, 2) + pow(p0_y - p5_y, 2)
base = distance_0_5 / 0.6
p4_x = hand_landmarks.landmark[4].x
p4_y = hand_landmarks.landmark[4].y
distance_5_4 = pow(p5_x - p4_x, 2) + pow(p5_y - p4_y, 2)
p8_x = hand_landmarks.landmark[8].x
p8_y = hand_landmarks.landmark[8].y
distance_0_8 = pow(p0_x - p8_x, 2) + pow(p0_y - p8_y, 2)
p12_x = hand_landmarks.landmark[12].x
p12_y = hand_landmarks.landmark[12].y
distance_0_12 = pow(p0_x - p12_x, 2) + pow(p0_y - p12_y, 2)
p16_x = hand_landmarks.landmark[16].x
p16_y = hand_landmarks.landmark[16].y
distance_0_16 = pow(p0_x - p16_x, 2) + pow(p0_y - p16_y, 2)
p20_x = hand_landmarks.landmark[20].x
p20_y = hand_landmarks.landmark[20].y
distance_0_20 = pow(p0_x - p20_x, 2) + pow(p0_y - p20_y, 2)
if distance_0_8 > base:
flag += 1
if distance_0_12 > base:
flag += 1
if distance_0_16 > base:
flag += 1
if distance_0_20 > base:
flag += 1
if distance_5_4 > base * 0.3:
flag += 1
if flag >= 10:
flag = 10
# 关键点可视化
mp_drawing.draw_landmarks(frame,
hand_landmarks,
mp_hands.HAND_CONNECTIONS)
cv2.putText(frame, gesture[flag], (50, 50), 0, 1.3, (0, 0, 255), 3)
cv2.imshow('MediaPipe Hands', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
1. 手势识别核心逻辑
这是本代码最核心、最复杂的部分。
python
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
# ... 距离计算 ...
# ... 手指判断 ...
if results.multi_hand_landmarks:
: 判断是否检测到了手。for hand_landmarks in results.multi_hand_landmarks:
: 遍历每一只检测到的手。如果是双手,这个循环会执行两次。
a. 计算基准距离 (Base Distance)
python
p0_x = hand_landmarks.landmark[0].x
p0_y = hand_landmarks.landmark[0].y
p5_x = hand_landmarks.landmark[5].x
p5_y = hand_landmarks.landmark[5].y
distance_0_5 = pow(p0_x - p5_x, 2) + pow(p0_y - p5_y, 2)
base = distance_0_5 / 0.6
p0
是手腕根部的关键点,p5
是拇指根部的关键点。distance_0_5
: 计算p0
和p5
之间的平方距离 (注意:这里用的是pow
计算平方和,没有开根号,因为比较大小时,平方距离和实际距离效果一样,但计算更快)。base = distance_0_5 / 0.6
: 这是一个非常巧妙的归一化技巧 。distance_0_5
的值会随着手离摄像头的远近而变化(手近则大,手远则小)。- 通过将这个距离除以一个常数(0.6),我们得到一个与手的大小相关的基准值
base
。 - 后续所有的距离判断都将基于这个
base
值,这样就可以消除手的远近对判断结果的影响,使得在不同距离下的手势识别都能保持准确。
b. 计算各手指尖到手腕的距离
python
p4_x = hand_landmarks.landmark[4].x
p4_y = hand_landmarks.landmark[4].y
distance_5_4 = pow(p5_x - p4_x, 2) + pow(p5_y - p4_y, 2)
p8_x = hand_landmarks.landmark[8].x
p8_y = hand_landmarks.landmark[8].y
distance_0_8 = pow(p0_x - p8_x, 2) + pow(p0_y - p8_y, 2)
# ... 同样的方式计算 distance_0_12 (中指), distance_0_16 (无名指), distance_0_20 (小指)
p4
: 拇指指尖p8
: 食指指尖p12
: 中指指尖p16
: 无名指指尖p20
: 小指指尖distance_5_4
: 计算拇指指尖p4
到拇指根部p5
的平方距离。distance_0_8
,distance_0_12
,distance_0_16
,distance_0_20
: 计算各个手指尖到手腕p0
的平方距离。
c. 判断手指是否伸直并计数
python
if distance_0_8 > base:
flag += 1
if distance_0_12 > base:
flag += 1
if distance_0_16 > base:
flag += 1
if distance_0_20 > base:
flag += 1
if distance_5_4 > base * 0.3:
flag += 1
- 逻辑:如果一个手指是伸直的,那么指尖离手腕(或指根)的距离就会比较远。
if distance_0_8 > base:
: 如果食指指尖p8
到手腕p0
的距离大于基准值base
,就认为食指是伸直的,flag
计数器加 1。- 这个判断逻辑同样适用于中指、无名指和小指。
- 拇指的判断略有不同 :
if distance_5_4 > base * 0.3:
- 拇指的运动方式和其他四指不同,它是向外侧张开,而不是向上伸直。
- 所以这里比较的是拇指指尖
p4
和拇指根部p5
的距离。 - 阈值也更小 (
base * 0.3
),因为拇指张开的幅度通常没有其他手指伸得那么远。
d. 处理双手情况
python
if flag >= 10:
flag = 10
- 这个判断是为了处理双手的情况。
- 当两只手都被检测到时,
for
循环会执行两次。 - 第一次处理左手,
flag
累加了左手伸直的手指数量(比如 5)。 - 第二次处理右手,
flag
在之前的基础上继续累加(5 + 5 = 10)。 if flag >= 10: flag = 10
确保了当两只手都伸出所有手指时(总共 10 个手指),flag
的值被限制在 10,以对应gesture
列表中的 "ten"。
