HeadBallGame/main.py
2025-03-01 00:55:58 +08:00

203 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()