基于手势识别的虚拟拖拽系统
github
项目简介
这是一个基于计算机视觉的虚拟拖拽系统,通过 MediaPipe 手势识别技术,实现了对虚拟图形的直观操控。用户可以通过自然的手势来拖拽、缩放和旋转屏幕上的虚拟图形,就像在现实世界中操作物体一样。
核心功能
1. 多形状支持
- 矩形:可旋转的基础图形,支持任意角度旋转
- 圆形:完美圆形,支持无级缩放
- 三角形:使用面积法实现精确的碰撞检测
2. 手势控制
- 拖拽:使用食指和中指进行精确定位和移动
- 缩放:通过大拇指和食指的捏合动作自然地调整大小
- 旋转:在拖拽过程中旋转手指即可旋转图形
- 复位:握拳手势可以将所有图形恢复到初始状态
3. 视觉反馈
- 实时状态显示(拖拽/缩放/空闲)
- FPS 计数器
- 操作指引
- 图形轨迹特效
技术实现
1. 手势识别系统
使用 MediaPipe 框架进行实时手部关键点检测,通过分析关键点的相对位置来识别不同的手势:
def _process_landmarks(self, hand_landmarks, resize_w: int, resize_h: int):# 获取手指关键点wrist = (landmark_list[0][1], landmark_list[0][2]) # 手腕thumb_tip = (landmark_list[4][1], landmark_list[4][2]) # 大拇指尖index_tip = (landmark_list[8][1], landmark_list[8][2]) # 食指尖middle_tip = (landmark_list[12][1], landmark_list[12][2]) # 中指尖palm_center = (landmark_list[9][1], landmark_list[9][2]) # 手掌中心# 计算手势参数drag_distance = math.hypot(middle_tip[0] - index_tip[0], middle_tip[1] - index_tip[1])scale_distance = math.hypot(index_tip[0] - thumb_tip[0], index_tip[1] - thumb_tip[1])angle = math.degrees(math.atan2(middle_tip[1] - index_tip[1], middle_tip[0] - index_tip[0]))
2. 碰撞检测系统
为不同形状实现了专门的碰撞检测算法:
- 圆形:使用点到圆心距离判定
if self.shape_type == ShapeType.CIRCLE:return math.hypot(point_x - self.x, point_y - self.y) <= self.size * self.scale / 2
- 矩形:考虑旋转角度的矩形碰撞检测
elif self.shape_type == ShapeType.RECTANGLE:# 计算旋转后的矩形顶点half_size = int(self.size * self.scale / 2)points = np.array([[-half_size, -half_size],[half_size, -half_size],[half_size, half_size],[-half_size, half_size]], dtype=np.float32)# 应用旋转变换angle = math.radians(self.rotation)rotation_matrix = np.array([[math.cos(angle), -math.sin(angle)],[math.sin(angle), math.cos(angle)]])points = np.dot(points, rotation_matrix)
- 三角形:使用面积法进行精确判定
elif self.shape_type == ShapeType.TRIANGLE:half_size = int(self.size * self.scale / 2)# 三角形的三个顶点top = (self.x, self.y - half_size)bottom_left = (self.x - half_size, self.y + half_size)bottom_right = (self.x + half_size, self.y + half_size)# 使用面积法判断点是否在三角形内def area(x1, y1, x2, y2, x3, y3):return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0)# 计算三角形总面积和子三角形面积A = area(top[0], top[1], bottom_left[0], bottom_left[1], bottom_right[0], bottom_right[1])A1 = area(point_x, point_y, bottom_left[0], bottom_left[1], bottom_right[0], bottom_right[1])A2 = area(top[0], top[1], point_x, point_y, bottom_right[0], bottom_right[1])A3 = area(top[0], top[1], bottom_left[0], bottom_left[1], point_x, point_y)
3. 图形变换系统
- 位置更新与轨迹记录
def update_position(self, x: int, y: int):self.trail.append((self.x, self.y))if len(self.trail) > Config.TRAIL_LENGTH:self.trail.pop(0)self.x = xself.y = y
- 旋转控制
def update_rotation(self, angle: float):self.rotation += angle * Config.ROTATION_SCALEself.rotation = self.rotation % 360
- 缩放控制
def update_scale(self, scale_factor: float):new_scale = self.scale + scale_factor * Config.SCALE_FACTORself.scale = max(Config.MIN_SCALE, min(Config.MAX_SCALE, new_scale))
4. 状态管理系统
使用 ShapeManager
类统一管理所有图形状态:
class ShapeManager:def __init__(self):self.shapes: List[Shape] = []self.active_index: int = -1self.drag_active: bool = Falseself.last_distance: float = 0self.last_angle: float = 0self.offset_x: float = 0self.offset_y: float = 0
关键状态处理:
- 拖拽状态
if drag_distance < self.config.DRAG_DISTANCE_THRESHOLD:shapeManager.drag_active = TrueshapeManager.set_offset(drag_center[0], drag_center[1])
- 缩放状态
if scale_distance < Config.SCALE_THRESHOLD:scaling_mode = TrueshapeManager.last_distance = scale_distance
- 复位状态
if is_fist:shapeManager.reset_all_shapes()scaling_mode = False
5. 视觉反馈系统
- 图形绘制
def draw(self, image: np.ndarray, is_active: bool = False) -> np.ndarray:overlay = image.copy()color = (255, 0, 255) if is_active else self.color# 绘制轨迹if len(self.trail) > 1:for i in range(len(self.trail) - 1):alpha = (i + 1) / len(self.trail) * 0.5cv2.line(overlay, self.trail[i], self.trail[i + 1], color, 2)image = cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0)
- 状态显示
# 显示状态
mode_text = "Scaling" if scaling_mode else "Dragging" if shapeManager.drag_active else "None"
cv2.putText(self.image, f"Mode: {mode_text}", (10, 30),cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)# 显示FPS
cv2.putText(self.image, f"FPS: {int(fps)}", (10, 70),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)