Python OpenCV MediaPipe 实现手部追踪与方向检测
在现代计算机视觉领域,手部追踪技术在虚拟现实、增强现实、人机交互等多个领域中发挥着重要作用。本文将介绍如何利用OpenCV和MediaPipe库实现一个实时的手部追踪与方向检测项目。通过详细的项目介绍、所用技术链接、原理解析以及代码讲解,帮助读者深入理解该项目的实现过程。
一、项目介绍
本项目旨在通过摄像头实时捕捉手部动作,利用MediaPipe进行手部关键点检测,并结合OpenCV进行图像处理与显示。具体功能包括:
该项目可以应用于手势控制、虚拟绘画、交互式展示等场景。
二、所用技术与链接
三、原理解析
1.MediaPipe手部检测
MediaPipe提供了高效的手部检测与追踪模型。通过该模型,可以实时检测手部的21个关键点,包括指尖、关节等。每个关键点由其在图像中的相对坐标(x, y, z)表示。
2.指尖位置与移动方向计算
在本项目中,我们主要关注食指指尖(Landmark 8)的坐标。通过连续帧中指尖位置的变化,可以计算出指尖的移动方向。具体步骤如下:
- 位置获取与平滑处理: 获取当前帧中指尖的坐标,并通过滑动窗口对位置进行平滑处理,以减少抖动。
- 位移计算: 计算当前帧与前一帧之间的位移(dx, dy)。
- 移动方向判断: 根据位移的角度,判断指尖的移动方向:
- 右: -45° < angle ≤ 45°
- 下: 45° < angle ≤ 135°
- 左: angle > 135° 或 angle ≤ -135°
- 上: -135° < angle ≤ -45°
- 轨迹记录与显示: 记录移动方向并绘制轨迹,形成手部移动的可视化路径。
3.移动锁定机制
为了避免频繁的方向判断和减少误判,项目中引入了移动锁定机制:
四、代码讲解
以下是项目的主要代码部分及其详细解释:
1.导入必要的库
import cv2
import mediapipe as mp
from collections import deque
import numpy as np
import math
cv2
: OpenCV库,用于图像捕捉与处理。mediapipe
: MediaPipe库,用于手部关键点检测。deque
: 双端队列,用于存储轨迹和位置,支持高效的添加与删除操作。numpy
: 用于数值计算,特别是均值计算。math
: 提供数学函数,如计算角度。2.初始化MediaPipe手部检测
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=1,
min_detection_confidence=0.7,
min_tracking_confidence=0.7
)
mp_draw = mp.solutions.drawing_utils
mp.solutions.hands
: 加载手部解决方案。Hands
: 初始化手部检测对象,设置参数:
static_image_mode=False
: 启用视频模式,提高检测速度。max_num_hands=1
: 只检测一只手。min_detection_confidence=0.7
: 最小检测置信度。min_tracking_confidence=0.7
: 最小跟踪置信度。mp_draw
: 用于绘制手部关键点和连接线。3.初始化摄像头与轨迹存储
cap = cv2.VideoCapture(0)
trajectory = deque(maxlen=1000) # 用于存储轨迹
trajectory.append((0, 0))
point_x, point_y = 0, 0
cv2.VideoCapture(0)
: 打开默认摄像头。trajectory
: 存储手部移动轨迹,最多存储1000个点。point_x
, point_y
: 当前轨迹点的坐标。4.定义阈值与平滑参数
movement_threshold = 20 # 运动触发阈值
neutral_threshold = 10 # 中立解除阈值
prev_x, prev_y = None, None
movement_locked = False
smoothing_window = 5
positions = deque(maxlen=smoothing_window)
movement_threshold
: 触发方向判断的最小位移。neutral_threshold
: 回到中立区域的位移阈值。prev_x
, prev_y
: 前一帧的指尖位置。movement_locked
: 移动锁定状态标志。smoothing_window
: 平滑窗口大小,用于位置平滑。positions
: 存储最近的指尖位置,用于计算平滑后的平均位置。5.角度计算函数
def calculate_angle(dx, dy):
angle = math.degrees(math.atan2(dy, dx))
return angle
6.主循环
while True:
success, img = cap.read()
if not success:
break
# 翻转图像以获得镜像效果
img = cv2.flip(img, 1)
# 将图像转换为RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 处理图像并检测手部
results = hands.process(img_rgb)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
# 绘制手部关键点
mp_draw.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS)
# 获取食指指尖的坐标(Landmark 8)
index_finger_tip = hand_landmarks.landmark[8]
img_h, img_w, _ = img.shape
tip_x, tip_y = int(index_finger_tip.x * img_w), int(index_finger_tip.y * img_h)
# 添加到平滑滤波器
positions.append((tip_x, tip_y))
if len(positions) == 0:
avg_x, avg_y = tip_x, tip_y
else:
avg_x = int(np.mean([p[0] for p in positions]))
avg_y = int(np.mean([p[1] for p in positions]))
if prev_x is not None and prev_y is not None:
dx = avg_x - prev_x
dy = avg_y - prev_y
magnitude = math.sqrt(dx**2 + dy**2)
direction = None
if not movement_locked:
if magnitude > movement_threshold:
angle = calculate_angle(dx, dy)
print(f"Angle: {angle}")
# 判断方向
if -45 < angle <= 45:
direction = '右'
point_x += 1
elif 45 < angle <= 135:
direction = '下'
point_y -= 1
elif angle > 135 or angle <= -135:
direction = '左'
point_x -= 1
elif -135 < angle <= -45:
direction = '上'
point_y += 1
if direction:
print(f"手部向{direction}移动 (dx: {dx}, dy: {dy}, angle: {angle})")
trajectory.append((point_x, point_y))
# 锁定,直到手部回到中立区域
movement_locked = True
else:
# 如果当前移动小于中立阈值,则解除锁定
if magnitude < neutral_threshold:
movement_locked = False
prev_x, prev_y = avg_x, avg_y
else:
prev_x, prev_y = avg_x, avg_y
else:
prev_x, prev_y = None, None
movement_locked = False
# 显示摄像头画面
cv2.imshow("Hand Tracking", img)
# 按下 'q' 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
7.主要步骤解析
-
捕获图像帧:
- 通过
cap.read()
从摄像头获取当前帧。 - 若获取失败,退出循环。
-
图像预处理:
- 翻转图像以获得镜像效果,增强用户体验。
- 将BGR图像转换为RGB,因为MediaPipe需要RGB格式的图像。
-
手部检测与关键点绘制:
- 使用
hands.process(img_rgb)
进行手部检测。 - 若检测到手部关键点,使用
mp_draw.draw_landmarks
在图像上绘制关键点和连接线。 -
获取食指指尖位置:
- 获取Landmark 8(食指指尖)的相对坐标,并转换为图像中的像素坐标(tip_x, tip_y)。
-
位置平滑处理:
- 将当前指尖位置添加到
positions
队列中,计算最近五帧的平均位置(avg_x, avg_y),以减少抖动影响。 -
位移与方向计算:
- 若有前一帧的位置(prev_x, prev_y),计算位移向量(dx, dy)及其幅度(magnitude)。
- 若未锁定移动状态,且幅度超过
movement_threshold
,则计算移动方向并更新轨迹。 - 若已锁定移动状态,且幅度低于
neutral_threshold
,则解除锁定。 -
轨迹记录与显示:
- 记录移动方向对应的点(point_x, point_y)到
trajectory
队列中,可用于后续的轨迹绘制与分析。 -
显示图像与退出机制:
- 使用
cv2.imshow
显示处理后的图像。 - 通过按下
'q'
键退出程序。
8.总代码
以下是整合后的完整代码,确保所有功能模块协同工作:
import cv2
import mediapipe as mp
from collections import deque
import numpy as np
import math
# 初始化MediaPipe手部检测
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=1,
min_detection_confidence=0.7,
min_tracking_confidence=0.7
)
mp_draw = mp.solutions.drawing_utils
# 初始化摄像头
cap = cv2.VideoCapture(0)
# 设置图像的宽度和高度为512x384
# cap.set(cv2.CAP_PROP_FRAME_WIDTH, 512) # 设置宽度为512
# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 384) # 设置高度为384
# 初始化Matplotlib
trajectory = deque(maxlen=1000) # 用于存储轨迹
trajectory.append((0, 0))
point_x, point_y = 0, 0
# 定义移动阈值
movement_threshold = 20 # 运动触发阈值
neutral_threshold = 10 # 中立解除阈值
# 前一帧的位置
prev_x, prev_y = None, None
# 动作锁定状态
movement_locked = False
# 平滑参数
smoothing_window = 5
positions = deque(maxlen=smoothing_window)
def calculate_angle(dx, dy):
angle = math.degrees(math.atan2(dy, dx))
return angle
while True:
success, img = cap.read()
if not success:
break
# 翻转图像以获得镜像效果
img = cv2.flip(img, 1)
# 将图像转换为RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 处理图像并检测手部
results = hands.process(img_rgb)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
# 绘制手部关键点
mp_draw.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS)
# 获取食指指尖的坐标(Landmark 8)
index_finger_tip = hand_landmarks.landmark[8]
img_h, img_w, _ = img.shape
tip_x, tip_y = int(index_finger_tip.x * img_w), int(index_finger_tip.y * img_h)
# 添加到平滑滤波器
positions.append((tip_x, tip_y))
if len(positions) == 0:
avg_x, avg_y = tip_x, tip_y
else:
avg_x = int(np.mean([p[0] for p in positions]))
avg_y = int(np.mean([p[1] for p in positions]))
if prev_x is not None and prev_y is not None:
dx = avg_x - prev_x
dy = avg_y - prev_y
# 打印当前位移值
#print(f"dx: {dx}, dy: {dy}")
magnitude = math.sqrt(dx**2 + dy**2)
direction = None
if not movement_locked:
if magnitude > movement_threshold:
angle = calculate_angle(dx, dy)
print(f"Angle: {angle}")
# 判断方向
if -45 < angle <= 45:
direction = '右'
point_x += 1
elif 45 < angle <= 135:
direction = '下'
point_y -= 1
elif angle > 135 or angle <= -135:
direction = '左'
point_x -= 1
elif -135 < angle <= -45:
direction = '上'
point_y += 1
if direction:
print(f"手部向{direction}移动 (dx: {dx}, dy: {dy}, angle: {angle})")
trajectory.append((point_x, point_y))
# 更新绘图
xs, ys = zip(*trajectory)
# 锁定,直到手部回到中立区域
movement_locked = True
else:
# 如果当前移动小于中立阈值,则解除锁定
if magnitude < neutral_threshold:
movement_locked = False
prev_x, prev_y = avg_x, avg_y
else:
prev_x, prev_y = avg_x, avg_y
else:
prev_x, prev_y = None, None
movement_locked = False
# 显示摄像头画面
cv2.imshow("Hand Tracking", img)
# 按下 'q' 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
五、结语
通过本文的介绍与代码讲解,读者可以了解到如何利用OpenCV和MediaPipe实现一个基础的手部追踪与方向检测系统。该项目不仅具有实际应用价值,还为深入学习计算机视觉与机器学习技术提供了良好的实践平台。欢迎读者在此基础上进行更多的探索与创新!
作者:岳麓未名