计算机视觉--凸包检测控制吃金币游戏
吃金币(基础搭建)(游戏搭建来源CSDN)cfg 配置文件中存放游戏需要的字体、图片、音频的路径下图是存放在resources 的 image 文件中的人运行的图片游戏的主要构成:①吃金币的小人 定义为hero类主要配置:1)规定hero的画面的初始位置、初始朝向2)定move函数,通过位置的移动和动作的变化描述小人的运动def move(self, screensize, direction):
吃金币(基础搭建)
(游戏搭建来源CSDN)
cfg 配置文件中存放游戏需要的字体、图片、音频的路径
下图是存放在resources 的 image 文件中的人运行的图片
游戏的主要构成:
①吃金币的小人 定义为hero类
主要配置:1)规定hero的画面的初始位置、初始朝向
2)定move函数,通过位置的移动和动作的变化描述小人的运动
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)将小人的变化绘制到背景中
②掉下来的金币 定义为food类
主要配置:1)规定食物的初始位置,总共分为两种金币,不同的金币得分不同
2)制定update函数,使其按一定的速度落下,当事物没到达底部时,持续更新金币的位置。
③游戏画面配置
主要是对游戏界面的构造,并使用pygame.time,使得界面一直处于主循环当中。
④main.py 游戏如何运行
主要实现:1)界面、音乐、字体初始化
2)得到一个hero实体,以及多组food实体
3)随机生成food,同时不断更新food在画面中的位置(竖直运动)
4)按键检测,如果检测到<---,则小人往左动一步,更新位置。
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)若检测到hero小人与食物有发生碰撞,则计分。
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)根据得分信息更新屏幕
鼓包检测:
2.2.1游戏1(吃金币)算法总结(基于opencv的凸包检测)
基于手指凸包检测,开始时需要捕捉图像,对捕捉的图像我们要进行如下的操作
①进行双边滤波
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)
双边滤波,就是在均值或者说普通加权滤波(如高斯滤波)的基础上,通过距离权和颜色权俩种权值对图像进行带权平滑处理,即能够去除噪声,又能进行边缘保护。
双边滤波的这个特性主要是因为他在平滑滤波时同时考虑了像素间的几何距离和色彩距离。在图像变化平缓的区域,邻域内像素亮度值相差不大,双边滤波转化为高斯低通滤波器;在图像变化剧烈的区域,滤波器利用边缘点附近亮度值相近的像素点的亮度值平均代替原亮度值。因此,双边滤波器既平滑滤波了图像,又保持了图像的边缘。
这个是它跟高斯滤波等方法的最大不同点,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。
因为本次我们需要检测的是皮肤,所以我们先设置一个肤色阈值,同时对图像进行一个阈值处理。
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)
contours = max(contours, key=lambda x: cv.contourArea(x))
cv.drawContours (img, [contours], -1, (255,255,0), 2)
cv.imshow("contours", img)
接下来就是利用opencv自带的凸包检测函数convexHull来实现手指根数的检测,这里首先简单介绍一下凸包这个概念。
凸包(Convex Hull)是一个计算几何(图形学)中的概念。在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有的点。
例子:假设平面上有p0~p12共13个点,过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。如下图:
hull = cv.convexHull(contours)
cv.drawContours (img, [hull], -1, (0, 255, 255), 2)
cv.imshow("hull", img)
对象上的任何凹陷都被称为凸缺陷,cv.convexityDefect()函数可以帮助我们找到凸缺陷。它会返回一个数组,其中每一行包含的值是[起点,终点,最远的点,到最远点的近似距离]。
hull = cv.convexHull (contours, returnPoints=False)
defects = cv.convexityDefects (contours, hull)
最主要的任务就是计算手指的个数,因为通过凸包检测和凸缺陷检测,我是可以确定手指尖的位置和指头之间的交接凹陷位置,如果相邻近的三个点可以构成三角形的话,同时这个夹角小于90度,那么我们就认为这里是有两个手头存在的。
那么我们现在的问题就是,已知三角形的三条边,我们如何判断这个对角是否小于90度,通过数学知识,我们可以利用余弦定理求出c的对角度数.
利用这两个个公式,我们就得到的θ的度数,若小于90度我们就可以当作有两个手指,若存在4个这样不同的θ,我们就可以认为有5个手指。
检测到手指后,我们主要通过吃金币的hero类(运动小人)来实现控制,将其设置为全局变量,同时运行游戏和手指检测这两个线程,若检测到手指的个数,就通过hero内置的move函数,控制小人的运动方向。这里是只有两个方向,当检测到手指头为1或者2的时候,小人向左运动,否则,小人向右运动。
global hero
if(cnt<=2):
hero.move(cfg.SCREENSIZE, 'left')
else:
hero.move(cfg.SCREENSIZE, 'right')
演示效果:
代码(主程序,不含配置文件):
# 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()
更多推荐
所有评论(0)