HeadBallGame/main.py

265 lines
9.7 KiB
Python
Raw Normal View History

2025-03-01 02:42:23 +00:00
from concurrent.futures import thread
from threading import Thread
import threading
2025-02-28 16:55:58 +00:00
import cv2
import mediapipe as mp
import pygame
import time
import random
2025-03-01 02:42:23 +00:00
import os
import tkinter as tk
from PIL import Image, ImageTk
2025-02-28 16:55:58 +00:00
# 初始化游戏窗口
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() # 游戏开始时间
2025-03-01 02:42:23 +00:00
def get_player_avatar():
global cap
"""获取当前游玩者的头像(人脸图像)。"""
mp_face = mp.solutions.face_detection # type: ignore
face_detection = mp_face.FaceDetection(min_detection_confidence=0.5)
ret, frame = cap.read()
if not ret:
return None # 如果未读取到帧,返回 None
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = face_detection.process(frame_rgb)
if results.detections:
max_area = 0
best_detection = None
# 找到最大的面框
for detection in results.detections:
bbox = detection.location_data.relative_bounding_box
area = bbox.width * bbox.height
if area > max_area:
max_area = area
best_detection = bbox
if best_detection:
# 计算面框在原始帧中的坐标
h, w, _ = frame.shape
x_min = int(best_detection.xmin * w)
y_min = int(best_detection.ymin * h)
x_max = int((best_detection.xmin + best_detection.width) * w)
y_max = int((best_detection.ymin + best_detection.height) * h)
# 增加边界,确保完整性
border_x = int(0.1 * (x_max - x_min)) # 边界宽度为框宽度的10%
border_y = int(0.1 * (y_max - y_min)) # 边界高度为框高度的10%
# 更新坐标以增加边界,同时确保坐标在有效范围内
x_min = max(x_min - border_x, 0) # 确保不小于0
y_min = max(y_min - border_y, 0) # 确保不小于0
x_max = min(x_max + border_x, w) # 确保不超出图像宽度
y_max = min(y_max + border_y, h) # 确保不超出图像高度
# 裁剪出面框区域
avatar = frame[y_min:y_max, x_min:x_max]
return avatar
return None # 如果未检测到人脸,返回 None
2025-02-28 16:55:58 +00:00
def you_failed(elapsed_time):
global obstacles
obstacles.clear()
"""处理失败的函数,弹出消息或重置游戏等"""
global start_time
2025-03-01 02:42:23 +00:00
print(f"You failed! Elapsed dis: {elapsed_time:.2f} meters")
if not os.path.exists("faces"):
os.makedirs("faces")
file_name = os.path.join("faces",f"{elapsed_time:.2f}.png")
avatar = get_player_avatar()
if avatar is not None:
cv2.imwrite(file_name,avatar)
2025-02-28 16:55:58 +00:00
start_time=time.time()
# 在此处添加其他失败处理逻辑,例如重置游戏、显示消息等
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
2025-03-01 02:42:23 +00:00
# elapsed_seconds = # 整数秒数
2025-02-28 16:55:58 +00:00
# 计算小球位置
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)
# 绘制计时器文本
2025-03-01 02:42:23 +00:00
timer_text = font.render(f"{elapsed_time:.2f} M", True, (255, 255, 255)) # 白色文本
2025-02-28 16:55:58 +00:00
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()