203 lines
7.5 KiB
Python
203 lines
7.5 KiB
Python
|
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()
|