回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页
best-icon

Deepseek+DF二哈视觉传感器——控制粒子效果 简单

头像 云天 2025.12.05 76 3

微信图片_20251205223153_560_21.jpg

        一、项目背景与目标

        在人机交互技术快速发展的当下,手势与姿态识别结合视觉传感器的交互方式成为研究热点。本项目旨在借助 Deepseek 的代码生成能力,实现从电脑摄像头的手势 / 姿态识别到 DF 二哈视觉传感器结合 Arduino 的硬件交互,最终完成对粒子效果的智能控制。具体目标为:首先通过电脑摄像头和 MediaPipe 实现手势控制粒子球的聚合与散开,再拓展至姿态识别控制,最后利用 DF 二哈视觉传感器采集信息,经 Arduino 串口通信传输至电脑,实现硬件驱动的粒子效果调控。

        二、前期准备:环境搭建与工具选型

  1. 软件环境:Python 3.8 及以上版本,安装 OpenCV(用于图像采集与处理)、MediaPipe(用于手势 / 姿态识别)、Pygame(用于粒子效果绘制)、PySerial(用于串口通信)等库;Mind+2.0(用于 Arduino UNO 程序编写与烧录)。
  2. pip install pygame opencv-python mediapipe numpy
  3. 硬件设备:电脑(带摄像头)、Arduino Uno 开发板、DF 二哈视觉传感器2.0、USB 数据线(用于 Arduino UNO与电脑通信)、4P传感器连接线(传感器与 Arduino 连接)。
  4. AI 工具:Deepseek 大模型,用于代码生成、调试与优化建议提供。

        三、电脑摄像头手势控制粒子效果的实现

        (一)Deepseek 代码生成与初始调试

        向 Deepseek 输入提示语:“帮我写一个 python 程序,可使用 mediapipe 实现手势控制粒子效果,效果是一个由很多粒子组成的旋转的球,当手张开后粒子散开,手握拳,粒子又形成球。”,Deepseek 生成了基于 Pygame 和 MediaPipe Hands 的初始代码,核心分为粒子类定义、手势识别模块、粒子效果绘制与交互逻辑三部分。

image.png
image.png

代码
import pygame
import cv2
import mediapipe as mp
import numpy as np
import math
import random
from enum import Enum

# 初始化Pygame
pygame.init()

# 屏幕尺寸
WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("手势控制粒子效果 - 张开手散开粒子,握拳聚集粒子")

# 粒子类
class Particle:
    def __init__(self):
        self.reset_to_ball()
        
    def reset_to_ball(self):
        # 球形初始位置
        self.radius = random.uniform(100, 200)  # 球体半径
        self.theta = random.uniform(0, 2 * math.pi)  # 方位角
        self.phi = random.uniform(0, math.pi)  # 极角
        
        # 计算球面上的初始位置
        self.x = WIDTH // 2 + self.radius * math.sin(self.phi) * math.cos(self.theta)
        self.y = HEIGHT // 2 + self.radius * math.sin(self.phi) * math.sin(self.theta)
        self.z = self.radius * math.cos(self.phi)
        
        # 目标位置(用于平滑移动)
        self.target_x = self.x
        self.target_y = self.y
        self.target_z = self.z
        
        # 速度和颜色
        self.speed = random.uniform(0.02, 0.05)
        self.color = (
            random.randint(100, 255),
            random.randint(100, 255),
            random.randint(100, 255)
        )
        self.size = random.uniform(2, 5)
        
        # 球体旋转参数
        self.rotation_speed = random.uniform(0.002, 0.005)
        
    def reset_to_scattered(self):
        # 随机散开位置
        self.target_x = random.uniform(0, WIDTH)
        self.target_y = random.uniform(0, HEIGHT)
        self.target_z = random.uniform(-100, 100)
        self.x = random.uniform(0, WIDTH)
        self.y = random.uniform(0, HEIGHT)
        self.z = random.uniform(-100, 100)
        
    def update_ball(self, time, center_x, center_y, rotation_speed_factor):
        # 更新球体上的位置
        self.theta += self.rotation_speed * rotation_speed_factor
        self.phi += self.speed * 0.1
        
        # 保持角度在合理范围内
        if self.theta > 2 * math.pi:
            self.theta -= 2 * math.pi
        if self.phi > 2 * math.pi:
            self.phi -= 2 * math.pi
            
        # 计算球面上的目标位置
        self.target_x = center_x + self.radius * math.sin(self.phi) * math.cos(self.theta + time * 0.001)
        self.target_y = center_y + self.radius * math.sin(self.phi) * math.sin(self.theta + time * 0.001)
        self.target_z = self.radius * math.cos(self.phi + time * 0.0005)
        
        # 平滑移动到目标位置
        self.x += (self.target_x - self.x) * 0.1
        self.y += (self.target_y - self.y) * 0.1
        self.z += (self.target_z - self.z) * 0.1
        
    def update_scattered(self):
        # 平滑移动到随机散开位置
        self.x += (self.target_x - self.x) * 0.02
        self.y += (self.target_y - self.y) * 0.02
        self.z += (self.target_z - self.z) * 0.02
        
    def draw(self, surface):
        # 计算粒子大小(根据Z坐标调整,实现透视效果)
        perspective = max(0.3, min(1.0, (self.z + 300) / 400))
        draw_size = max(1, int(self.size * perspective))
        
        # 计算粒子颜色(根据Z坐标调整亮度)
        color_factor = max(0.3, min(1.0, (self.z + 200) / 300))
        draw_color = (
            min(255, int(self.color[0] * color_factor)),
            min(255, int(self.color[1] * color_factor)),
            min(255, int(self.color[2] * color_factor))
        )
        
        # 绘制粒子
        pygame.draw.circle(surface, draw_color, (int(self.x), int(self.y)), draw_size)
        
        # 为一些粒子添加发光效果
        if draw_size > 2:
            glow_color = (
                min(255, draw_color[0] + 50),
                min(255, draw_color[1] + 50),
                min(255, draw_color[2] + 50)
            )
            pygame.draw.circle(surface, glow_color, (int(self.x), int(self.y)), draw_size // 2)

# 手势状态枚举
class HandState(Enum):
    OPEN = 1
    CLOSED = 2
    UNKNOWN = 3

# 手势识别类
class HandGestureRecognizer:
    def __init__(self):
        self.mp_hands = mp.solutions.hands
        self.hands = self.mp_hands.Hands(
            static_image_mode=False,
            max_num_hands=1,
            min_detection_confidence=0.7,
            min_tracking_confidence=0.5
        )
        self.mp_draw = mp.solutions.drawing_utils
        
        # 打开摄像头
        self.cap = cv2.VideoCapture(0)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        
        # 手势状态
        self.hand_state = HandState.UNKNOWN
        self.hand_landmarks = None
        self.hand_center = (0, 0)
        
    def recognize_gesture(self, landmarks):
        """识别手势状态(张开或握拳)"""
        # 获取关键点坐标
        wrist = landmarks.landmark[self.mp_hands.HandLandmark.WRIST]
        thumb_tip = landmarks.landmark[self.mp_hands.HandLandmark.THUMB_TIP]
        index_tip = landmarks.landmark[self.mp_hands.HandLandmark.INDEX_FINGER_TIP]
        middle_tip = landmarks.landmark[self.mp_hands.HandLandmark.MIDDLE_FINGER_TIP]
        ring_tip = landmarks.landmark[self.mp_hands.HandLandmark.RING_FINGER_TIP]
        pinky_tip = landmarks.landmark[self.mp_hands.HandLandmark.PINKY_TIP]
        
        # 计算指尖到手腕的距离
        def distance(p1, p2):
            return math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2)
        
        thumb_dist = distance(thumb_tip, wrist)
        index_dist = distance(index_tip, wrist)
        middle_dist = distance(middle_tip, wrist)
        ring_dist = distance(ring_tip, wrist)
        pinky_dist = distance(pinky_tip, wrist)
        
        # 计算平均距离
        avg_dist = (thumb_dist + index_dist + middle_dist + ring_dist + pinky_dist) / 5
        
        # 设置阈值判断手势
        if avg_dist > 0.25:  # 张开手
            return HandState.OPEN
        else:  # 握拳
            return HandState.CLOSED
    
    def update(self):
        """更新手势识别"""
        success, frame = self.cap.read()
        if not success:
            return False
            
        # 翻转帧以获得镜像效果
        frame = cv2.flip(frame, 1)
        
        # 转换为RGB
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        # 处理手势
        results = self.hands.process(frame_rgb)
        
        if results.multi_hand_landmarks:
            self.hand_landmarks = results.multi_hand_landmarks[0]
            self.hand_state = self.recognize_gesture(self.hand_landmarks)
            
            # 计算手部中心点(用于控制粒子球中心)
            wrist = self.hand_landmarks.landmark[self.mp_hands.HandLandmark.WRIST]
            # 将坐标映射到屏幕
            self.hand_center = (
                int((1 - wrist.x) * WIDTH),  # 镜像翻转
                int(wrist.y * HEIGHT)
            )
        else:
            self.hand_landmarks = None
            self.hand_state = HandState.UNKNOWN
            
        return True
    
    def draw_hand_overlay(self, surface):
        """在Pygame表面上绘制手部关键点"""
        if self.hand_landmarks:
            # 绘制关键点
            for landmark in self.hand_landmarks.landmark:
                x = int((1 - landmark.x) * WIDTH)  # 镜像翻转
                y = int(landmark.y * HEIGHT)
                pygame.draw.circle(surface, (0, 255, 0), (x, y), 5)
                
            # 绘制连接线
            connections = self.mp_hands.HAND_CONNECTIONS
            for connection in connections:
                start = self.hand_landmarks.landmark[connection[0]]
                end = self.hand_landmarks.landmark[connection[1]]
                
                start_pos = (int((1 - start.x) * WIDTH), int(start.y * HEIGHT))
                end_pos = (int((1 - end.x) * WIDTH), int(end.y * HEIGHT))
                
                pygame.draw.line(surface, (255, 255, 255), start_pos, end_pos, 2)
            
            # 绘制手势状态指示器
            state_text = "张开手" if self.hand_state == HandState.OPEN else "握拳"
            state_color = (0, 255, 0) if self.hand_state == HandState.OPEN else (255, 0, 0)
            
            font = pygame.font.SysFont(None, 36)
            text = font.render(f"手势: {state_text}", True, state_color)
            surface.blit(text, (self.hand_center[0] - 50, self.hand_center[1] - 40))
    
    def release(self):
        """释放资源"""
        self.cap.release()
        self.hands.close()

# 创建粒子系统
class ParticleSystem:
    def __init__(self, num_particles=300):
        self.particles = [Particle() for _ in range(num_particles)]
        self.state = "ball"  # 初始状态为球体
        self.transition_progress = 0  # 过渡进度
        self.transition_speed = 0.02  # 过渡速度
        
    def update(self, hand_state, hand_center, time):
        # 根据手势状态确定目标状态
        target_state = "scattered" if hand_state == HandState.OPEN else "ball"
        
        # 如果状态变化,开始过渡
        if self.state != target_state:
            self.transition_progress += self.transition_speed
            if self.transition_progress >= 1:
                self.state = target_state
                self.transition_progress = 0
                
                # 如果完全过渡到散开状态,重置粒子位置
                if self.state == "scattered":
                    for particle in self.particles:
                        particle.reset_to_scattered()
        
        # 更新粒子
        for particle in self.particles:
            if self.state == "ball" and target_state == "ball":
                # 球体状态
                rotation_factor = 1.0 if hand_state == HandState.CLOSED else 0.3
                particle.update_ball(time, hand_center[0], hand_center[1], rotation_factor)
            elif self.state == "scattered" and target_state == "scattered":
                # 散开状态
                particle.update_scattered()
            else:
                # 过渡状态:混合两种状态
                if target_state == "scattered":
                    # 从球体过渡到散开
                    particle.update_ball(time, hand_center[0], hand_center[1], 0.5)
                    
                    # 混合目标位置
                    scatter_x = particle.x + (particle.target_x - particle.x) * self.transition_progress
                    scatter_y = particle.y + (particle.target_y - particle.y) * self.transition_progress
                    scatter_z = particle.z + (particle.target_z - particle.z) * self.transition_progress
                    
                    particle.x += (scatter_x - particle.x) * 0.1
                    particle.y += (scatter_y - particle.y) * 0.1
                    particle.z += (scatter_z - particle.z) * 0.1
                else:
                    # 从散开过渡到球体
                    particle.update_scattered()
                    
                    # 计算球体上的目标位置
                    center_x = hand_center[0]
                    center_y = hand_center[1]
                    
                    # 混合目标位置
                    ball_x = particle.x + (center_x - particle.x) * self.transition_progress
                    ball_y = particle.y + (center_y - particle.y) * self.transition_progress
                    
                    particle.x += (ball_x - particle.x) * 0.1
                    particle.y += (ball_y - particle.y) * 0.1
    
    def draw(self, surface):
        # 绘制所有粒子
        for particle in self.particles:
            particle.draw(surface)

