目录
1. 人机交互(HCI - Human-Computer Interaction)
效果展示
手指竖起统计
应用场景
1. 人机交互(HCI - Human-Computer Interaction)
-
手势控制
-
用手指数量或特定手势控制电脑、智能设备(如切换幻灯片、控制音量、播放/暂停视频等)。
-
结合 手势识别模型 实现更多复杂交互,如 手势拖拽、点击、滑动。
-
2. 游戏控制
-
体感游戏
-
通过手势控制角色移动、跳跃、攻击等操作(如 VR/AR 游戏)。
-
结合 OpenCV 的 手势跟踪 技术,可以开发简单的基于手势的游戏,如“猜拳游戏”或“石头剪刀布”。
-
3. 智能家居控制
-
手势开关灯、调节音量、切换电视频道
-
结合 IoT 设备,用手势控制智能家居设备。
-
例如 比出 5 根手指 → 开灯,比出 1 根手指 → 关灯。
-
4. 无接触式操作(医疗 & 公共场景)
-
手势识别替代按钮操作,减少物理接触:
-
医院:医生在无菌环境下控制计算机(如切换病人 X 光片)。
-
银行/电梯:使用手势控制 ATM 机、无接触式电梯按钮。
-
5. 手语识别
-
结合手指计数和角度检测,实现简单的 手语识别,帮助听障人士与他人交流。
6. 机器人控制
-
机器人通过手势接收指令,例如:
-
伸出 1 根手指 → 前进
-
伸出 2 根手指 → 后退
-
伸出 5 根手指 → 停止
-
7. 运动分析 & 体感健身
-
识别健身动作,如瑜伽手势、太极动作检测。
-
结合 姿态识别(Pose Estimation),跟踪手势姿势,进行训练指导(如纠正动作)。
8. 远程会议手势互动
-
在视频会议(Zoom、Teams)中使用手势:
-
举起 1 根手指 → 发送 "请发言" 信号
-
伸出 5 根手指 → 发送 "赞同" 信号
-
比出 "OK" 手势 → 发送 "同意" 信息+
-
代码详解
1. 初始化必要的模块
-
使用
cv2.VideoCapture(0)
访问摄像头,获取视频流。 -
通过
mediapipe.solutions.hands.Hands()
初始化 MediaPipe Hands 模块,用于检测手部关键点。
2. 计算手指数
-
手指识别逻辑:
-
食指、中指、无名指、小指:比较指尖(tip)和指关节(pip)的 y 轴坐标,判断是否竖起。
-
拇指:由于拇指是水平展开的,需要根据 x 轴坐标判断竖起状态(左手和右手的判断方向相反)。
-
-
使用
count_fingers_and_get_status()
方法遍历每根手指,计算伸直的手指数并存储状态信息。
3. 绘制手部信息
-
通过 MediaPipe 提供的
hand_landmarks.landmark
获取 21 个关键点坐标,并将它们转换为图像坐标系。 -
绘制关键点和骨骼:
-
关键点(关节点)使用 单独的颜色 标注,与骨骼线颜色不同。
-
手指骨骼线的颜色:
-
手指竖起时(raised) → 亮色(红色或绿色)。
-
手指弯曲时(not raised) → 暗色(深红或深绿)。
-
-
手腕到手指根部的线条采用 基础颜色,用于区分左右手。
-
4. 统计并显示信息
-
统计手指数总和,并在屏幕上显示:
-
每只手单独统计手指数,并在手腕附近显示该手的 手指数和左右手标签。
-
在画面左上角显示 总手指数(Total Fingers)。
-
-
计算 FPS(帧率) 以评估性能,并在屏幕上实时显示。
5. 处理视频流
-
通过
cv2.flip(frame, 1)
进行水平翻转,使其符合用户视觉习惯。 -
使用
cv2.imshow()
实时显示处理后的视频流。 -
按下 Esc 键 退出程序。
优点:
-
颜色区分明显:
-
左右手信息颜色不同,方便区分。
-
骨骼颜色 区分 竖起和弯曲 的手指,使检测结果更直观。
-
关键点颜色 与骨骼颜色不同,使整体显示更清晰。
-
-
计算量较低:
-
只进行最必要的计算(避免过多
for
循环)。 -
FPS 显示方便评估性能。
-
当前版本用于 实时手指计数、手势识别 等应用,如果需要进一步扩展,比如 手势控制系统,可以在 count_fingers_and_get_status()
里增加 手势模式判断。如果这篇文章点赞数量不错,我可以将其升级。
完整代码
import cv2
import mediapipe as mp
import time
# 初始化 MediaPipe Hands 模块
mp_hands = mp.solutions.hands
# 不直接使用 mp_drawing.draw_landmarks,因为我们需要自定义绘制颜色
# 设置检测参数
hands = mp_hands.Hands(static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
cap = cv2.VideoCapture(0)
prev_time = 0
def count_fingers_and_get_status(hand_landmarks, hand_label):
"""
计算每根手指是否伸直,并返回伸直的手指数量及状态字典
返回:
count: 总计伸直的手指数
status: 字典,key 为 'thumb', 'index', 'middle', 'ring', 'pinky',值为 True/False
"""
count = 0
status = {}
landmarks = hand_landmarks.landmark
# 食指、中指、无名指、小指判断:
# 对应 finger: tip 与 pip(取第二个关节,即:指尖与关节 6,10,14,18 对比)
fingers = {
"index": (8, 6),
"middle": (12, 10),
"ring": (16, 14),
"pinky": (20, 18)
}
for finger, (tip_id, pip_id) in fingers.items():
if landmarks[tip_id].y < landmarks[pip_id].y:
status[finger] = True
count += 1
else:
status[finger] = False
# 拇指判断:
# 右手:如果拇指 tip 在 x 轴上比拇指 IP(3号点)靠右,则认为伸直;左手则相反
thumb_tip = landmarks[4]
thumb_ip = landmarks[3]
if hand_label == "Right":
if thumb_tip.x > thumb_ip.x:
status["thumb"] = True
count += 1
else:
status["thumb"] = False
else: # Left hand
if thumb_tip.x < thumb_ip.x:
status["thumb"] = True
count += 1
else:
status["thumb"] = False
return count, status
# 为各手指的骨骼绘制定义分段(不包括腕部到基部的连接)
finger_segments = {
"thumb": [(1, 2), (2, 3), (3, 4)],
"index": [(5, 6), (6, 7), (7, 8)],
"middle": [(9, 10), (10, 11), (11, 12)],
"ring": [(13, 14), (14, 15), (15, 16)],
"pinky": [(17, 18), (18, 19), (19, 20)]
}
# 定义不同手的基色(用于文本、腕部到各手指根部的连接)
hand_base_colors = {
"Left": (255, 0, 0), # 蓝色(BGR格式)
"Right": (0, 255, 0) # 绿色
}
# 定义手指伸直与否的颜色(针对骨骼线),这里分别定义“高亮”和“暗淡”的颜色
finger_colors = {
"Left": {"raised": (255, 100, 100), "not": (100, 0, 0)},
"Right": {"raised": (100, 255, 100), "not": (0, 100, 0)}
}
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1)
img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(img_rgb)
total_fingers = 0
if results.multi_hand_landmarks and results.multi_handedness:
for hand_landmarks, handedness in zip(results.multi_hand_landmarks, results.multi_handedness):
# 获取左右手标签
hand_label = handedness.classification[0].label # "Left" 或 "Right"
base_color = hand_base_colors[hand_label]
f_color = finger_colors[hand_label]
# 计算手指伸直数量和状态
count, finger_status = count_fingers_and_get_status(hand_landmarks, hand_label)
total_fingers += count
h, w, _ = frame.shape
# 将所有 21 个关键点转换为图像坐标
lm_coords = []
for lm in hand_landmarks.landmark:
lm_coords.append((int(lm.x * w), int(lm.y * h)))
# 绘制腕部到各手指根部的连接线(腕部为 0 点,各手指根部分别为:thumb->1, index->5, middle->9, ring->13, pinky->17)
for root in [1, 5, 9, 13, 17]:
cv2.line(frame, lm_coords[0], lm_coords[root], base_color, 2)
# 绘制各手指骨骼线,并根据是否伸直改变颜色
for finger, segments in finger_segments.items():
# 根据 finger_status 选择颜色
color = f_color["raised"] if finger_status.get(finger, False) else f_color["not"]
for (start, end) in segments:
cv2.line(frame, lm_coords[start], lm_coords[end], color, 3)
# 绘制小圆点标记关键点
cv2.circle(frame, lm_coords[start], 4, color, -1)
cv2.circle(frame, lm_coords[end], 4, color, -1)
# 在手附近显示该手的手指数量(在第一个关键点附近显示)
cx, cy = lm_coords[0]
cv2.putText(frame, f'{hand_label}: {count}', (cx - 30, cy - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, base_color, 2)
# FPS 计算
curr_time = time.time()
fps = 1 / (curr_time - prev_time) if (curr_time - prev_time) > 0 else 0
prev_time = curr_time
cv2.putText(frame, f'FPS: {int(fps)}', (10, 80),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2)
cv2.putText(frame, f'Total Fingers: {total_fingers}', (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2)
cv2.imshow("Finger Count", frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
总结
期待更新吗?
一键三连吧铁子们,这真的对我很有帮助!!!!我们再见!!!
一键三连吧铁子们,这真的对我很有帮助!!!!我们再见!!!
一键三连吧铁子们,这真的对我很有帮助!!!!我们再见!!!