计算机视觉进阶教学之Mediapipe库(一)
目录
简介
一、Mediapipe Python的安装和应用
二、手部检测
1. 导入必要的库
2. 初始化 MediaPipe 组件
3. 配置并创建手部检测模型实例
4. 启动摄像头并进入主循环
5. 绘制检测结果
三、手势识别
1. 手势识别核心逻辑
a. 计算基准距离 (Base Distance)
b. 计算各手指尖到手腕的距离
c. 判断手指是否伸直并计数
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坐标。
- 官网地址:https://mediapipe.dev/
- 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
具体详细应用可以详细查看官网介绍
Mediapipe
二、手部检测
这段代码实现了一个实时的手部关键点检测系统。它通过电脑的摄像头捕获视频流,然后使用 Google 的 MediaPipe 库来识别画面中的人手,并实时地在视频上绘制出 21 个手部关键点及其连接关系。
1. 导入必要的库
import cv2
import mediapipe as mp
cv2
: 这是 OpenCV 库,一个强大的计算机视觉库。在这里,它的主要作用是:- 从摄像头捕获视频帧 (
cv2.VideoCapture
)。 - 在视频帧上绘制文字和关键点 (
cv2.putText
,cv2.imshow
)。 - 处理图像颜色空间 (
cv2.cvtColor
)。 - 显示视频窗口并等待用户按键 (
cv2.imshow
,cv2.waitKey
)。
- 从摄像头捕获视频帧 (
mediapipe
: 这是 Google 开发的一个开源机器学习库,特别擅长处理实时感知任务,如手部、面部、姿势识别等。
2. 初始化 MediaPipe 组件
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
mp.solutions.drawing_utils
: 这是 MediaPipe 提供的一个绘图工具包。它包含了方便的函数,可以直接在图像上绘制出检测到的关键点(landmarks)和它们之间的连接(connections)。mp.solutions.hands
: 这是 MediaPipe 的手部检测解决方案。它封装了预训练好的模型,可以直接用于检测图像或视频中的人手
3. 配置并创建手部检测模型实例
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. 启动摄像头并进入主循环
cap = cv2.VideoCapture(0)
while True:flag = 0ret, 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. 绘制检测结果
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].xy = hand_landmarks.landmark[i].yz = 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的数字手势(即伸出几个手指,就识别为相应的数字)。它通过分析手部关键点之间的距离来判断每个手指是否伸直,然后根据伸直手指的数量来确定手势。
import cv2
import mediapipe as mp
gesture = ["none", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
flag = 0mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.handshands = 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 = 0ret, 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].xp0_y = hand_landmarks.landmark[0].yp5_x = hand_landmarks.landmark[5].xp5_y = hand_landmarks.landmark[5].ydistance_0_5 = pow(p0_x - p5_x, 2) + pow(p0_y - p5_y, 2)base = distance_0_5 / 0.6p4_x = hand_landmarks.landmark[4].xp4_y = hand_landmarks.landmark[4].ydistance_5_4 = pow(p5_x - p4_x, 2) + pow(p5_y - p4_y, 2)p8_x = hand_landmarks.landmark[8].xp8_y = hand_landmarks.landmark[8].ydistance_0_8 = pow(p0_x - p8_x, 2) + pow(p0_y - p8_y, 2)p12_x = hand_landmarks.landmark[12].xp12_y = hand_landmarks.landmark[12].ydistance_0_12 = pow(p0_x - p12_x, 2) + pow(p0_y - p12_y, 2)p16_x = hand_landmarks.landmark[16].xp16_y = hand_landmarks.landmark[16].ydistance_0_16 = pow(p0_x - p16_x, 2) + pow(p0_y - p16_y, 2)p20_x = hand_landmarks.landmark[20].xp20_y = hand_landmarks.landmark[20].ydistance_0_20 = pow(p0_x - p20_x, 2) + pow(p0_y - p20_y, 2)if distance_0_8 > base:flag += 1if distance_0_12 > base:flag += 1if distance_0_16 > base:flag += 1if distance_0_20 > base:flag += 1if distance_5_4 > base * 0.3:flag += 1if 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. 手势识别核心逻辑
这是本代码最核心、最复杂的部分。
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)
p0_x = hand_landmarks.landmark[0].xp0_y = hand_landmarks.landmark[0].yp5_x = hand_landmarks.landmark[5].xp5_y = hand_landmarks.landmark[5].ydistance_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. 计算各手指尖到手腕的距离
p4_x = hand_landmarks.landmark[4].xp4_y = hand_landmarks.landmark[4].ydistance_5_4 = pow(p5_x - p4_x, 2) + pow(p5_y - p4_y, 2)p8_x = hand_landmarks.landmark[8].xp8_y = hand_landmarks.landmark[8].ydistance_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. 判断手指是否伸直并计数
if distance_0_8 > base:flag += 1if distance_0_12 > base:flag += 1if distance_0_16 > base:flag += 1if distance_0_20 > base:flag += 1if 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. 处理双手情况
if flag >= 10:flag = 10
- 这个判断是为了处理双手的情况。
- 当两只手都被检测到时,
for
循环会执行两次。 - 第一次处理左手,
flag
累加了左手伸直的手指数量(比如 5)。 - 第二次处理右手,
flag
在之前的基础上继续累加(5 + 5 = 10)。 if flag >= 10: flag = 10
确保了当两只手都伸出所有手指时(总共 10 个手指),flag
的值被限制在 10,以对应gesture
列表中的 "ten"。