From b823e3a41a196bff02f4c4c90c53757e532239a5 Mon Sep 17 00:00:00 2001 From: Zengtudor Date: Sat, 1 Mar 2025 00:55:58 +0800 Subject: [PATCH] update --- main.py | 203 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 15 ++++ 2 files changed, 218 insertions(+) create mode 100644 main.py create mode 100644 pyproject.toml diff --git a/main.py b/main.py new file mode 100644 index 0000000..ab46feb --- /dev/null +++ b/main.py @@ -0,0 +1,203 @@ +import cv2 +import mediapipe as mp +# 在Pygame中使用归一化坐标 +import pygame +import time +import random +# 初始化游戏窗口 +pygame.init() +# 字体初始化 +font = pygame.font.Font(None, 36) # None使用默认字体,大小为36 +screen = pygame.display.set_mode((600, 800)) +ball_radius = 20 +ball_color = (255, 105, 180) # 粉色 + + +# 初始化摄像头 +cap = cv2.VideoCapture(0) +cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) # 设置宽度 +cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 设置高度 + +# 验证设置 +actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) +actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) +print(f"Working Resolution: {actual_width}x{actual_height}") +# 创建自适应窗口 +# cv2.namedWindow('Face Detection', cv2.WINDOW_NORMAL) +# cv2.resizeWindow('Face Detection', actual_width, actual_height) + +# 初始化MediaPipe +mp_face = mp.solutions.face_detection # type: ignore +face_detection = mp_face.FaceDetection(min_detection_confidence=0.5) +# 获取实际分辨率 +width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) +height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) +print(f"实际分辨率: {width}x{height}") +# 定义目标裁剪尺寸 +CROP_SIZE = 720 + +# 计算裁剪区域坐标 +x_start = (width - CROP_SIZE) // 2 +y_start = (height - CROP_SIZE) // 2 +x_end = x_start + CROP_SIZE +y_end = y_start + CROP_SIZE + +clock = pygame.time.Clock() # 用于控制帧率 + +# 全局变量初始化 (请确保在主程序中进行初始化) +OBSTACLE_INTERVAL = 1400 # 毫秒,障碍物生成间隔 +OBSTACLE_SPEED = 5 # 障碍物下移速度 +start_time = time.time() # 游戏开始时间# 全局变量初始化 +obstacles = [] # 存储当前障碍物的列表 +obstacle_timer = 0 # 通用障碍物生成计时器 + +# 新增全局变量用于跟踪左右两侧障碍物的生成时间 +last_obstacle_time_left = 0 +last_obstacle_time_right = 0 +MIN_OBSTACLE_INTERVAL = 2000 # 毫秒,同一侧障碍物最小生成间隔 + +start_time = time.time() # 游戏开始时间 +def you_failed(elapsed_time): + global obstacles + obstacles.clear() + """处理失败的函数,弹出消息或重置游戏等""" + global start_time + start_time=time.time() + print(f"You failed! Elapsed time: {elapsed_time:.2f} seconds") + # 在此处添加其他失败处理逻辑,例如重置游戏、显示消息等 + +def draw_ball(normalized_x, normalized_y): + global obstacles, obstacle_timer, last_obstacle_time_left, last_obstacle_time_right, start_time + screen.fill((0, 0, 0)) + """在Pygame窗口中绘制小球并处理障碍物""" + # 计算经过的时间 + elapsed_time = time.time() - start_time + elapsed_seconds = int(elapsed_time) # 整数秒数 + + # 计算小球位置 + ball_x = int(normalized_x * (screen.get_width() - ball_radius * 2)) + ball_radius + ball_y = int(0.6 * (screen.get_height() - ball_radius * 2)) + ball_radius + + # # 检查小球是否碰到屏幕边界 + # if ball_x - ball_radius <= 0 or ball_x + ball_radius >= screen.get_width(): + # elapsed_time = time.time() - start_time + # you_failed(elapsed_time) + # return + + # 绘制小球 + pygame.draw.circle(screen, ball_color, (ball_x, ball_y), ball_radius) + + # 生成障碍物 + current_time = pygame.time.get_ticks() + if current_time - obstacle_timer > OBSTACLE_INTERVAL: + # 尝试随机选择左右两侧生成障碍物 + sides = ['left', 'right'] + random.shuffle(sides) # 随机打乱侧别顺序以增加随机性 + # 障碍物宽度和高度 + obstacle_width = random.randint(200, 500) + obstacle_height = random.randint(20, 100) + for side in sides: + if side == 'left': + # 检查左侧的障碍物生成间隔 + if current_time - last_obstacle_time_left >= MIN_OBSTACLE_INTERVAL: + x_pos = 0 + last_obstacle_time_left = current_time + else: + continue # 左侧间隔不够,尝试右侧 + else: + # 检查右侧的障碍物生成间隔 + if current_time - last_obstacle_time_right >= MIN_OBSTACLE_INTERVAL: + x_pos = screen.get_width() - obstacle_width + last_obstacle_time_right = current_time + else: + continue # 右侧间隔不够,尝试左侧S + # 确定x_pos根据侧别 + if side == 'left': + x_pos = 0 + else: + x_pos = screen.get_width() - obstacle_width + y_pos = -obstacle_height # 从屏幕上方外部生成 + obstacles.append(pygame.Rect(x_pos, y_pos, obstacle_width, obstacle_height)) + obstacle_timer = current_time # 重置生成计时器 + break # 成功生成一个障碍物后退出循环 + + # 移动并绘制障碍物 + for obstacle in obstacles[:]: + obstacle.y += OBSTACLE_SPEED + + # 绘制障碍物 + pygame.draw.rect(screen, (0, 255, 0), obstacle) # 绿色障碍物 + + # 检查是否与小球碰撞 + if obstacle.colliderect(pygame.Rect(ball_x - ball_radius, ball_y - ball_radius, ball_radius * 2, ball_radius * 2)): + elapsed_time = time.time() - start_time + you_failed(elapsed_time) + return + + # 移除已离开屏幕的障碍物 + if obstacle.y > screen.get_height(): + obstacles.remove(obstacle) + + # 绘制计时器文本 + timer_text = font.render(f"{elapsed_seconds} M", True, (255, 255, 255)) # 白色文本 + screen.blit(timer_text, (10, 10)) # 在左上角绘制文本(10, 10)是坐标 + pygame.display.flip() + clock.tick(60) + +# 验证裁剪可行性 +if x_start < 0 or y_start < 0: + raise ValueError("摄像头分辨率不足以裁剪720x720区域") + +mp_drawing = mp.solutions.drawing_utils # type: ignore + +while cap.isOpened(): + ret, frame = cap.read() + if not ret: break + frame = cv2.flip(frame,1) + # 人脸检测 + rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + results = face_detection.process(rgb_frame) + if results.detections: + for detection in results.detections: + # 使用MediaPipe自带绘制方法 + mp_drawing.draw_detection(frame, detection) + if results.detections: + detection = results.detections[0] + max_area = 0 + + for idetection in results.detections: + bbox = idetection.location_data.relative_bounding_box + area = bbox.width * bbox.height # 计算面框的面积 + if area > max_area: # 找到最大的 + max_area = area + detection = idetection + bbox = detection.location_data.relative_bounding_box + # 计算原始坐标(相对于完整帧) + raw_x = int(bbox.xmin * width) + raw_y = int(bbox.ymin * height) + raw_w = int(bbox.width * width) + raw_h = int(bbox.height * height) + center_x = raw_x + raw_w // 2 + center_y = raw_y + raw_h // 2 + + # 转换为裁剪区域坐标 + crop_x = center_x - x_start + crop_y = center_y - y_start + + # 归一化到0-1范围(带边界保护) + normalized_x = max(0.0, min(1.0, crop_x / CROP_SIZE)) + normalized_y = max(0.0, min(1.0, crop_y / CROP_SIZE)) + draw_ball(normalized_x,normalized_y) + # print(f"归一化坐标: ({normalized_x:.2f}, {normalized_y:.2f})") + + # 可视化裁剪区域 + cv2.rectangle(frame, + (x_start, y_start), + (x_end, y_end), + (0,255,0), 2) + cv2.imshow('Preview', frame) + if cv2.waitKey(1) == ord('q'): + break + +cap.release() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..153d89c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "HeadBallGame" +version = "0.1.0" +description = "Default template for PDM package" +authors = [ + {name = "Zengtudor", email = "Zengtudor@outlook.com"}, +] +dependencies = ["opencv-python>=4.11.0.86", "mediapipe>=0.10.21", "numpy>=1.26.4", "pygame>=2.6.1"] +requires-python = "==3.12.*" +readme = "README.md" +license = {text = "MIT"} + + +[tool.pdm] +distribution = false