# 主程序
def main():
    # 创建手势识别器
    recognizer = HandGestureRecognizer()
    
    # 创建粒子系统
    particle_system = ParticleSystem(num_particles=400)
    
    # 创建时钟对象
    clock = pygame.time.Clock()
    
    # 主循环
    running = True
    time = 0
    
    # 手部中心默认位置(屏幕中心)
    hand_center = (WIDTH // 2, HEIGHT // 2)
    
    while running:
        # 处理事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
        
        # 更新手势识别
        if not recognizer.update():
            print("无法读取摄像头")
            running = False
            continue
            
        # 获取手势状态
        hand_state = recognizer.hand_state
        if recognizer.hand_center != (0, 0):
            hand_center = recognizer.hand_center
        
        # 更新时间
        time += 1
        
        # 清屏
        screen.fill((0, 0, 0))
        
        # 更新粒子系统
        particle_system.update(hand_state, hand_center, time)
        
        # 绘制粒子
        particle_system.draw(screen)
        
        # 绘制手部关键点
        recognizer.draw_hand_overlay(screen)
        
        # 绘制说明文本
        font = pygame.font.SysFont(None, 36)
        instructions = [
            "手势控制粒子效果",
            "张开手:粒子散开",
            "握拳:粒子聚集成旋转球体",
            "按ESC退出"
        ]
        
        for i, text in enumerate(instructions):
            text_surface = font.render(text, True, (255, 255, 255))
            screen.blit(text_surface, (10, 10 + i * 40))
        
        # 显示状态
        state_text = "散开" if hand_state == HandState.OPEN else "聚集" if hand_state == HandState.CLOSED else "未检测到手"
        state_color = (255, 100, 100) if hand_state == HandState.OPEN else (100, 255, 100) if hand_state == HandState.CLOSED else (150, 150, 150)
        
        state_surface = font.render(f"状态: {state_text}", True, state_color)
        screen.blit(state_surface, (WIDTH - 200, 10))
        
        # 更新显示
        pygame.display.flip()
        
        # 控制帧率
        clock.tick(60)
    
    # 释放资源
    recognizer.release()
    pygame.quit()

if __name__ == "__main__":
    main()

运行效果

image.png
image.png

初始运行时出现了几个问题:

一是球跟随掌跟移动,我想让其跟随掌心移动;二是手张开,粒子没有扩散的过程;三是没有实现全屏显示;四是要中文未正常显示;五是程序出现手势在摄像头近大远小,使得远处的手势张开也识别成握拳,近处的手势握拳识别成张开;六是摄像头画面镜像问题,手向左移时,画面中的手势向右移。

        (二)Deepseek 代码优化与调试

        将问题反馈给 Deepseek,要求优化。并提出:用粒子勾勒出手指和手掌的轮廓,零星粒子是从手部上掉落下来。

        Deepseek给出以下程序。

代码
import pygame
import cv2
import mediapipe as mp
import numpy as np
import math
import random

# 初始化Pygame
pygame.init()

# 全局变量声明
global WIDTH, HEIGHT, screen

# 初始窗口设置
WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
pygame.display.set_caption("手势粒子效果 - 宽轮廓多源坠落")

# 球体粒子颜色配置
class SphereColors:
    @staticmethod
    def get_color():
        """球体粒子颜色 - 暖色调"""
        color_type = random.choice(['red', 'orange', 'yellow', 'pink'])
        if color_type == 'red':
            return (255, random.randint(50, 150), random.randint(50, 100))
        elif color_type == 'orange':
            return (255, random.randint(150, 200), random.randint(50, 100))
        elif color_type == 'yellow':
            return (255, 255, random.randint(100, 200))
        else:  # pink
            return (255, random.randint(100, 200), random.randint(150, 255))

# 轮廓粒子颜色配置
class ContourColors:
    @staticmethod
    def get_color(layer_index, total_layers):
        """轮廓粒子颜色 - 冷色调渐变"""
        # 从蓝到紫的渐变
        ratio = layer_index / total_layers
        
        if ratio < 0.33:  # 内层: 亮蓝色
            return (100, 200 + int(55 * ratio), 255)
        elif ratio < 0.66:  # 中层: 青色
            return (100, 255, 200 + int(55 * (1 - ratio)))
        else:  # 外层: 紫色
            return (150 + int(105 * ratio), 100, 255)

# 坠落粒子颜色配置
class FallingColors:
    @staticmethod
    def get_color():
        """坠落粒子颜色 - 亮色"""
        color_type = random.choice(['cyan', 'light_blue', 'light_purple', 'white', 'light_green'])
        if color_type == 'cyan':
            return (random.randint(150, 200), 255, 255)
        elif color_type == 'light_blue':
            return (random.randint(150, 200), random.randint(200, 255), 255)
        elif color_type == 'light_purple':
            return (random.randint(200, 255), random.randint(150, 200), 255)
        elif color_type == 'white':
            return (255, 255, 255)
        else:  # light_green
            return (random.randint(150, 200), 255, random.randint(150, 200))

# 粒子基类
class Particle:
    def __init__(self, x, y, z=0):
        self.x = x
        self.y = y
        self.z = z
        self.vx = 0
        self.vy = 0
        self.vz = 0
        self.size = random.uniform(1.5, 4)
        self.lifetime = random.uniform(1.0, 3.0)
        self.max_lifetime = self.lifetime
        
    def update_lifetime(self, dt):
        self.lifetime -= dt
        return self.lifetime > 0
        
    def get_alpha(self):
        return max(0.1, min(1.0, self.lifetime / self.max_lifetime))
        
    def draw(self, surface):
        alpha = self.get_alpha()
        draw_size = max(1, int(self.size * alpha))
        
        # 应用透明度
        if hasattr(self, 'color'):
            r, g, b = self.color
            draw_color = (int(r * alpha), int(g * alpha), int(b * alpha))
        else:
            draw_color = (255, 255, 255)
            
        pygame.draw.circle(surface, draw_color, (int(self.x), int(self.y)), draw_size)

# 球体粒子
class SphereParticle(Particle):
    def __init__(self, x, y, z):
        super().__init__(x, y, z)
        self.color = SphereColors.get_color()
        self.theta = random.uniform(0, 2 * math.pi)
        self.phi = random.uniform(0, math.pi)
        self.rotation_speed = random.uniform(0.002, 0.005)
        self.scatter_angle = random.uniform(0, 2 * math.pi)
        self.scatter_distance = random.uniform(300, 600)
        
    def update_ball(self, center_x, center_y, ball_radius, rotation_factor):
        self.theta += self.rotation_speed * rotation_factor
        self.phi += self.rotation_speed * 0.5
        self.theta %= 2 * math.pi
        
        self.target_x = center_x + ball_radius * math.sin(self.phi) * math.cos(self.theta)
        self.target_y = center_y + ball_radius * math.sin(self.phi) * math.sin(self.theta)
        
        # 平滑移动
        dx = self.target_x - self.x
        dy = self.target_y - self.y
        self.x += dx * 0.1
        self.y += dy * 0.1
        
    def update_scatter(self, center_x, center_y, progress):
        if progress < 0.01:
            return
            
        current_distance = self.scatter_distance * progress
        angle = self.scatter_angle + progress * 2
        
        target_x = center_x + math.cos(angle) * current_distance
        target_y = center_y + math.sin(angle) * current_distance
        
        dx = target_x - self.x
        dy = target_y - self.y
        self.x += dx * 0.05
        self.y += dy * 0.05

# 轮廓粒子 - 加宽版本
class ContourParticle(Particle):
    def __init__(self, start_point, end_point, position_ratio, layer_index, total_layers):
        super().__init__(0, 0)
        self.start_point = start_point
        self.end_point = end_point
        self.position_ratio = position_ratio
        self.layer_index = layer_index
        self.total_layers = total_layers
        
        # 颜色与球体粒子明显区分
        self.color = ContourColors.get_color(layer_index, total_layers)
        
        # 移动参数
        self.speed = random.uniform(0.15, 0.35)
        self.move_direction = random.choice([-1, 1])
        
        # 轮廓粒子稍大
        self.size = random.uniform(2.0, 4.5)
        
        # 更新初始位置
        self.update_contour_position()
        
    def update_contour_position(self):
        """计算在加宽轮廓上的位置"""
        dx = self.end_point[0] - self.start_point[0]
        dy = self.end_point[1] - self.start_point[1]
        
        # 基础位置(在线段上)
        base_x = self.start_point[0] + dx * self.position_ratio
        base_y = self.start_point[1] + dy * self.position_ratio
        
        # 计算法线方向以创建宽度
        if dx == 0 and dy == 0:
            normal_x, normal_y = 0, 0
        else:
            length = math.sqrt(dx*dx + dy*dy)
            unit_x = dx / length
            unit_y = dy / length
            normal_x = -unit_y
            normal_y = unit_x
        
        # 根据层索引计算宽度偏移 - 增加宽度
        width_multiplier = 8  # 增加宽度系数
        width_offset = (self.layer_index - self.total_layers/2) * width_multiplier
        
        # 应用宽度偏移
        offset_x = normal_x * width_offset
        offset_y = normal_y * width_offset
        
        # 添加一些随机扰动,使轮廓更自然
        offset_x += random.uniform(-1.5, 1.5)
        offset_y += random.uniform(-1.5, 1.5)
        
        self.x = base_x + offset_x
        self.y = base_y + offset_y
        
    def update_contour_segment(self, new_start, new_end):
        """更新轮廓线段"""
        self.start_point = new_start
        self.end_point = new_end
        self.update_contour_position()
        
    def move_along_contour(self, dt):
        """沿轮廓移动"""
        self.position_ratio += self.speed * self.move_direction * dt / 40
        
        # 边界检查
        if self.position_ratio >= 1.0:
            self.position_ratio = 1.0
            self.move_direction = -1
        elif self.position_ratio <= 0.0:
            self.position_ratio = 0.0
            self.move_direction = 1
            
        self.update_contour_position()
        
    def update_position(self, dt):
        self.move_along_contour(dt)
        return self.update_lifetime(dt * 0.2)

# 坠落粒子 - 从多个位置掉落
class FallingParticle(Particle):
    def __init__(self, start_x, start_y):
        super().__init__(start_x, start_y)
        
        # 坠落粒子颜色 - 明亮显眼
        self.color = FallingColors.get_color()
        
        # 物理参数
        self.fall_speed = random.uniform(1.5, 4.0)
        self.wind_force = random.uniform(-0.3, 0.3)
        self.size = random.uniform(1.8, 3.5)  # 稍大一些
        
        # 初始速度
        self.vx = random.uniform(-0.5, 0.5)
        self.vy = random.uniform(-1.0, 0)
        
        # 生命周期
        self.lifetime = random.uniform(2.0, 6.0)
        self.max_lifetime = self.lifetime
        
    def update_fall(self, dt):
        # 重力加速
        self.vy += self.fall_speed * dt
        self.vx += self.wind_force * dt
        
        # 更新位置
        self.x += self.vx
        self.y += self.vy
        
        # 边界检查
        if self.y > HEIGHT + 50:
            return False
        if self.x < -50 or self.x > WIDTH + 50:
            return False
            
        return self.update_lifetime(dt)

# 手势识别
class HandTracker:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.mp_hands = mp.solutions.hands
        self.hands = self.mp_hands.Hands(
            static_image_mode=False,
            max_num_hands=1,
            min_detection_confidence=0.7,
            min_tracking_confidence=0.5
        )
        
        self.cap = cv2.VideoCapture(0)
        if not self.cap.isOpened():
            print("无法打开摄像头")
            return
            
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        
        self.hand_landmarks = None
        self.contour_points = []
        self.contour_segments = []
        
        # 手部连接定义
        self.hand_connections = [
            (0, 1), (1, 2), (2, 3), (3, 4),
            (0, 5), (5, 6), (6, 7), (7, 8),
            (0, 9), (9, 10), (10, 11), (11, 12),
            (0, 13), (13, 14), (14, 15), (15, 16),
            (0, 17), (17, 18), (18, 19), (19, 20),
            (5, 9), (9, 13), (13, 17)
        ]
        
        # 所有关节点索引(用于多源坠落)
        self.all_joint_indices = list(range(21))
        
    def extract_contour_points(self, landmarks):
        """提取手部关键点"""
        points = []
        for i in range(21):
            landmark = landmarks.landmark[i]
            x = int(landmark.x * self.width)
            y = int(landmark.y * self.height)
            points.append((x, y))
        return points
    
    def get_contour_segments(self):
        """获取轮廓线段"""
        segments = []
        if len(self.contour_points) == 21:
            for start_idx, end_idx in self.hand_connections:
                if start_idx < 21 and end_idx < 21:
                    segments.append((self.contour_points[start_idx], self.contour_points[end_idx]))
        return segments
    
    def get_random_joint_position(self):
        """随机获取一个关节点位置(用于多源坠落)"""
        if not self.contour_points:
            return None
        joint_idx = random.choice(self.all_joint_indices)
        if joint_idx < len(self.contour_points):
            return self.contour_points[joint_idx]
        return None
    
    def get_finger_tip_positions(self):
        """获取指尖位置"""
        tips = []
        tip_indices = [4, 8, 12, 16, 20]
        if not self.contour_points:
            return tips
            
        for idx in tip_indices:
            if idx < len(self.contour_points):
                tips.append(self.contour_points[idx])
        return tips
    
    def update(self):
        if not self.cap.isOpened():
            return False
            
        success, frame = self.cap.read()
        if not success:
            return False
        
        # 镜像
        frame = cv2.flip(frame, 1)
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        results = self.hands.process(frame_rgb)
        
        if results.multi_hand_landmarks:
            self.hand_landmarks = results.multi_hand_landmarks[0]
            self.contour_points = self.extract_contour_points(self.hand_landmarks)
            self.contour_segments = self.get_contour_segments()
        else:
            self.hand_landmarks = None
            self.contour_points = []
            self.contour_segments = []
            
        return True
    
    def release(self):
        if self.cap.isOpened():
            self.cap.release()
        if self.hands:
            self.hands.close()

# 主粒子系统
class MainParticleSystem:
    def __init__(self, num_particles=500):
        self.particles = []
        self.num_particles = num_particles
        self.state = "ball"
        self.transition_progress = 0
        self.transition_speed = 0.04
        self.ball_radius = 150
        self.center_x = WIDTH // 2
        self.center_y = HEIGHT // 2
        
        # 初始化粒子
        for _ in range(num_particles):
            theta = random.uniform(0, 2 * math.pi)
            phi = random.uniform(0, math.pi)
            
            x = self.center_x + self.ball_radius * math.sin(phi) * math.cos(theta)
            y = self.center_y + self.ball_radius * math.sin(phi) * math.sin(theta)
            z = self.ball_radius * math.cos(phi)
            
            particle = SphereParticle(x, y, z)
            self.particles.append(particle)
    
    def update(self, hand_state, palm_center, gesture_strength):
        # 更新中心位置
        if palm_center:
            self.center_x = palm_center[0]
            self.center_y = palm_center[1]
        
        # 状态控制
        target_state = "scattered" if hand_state == "OPEN" else "ball"
        
        # 球体半径
        if hand_state == "CLOSED":
            self.ball_radius = 120 + gesture_strength * 60
        else:
            self.ball_radius = 150
        
        # 状态过渡
        if self.state != target_state:
            if target_state == "scattered":
                self.transition_progress += self.transition_speed
                if self.transition_progress >= 1:
                    self.state = "scattered"
                    self.transition_progress = 1
            else:
                self.transition_progress -= self.transition_speed
                if self.transition_progress <= 0:
                    self.state = "ball"
                    self.transition_progress = 0
        
        # 更新粒子
        for particle in self.particles:
            if self.state == "ball" and self.transition_progress <= 0:
                particle.update_ball(self.center_x, self.center_y, self.ball_radius, 1.0)
            elif self.state == "scattered" and self.transition_progress >= 1:
                particle.update_scatter(self.center_x, self.center_y, 1.0)
            elif target_state == "scattered":
                particle.update_scatter(self.center_x, self.center_y, self.transition_progress)
    
    def draw(self, surface):
        for particle in self.particles:
            particle.draw(surface)

# 手部轮廓粒子系统 - 加宽版本
class HandContourSystem:
    def __init__(self):
        self.contour_particles = []
        self.falling_particles = []
        
        # 轮廓参数 - 增加宽度
        self.particles_per_segment = 10  # 增加密度
        self.contour_width_layers = 5    # 增加层数
        
        # 坠落粒子参数
        self.max_falling_particles = 80  # 增加数量
        
        # 坠落源概率
        self.finger_tip_probability = 0.4  # 40%从指尖
        self.other_joint_probability = 0.6  # 60%从其他关节点
    
    def update(self, contour_segments, hand_tracker, dt):
        # 更新轮廓粒子
        self.update_contour_particles(contour_segments, dt)
        
        # 更新坠落粒子
        self.update_falling_particles(dt)
        
        # 生成新的坠落粒子 - 多源
        self.generate_falling_particles(hand_tracker)
    
    def update_contour_particles(self, contour_segments, dt):
        if not contour_segments:
            self.contour_particles = []
            return
        
        # 创建或更新粒子
        if len(self.contour_particles) == 0:
            self.create_contour_particles(contour_segments)
        else:
            self.update_existing_particles(contour_segments, dt)
    
    def create_contour_particles(self, contour_segments):
        """创建加宽轮廓粒子"""
        for segment in contour_segments:
            for i in range(self.particles_per_segment):
                position_ratio = i / self.particles_per_segment
                
                # 创建多层粒子形成宽度
                for layer_index in range(self.contour_width_layers):
                    particle = ContourParticle(
                        segment[0], segment[1], 
                        position_ratio, 
                        layer_index, 
                        self.contour_width_layers
                    )
                    self.contour_particles.append(particle)
    
    def update_existing_particles(self, contour_segments, dt):
        """更新现有粒子"""
        particles_per_segment = self.particles_per_segment * self.contour_width_layers
        segment_count = len(contour_segments)
        
        for i, particle in enumerate(self.contour_particles):
            segment_idx = (i // particles_per_segment) % segment_count
            
            if segment_idx < len(contour_segments):
                segment = contour_segments[segment_idx]
                particle.update_contour_segment(segment[0], segment[1])
                
                if not particle.update_position(dt):
                    # 重置粒子
                    new_ratio = random.random()
                    particle.position_ratio = new_ratio
                    particle.lifetime = random.uniform(1.0, 3.0)
                    particle.max_lifetime = particle.lifetime
    
    def generate_falling_particles(self, hand_tracker):
        """从多个位置生成坠落粒子 - 修复了start_x, start_y未定义的问题"""
        if len(self.falling_particles) >= self.max_falling_particles:
            return
        
        # 初始化位置变量
        start_x = None
        start_y = None
        
        # 随机决定粒子来源
        if random.random() < self.finger_tip_probability:
            # 从指尖生成
            tips = hand_tracker.get_finger_tip_positions()
            if tips and len(tips) > 0:
                tip_pos = random.choice(tips)
                start_x = tip_pos[0] + random.uniform(-15, 15)
                start_y = tip_pos[1] - random.uniform(0, 10)
        else:
            # 从其他关节点生成
            joint_pos = hand_tracker.get_random_joint_position()
            if joint_pos:
                start_x = joint_pos[0] + random.uniform(-20, 20)
                start_y = joint_pos[1] + random.uniform(-10, 10)
        
        # 如果没有获取到有效位置,则返回
        if start_x is None or start_y is None:
            return
        
        # 创建坠落粒子
        if random.random() < 0.4:  # 控制生成频率
            particle = FallingParticle(start_x, start_y)
            self.falling_particles.append(particle)
    
    def update_falling_particles(self, dt):
        # 使用列表推导式移除已死亡的粒子
        alive_particles = []
        for particle in self.falling_particles:
            if particle.update_fall(dt):
                alive_particles.append(particle)
        self.falling_particles = alive_particles
    
    def draw(self, surface):
        # 绘制轮廓粒子
        for particle in self.contour_particles:
            particle.draw(surface)
        
        # 绘制坠落粒子
        for particle in self.falling_particles:
            particle.draw(surface)

# 主程序
def main():
    global screen, WIDTH, HEIGHT
    
    # 初始化
    hand_tracker = HandTracker(WIDTH, HEIGHT)
    if not hand_tracker.cap.isOpened():
        print("无法打开摄像头,请检查摄像头连接")
        pygame.quit()
        return
    
    main_particles = MainParticleSystem(num_particles=600)
    hand_contour = HandContourSystem()
    
    clock = pygame.time.Clock()
    running = True
    last_time = pygame.time.get_ticks()
    
    # 手势状态
    hand_state = "UNKNOWN"
    gesture_strength = 0.0
    palm_x, palm_y = WIDTH // 2, HEIGHT // 2  # 默认中心位置
    
    while running:
        # 时间增量
        current_time = pygame.time.get_ticks()
        dt = (current_time - last_time) / 1000.0
        last_time = current_time
        
        # 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_f:
                    # 切换全屏
                    if screen.get_flags() & pygame.FULLSCREEN:
                        screen = pygame.display.set_mode((1200, 800))
                        WIDTH, HEIGHT = 1200, 800
                    else:
                        screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
                        WIDTH, HEIGHT = screen.get_size()
                    
                    # 重新创建手部跟踪器,使用新的尺寸
                    hand_tracker.release()
                    hand_tracker = HandTracker(WIDTH, HEIGHT)
                    
                    # 重置粒子中心位置
                    palm_x, palm_y = WIDTH // 2, HEIGHT // 2
                    
                elif event.key == pygame.K_SPACE:
                    main_particles.state = "scattered" if main_particles.state == "ball" else "ball"
                elif event.key == pygame.K_r:
                    hand_contour = HandContourSystem()
        
        # 更新手势识别
        if not hand_tracker.update():
            print("摄像头错误")
            running = False
            continue
        
        # 简单手势识别
        if hand_tracker.contour_points:
            # 计算掌心位置
            if len(hand_tracker.contour_points) >= 21:
                palm_x = hand_tracker.contour_points[0][0]
                palm_y = hand_tracker.contour_points[0][1]
                
                # 简单手势判断
                tips = hand_tracker.get_finger_tip_positions()
                if tips and len(tips) >= 3:
                    # 计算指尖到掌心的平均距离
                    total_dist = 0
                    for tip in tips:
                        dx = tip[0] - palm_x
                        dy = tip[1] - palm_y
                        total_dist += math.sqrt(dx*dx + dy*dy)
                    
                    avg_dist = total_dist / len(tips)
                    
                    if avg_dist > 150:
                        hand_state = "OPEN"
                        gesture_strength = min(1.0, avg_dist / 300)
                    else:
                        hand_state = "CLOSED"
                        gesture_strength = max(0.0, 1.0 - avg_dist / 150)
        else:
            hand_state = "UNKNOWN"
            gesture_strength = 0.0
        
        # 清屏
        screen.fill((8, 8, 20))  # 深蓝背景
        
        # 更新和绘制主粒子
        main_particles.update(hand_state, (palm_x, palm_y), gesture_strength)
        main_particles.draw(screen)
        
        # 更新和绘制手部轮廓
        hand_contour.update(hand_tracker.contour_segments, hand_tracker, dt)
        hand_contour.draw(screen)
        
        # 更新显示
        pygame.display.flip()
        clock.tick(60)
    
    # 清理
    hand_tracker.release()
    pygame.quit()

if __name__ == "__main__":
    main()

运行程序效果

293115bfdae3562d14a3b9e7dd4285ed.jpg

        四、姿态识别控制粒子效果的拓展

        在手势控制实现后,向 Deepseek 提出新需求:“修改上述程序,将手势识别改为人体姿态识别,通过手臂举起 / 放下控制粒子球的散开与聚合。

 

代码
import pygame
import cv2
import numpy as np
import math
import random
import sys

# 尝试导入mediapipe,如果失败则使用模拟模式
try:
    import mediapipe as mp
    HAS_MEDIAPIPE = True
except ImportError:
    print("警告: mediapipe 未安装,将使用模拟模式")
    print("要安装 mediapipe,请运行: pip install mediapipe -i https://pypi.tuna.tsinghua.edu.cn/simple")
    HAS_MEDIAPIPE = False

# 初始化Pygame
pygame.init()

# 全局变量声明
global WIDTH, HEIGHT, screen

# 初始窗口设置 - 以窗口模式启动(避免全屏时出现问题)
WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
pygame.display.set_caption("人体姿态粒子效果 - 完整轮廓显示")

# 球体粒子颜色配置
class SphereColors:
    @staticmethod
    def get_color():
        """球体粒子颜色 - 暖色调"""
        color_type = random.choice(['red', 'orange', 'yellow', 'pink'])
        if color_type == 'red':
            return (255, random.randint(50, 150), random.randint(50, 100))
        elif color_type == 'orange':
            return (255, random.randint(150, 200), random.randint(50, 100))
        elif color_type == 'yellow':
            return (255, 255, random.randint(100, 200))
        else:  # pink
            return (255, random.randint(100, 200), random.randint(150, 255))

# 轮廓粒子颜色配置
class ContourColors:
    @staticmethod
    def get_color(layer_index, total_layers):
        """轮廓粒子颜色 - 冷色调渐变"""
        # 从蓝到紫的渐变
        ratio = layer_index / total_layers
        
        if ratio < 0.33:  # 内层: 亮蓝色
            return (100, 200 + int(55 * ratio), 255)
        elif ratio < 0.66:  # 中层: 青色
            return (100, 255, 200 + int(55 * (1 - ratio)))
        else:  # 外层: 紫色
            return (150 + int(105 * ratio), 100, 255)

# 坠落粒子颜色配置
class FallingColors:
    @staticmethod
    def get_color():
        """坠落粒子颜色 - 亮色"""
        color_type = random.choice(['cyan', 'light_blue', 'light_purple', 'white', 'light_green'])
        if color_type == 'cyan':
            return (random.randint(150, 200), 255, 255)
        elif color_type == 'light_blue':
            return (random.randint(150, 200), random.randint(200, 255), 255)
        elif color_type == 'light_purple':
            return (random.randint(200, 255), random.randint(150, 200), 255)
        elif color_type == 'white':
            return (255, 255, 255)
        else:  # light_green
            return (random.randint(150, 200), 255, random.randint(150, 200))

# 粒子基类
class Particle:
    def __init__(self, x, y, z=0):
        self.x = x
        self.y = y
        self.z = z
        self.vx = 0
        self.vy = 0
        self.vz = 0
        self.size = random.uniform(1.5, 4)
        self.lifetime = random.uniform(1.0, 3.0)
        self.max_lifetime = self.lifetime
        
    def update_lifetime(self, dt):
        self.lifetime -= dt
        return self.lifetime > 0
        
    def get_alpha(self):
        return max(0.1, min(1.0, self.lifetime / self.max_lifetime))
        
    def draw(self, surface):
        alpha = self.get_alpha()
        draw_size = max(1, int(self.size * alpha))
        
        # 应用透明度
        if hasattr(self, 'color'):
            r, g, b = self.color
            draw_color = (int(r * alpha), int(g * alpha), int(b * alpha))
        else:
            draw_color = (255, 255, 255)
            
        pygame.draw.circle(surface, draw_color, (int(self.x), int(self.y)), draw_size)

# 球体粒子
class SphereParticle(Particle):
    def __init__(self, x, y, z):
        super().__init__(x, y, z)
        self.color = SphereColors.get_color()
        self.theta = random.uniform(0, 2 * math.pi)
        self.phi = random.uniform(0, math.pi)
        self.rotation_speed = random.uniform(0.002, 0.005)
        self.scatter_angle = random.uniform(0, 2 * math.pi)
        self.scatter_distance = random.uniform(300, 600)
        
    def update_ball(self, center_x, center_y, ball_radius, rotation_factor):
        self.theta += self.rotation_speed * rotation_factor
        self.phi += self.rotation_speed * 0.5
        self.theta %= 2 * math.pi
        
        self.target_x = center_x + ball_radius * math.sin(self.phi) * math.cos(self.theta)
        self.target_y = center_y + ball_radius * math.sin(self.phi) * math.sin(self.theta)
        
        # 平滑移动
        dx = self.target_x - self.x
        dy = self.target_y - self.y
        self.x += dx * 0.1
        self.y += dy * 0.1
        
    def update_scatter(self, center_x, center_y, progress):
        if progress < 0.01:
            return
            
        current_distance = self.scatter_distance * progress
        angle = self.scatter_angle + progress * 2
        
        target_x = center_x + math.cos(angle) * current_distance
        target_y = center_y + math.sin(angle) * current_distance
        
        dx = target_x - self.x
        dy = target_y - self.y
        self.x += dx * 0.05
        self.y += dy * 0.05

# 轮廓粒子 - 加宽版本
class ContourParticle(Particle):
    def __init__(self, start_point, end_point, position_ratio, layer_index, total_layers):
        super().__init__(0, 0)
        self.start_point = start_point
        self.end_point = end_point
        self.position_ratio = position_ratio
        self.layer_index = layer_index
        self.total_layers = total_layers
        
        # 颜色与球体粒子明显区分
        self.color = ContourColors.get_color(layer_index, total_layers)
        
        # 移动参数
        self.speed = random.uniform(0.15, 0.35)
        self.move_direction = random.choice([-1, 1])
        
        # 轮廓粒子稍大
        self.size = random.uniform(2.0, 4.5)
        
        # 更新初始位置
        self.update_contour_position()
        
    def update_contour_position(self):
        """计算在加宽轮廓上的位置"""
        dx = self.end_point[0] - self.start_point[0]
        dy = self.end_point[1] - self.start_point[1]
        
        # 基础位置(在线段上)
        base_x = self.start_point[0] + dx * self.position_ratio
        base_y = self.start_point[1] + dy * self.position_ratio
        
        # 计算法线方向以创建宽度
        if dx == 0 and dy == 0:
            normal_x, normal_y = 0, 0
        else:
            length = math.sqrt(dx*dx + dy*dy)
            unit_x = dx / length
            unit_y = dy / length
            normal_x = -unit_y
            normal_y = unit_x
        
        # 根据层索引计算宽度偏移 - 增加宽度
        width_multiplier = 8  # 增加宽度系数
        width_offset = (self.layer_index - self.total_layers/2) * width_multiplier
        
        # 应用宽度偏移
        offset_x = normal_x * width_offset
        offset_y = normal_y * width_offset
        
        # 添加一些随机扰动,使轮廓更自然
        offset_x += random.uniform(-1.5, 1.5)
        offset_y += random.uniform(-1.5, 1.5)
        
        self.x = base_x + offset_x
        self.y = base_y + offset_y
        
    def update_contour_segment(self, new_start, new_end):
        """更新轮廓线段"""
        self.start_point = new_start
        self.end_point = new_end
        self.update_contour_position()
        
    def move_along_contour(self, dt):
        """沿轮廓移动"""
        self.position_ratio += self.speed * self.move_direction * dt / 40
        
        # 边界检查
        if self.position_ratio >= 1.0:
            self.position_ratio = 1.0
            self.move_direction = -1
        elif self.position_ratio <= 0.0:
            self.position_ratio = 0.0
            self.move_direction = 1
            
        self.update_contour_position()
        
    def update_position(self, dt):
        self.move_along_contour(dt)
        return self.update_lifetime(dt * 0.2)

# 坠落粒子 - 从多个位置掉落
class FallingParticle(Particle):
    def __init__(self, start_x, start_y):
        super().__init__(start_x, start_y)
        
        # 坠落粒子颜色 - 明亮显眼
        self.color = FallingColors.get_color()
        
        # 物理参数
        self.fall_speed = random.uniform(1.5, 4.0)
        self.wind_force = random.uniform(-0.3, 0.3)
        self.size = random.uniform(1.8, 3.5)  # 稍大一些
        
        # 初始速度
        self.vx = random.uniform(-0.5, 0.5)
        self.vy = random.uniform(-1.0, 0)
        
        # 生命周期
        self.lifetime = random.uniform(2.0, 6.0)
        self.max_lifetime = self.lifetime
        
    def update_fall(self, dt):
        # 重力加速
        self.vy += self.fall_speed * dt
        self.vx += self.wind_force * dt
        
        # 更新位置
        self.x += self.vx
        self.y += self.vy
        
        # 边界检查
        if self.y > HEIGHT + 50:
            return False
        if self.x < -50 or self.x > WIDTH + 50:
            return False
            
        return self.update_lifetime(dt)

# 人体姿态识别类 - 完整身体轮廓
class PoseTracker:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.use_simulation = False
        
        # 尝试使用真实的mediapipe
        if HAS_MEDIAPIPE:
            try:
                self.mp_pose = mp.solutions.pose
                self.pose = self.mp_pose.Pose(
                    static_image_mode=False,
                    model_complexity=1,  # 使用简化模型以减少资源消耗
                    smooth_landmarks=True,
                    min_detection_confidence=0.5,
                    min_tracking_confidence=0.5
                )
                
                # 尝试打开摄像头
                self.cap = cv2.VideoCapture(0)
                if not self.cap.isOpened():
                    print("警告: 无法打开摄像头,将使用模拟模式")
                    self.use_simulation = True
                else:
                    self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
                    self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
                    
            except Exception as e:
                print(f"初始化MediaPipe失败: {e}")
                print("将使用模拟模式")
                self.use_simulation = True
        else:
            self.use_simulation = True
        
        # 如果使用模拟模式,初始化模拟数据
        if self.use_simulation:
            print("使用模拟模式 - 按空格键切换粒子状态,鼠标移动控制粒子中心")
            self.cap = None
            self.pose = None
        
        self.pose_landmarks = None
        self.body_points = []
        self.body_segments = []
        
        # 关键点索引定义
        self.NOSE = 0
        self.LEFT_SHOULDER = 11
        self.RIGHT_SHOULDER = 12
        self.LEFT_ELBOW = 13
        self.RIGHT_ELBOW = 14
        self.LEFT_WRIST = 15
        self.RIGHT_WRIST = 16
        self.LEFT_HIP = 23
        self.RIGHT_HIP = 24
        self.LEFT_KNEE = 25
        self.RIGHT_KNEE = 26
        self.LEFT_ANKLE = 27
        self.RIGHT_ANKLE = 28
        self.LEFT_HEEL = 29
        self.RIGHT_HEEL = 30
        self.LEFT_FOOT_INDEX = 31
        self.RIGHT_FOOT_INDEX = 32
        
        # 模拟模式下的控制状态
        self.simulated_arms_up = False
        self.simulated_mouse_pos = (width // 2, height // 2)
        
    def extract_body_points(self, landmarks):
        """提取人体所有关键点"""
        points = []
        for i in range(33):  # MediaPipe Pose有33个关键点
            landmark = landmarks.landmark[i]
            x = int(landmark.x * self.width)
            y = int(landmark.y * self.height)
            # 添加可见性分数
            visibility = landmark.visibility if hasattr(landmark, 'visibility') else 1.0
            points.append((x, y, visibility))
        return points
    
    def get_simulated_body_points(self):
        """生成模拟的身体关键点(完整人体)"""
        points = []
        center_x, center_y = self.simulated_mouse_pos
        
        # 生成一个完整的站立姿势
        # 头部 (0-10)
        points.append((center_x, center_y - 150, 1.0))  # 鼻子 0
        points.append((center_x - 30, center_y - 170, 1.0))  # 左眼内角 1
        points.append((center_x + 30, center_y - 170, 1.0))  # 右眼内角 2
        points.append((center_x - 50, center_y - 160, 1.0))  # 左眼外角 3
        points.append((center_x + 50, center_y - 160, 1.0))  # 右眼外角 4
        points.append((center_x - 20, center_y - 140, 1.0))  # 左耳 5
        points.append((center_x + 20, center_y - 140, 1.0))  # 右耳 6
        points.append((center_x, center_y - 120, 1.0))  # 左嘴角 7
        points.append((center_x, center_y - 120, 1.0))  # 右嘴角 8
        
        # 躯干和手臂
        # 肩膀 (11-12)
        points.append((center_x - 40, center_y - 100, 1.0))  # 左肩 11
        points.append((center_x + 40, center_y - 100, 1.0))  # 右肩 12
        
        # 手臂位置基于模拟的手臂状态
        if self.simulated_arms_up:
            # 双手举起
            points.append((center_x - 40, center_y - 150, 1.0))  # 左肘 13
            points.append((center_x + 40, center_y - 150, 1.0))  # 右肘 14
            points.append((center_x - 40, center_y - 200, 1.0))  # 左手腕 15
            points.append((center_x + 40, center_y - 200, 1.0))  # 右手腕 16
        else:
            # 双手放下
            points.append((center_x - 70, center_y - 70, 1.0))  # 左肘 13
            points.append((center_x + 70, center_y - 70, 1.0))  # 右肘 14
            points.append((center_x - 90, center_y - 40, 1.0))  # 左手腕 15
            points.append((center_x + 90, center_y - 40, 1.0))  # 右手腕 16
        
        # 填充中间的点 (9-10, 17-22)
        for i in range(9, 11):  # 嘴
            points.append((center_x, center_y - 120, 1.0))
        
        for i in range(17, 23):  # 身体中间点
            if i % 2 == 0:
                points.append((center_x, center_y - 50, 0.5))
            else:
                points.append((center_x, center_y - 60, 0.5))
        
        # 臀部 (23-24)
        points.append((center_x - 30, center_y, 1.0))  # 左臀 23
        points.append((center_x + 30, center_y, 1.0))  # 右臀 24
        
        # 腿部 (25-28)
        points.append((center_x - 30, center_y + 100, 1.0))  # 左膝 25
        points.append((center_x + 30, center_y + 100, 1.0))  # 右膝 26
        points.append((center_x - 30, center_y + 200, 1.0))  # 左踝 27
        points.append((center_x + 30, center_y + 200, 1.0))  # 右踝 28
        
        # 脚部 (29-32)
        points.append((center_x - 30, center_y + 220, 1.0))  # 左脚跟 29
        points.append((center_x + 30, center_y + 220, 1.0))  # 右脚跟 30
        points.append((center_x - 40, center_y + 220, 1.0))  # 左脚趾 31
        points.append((center_x + 40, center_y + 220, 1.0))  # 右脚趾 32
        
        return points
    
    def get_body_segments(self):
        """获取完整的身体连线线段(包括躯干、腿、脚)"""
        segments = []
        
        if not self.body_points or len(self.body_points) < 33:
            return segments
        
        # 完整的人体连接(基于MediaPipe Pose连接)
        connections = [
            # 面部轮廓
            (0, 1), (1, 2), (2, 3), (3, 7),  # 鼻子到眼睛到耳朵
            (0, 4), (4, 5), (5, 6), (6, 8),  # 鼻子到眼睛到耳朵的另一侧
            
            # 躯干
            (11, 12),  # 肩膀之间
            (11, 23), (12, 24),  # 肩膀到臀部
            (23, 24),  # 臀部之间
            
            # 左臂
            (11, 13), (13, 15),  # 左肩到左肘到左手腕
            
            # 右臂
            (12, 14), (14, 16),  # 右肩到右肘到右手腕
            
            # 左腿
            (23, 25), (25, 27), (27, 29), (29, 31),  # 左臀到左膝到左脚踝到左脚跟到左脚趾
            
            # 右腿
            (24, 26), (26, 28), (28, 30), (30, 32),  # 右臀到右膝到右脚踝到右脚跟到右脚趾
            
            # 连接脚部
            (27, 31), (28, 32),  # 脚踝到脚趾
        ]
        
        for start_idx, end_idx in connections:
            if (start_idx < len(self.body_points) and end_idx < len(self.body_points) and
                self.body_points[start_idx][2] > 0.1 and self.body_points[end_idx][2] > 0.1):
                
                start_point = (self.body_points[start_idx][0], self.body_points[start_idx][1])
                end_point = (self.body_points[end_idx][0], self.body_points[end_idx][1])
                
                # 只添加有效的连接
                dist = math.sqrt((start_point[0] - end_point[0])**2 + (start_point[1] - end_point[1])**2)
                if dist < self.width * 0.5:
                    segments.append((start_point, end_point))
        
        return segments
    
    def get_all_joint_positions(self):
        """获取所有关节点位置(用于生成坠落粒子)"""
        positions = []
        if len(self.body_points) > 0:
            # 只使用可见性较高的关节点
            for i, point in enumerate(self.body_points):
                if point[2] > 0.3:  # 可见性阈值
                    positions.append((point[0], point[1]))
        return positions
    
    def get_hand_positions(self):
        """获取手部位置(手腕)"""
        positions = []
        hand_indices = [self.LEFT_WRIST, self.RIGHT_WRIST]
        
        for idx in hand_indices:
            if idx < len(self.body_points) and self.body_points[idx][2] > 0.3:
                positions.append((self.body_points[idx][0], self.body_points[idx][1]))
        
        return positions
    
    def detect_arms_up(self):
        """检测双手是否举起"""
        if self.use_simulation:
            # 模拟模式下,返回预设的状态
            return self.simulated_arms_up, 1.0 if self.simulated_arms_up else 0.0
        
        if len(self.body_points) < 25:
            return False, 0.0
        
        # 获取必要的关键点
        left_shoulder = self.body_points[self.LEFT_SHOULDER]
        right_shoulder = self.body_points[self.RIGHT_SHOULDER]
        left_wrist = self.body_points[self.LEFT_WRIST]
        right_wrist = self.body_points[self.RIGHT_WRIST]
        
        # 检查关键点可见性
        if (left_shoulder[2] < 0.3 or right_shoulder[2] < 0.3 or 
            left_wrist[2] < 0.3 or right_wrist[2] < 0.3):
            return False, 0.0
        
        # 计算手腕相对于肩膀的高度
        left_wrist_relative_y = left_wrist[1] - left_shoulder[1]
        right_wrist_relative_y = right_wrist[1] - right_shoulder[1]
        
        # 双臂举起的判断条件:手腕在肩膀上方
        left_arm_up = left_wrist_relative_y < -30
        right_arm_up = right_wrist_relative_y < -30
        
        # 计算举起强度
        left_strength = max(0, min(1, (-left_wrist_relative_y - 30) / 150))
        right_strength = max(0, min(1, (-right_wrist_relative_y - 30) / 150))
        avg_strength = (left_strength + right_strength) / 2
        
        return left_arm_up and right_arm_up, avg_strength
    
    def get_body_center(self):
        """获取身体中心点(两肩中点)"""
        if self.use_simulation:
            return self.simulated_mouse_pos
        
        if len(self.body_points) > 12:
            left_shoulder = self.body_points[self.LEFT_SHOULDER]
            right_shoulder = self.body_points[self.RIGHT_SHOULDER]
            
            if left_shoulder[2] > 0.3 and right_shoulder[2] > 0.3:
                center_x = (left_shoulder[0] + right_shoulder[0]) // 2
                center_y = (left_shoulder[1] + right_shoulder[1]) // 2
                return (center_x, center_y)
        
        # 如果无法获取肩膀位置,返回屏幕中心
        return (self.width // 2, self.height // 2)
    
    def get_hand_height_ratio(self):
        """获取双手高度比例"""
        if self.use_simulation:
            return 1.0 if self.simulated_arms_up else 0.0
        
        if len(self.body_points) < 25:
            return 0.0
        
        left_wrist = self.body_points[self.LEFT_WRIST]
        right_wrist = self.body_points[self.RIGHT_WRIST]
        left_shoulder = self.body_points[self.LEFT_SHOULDER]
        right_shoulder = self.body_points[self.RIGHT_SHOULDER]
        
        if (left_wrist[2] < 0.3 or right_wrist[2] < 0.3 or 
            left_shoulder[2] < 0.3 or right_shoulder[2] < 0.3):
            return 0.0
        
        # 计算手腕相对于肩膀的高度比例
        max_height = self.height * 0.4
        left_height = max(0, left_shoulder[1] - left_wrist[1])
        right_height = max(0, right_shoulder[1] - right_wrist[1])
        
        avg_height = (left_height + right_height) / 2
        height_ratio = min(1.0, avg_height / max_height)
        
        return height_ratio
    
    def update(self):
        if self.use_simulation:
            # 模拟模式:生成模拟的身体关键点
            self.body_points = self.get_simulated_body_points()
            self.body_segments = self.get_body_segments()
            return True
        
        # 真实模式:使用摄像头
        if not self.cap or not self.cap.isOpened():
            return False
            
        success, frame = self.cap.read()
        if not success:
            return False
        
        # 镜像
        frame = cv2.flip(frame, 1)
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        results = self.pose.process(frame_rgb)
        
        if results.pose_landmarks:
            self.pose_landmarks = results.pose_landmarks
            self.body_points = self.extract_body_points(self.pose_landmarks)
            self.body_segments = self.get_body_segments()
        else:
            self.pose_landmarks = None
            self.body_points = []
            self.body_segments = []
            
        return True
    
    def update_simulation(self, mouse_pos, arms_up):
        """更新模拟模式下的状态"""
        self.simulated_mouse_pos = mouse_pos
        self.simulated_arms_up = arms_up
    
    def release(self):
        if self.cap and self.cap.isOpened():
            self.cap.release()

# 主粒子系统
class MainParticleSystem:
    def __init__(self, num_particles=500):
        self.particles = []
        self.num_particles = num_particles
        self.state = "ball"
        self.transition_progress = 0
        self.transition_speed = 0.04
        self.ball_radius = 150
        self.center_x = WIDTH // 2
        self.center_y = HEIGHT // 2
        
        # 初始化粒子
        for _ in range(num_particles):
            theta = random.uniform(0, 2 * math.pi)
            phi = random.uniform(0, math.pi)
            
            x = self.center_x + self.ball_radius * math.sin(phi) * math.cos(theta)
            y = self.center_y + self.ball_radius * math.sin(phi) * math.sin(theta)
            z = self.ball_radius * math.cos(phi)
            
            particle = SphereParticle(x, y, z)
            self.particles.append(particle)
    
    def update(self, arms_up, center_pos, strength):
        # 更新中心位置
        if center_pos:
            self.center_x = center_pos[0]
            self.center_y = center_pos[1]
        
        # 状态控制:双手举起时粒子扩散,否则聚集
        target_state = "scattered" if arms_up else "ball"
        
        # 球体半径根据举起强度调整
        if arms_up:
            self.ball_radius = 80 + strength * 200
        else:
            self.ball_radius = 100
        
        # 状态过渡
        if self.state != target_state:
            if target_state == "scattered":
                self.transition_progress += self.transition_speed
                if self.transition_progress >= 1:
                    self.state = "scattered"
                    self.transition_progress = 1
            else:
                self.transition_progress -= self.transition_speed
                if self.transition_progress <= 0:
                    self.state = "ball"
                    self.transition_progress = 0
        
        # 更新粒子
        for particle in self.particles:
            if self.state == "ball" and self.transition_progress <= 0:
                particle.update_ball(self.center_x, self.center_y, self.ball_radius, 1.0)
            elif self.state == "scattered" and self.transition_progress >= 1:
                particle.update_scatter(self.center_x, self.center_y, 1.0)
            elif target_state == "scattered":
                particle.update_scatter(self.center_x, self.center_y, self.transition_progress)
    
    def draw(self, surface):
        for particle in self.particles:
            particle.draw(surface)

# 身体轮廓粒子系统 - 完整人体
class BodyContourSystem:
    def __init__(self):
        self.contour_particles = []
        self.falling_particles = []
        
        # 轮廓参数
        self.particles_per_segment = 10  # 增加密度以显示更多细节
        self.contour_width_layers = 4    # 增加层数以显示宽度
        
        # 坠落粒子参数
        self.max_falling_particles = 100
        
        # 身体部位颜色
        self.colors = {
            'head': (255, 200, 100),     # 头部 - 橙色
            'torso': (150, 100, 255),    # 躯干 - 紫色
            'arms': (100, 200, 255),     # 手臂 - 蓝色
            'legs': (100, 255, 200),     # 腿部 - 青色
            'feet': (255, 100, 100)      # 脚部 - 红色
        }
        
    def update(self, body_segments, dt, arms_up=False, arms_strength=0.0):
        # 更新轮廓粒子
        self.update_contour_particles(body_segments, dt)
        
        # 更新坠落粒子
        self.update_falling_particles(dt)
        
        # 根据手臂状态调整坠落粒子生成
        if arms_up:
            self.generate_falling_particles(body_segments, arms_strength)
    
    def update_contour_particles(self, body_segments, dt):
        if not body_segments:
            self.contour_particles = []
            return
        
        # 创建或更新粒子
        if len(self.contour_particles) == 0:
            self.create_contour_particles(body_segments)
        else:
            self.update_existing_particles(body_segments, dt)
    
    def create_contour_particles(self, body_segments):
        """创建完整身体轮廓粒子"""
        for segment in body_segments:
            for i in range(self.particles_per_segment):
                position_ratio = i / self.particles_per_segment
                
                # 创建多层粒子形成宽度
                for layer_index in range(self.contour_width_layers):
                    particle = ContourParticle(
                        segment[0], segment[1], 
                        position_ratio, 
                        layer_index, 
                        self.contour_width_layers
                    )
                    self.contour_particles.append(particle)
    
    def update_existing_particles(self, body_segments, dt):
        """更新现有粒子"""
        particles_per_segment = self.particles_per_segment * self.contour_width_layers
        
        # 确保有足够的粒子
        if len(self.contour_particles) < len(body_segments) * particles_per_segment:
            self.create_contour_particles(body_segments)
            return
        
        segment_count = len(body_segments)
        
        for i, particle in enumerate(self.contour_particles):
            segment_idx = (i // particles_per_segment) % segment_count
            
            if segment_idx < len(body_segments):
                segment = body_segments[segment_idx]
                particle.update_contour_segment(segment[0], segment[1])
                
                if not particle.update_position(dt):
                    # 重置粒子
                    new_ratio = random.random()
                    particle.position_ratio = new_ratio
                    particle.lifetime = random.uniform(1.0, 3.0)
                    particle.max_lifetime = particle.lifetime
    
    def generate_falling_particles(self, body_segments, frequency=0.4):
        """从身体轮廓生成坠落粒子"""
        if len(self.falling_particles) >= self.max_falling_particles:
            return
        
        if not body_segments:
            return
        
        # 随机选择一个线段上的点
        if random.random() < frequency:
            segment = random.choice(body_segments)
            # 在线段上随机选择一个点
            t = random.random()
            start_x = segment[0][0] * (1 - t) + segment[1][0] * t
            start_y = segment[0][1] * (1 - t) + segment[1][1] * t
            
            # 添加一些随机偏移
            start_x += random.uniform(-15, 15)
            start_y += random.uniform(-10, 10)
            
            particle = FallingParticle(start_x, start_y)
            self.falling_particles.append(particle)
    
    def update_falling_particles(self, dt):
        # 移除已死亡的粒子
        alive_particles = []
        for particle in self.falling_particles:
            if particle.update_fall(dt):
                alive_particles.append(particle)
        self.falling_particles = alive_particles
    
    def draw(self, surface):
        # 绘制轮廓粒子
        for particle in self.contour_particles:
            particle.draw(surface)
        
        # 绘制坠落粒子
        for particle in self.falling_particles:
            particle.draw(surface)

# 主程序
def main():
    global screen, WIDTH, HEIGHT
    
    # 初始化
    pose_tracker = PoseTracker(WIDTH, HEIGHT)
    main_particles = MainParticleSystem(num_particles=600)
    body_contour = BodyContourSystem()
    
    clock = pygame.time.Clock()
    running = True
    last_time = pygame.time.get_ticks()
    
    # 姿态状态
    arms_up = False
    arms_strength = 0.0
    body_center = (WIDTH // 2, HEIGHT // 2)
    
    # 字体
    debug_font = pygame.font.SysFont("SimHei", 24)
    instruction_font = pygame.font.SysFont("SimHei", 24)
    
    # 鼠标位置
    mouse_x, mouse_y = WIDTH // 2, HEIGHT // 2
    
    while running:
        # 时间增量
        current_time = pygame.time.get_ticks()
        dt = (current_time - last_time) / 1000.0
        last_time = current_time
        
        # 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_f:
                    # 切换全屏
                    if screen.get_flags() & pygame.FULLSCREEN:
                        screen = pygame.display.set_mode((1200, 800))
                        WIDTH, HEIGHT = 1200, 800
                    else:
                        screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
                        WIDTH, HEIGHT = screen.get_size()
                    
                    # 重新创建姿态跟踪器
                    pose_tracker.release()
                    pose_tracker = PoseTracker(WIDTH, HEIGHT)
                    
                    # 重置粒子中心位置
                    body_center = (WIDTH // 2, HEIGHT // 2)
                    
                elif event.key == pygame.K_SPACE:
                    # 空格键手动切换粒子状态
                    if pose_tracker.use_simulation:
                        pose_tracker.simulated_arms_up = not pose_tracker.simulated_arms_up
                elif event.key == pygame.K_r:
                    body_contour = BodyContourSystem()
                elif event.key == pygame.K_d:
                    # 切换调试模式
                    show_debug = not hasattr(main, "show_debug")
                    main.show_debug = show_debug
                elif event.key == pygame.K_m:
                    # 切换模式
                    pose_tracker.use_simulation = not pose_tracker.use_simulation
                elif event.key == pygame.K_c:
                    # 清除轮廓
                    body_contour.contour_particles = []
            
            elif event.type == pygame.MOUSEMOTION:
                # 更新鼠标位置
                mouse_x, mouse_y = event.pos
                if pose_tracker.use_simulation:
                    pose_tracker.update_simulation((mouse_x, mouse_y), pose_tracker.simulated_arms_up)
        
        # 更新姿态识别
        if not pose_tracker.update():
            # 如果更新失败,使用模拟模式
            pose_tracker.use_simulation = True
        
        # 检测双臂状态
        arms_up, arms_strength = pose_tracker.detect_arms_up()
        body_center = pose_tracker.get_body_center()
        
        # 清屏
        screen.fill((8, 8, 20))  # 深蓝背景
        
        # 更新和绘制主粒子
        main_particles.update(arms_up, body_center, arms_strength)
        main_particles.draw(screen)
        
        # 更新和绘制身体轮廓
        body_contour.update(pose_tracker.body_segments, dt, arms_up, arms_strength)
        body_contour.draw(screen)
        
        # 显示操作提示
        mode_text = "模拟模式" if pose_tracker.use_simulation else "摄像头模式"
        mode_display = instruction_font.render(f"模式: {mode_text}", True, (255, 255, 255))
        screen.blit(mode_display, (20, 20))
        
        instruction_text = instruction_font.render("双手举起控制粒子扩散", True, (255, 255, 255))
        screen.blit(instruction_text, (WIDTH // 2 - instruction_text.get_width() // 2, 60))
        
        # 显示状态提示
        if arms_up:
            state_text = instruction_font.render("状态: 粒子扩散中", True, (100, 255, 100))
        else:
            state_text = instruction_font.render("状态: 粒子聚集", True, (255, 100, 100))
        screen.blit(state_text, (WIDTH // 2 - state_text.get_width() // 2, 100))
        
        # 显示身体部位计数
        segment_count = len(pose_tracker.body_segments)
        segment_text = f"轮廓线段: {segment_count}"
        segment_display = debug_font.render(segment_text, True, (200, 200, 255))
        screen.blit(segment_display, (20, 100))
        
        # 显示控制提示
        controls = [
            "ESC: 退出",
            "F: 全屏切换",
            "空格: 切换手臂状态",
            "M: 切换模式",
            "R: 重置轮廓",
            "C: 清除轮廓",
            "D: 切换调试信息"
        ]
        
        for i, control in enumerate(controls):
            control_text = debug_font.render(control, True, (200, 200, 200))
            screen.blit(control_text, (WIDTH - 200, 20 + i * 25))
        
        # 显示调试信息
        if hasattr(main, "show_debug") and main.show_debug:
            debug_info = f"双臂举起: {'是' if arms_up else '否'}, 强度: {arms_strength:.2f}"
            debug_info += f", 身体点数: {len(pose_tracker.body_points)}"
            debug_info += f", 轮廓线段: {segment_count}"
            debug_text = debug_font.render(debug_info, True, (255, 255, 255))
            screen.blit(debug_text, (20, 140))
        
        # 更新显示
        pygame.display.flip()
        clock.tick(60)
    
    # 清理
    pose_tracker.release()
    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()

演示效果

        五、DF 二哈视觉传感器与 Arduino 的硬件集成

88813933feeeed09f653e8545ef5f10c.jpg
03b1372cf43ac5dded0e6de1ec6a6dba.jpg

        (一)DF 二哈视觉传感器的配置与 Arduino 编程

        向 Deepseek 输入需求:“编写 Arduino Uno 与 DF 二哈视觉传感器的通信程序,传感器识别手势(握拳 / 张开)后,将数据通过串口发送至电脑;同时编写 Python 程序,接收串口数据并控制粒子效果,保持粒子效果的逻辑与之前一致。”

        将Deepseek写的arduino c程序,自已改写成图形化程序。

screenshot-arduinot程序-1764948565841.png

        (二)Python 串口通信与粒子效果联动

        Deepseek 修改了 Python 程序,新增串口通信模块:使用 PySerial 库打开电脑串口(COM38,波特率 115200),实时读取 Arduino 发送的指令,将指令映射为粒子效果的控制信号。

 

代码
import pygame
import cv2
import numpy as np
import math
import random
import serial
import serial.tools.list_ports
import threading
import time

# 初始化Pygame
pygame.init()

# 全局变量声明
global WIDTH, HEIGHT, screen

# 初始窗口设置
WIDTH, HEIGHT = 1920, 1080
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
pygame.display.set_caption("手势粒子效果 - 二哈2手势控制")

# 手势定义(与Arduino保持一致)
GESTURE_NONE = 0
GESTURE_OPEN = 1      # 张开手
GESTURE_CLOSED = 2    # 握拳
GESTURE_TWO = 3        # 数字2
GESTURE_FOUR = 4      # 数字4

# 粒子颜色配置 - 根据手势类型改变颜色主题
class ParticleColors:
    @staticmethod
    def get_sphere_color(gesture_type):
        """球体粒子颜色 - 根据手势类型变化"""
        color_themes = {
            GESTURE_OPEN: 'warm',      # 暖色
            GESTURE_CLOSED: 'cool',    # 冷色
            GESTURE_TWO: 'vibrant',     # 鲜艳色
            GESTURE_FOUR: 'neon',      # 霓虹色

        }
        
        theme = color_themes.get(gesture_type, 'warm')
        
        if theme == 'warm':
            return (255, random.randint(100, 200), random.randint(50, 150))
        elif theme == 'cool':
            return (random.randint(50, 150), random.randint(150, 250), 255)
        elif theme == 'vibrant':
            return (random.randint(200, 255), random.randint(100, 200), random.randint(0, 100))
        elif theme == 'neon':
            colors = [(255, 20, 147), (0, 255, 255), (255, 255, 0), (255, 0, 255)]
            return random.choice(colors)
        elif theme == 'purple':
            return (180 + random.randint(0, 75), 70 + random.randint(0, 50), 220 + random.randint(0, 35))
        elif theme == 'green':
            return (50 + random.randint(0, 50), 180 + random.randint(0, 75), 100 + random.randint(0, 50))
        elif theme == 'orange':
            return (255, 120 + random.randint(0, 80), 30 + random.randint(0, 50))
        else:
            return (255, 200, 100)
    
    @staticmethod
    def get_contour_color(gesture_type, layer_index, total_layers):
        """轮廓粒子颜色 - 根据手势类型变化"""
        ratio = layer_index / total_layers
        
        if gesture_type == GESTURE_OPEN:
            # 张开手 - 蓝色渐变
            return (100, 150 + int(105 * ratio), 255)
        elif gesture_type == GESTURE_CLOSED:
            # 握拳 - 红色渐变
            return (200 + int(55 * ratio), 100, 50 + int(55 * (1 - ratio)))
        elif gesture_type == GESTURE_TWO:
            # TWO手势 - 绿色渐变
            return (100, 200 + int(55 * ratio), 100 + int(55 * (1 - ratio)))
        elif gesture_type == GESTURE_FOUR:
            # 摇滚手势 - 紫色渐变
            return (150 + int(105 * ratio), 50, 150 + int(105 * (1 - ratio)))
        else:
            # 默认 - 青色渐变
            return (100, 200 + int(55 * ratio), 200 + int(55 * (1 - ratio)))
    
    @staticmethod
    def get_falling_color(gesture_type):
        """坠落粒子颜色 - 根据手势类型变化"""
        if gesture_type == GESTURE_OPEN:
            return (random.randint(150, 200), 255, 255)  # 青色
        elif gesture_type == GESTURE_CLOSED:
            return (255, random.randint(100, 150), random.randint(100, 150))  # 粉色
        elif gesture_type == GESTURE_TWO:
            return (random.randint(100, 150), 255, random.randint(100, 150))  # 绿色
        elif gesture_type == GESTURE_FOUR:
            return (255, 255, random.randint(100, 150))  # 黄色
        
        else:
            return (255, 255, 255)  # 白色

# 粒子基类
class Particle:
    def __init__(self, x, y, z=0):
        self.x = x
        self.y = y
        self.z = z
        self.vx = 0
        self.vy = 0
        self.vz = 0
        self.size = random.uniform(1.5, 4)
        self.lifetime = random.uniform(1.0, 3.0)
        self.max_lifetime = self.lifetime
        
    def update_lifetime(self, dt):
        self.lifetime -= dt
        return self.lifetime > 0
        
    def get_alpha(self):
        return max(0.1, min(1.0, self.lifetime / self.max_lifetime))
        
    def draw(self, surface):
        alpha = self.get_alpha()
        draw_size = max(1, int(self.size * alpha))
        
        if hasattr(self, 'color'):
            r, g, b = self.color
            draw_color = (int(r * alpha), int(g * alpha), int(b * alpha))
        else:
            draw_color = (255, 255, 255)
            
        pygame.draw.circle(surface, draw_color, (int(self.x), int(self.y)), draw_size)

# 球体粒子 - 根据手势类型改变行为
class SphereParticle(Particle):
    def __init__(self, x, y, z, gesture_type=GESTURE_OPEN):
        super().__init__(x, y, z)
        self.gesture_type = gesture_type
        self.color = ParticleColors.get_sphere_color(gesture_type)
        
        # 根据手势类型调整参数
        if gesture_type == GESTURE_OPEN:
            self.rotation_speed = random.uniform(0.001, 0.003)  # 慢速
            self.scatter_distance = random.uniform(200, 400)
        elif gesture_type == GESTURE_CLOSED:
            self.rotation_speed = random.uniform(0.003, 0.006)  # 快速
            self.scatter_distance = random.uniform(100, 300)
        elif gesture_type == GESTURE_TWO:
            self.rotation_speed = random.uniform(0.002, 0.004)  # 中速
            self.scatter_distance = random.uniform(300, 500)
        elif gesture_type == GESTURE_FOUR:
            self.rotation_speed = random.uniform(0.005, 0.008)  # 非常快
            self.scatter_distance = random.uniform(400, 600)
        else:
            self.rotation_speed = random.uniform(0.002, 0.005)
            self.scatter_distance = random.uniform(200, 400)
            
        self.theta = random.uniform(0, 2 * math.pi)
        self.phi = random.uniform(0, math.pi)
        self.scatter_angle = random.uniform(0, 2 * math.pi)
        
    def update_ball(self, center_x, center_y, ball_radius, rotation_factor):
        self.theta += self.rotation_speed * rotation_factor
        self.phi += self.rotation_speed * 0.5
        self.theta %= 2 * math.pi
        
        # 根据手势类型调整球体形状
        if self.gesture_type == GESTURE_TWO:
            # TWO手势 - 形成环形
            effective_radius = ball_radius * 0.7
        elif self.gesture_type == GESTURE_FOUR:
            # 摇滚手势 - 不规则形状
            effective_radius = ball_radius * (0.8 + 0.4 * math.sin(self.theta * 2))
        else:
            effective_radius = ball_radius
            
        self.target_x = center_x + effective_radius * math.sin(self.phi) * math.cos(self.theta)
        self.target_y = center_y + effective_radius * math.sin(self.phi) * math.sin(self.theta)
        
        # 根据手势类型调整移动速度
        if self.gesture_type == GESTURE_CLOSED:
            move_factor = 0.15  # 握拳时粒子移动更快
        elif self.gesture_type == GESTURE_FOUR:
            move_factor = 0.2   # 摇滚手势时粒子移动最快
        else:
            move_factor = 0.1   # 默认速度
            
        dx = self.target_x - self.x
        dy = self.target_y - self.y
        self.x += dx * move_factor
        self.y += dy * move_factor
        
    def update_scatter(self, center_x, center_y, progress):
        if progress < 0.01:
            return
            
        current_distance = self.scatter_distance * progress
        
        # 根据手势类型调整散射角度
        if self.gesture_type == GESTURE_TWO:
            angle = self.scatter_angle + progress * 1  # 慢速旋转
        elif self.gesture_type == GESTURE_FOUR:
            angle = self.scatter_angle + progress * 3  # 快速旋转
        else:
            angle = self.scatter_angle + progress * 2  # 正常旋转
            
        target_x = center_x + math.cos(angle) * current_distance
        target_y = center_y + math.sin(angle) * current_distance
        
        dx = target_x - self.x
        dy = target_y - self.y
        
        # 根据手势类型调整散射速度
        if self.gesture_type == GESTURE_CLOSED:
            move_factor = 0.03  # 握拳时散射较慢
        elif self.gesture_type == GESTURE_OPEN:
            move_factor = 0.05  # 张开手时散射正常
        else:
            move_factor = 0.07  # 其他手势散射较快
            
        self.x += dx * move_factor
        self.y += dy * move_factor

# 二哈2手势数据处理器
class HuskyLensHandler:
    def __init__(self, port=None):
        self.port = port
        self.serial_conn = None
        self.gesture_data = {
            'gesture_id': GESTURE_NONE,
            'x': WIDTH // 2,
            'y': HEIGHT // 2,
            'width': 0,
            'height': 0
        }
        self.running = False
        self.thread = None
        
    def find_arduino_port(self):
        """自动查找Arduino端口"""
        ports = serial.tools.list_ports.comports()
        for port in ports:
            if 'Arduino' in port.description or 'CH340' in port.description or 'USB' in port.description:
                return port.device
        return None
    
    def connect(self):
        """连接到Arduino"""
        if self.port is None:
            self.port = self.find_arduino_port()
            
        if self.port is None:
            print("未找到Arduino设备,请检查连接")
            return False
            
        try:
            self.serial_conn = serial.Serial(self.port, 115200, timeout=0.1)
            print(f"已连接到串口: {self.port}")
            return True
        except Exception as e:
            print(f"连接串口失败: {e}")
            return False
    
    def start_reading(self):
        """启动读取线程"""
        if self.serial_conn is None:
            if not self.connect():
                return False
                
        self.running = True
        self.thread = threading.Thread(target=self._read_serial_thread)
        self.thread.daemon = True
        self.thread.start()
        return True
    
    def _read_serial_thread(self):
        """串口读取线程"""
        while self.running:
            try:
                if self.serial_conn.in_waiting > 0:
                    line = self.serial_conn.readline().decode('utf-8').strip()
                    if line:
                        self._parse_gesture_data(line)
            except Exception as e:
                print(f"读取串口数据错误: {e}")
                time.sleep(0.1)
    
    def _parse_gesture_data(self, data_str):
     """解析手势数据 - 5个参数:gesture_id,x,y,width,height"""
     try:
        parts = data_str.split(',')
        if len(parts) >= 5:
            # 数据格式: gesture_id,x,y,width,height
            self.gesture_data['gesture_id'] = int(parts[0])
            
            # 将二哈2的坐标映射到屏幕坐标
            # 二哈2分辨率通常为640x480
            husky_x = int(parts[1])
            husky_y = int(parts[2])
            
            # 映射到屏幕坐标,并进行镜像翻转(左右翻转)
            # 原始: self.gesture_data['x'] = int(husky_x * WIDTH / 640)
            # 修改为镜像翻转:
            self.gesture_data['x'] = int(WIDTH - (husky_x * WIDTH / 480))
            
            # Y坐标保持不变
            self.gesture_data['y'] = int(husky_y * HEIGHT / 480)
            
            self.gesture_data['width'] = int(parts[3])
            self.gesture_data['height'] = int(parts[4])
     except Exception as e:
        print(f"解析手势数据错误: {e}")
    
    def get_gesture(self):
        """获取当前手势数据"""
        return self.gesture_data.copy()
    
    def stop(self):
        """停止读取"""
        self.running = False
        if self.thread:
            self.thread.join(timeout=1)
        if self.serial_conn:
            self.serial_conn.close()
    
    def get_hand_state(self):
        """转换为手势状态"""
        gesture_id = self.gesture_data['gesture_id']
        
        # 将手势ID映射为状态字符串
        if gesture_id == GESTURE_OPEN:
            return "OPEN", 1.0
        elif gesture_id == GESTURE_CLOSED:
            return "CLOSED", 1.0
        elif gesture_id == GESTURE_TWO:
            return "TWO", 1.0
        elif gesture_id == GESTURE_FOUR:
            return "FOUR", 1.0
       
        else:
            return "UNKNOWN", 0.0

# 主粒子系统 - 根据手势类型调整
class MainParticleSystem:
    def __init__(self, num_particles=500):
        self.particles = []
        self.num_particles = num_particles
        self.state = "ball"
        self.transition_progress = 0
        self.transition_speed = 0.04
        self.ball_radius = 150
        self.center_x = WIDTH // 2
        self.center_y = HEIGHT // 2
        self.current_gesture = GESTURE_OPEN
        
        # 初始化粒子
        for _ in range(num_particles):
            theta = random.uniform(0, 2 * math.pi)
            phi = random.uniform(0, math.pi)
            
            x = self.center_x + self.ball_radius * math.sin(phi) * math.cos(theta)
            y = self.center_y + self.ball_radius * math.sin(phi) * math.sin(theta)
            z = self.ball_radius * math.cos(phi)
            
            particle = SphereParticle(x, y, z, self.current_gesture)
            self.particles.append(particle)
    
    def update(self, hand_state, palm_center, gesture_type):
        if palm_center:
            self.center_x = palm_center[0]
            self.center_y = palm_center[1]
        
        # 根据手势类型确定目标状态
        if hand_state == "OPEN" or hand_state == "TWO" or hand_state == "FOUR":
            target_state = "scattered"
        else:
            target_state = "ball"
        
        # 根据手势类型调整球体半径
        if hand_state == "CLOSED":
            self.ball_radius = 80
        elif hand_state == "TWO":
            self.ball_radius = 120
        elif hand_state == "FOUR":
            self.ball_radius = 180
        elif hand_state == "OPEN":
            self.ball_radius = 150
        else:
            self.ball_radius = 100
        
        # 状态过渡
        if self.state != target_state:
            if target_state == "scattered":
                self.transition_progress += self.transition_speed
                if self.transition_progress >= 1:
                    self.state = "scattered"
                    self.transition_progress = 1
            else:
                self.transition_progress -= self.transition_speed
                if self.transition_progress <= 0:
                    self.state = "ball"
                    self.transition_progress = 0
        
        # 更新当前手势类型
        gesture_id_map = {
            "OPEN": GESTURE_OPEN,
            "CLOSED": GESTURE_CLOSED,
            "TWO": GESTURE_TWO,
            "FOUR": GESTURE_FOUR,
  
            "UNKNOWN": GESTURE_OPEN
        }
        self.current_gesture = gesture_id_map.get(hand_state, GESTURE_OPEN)
        
        # 更新粒子
        for particle in self.particles:
            # 更新粒子颜色和参数
            particle.gesture_type = self.current_gesture
            particle.color = ParticleColors.get_sphere_color(self.current_gesture)
            
            if self.state == "ball" and self.transition_progress <= 0:
                particle.update_ball(self.center_x, self.center_y, self.ball_radius, 1.0)
            elif self.state == "scattered" and self.transition_progress >= 1:
                particle.update_scatter(self.center_x, self.center_y, 1.0)
            elif target_state == "scattered":
                particle.update_scatter(self.center_x, self.center_y, self.transition_progress)
    
    def draw(self, surface):
        for particle in self.particles:
            particle.draw(surface)

# 手部轮廓粒子系统
class HandContourSystem:
    def __init__(self):
        self.contour_particles = []
        self.falling_particles = []
        
        self.particles_per_segment = 10
        self.contour_width_layers = 5
        self.max_falling_particles = 80
        self.current_gesture = GESTURE_OPEN
        
        # 用于存储手部位置和大小的引用
        self.current_center = None
        self.current_size = 100
        
    def update(self, hand_center, hand_size, gesture_type):
        # 更新当前手部位置、大小和手势类型
        self.current_center = hand_center
        self.current_size = hand_size
        self.current_gesture = gesture_type
        
        # 根据手部位置和大小生成轮廓粒子
        if hand_center and hand_size > 20:
            if len(self.contour_particles) == 0:
                self.generate_contour_particles(hand_center, hand_size)
            
            # 更新现有粒子
            self.update_existing_particles()
            
            # 根据手势类型生成坠落粒子
            if gesture_type == GESTURE_OPEN:
                self.generate_falling_particles(frequency=0.6)
            elif gesture_type == GESTURE_CLOSED:
                self.generate_falling_particles(frequency=0.2)
            elif gesture_type == GESTURE_TWO:
                self.generate_falling_particles(frequency=0.4, pattern="circle")
            elif gesture_type == GESTURE_FOUR:
                self.generate_falling_particles(frequency=0.8, pattern="explosion")
            else:
                self.generate_falling_particles(frequency=0.4)
        
        # 更新坠落粒子
        self.update_falling_particles()
    
    def generate_contour_particles(self, center, size):
        """生成圆形轮廓粒子(模拟手部轮廓)"""
        num_particles = 50
        self.contour_particles = []  # 清空现有粒子
        
        for i in range(num_particles):
            angle = 2 * math.pi * i / num_particles
            radius = size * 0.8
            
            # 创建多层轮廓
            for layer in range(self.contour_width_layers):
                offset_radius = radius + layer * 3
                offset_x = center[0] + offset_radius * math.cos(angle + layer * 0.1)
                offset_y = center[1] + offset_radius * math.sin(angle + layer * 0.1)
                
                # 创建轮廓粒子
                particle = self.create_contour_particle(offset_x, offset_y, layer, angle, center, size)
                self.contour_particles.append(particle)
    
    def create_contour_particle(self, x, y, layer_index, angle, center, size):
        """创建单个轮廓粒子"""
        # 定义一个内部类
        class SimpleContourParticle(Particle):
            def __init__(self, x, y, layer_index, total_layers, angle, center, size, gesture_type):
                super().__init__(x, y)
                self.color = ParticleColors.get_contour_color(gesture_type, layer_index, total_layers)
                self.size = random.uniform(2.0, 4.5)
                self.speed = random.uniform(0.02, 0.05)
                self.angle = angle
                self.layer_index = layer_index
                self.total_layers = total_layers
                self.center = center
                self.base_size = size
                self.gesture_type = gesture_type
                
            def update(self):
                # 根据手势类型调整移动速度
                if self.gesture_type == GESTURE_FOUR:
                    self.speed = random.uniform(0.05, 0.08)  # 摇滚手势更快
                elif self.gesture_type == GESTURE_CLOSED:
                    self.speed = random.uniform(0.01, 0.03)  # 握拳更慢
                    
                self.angle += self.speed
                
                # 根据手势类型调整半径
                if self.gesture_type == GESTURE_TWO:
                    radius = self.base_size * 0.6 + self.layer_index * 3  # TWO手势更小
                elif self.gesture_type == GESTURE_FOUR:
                    radius = self.base_size * (0.8 + 0.2 * math.sin(self.angle * 3)) + self.layer_index * 3  # 摇滚手势波动
                else:
                    radius = self.base_size * 0.8 + self.layer_index * 3
                    
                self.x = self.center[0] + radius * math.cos(self.angle)
                self.y = self.center[1] + radius * math.sin(self.angle)
                return self.update_lifetime(0.016)
        
        return SimpleContourParticle(x, y, layer_index, self.contour_width_layers, angle, center, size, self.current_gesture)
    
    def update_existing_particles(self):
        """更新现有粒子"""
        if self.current_center is None:
            return
            
        for particle in self.contour_particles[:]:
            # 更新粒子的中心位置、大小和手势类型
            if hasattr(particle, 'center'):
                particle.center = self.current_center
            if hasattr(particle, 'base_size'):
                particle.base_size = self.current_size
            if hasattr(particle, 'gesture_type'):
                particle.gesture_type = self.current_gesture
                particle.color = ParticleColors.get_contour_color(
                    self.current_gesture, 
                    particle.layer_index, 
                    particle.total_layers
                )
                
            if hasattr(particle, 'update'):
                if not particle.update():
                    # 重置粒子
                    particle.lifetime = random.uniform(1.0, 3.0)
                    particle.max_lifetime = particle.lifetime
    
    def generate_falling_particles(self, frequency=0.4, pattern="random"):
        """生成坠落粒子 - 根据手势类型改变模式"""
        if len(self.falling_particles) >= self.max_falling_particles:
            return
        
        if self.current_center is None:
            return
            
        if random.random() < frequency:
            # 根据模式生成粒子
            if pattern == "circle":
                # 圆形模式 - 用于数字2手势
                angle = random.uniform(0, 2 * math.pi)
                distance = random.uniform(0, self.current_size * 0.3)
                start_x = self.current_center[0] + distance * math.cos(angle)
                start_y = self.current_center[1] + distance * math.sin(angle)
            elif pattern == "explosion":
                # 爆炸模式 - 用于数字4手势
                angle = random.uniform(0, 2 * math.pi)
                distance = random.uniform(self.current_size * 0.5, self.current_size * 1.5)
                start_x = self.current_center[0] + distance * math.cos(angle)
                start_y = self.current_center[1] + distance * math.sin(angle)
            else:
                # 随机模式
                angle = random.uniform(0, 2 * math.pi)
                distance = random.uniform(0, self.current_size * 0.5)
                start_x = self.current_center[0] + distance * math.cos(angle)
                start_y = self.current_center[1] + distance * math.sin(angle)
            
            particle = FallingParticle(start_x, start_y, self.current_gesture)
            self.falling_particles.append(particle)
    
    def update_falling_particles(self):
        alive_particles = []
        for particle in self.falling_particles:
            if particle.update_fall(0.016):  # 假设60fps
                alive_particles.append(particle)
        self.falling_particles = alive_particles
    
    def draw(self, surface):
        for particle in self.contour_particles:
            particle.draw(surface)
        for particle in self.falling_particles:
            particle.draw(surface)

# 坠落粒子
class FallingParticle(Particle):
    def __init__(self, start_x, start_y, gesture_type=GESTURE_OPEN):
        super().__init__(start_x, start_y)
        self.gesture_type = gesture_type
        self.color = ParticleColors.get_falling_color(gesture_type)
        
        # 根据手势类型调整物理参数
        if gesture_type == GESTURE_OPEN:
            self.fall_speed = random.uniform(1.5, 3.0)
            self.wind_force = random.uniform(-0.2, 0.2)
            self.size = random.uniform(1.8, 3.0)
        elif gesture_type == GESTURE_CLOSED:
            self.fall_speed = random.uniform(0.5, 1.5)
            self.wind_force = random.uniform(-0.1, 0.1)
            self.size = random.uniform(2.0, 3.5)
        elif gesture_type == GESTURE_TWO:
            self.fall_speed = random.uniform(2.0, 4.0)
            self.wind_force = random.uniform(-0.3, 0.3)
            self.size = random.uniform(2.5, 4.0)
        elif gesture_type == GESTURE_FOUR:
            self.fall_speed = random.uniform(3.0, 6.0)
            self.wind_force = random.uniform(-0.5, 0.5)
            self.size = random.uniform(3.0, 5.0)
        else:
            self.fall_speed = random.uniform(1.5, 4.0)
            self.wind_force = random.uniform(-0.3, 0.3)
            self.size = random.uniform(1.8, 3.5)
            
        self.vx = random.uniform(-0.5, 0.5)
        self.vy = random.uniform(-1.0, 0)
        self.lifetime = random.uniform(2.0, 6.0)
        self.max_lifetime = self.lifetime
        
    def update_fall(self, dt):
        self.vy += self.fall_speed * dt
        self.vx += self.wind_force * dt
        self.x += self.vx
        self.y += self.vy
        
        if self.y > HEIGHT + 50:
            return False
        if self.x < -50 or self.x > WIDTH + 50:
            return False
            
        return self.update_lifetime(dt)

# 主程序
def main():
    global screen, WIDTH, HEIGHT
    
    # 初始化二哈2处理器
    husky_handler = HuskyLensHandler()
    
    # 尝试连接二哈2
    if not husky_handler.connect():
        print("警告: 无法连接到二哈2,将使用虚拟手势数据")
        use_virtual_hand = True
    else:
        husky_handler.start_reading()
        use_virtual_hand = False
        print("二哈2连接成功!")
    
    # 初始化粒子系统
    main_particles = MainParticleSystem(num_particles=600)
    hand_contour = HandContourSystem()
    
    clock = pygame.time.Clock()
    running = True
    last_time = pygame.time.get_ticks()
    
    # 手势状态
    hand_state = "UNKNOWN"
    gesture_strength = 1.0  # 固定强度为1.0,因为二哈2不提供置信度
    palm_x, palm_y = WIDTH // 2, HEIGHT // 2
    hand_size = 100
    
    # 调试信息
    debug_font = pygame.font.SysFont("simhei", 24)
    show_debug = True
    
    # 虚拟手控制(用于测试)
    virtual_hand_pos = [WIDTH // 2, HEIGHT // 2]
    virtual_hand_size = 100
    virtual_gesture = "UNKNOWN"
    
    # 手势名称映射
    gesture_names = {
        GESTURE_NONE: "无手势",
        GESTURE_OPEN: "张开手",
        GESTURE_CLOSED: "握拳",
        GESTURE_TWO: "数字2手势",
        GESTURE_FOUR: "数字4手势",
    }
    
    while running:
        current_time = pygame.time.get_ticks()
        dt = (current_time - last_time) / 1000.0
        last_time = current_time
        
        # 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_f:
                    # 切换全屏
                    if screen.get_flags() & pygame.FULLSCREEN:
                        screen = pygame.display.set_mode((1200, 800))
                        WIDTH, HEIGHT = 1200, 800
                    else:
                        screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
                        WIDTH, HEIGHT = screen.get_size()
                        
                    # 更新粒子中心位置
                    palm_x, palm_y = WIDTH // 2, HEIGHT // 2
                    virtual_hand_pos = [palm_x, palm_y]
                    
                elif event.key == pygame.K_SPACE:
                    main_particles.state = "scattered" if main_particles.state == "ball" else "ball"
                elif event.key == pygame.K_r:
                    hand_contour = HandContourSystem()
                elif event.key == pygame.K_d:
                    show_debug = not show_debug
                elif event.key == pygame.K_1:
                    virtual_gesture = "OPEN"
                elif event.key == pygame.K_2:
                    virtual_gesture = "CLOSED"
                elif event.key == pygame.K_3:
                    virtual_gesture = "TWO"
                elif event.key == pygame.K_4:
                    virtual_gesture = "FOUR"
               
                elif event.key == pygame.K_0:
                    virtual_gesture = "UNKNOWN"
            elif event.type == pygame.MOUSEMOTION:
                virtual_hand_pos = list(event.pos)
        
        # 获取手势数据
        if not use_virtual_hand:
            # 从二哈2获取数据
            gesture_data = husky_handler.get_gesture()
            hand_state, gesture_strength = husky_handler.get_hand_state()
            
            # 获取手势ID对应的名称
            gesture_id = gesture_data['gesture_id']
            gesture_name = gesture_names.get(gesture_id, "未知手势")
            
            # 更新手部位置和大小
            if gesture_data['gesture_id'] != GESTURE_NONE:
                palm_x = gesture_data['x']
                palm_y = gesture_data['y']
                hand_size = max(gesture_data['width'], gesture_data['height']) * 2
        else:
            # 使用虚拟手数据
            hand_state = virtual_gesture
            gesture_strength = 1.0
            palm_x, palm_y = virtual_hand_pos
            
            # 获取当前手势ID
            gesture_id_map = {
                "OPEN": GESTURE_OPEN,
                "CLOSED": GESTURE_CLOSED,
                "TWO": GESTURE_TWO,
                "FOUR": GESTURE_FOUR,

                "UNKNOWN": GESTURE_NONE
            }
            gesture_id = gesture_id_map.get(hand_state, GESTURE_NONE)
            gesture_name = gesture_names.get(gesture_id, "未知手势")
            
            # 鼠标滚轮调整手部大小
            if pygame.mouse.get_pressed()[0]:
                virtual_hand_size = min(200, virtual_hand_size + 2)
            elif pygame.mouse.get_pressed()[2]:
                virtual_hand_size = max(50, virtual_hand_size - 2)
            
            hand_size = virtual_hand_size
        
        # 限制手部位置
        palm_x = max(50, min(WIDTH - 50, palm_x))
        palm_y = max(50, min(HEIGHT - 50, palm_y))
        hand_size = max(30, min(200, hand_size))
        
        # 清屏
        screen.fill((8, 8, 20))
        
        # 更新和绘制主粒子
        main_particles.update(hand_state, (palm_x, palm_y), gesture_id)
        main_particles.draw(screen)
        
        # 更新和绘制手部轮廓
        hand_contour.update((palm_x, palm_y), hand_size, gesture_id)
        hand_contour.draw(screen)
        
        # 显示调试信息
        if show_debug:
            debug_info = [
                f"手势: {gesture_name} ({hand_state})",
                f"位置: ({int(palm_x)}, {int(palm_y)})",
                f"大小: {int(hand_size)}",
                "--- 控制 ---",
                "ESC: 退出",
                "F: 全屏切换",
                "空格: 切换粒子状态",
                "R: 重置轮廓",
                "D: 切换调试信息"
            ]
            
            if use_virtual_hand:
                debug_info.insert(0, "模式: 虚拟手控制")
                debug_info.insert(4, "1:张开手 2:握拳 3:TWO 4:摇滚")
                debug_info.insert(5, "5:数字1 6:数字2 7:数字3 0:未知")
                debug_info.insert(6, "鼠标移动: 控制手部位置")
                debug_info.insert(7, "鼠标左右键: 调整手部大小")
            else:
                debug_info.insert(0, "模式: 二哈2手势控制")
            
            for i, info in enumerate(debug_info):
                debug_text = debug_font.render(info, True, (255, 255, 255))
                screen.blit(debug_text, (10, 10 + i * 25))
            
            # 显示手势效果说明
            effect_info = [
                "--- 手势效果说明 ---",
                "张开手: 蓝色渐变,粒子散射",
                "握拳: 红色渐变,粒子聚集",
                "TWO手势: 绿色渐变,圆形效果",
                "摇滚手势: 紫色渐变,爆炸效果",
                "数字手势: 不同颜色主题"
            ]
            
            for i, info in enumerate(effect_info):
                debug_text = debug_font.render(info, True, (200, 200, 200))
                screen.blit(debug_text, (WIDTH - 300, 10 + i * 25))
        
        # 更新显示
        pygame.display.flip()
        clock.tick(60)
    
    # 清理
    if not use_virtual_hand:
        husky_handler.stop()
    pygame.quit()

if __name__ == "__main__":
    main()

演示视频

        六、项目总结与拓展思考

        本项目借助 Deepseek 的代码生成与调试能力,快速完成了从软件模拟的视觉识别到硬件集成的交互系统搭建,实现了粒子效果的多模式控制。项目中,Deepseek 不仅承担了代码编写的工作,还在调试阶段提供了针对性的优化建议,大幅降低了开发周期与技术门槛。

        从拓展角度看,可进一步优化 DF 二哈视觉传感器的识别精度(如增加手势种类,实现粒子颜色、大小的多维度控制);也可将 Arduino 与其他硬件(如舵机、LED 灯带)结合,实现粒子效果与物理硬件的联动交互;此外,还可利用 Deepseek 优化粒子效果的渲染算法,实现更复杂的 3D 粒子场景构建。

        本项目验证了 AI 大模型在快速原型开发与硬件软件集成中的实用价值,也为视觉交互与粒子效果结合的创意项目提供了可复用的技术框架。

评论

user-avatar
  • QFyXUwRx

    QFyXUwRx2025.12.10

    会玩会玩!

    0
    • 小含糊online

      小含糊online2025.12.09

      会玩会玩!

      0
      • 木子哦

        木子哦2025.12.08

        会玩会玩!

        0