Python实战项目:自学OpenCV手势识别AI功能
一、项目概述
通过摄像头实时捕捉手部动作,实现通过食指与中指捏合手势拖拽屏幕方块的效果。使用以下核心技术:
MediaPipe手部关键点检测
OpenCV图像处理
几何距离计算
透明物体绘制
二、环境准备
需安装的库
python
pip install opencv-python mediapipe numpy
硬件要求
普通USB摄像头
支持Python3的环境
三、代码结构解析
3.1 初始化模块
# MediaPipe初始化
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
model_complexity=1, # 平衡精度模式
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
# 摄像头初始化
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
此处我们可以改变参数会得到不一样效果,一般默认0.5。
model_complexity 作用:控制手部检测模型的复杂度等级 取值范围:0(轻量级)、1(平衡)、2(高精度)。默认值为 1。 min_detection_confidence 作用:手部检测的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5。 min_tracking_confidence 作用:手部跟踪的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5
3.2 核心逻辑实现
手部关键点检测
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(frame)
# 绘制关键点连线
mp_drawing.draw_landmarks(
frame,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles.get_default_hand_landmarks_style(),
mp_drawing_styles.get_default_hand_connections_style()
)
手势判定逻辑
利用两点之间距离公式即可计算两指距离
# 计算双指距离
finger_len = math.hypot((index_finger_x-middle_finger_x),
(index_finger_y-middle_finger_y))
# 拖拽判定条件
if finger_len < 30:
if (index_finger_x > square_x and
index_finger_x < (square_x+square_width) and
index_finger_y > square_y and
index_finger_y < (square_y+square_width)):
on_square = True
透明方块实现(为了更好的看见手势放在方块上)
overlay = frame.copy()
cv2.rectangle(overlay, (square_x,square_y),
(square_x+square_width, square_y+square_height),
square_color, -1)
frame = cv2.addWeighted(overlay, 0.5, frame, 0.5, 0)
四、技术原理解析
4.1 MediaPipe工作机制
21个手部关键点:每个手掌检测到21个关键点(0-20号)
关键点索引:
8号点:食指指尖
12号点:中指指尖
置信度参数:通过调整检测/跟踪置信度平衡性能与精度
4.2 拖拽算法
-
距离计算:使用math.hypot计算欧氏距离
-
位置偏移补偿:通过L1/L2记录初始接触偏移量(只要保持相对位置不变,那么方块就可以随着手移动)
-
状态机控制:on_square标志位管理拖拽状态
# 记录初始偏移
L1 = abs(index_finger_x - square_x)
L2 = abs(index_finger_y - square_y)
# 更新方块位置
square_x = index_finger_x - L1
square_y = index_finger_y - L2
五、效果演示
特征说明:
双指间距<30时触发抓取
蓝色表示拖拽中状态
半透明视觉效果
实时60FPS+的流畅度
六、优化方向
-
手势优化:增加张开/握拳状态识别
-
多物体交互:实现多个可拖拽物体
-
3D控制:结合深度信息实现Z轴控制
-
碰撞检测:添加边界限制与物体碰撞
七、完整代码
import math
import cv2
import numpy as np
#mp相关参数
import mediapipe as mp
mp_drawing=mp.solutions.drawing_utils
mp_drawing_styles=mp.solutions.drawing_styles
mp_hands=mp.solutions.hands
#model_complexity 作用:控制手部检测模型的复杂度等级 取值范围:0(轻量级)、1(平衡)、2(高精度)。默认值为 1。
#min_detection_confidence 作用:手部检测的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5。
#min_tracking_confidence 作用:手部跟踪的置信度阈值。 取值范围:[0.0, 1.0],默认值为 0.5
hands=mp_hands.Hands(
model_complexity=1,
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
#获取摄像头视频流
cap=cv2.VideoCapture(0)
#获取画面宽度和高度(因为mediapipe框架中对长宽进行了计算)
width=int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height=int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
#方块、坐标相关参数
square_x=100
square_y=100
square_width=100
square_height=100
L1=0
L2=0
on_square=False
square_color=(255,0,255)
while True:
#获取每一帧
ret,frame=cap.read()
#对图像进行处理
frame=cv2.flip(frame,1)
#mediapipe处理
frame.flags.writeable=False
frame=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
results=hands.process(frame)
frame.flags.writeable=True
frame=cv2.cvtColor(frame,cv2.COLOR_RGB2BGR)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(
frame,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles.get_default_hand_landmarks_style(),
mp_drawing_styles.get_default_hand_connections_style()
)
x_list=[]
y_list=[]
for landmark in hand_landmarks.landmark:
x_list.append(landmark.x)
y_list.append(landmark.y)
#获取食指坐标
index_finger_x=int(x_list[8]*width)
index_finger_y=int(y_list[8]*height)
#获取中指坐标
middle_finger_x=int(x_list[12]*width)
middle_finger_y=int(y_list[12]*height)
#计算食指与中指指尖距离(两点之间坐标公式)
finger_len=math.hypot((index_finger_x-middle_finger_x),(index_finger_y-middle_finger_y))
#画一个圆来验证是否可以显示在屏幕上
#cv2.circle(frame,(index_finger_x,index_finger_y),20,(255,0,255),-1)
#如果距离小于30才开始激活
if finger_len<30:
#判断食指指尖在不在方块上
if (index_finger_x > square_x and index_finger_x<(square_x+square_width)
and index_finger_y>square_y and index_finger_y<(square_y+square_width)):
if on_square==False:
#print("在方块上")
L1=abs(index_finger_x-square_x)
L2=abs(index_finger_y-square_y)
on_square=True
else:
pass
else:
on_square=False
square_color=(255,0,255)
if on_square:
square_x=index_finger_x-L1
square_y=index_finger_y-L2
square_color=(255,0,0)
#注意颜色是BGR的顺序,方块参数是对角线上的两点坐标,-1表示实心
#cv2.rectangle(frame,(square_x,square_y),(square_x+square_width,square_height+square_y),(255,0,0),-1)
#画一个半透明的方块
overplay=frame.copy()
cv2.rectangle(frame,(square_x,square_y),(square_x+
square_width,square_height+square_y),square_color,-1)
frame=cv2.addWeighted(overplay,0.5,frame,0.5,0)
#使鼠标可以调整窗口的大小
#cv2.namedWindow('window', cv2.WINDOW_NORMAL)
cv2.imshow('window', frame)
#当按esc键时退出窗口
if cv2.waitKey(10) & 0xFF ==27:
break
#释放并关闭 窗口
cap.release()
cv2.destroyWindow('window')
注意事项:
-
手指尽量保持竖直状态便于识别
-
保证环境光照充足
-
可根据摄像头分辨率调整检测阈值
以下是gitee上的源码:creative: 本科大学生个人学习与练习阶段
注:本人普通双非大学生一名,出于个人兴趣网上学习opencv,听从老师建议决定记录自己学习成果,如有问题欢迎各位点评。同时,如有一些不专业或是其他差错,请谅解!
作者:YBR-crime