Coma moedas de ouro ( construção básica )
(Fonte de construção do jogo CSDN)
O arquivo de configuração cfg armazena os caminhos de fontes, imagens e áudio exigidos pelo jogo
A foto abaixo é uma foto de pessoas correndo no arquivo de imagem de recursos
Os principais componentes do jogo:
①O vilão que come moedas de ouro é definido como a classe do herói
Configuração principal: 1) Especifique a posição inicial e a orientação inicial da tela do herói
2) Defina a função de movimento para descrever o movimento do vilão através do movimento da posição e a mudança da ação
def move(self, screensize, direction):
assert direction in ['left', 'right']
if direction != self.diretion:
self.images = self.images_left.copy() if direction == 'left' else self.images_right.copy()
self.image = self.images[0]
self.diretion = direction
self.switch_frame_count = 0
self.switch_frame_count += 1
if self.switch_frame_count % self.switch_frame_freq == 0:
self.switch_frame_count = 0
self.frame_index = (self.frame_index + 1) % len(self.images)
self.image = self.images[self.frame_index]
if direction == 'left':
self.rect.left = max(self.rect.left-self.speed, 0)
else:
self.rect.left = min(self.rect.left+self.speed, screensize[0])
- 3) Desenhe a mudança do vilão em segundo plano
②Moedas de ouro em queda são definidas como classe alimentar
Configuração principal: 1) Especifique a posição inicial da comida, dividida em dois tipos de moedas de ouro no total, e diferentes moedas de ouro têm pontuações diferentes
2) Formule uma função de atualização para fazê-la cair a uma certa velocidade. Quando as coisas não chegarem ao fundo, atualize continuamente a posição da moeda de ouro.
③Configuração da tela do jogo
É principalmente para construir a interface do jogo e usar pygame.time para manter a interface no loop principal.
④Como o jogo main.py funciona?
Realização principal: 1) Interface, música, inicialização de fonte
2) Obtenha uma entidade de herói e vários grupos de entidades de alimentos
3) Gere alimentos aleatoriamente e atualize continuamente a posição dos alimentos na tela (movimento vertical)
4) Detecção de botão, se <--- for detectado, o vilão se moverá um passo para a esquerda e atualizará a posição.
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:
hero.move((600,400), 'left')
if key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:
hero.move((600,400), 'right')
- 5) Se for detectado que o herói vilão colide com a comida, será pontuado.
for food in food_sprites_group:
if pygame.sprite.collide_mask(food, hero):
game_sounds['get'].play()
food_sprites_group.remove(food)
score += food.score
if score > highest_score: highest_score = score
- 6) Atualize a tela com base nas informações de pontuação
Detecção de tambor:
2.2.1 Jogo 1 (comer moedas de ouro) resumo do algoritmo (detecção de casco convexo baseado em opencv)
Com base na detecção de casco convexo do dedo, a imagem precisa ser capturada no início e precisamos executar as seguintes operações na imagem capturada
① Realize filtragem bilateral
ret, frame = camera.read()
frame = cv2.bilateralFilter(frame, 5, 50, 100) # filtragem bilateral
frame = cv2.flip(frame,1) # Flip 0: Virar ao longo do eixo X (virar vertical) Maior que 0: Virar ao longo do eixo Y #Flip ( virar horizontal) Menor que 0: Virar primeiro ao longo do eixo X, depois inverta ao longo do eixo Y, etc. O valor é girado em 180°
cv2.imshow('origem',quadro)
A filtragem bilateral é baseada na filtragem ponderada média ou comum (como a filtragem gaussiana) e executa a suavização ponderada na imagem por meio de dois pesos de peso de distância e peso de cor, que podem remover ruídos e proteger bordas.
Essa característica da filtragem bilateral se dá principalmente porque ele considera tanto a distância geométrica quanto a distância de cor entre os pixels ao suavizar o filtro. Na área onde a imagem muda suavemente, os valores de brilho dos pixels na vizinhança não são muito diferentes, e o filtro bilateral é transformado em um filtro gaussiano passa-baixo; valor de brilho original. Portanto, o filtro bilateral não apenas suaviza a imagem filtrada, mas também mantém as bordas da imagem .
Esta é a maior diferença entre ele e a filtragem gaussiana e outros métodos. É um processamento de compromisso que combina a proximidade espacial da imagem e a semelhança dos valores de pixel, enquanto considera a informação espacial e a semelhança de cinza para atingir o objetivo de preservação de borda e redução de ruído . É simples, não iterativo e local.
Como o que precisamos detectar desta vez é a pele, primeiro definimos um limite de cor de pele e, ao mesmo tempo, executamos um processamento de limite na imagem.
inferior = np.array([0, 48, 80], dtype = "uint8")
upper = np.array([20, 255, 255], dtype = "uint8")
#Defina o limite da cor da pele, que pode ser ajustado de acordo com a luz ou a cor da minha pele
skinRegionHSV = cv.inRange(hsvim, inferior, superior)
borrado = cv.blur(skinRegionHSV, (2,2))
ret,thresh = cv.threshold(desfocado,0,255,cv.THRESH_BINARY)
cv.imshow("debulhar", debulhar)
②Escala de cinza da imagem, filtro médio (remover ruído, facilitar o processamento da imagem)
③Detecção da cor da pele
Após a detecção, obteremos uma imagem binária apenas com as mãos. Esse efeito pode ser alcançado conforme o esperado, mas, na verdade, é difícil remover vários ruídos ao redor e existem muitos ruídos na realidade.
Após a obtenção da imagem binarizada, o próximo passo é detectar um casco convexo do dedo, o primeiro passo é desenhar o contorno do dedo.
# Desenho de contorno
contornos, hierarquia = cv.findContours (thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
contornos = max(contornos, chave=lambda x: cv.contourArea(x))
cv.drawContours (img, [contornos], -1, ( 255,255,0), 2)
cv.imshow("contornos", img)
O próximo passo é usar a função de detecção de casco convexo convexHull que vem com opencv para detectar o número de dedos Aqui, primeiro introduza brevemente o conceito de casco convexo.
Convex Hull é um conceito em geometria computacional (gráficos). No espaço euclidiano bidimensional, a casca convexa pode ser imaginada como um elástico que apenas envolve todos os pontos. Em termos vagos, dado um ponto definido em um plano bidimensional, um casco convexo é um polígono convexo formado pela conexão dos pontos mais externos, que podem conter todos os pontos no conjunto de pontos.
Exemplo: Supondo que existam 13 pontos p0~p12 no plano, um polígono é feito através de alguns pontos para que este polígono possa "envolver" todos os pontos. Quando este polígono é convexo, chamamos de "casco convexo". Como mostrado abaixo:
casco = cv.convexHull(contornos)
cv.drawContours (img, [casco], -1, (0, 255, 255), 2)
cv.imshow("casco", img)
Qualquer depressão em um objeto é chamada de defeito convexo, e a função cv.convexityDefect() pode nos ajudar a encontrar defeitos convexos. Ele retornará um array onde cada linha contém os valores [ponto inicial, ponto final, ponto mais distante, distância aproximada ao ponto mais distante].
hull = cv.convexHull (contours, returnPoints=False)
defeitos = cv.convexityDefects (contours, hull)
A principal tarefa é contar o número de dedos, porque através da detecção de casco convexo e detecção de defeito convexo, posso determinar a posição da ponta do dedo e a posição da depressão de transferência entre os dedos, se os três pontos adjacentes puderem formar um triângulo, e o ângulo incluído for inferior a 90 graus, então pensamos que há dois aqui.
Então nosso problema agora é, dados os três lados do triângulo, como podemos julgar se o ângulo diagonal é menor que 90 graus? Através do conhecimento matemático, podemos usar a lei dos cossenos para encontrar o número de ângulos diagonais de c.
Usando essas duas fórmulas, podemos obter o grau de θ. Se for menor que 90 graus, podemos considerá-lo como tendo dois dedos. Se houver 4 θ diferentes, podemos considerá-lo como tendo 5 dedos.
Depois que os dedos são detectados, implementamos principalmente o controle por meio da classe herói (vilão do esporte) que come moedas de ouro, definimos como uma variável global e executamos os dois threads do jogo e a detecção de dedos ao mesmo tempo. de dedos é detectado, passe A função de movimento integrada do herói controla a direção do movimento do vilão. Existem apenas duas direções aqui: quando o dedo detectado é 1 ou 2, o vilão se move para a esquerda, caso contrário, o vilão se move para a direita.
herói global
if(cnt<=2):
hero.move(cfg.SCREENSIZE, 'esquerda')
outro:
hero.move(cfg.SCREENSIZE, 'direita')
Efeito de demonstração:
Código (programa principal, excluindo arquivos de configuração):
# coding: utf-8
# In[1]:
import threading
import time
import os
import cv2
import numpy as np
import copy
import time
from modules import *
import os
import cfg
import cv2 as cv
import numpy as np
import sys
import pygame
import random
from modules import *
import cv2
cnt1=1
def main_gesture():
cap_region_x_begin = 0.3 # 起点/总宽度
cap_region_y_end = 0.8
threshold = 60 # 二值化阈值
blurValue = 41 # 高斯模糊参数
bgSubThreshold = 50
learningRate = 0
isBgCaptured = 0
triggerSwitch = False
camera = cv2.VideoCapture(1)
camera.set(10, 200)
t1=time.time()
i=1
while camera.isOpened():
ret, frame = camera.read()
frame = cv2.bilateralFilter(frame, 5, 50, 100) # 双边滤波
frame = cv2.flip(frame,1) # 翻转 0:沿X轴翻转(垂直翻转) 大于0:沿Y轴翻转(水平翻转) 小于0:先沿X轴翻转,再沿Y轴翻转,等价于旋转180°
cv2.imshow('orig',frame)
if isBgCaptured == 1:
img=frame
img = img[0:int(cap_region_y_end * frame.shape[0]),int(cap_region_x_begin * frame.shape[1]):frame.shape[1]] # 剪切右上角矩形框区域
hsvim = cv.cvtColor(img, cv.COLOR_BGR2HSV)
lower = np.array([0, 48, 80], dtype = "uint8")
upper = np.array([20, 255, 255], dtype = "uint8")
skinRegionHSV = cv.inRange(hsvim, lower, upper)
blurred = cv.blur(skinRegionHSV, (2,2))
ret,thresh = cv.threshold(blurred,0,255,cv.THRESH_BINARY)
cv.imshow("thresh", thresh)
_,contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
try:
contours = max(contours, key=lambda x: cv.contourArea(x))
except ValueError as e:
cv.imshow('final_result',img)
continue
cv.drawContours(img, [contours], -1, (255,255,0), 2)
hull = cv.convexHull(contours)
cv.drawContours(img, [hull], -1, (0, 255, 255), 2)
hull = cv.convexHull(contours, returnPoints=False)
defects = cv.convexityDefects(contours, hull)
if defects is not None:
cnt = 0
for i in range(defects.shape[0]): # calculate the angle
s, e, f, d = defects[i][0]
start = tuple(contours[s][0])
end = tuple(contours[e][0])
far = tuple(contours[f][0])
a = np.sqrt((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2)
b = np.sqrt((far[0] - start[0]) ** 2 + (far[1] - start[1]) ** 2)
c = np.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)
angle = np.arccos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) # cosine theorem
if angle <= np.pi / 2: # angle less than 90 degree, treat as fingers
cnt += 1
cv.circle(img, far, 4, [0, 0, 255], -1)
t2=time.time()
if cnt > 0:
cnt = cnt
if(t2-t1<=2):
cnt=cnt1
else:
cnt1=cnt
t1=t2
if(cnt<=2):
ttt="<<-"
else:
ttt="->>"
cv.putText(img, ttt, (200, 80), cv.FONT_HERSHEY_SIMPLEX,3, (0,0, 255) , 1, cv.LINE_AA)
cv.imshow('final_result',img)
global hero
if(cnt<=2):
hero.move(cfg.SCREENSIZE, 'left')
else:
hero.move(cfg.SCREENSIZE, 'right')
k = cv2.waitKey(10)
if k == ord('b'):
bgModel = cv2.createBackgroundSubtractorMOG2(0, bgSubThreshold)
isBgCaptured = 1
print('!!!Background Captured!!!')
elif k == ord('r'): # 按下'r'会重置背景
bgModel = None
triggerSwitch = False
isBgCaptured = 0
print('!!!Reset BackGround!!!')
elif k == ord('n'):
triggerSwitch = True
print('!!!Trigger On!!!')
elif k==ord('q'):
camera.release()
break
import os
import cfg
import sys
import pygame
import random
from modules import *
'''游戏初始化'''
def initGame():
# 初始化pygame, 设置展示窗口
pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('catch coins —— 吃金币游戏')
# 加载必要的游戏素材
game_images = {}
for key, value in cfg.IMAGE_PATHS.items():
if isinstance(value, list):
images = []
for item in value: images.append(pygame.image.load(item))
game_images[key] = images
else:
game_images[key] = pygame.image.load(value)
game_sounds = {}
for key, value in cfg.AUDIO_PATHS.items():
if key == 'bgm': continue
game_sounds[key] = pygame.mixer.Sound(value)
# 返回初始化数据
return screen, game_images, game_sounds
'''主函数'''
def main():
# 初始化
screen, game_images, game_sounds = initGame()
# 播放背景音乐
pygame.mixer.music.load(cfg.AUDIO_PATHS['bgm'])
pygame.mixer.music.play(-1, 0.0)
# 字体加载
font = pygame.font.Font(cfg.FONT_PATH, 40)
# 定义hero
global hero
hero = Hero(game_images['hero'], position=(375, 320))
# 定义食物组
food_sprites_group = pygame.sprite.Group()
generate_food_freq = random.randint(10, 20)
generate_food_count = 0
# 当前分数/历史最高分
score = 0
highest_score = 0 if not os.path.exists(cfg.HIGHEST_SCORE_RECORD_FILEPATH) else int(open(cfg.HIGHEST_SCORE_RECORD_FILEPATH).read())
# 游戏主循环
clock = pygame.time.Clock()
while True:
# --填充背景
screen.fill(0)
screen.blit(game_images['background'], (0, 0))
cfg.SCREENSIZE=(600,400)
# --倒计时信息
countdown_text = 'Count down: ' + str((90000 - pygame.time.get_ticks()) // 60000) + ":" + str((90000 - pygame.time.get_ticks()) // 1000 % 60).zfill(2)
countdown_text = font.render(countdown_text, True, (0, 0, 0))
countdown_rect = countdown_text.get_rect()
countdown_rect.topright = [cfg.SCREENSIZE[0]-30, 5]
screen.blit(countdown_text, countdown_rect)
# --按键检测
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:
hero.move((600,400), 'left')
if key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:
hero.move((600,400), 'right')
# --随机生成食物
generate_food_count += 1
if generate_food_count > generate_food_freq:
generate_food_freq = random.randint(10, 20)
generate_food_count = 0
food = Food(game_images, random.choice(['gold',] * 10 + ['apple']), cfg.SCREENSIZE)
food_sprites_group.add(food)
# --更新食物
for food in food_sprites_group:
if food.update(): food_sprites_group.remove(food)
# --碰撞检测
for food in food_sprites_group:
if pygame.sprite.collide_mask(food, hero):
game_sounds['get'].play()
food_sprites_group.remove(food)
score += food.score
if score > highest_score: highest_score = score
# --画hero
hero.draw(screen)
# --画食物
food_sprites_group.draw(screen)
# --显示得分
score_text = f'Score: {score}, Highest: {highest_score}'
score_text = font.render(score_text, True, (0, 0, 0))
score_rect = score_text.get_rect()
score_rect.topleft = [5, 5]
screen.blit(score_text, score_rect)
# --判断游戏是否结束
if pygame.time.get_ticks() >= 90000:
break
# --更新屏幕
pygame.display.flip()
clock.tick(cfg.FPS)
# 游戏结束, 记录最高分并显示游戏结束画面
fp = open(cfg.HIGHEST_SCORE_RECORD_FILEPATH, 'w')
fp.write(str(highest_score))
fp.close()
return showEndGameInterface(screen, cfg, score, highest_score)
import threading
import time
import os
from multiprocessing import Process,Value
#线程条件变量
threading.Thread(target=main).start()
threading.Thread(target=main_gesture).start()