opencv飞机大战年度巨制

Posted 洋葱鳞片

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了opencv飞机大战年度巨制相关的知识,希望对你有一定的参考价值。

参考连接

我写在最前面了希望各路神仙给我点c币吧参考资源

Opencv体感飞机大战
一、项目背景
(1) 项目简介:
利用基于OpenCv-python 的现有姿态识别模块实现对手部姿势的识别,来控制基于pygame库编写的小游戏
(2) 背景介绍:

  1. opencv背景:
    OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。OpenCV用C++语言编写,它具有C ++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMX和SSE指令, 如今也提供对于C#、Ch、Ruby,GO的支持
  2. 姿态检测背景:
    感知手的形状和运动的能力是改进各种技术领域和平台上的用户体验的重要组成部分。例如,它可以形成手语理解和手势控制的基础,也可以使数字内容和信息叠加在增强现实的物理世界之上。尽管对人类来说很自然,但强大的实时手部感知无疑是一项具有挑战性的计算机视觉任务,因为双手经常会遮挡自己或彼此(例如手指/手掌遮挡和手部抖动),而且缺乏高对比度模式。
    MediaPipe Hands是一款高保真的手部和手指跟踪解决方案。它使用机器学习技术(ML)从单帧图像中推断出21个手部的3D地标。当前最先进的方法主要依赖于强大的桌面环境进行推理,而方法实现了移动电话上的实时性能,甚至可以扩展到多手。希望,为更广泛的研究和开发社区提供这种手部感知功能,将导致创造性用例的出现,激发新的应用程序和新的研究途径。
    (3) 项目开发目的:
    现在游戏多基于鼠标键盘控制,游戏角色的操作,当然也用AR游戏但是设备需求高,也需要一定的硬件支持,为了权衡两者,做出一个可以实现交互的游戏。简单的摄像头姿态判断来控制游戏角色的操作,根据人体的姿态来操作相比于传统游戏来说,更具有可玩行,比起坐在屏幕前更为灵活,手脑并用,更为益智。

二、设计思路
说明设计思路,画出设计流程
在对智能交互的案例有所理解之后,我对其中的手部姿态识别产生了很浓郁的兴趣,在浏览相关的视觉处理案例后,Opencv体感飞机大战的想法冒出,是不是可以根据上课所学来对实验案例进行拓展衍生。
准备过程比较复杂先是对项目所以依赖的库进行一个简单的分析,游戏模块用到了pygame的库,姿态识别用到的就是 opencv为主加上mediapipe的库,这些库都依赖于python 3.6X为了稳定我选择了比现在低几个版本。其次就是对飞机大战游戏的源代码的改良以及理解,在对其进行揉合的时候中间的参数可能会受影响,还有对姿态识别的部分优化在老师上课讲解HandTrackingModule.py封装里面我又借鉴了开源库的案例添加了左右手的标记。思路很是清晰明了了。

三、模块划分

画出系统结构图,描述每个功能模块的作用以及实现方法或技术
(1) 对项目所需环境配置
Python 3.6X
主要依赖库
mediapipe== 0.8.3
opencv-python== 4.5.4.60
pygame==2.1.0
(2) 编写调试键盘版本的飞机大战游戏案例
搞清楚飞机大战里面的参数
适当修改
(3) 编写姿态判断函数
姿态判断手部坐标
关键参数传传递
(4) 对飞机功能适当修剪
糅合完成项目


四、系统设计
对飞机大战游戏的选择,已经出现过许多开源的飞机大战小游戏我在择时选取了结构相对简单的一个move_player()主要对飞机进行移动控制,还有许多模块有飞机子弹打中的hit()模块,敌机生成Enemy()模块,

fly_game.py

import sys
import pygame
import random
import math
# 初始化
pygame.init()
# 设置窗口大小
screen = pygame.display.set_mode((800,600))
# 设置窗口标题
pygame.display.set_caption("飞机大战游戏")
# 设置窗口图标
icon = pygame.image.load('./img/logo.png')
pygame.display.set_icon(icon)
# 设置加载背景图
bgImg = pygame.image.load('./img/bg.jpg')
# 设置飞机属性
playerImg = pygame.image.load('./img/plane.png')
playerX = 400
playerY = 500
player_w = (playerImg.get_rect().width)
player_h = (playerImg.get_rect().height)
player_step = 0
# 设置分数属性
scoreImg = pygame.image.load('./img/score.png')
score = 0
font = pygame.font.Font('freesansbold.ttf', 36)
def show_score():
  text = ("Score: %d"%score)
  score_render = font.render(text, True, (0,255,255))
  screen.blit(score_render, (95, 65))
# 游戏结束标志
is_over = False
font2 = pygame.font.Font('freesansbold.ttf', 100)
def check_game_lose():
  global is_over
  if is_over:
    text = ("GAME OVER !")
    score_render = font2.render(text, True, (255,0,0))
    screen.blit(score_render, (50, 200))
def check_game_win():
  if score == 100:
    text = ("YOU WIN !")
    score_render = font2.render(text, True, (128,255,0))
    screen.blit(score_render, (120, 250)) 

# 添加背景音乐
pygame.mixer.music.load('./sound/bg.mp3')
pygame.mixer.music.play(-1)
# 添加其它音效(爆炸音+子弹音)
bao_sound = pygame.mixer.Sound('./sound/bao.wav')
bullet_sound = pygame.mixer.Sound('./sound/bullet.wav')
# 设置子弹属性
class Bullet():
  def __init__(self):
    self.img = pygame.image.load('./img/bullet.png')
    self.x = playerX + 22
    self.y = playerY - 5
    self.w = (self.img.get_rect().width)
    self.h = (self.img.get_rect().height)
    self.step = 0.2
  # 击中敌方
  def hit(self):
    global score
    for e in enemies:
      if cal_distance(self.x, self.y, e.x, e.y) < 30:
        score += 10
        bao_sound.play()
        bullets.remove(self)
        enemies.remove(e)  

# 设置敌人属性
enemy_num = 10
class Enemy():
  def __init__(self):
    self.img = pygame.image.load('./img/enemy.png')
    self.x = random.randint(100, 700)
    self.y = random.randint(80, 200)
    self.w = (self.img.get_rect().width)
    self.h = (self.img.get_rect().height)
    self.step = random.randint(1,20)/100
bullets = []
def show_bullet():
  for b in bullets: 
    screen.blit(b.img,(b.x, b.y))
    b.hit()
    b.y -= b.step
    if b.y <=0:
      bullets.remove(b)
enemies = []
for i in range(enemy_num):
  enemies.append(Enemy())
def show_enemy():
  global is_over
  for e in enemies: 
    screen.blit(e.img,(e.x, e.y))
    e.x += e.step
    if e.x >= 800 - e.w or e.x <=0:
      e.step *= -1
      e.y += 30
      if e.y >=480:
        is_over = True
        enemies.clear()
#移动
def move_player():
  global playerX, player_step
  playerX += player_step
  if playerX >= 800 - player_w:
    playerX = 800 - player_w
  if playerX <= 0:
    playerX = 0
def cal_distance(bx, by, ex, ey):
  a = bx - ex
  b = by - ey
  return math.sqrt(a*a + b*b)
# 主循环标志位
running = True
# 游戏主循环
while running:
  # 将背景图绘制到游戏窗口
  screen.blit(bgImg,(0,0))
  screen.blit(scoreImg,(0,0))
  # 显示分数
  show_score()
  
  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      running = False
    # 判断是键盘按键按下
    if event.type ==pygame.KEYDOWN:
      if event.key == pygame.K_RIGHT:
        player_step = 0.3
      elif event.key == pygame.K_LEFT:
        player_step = -0.3
      elif event.key == pygame.K_SPACE:
        # 创建一个子弹
        bullet_sound.play()
        b = Bullet()
        bullets.append(b)
    if event.type ==pygame.KEYUP:
      player_step = 0  
     
  # 将飞机绘制到游戏窗口
  screen.blit(playerImg,(playerX, playerY))
  move_player()
  show_enemy()
  show_bullet()
  check_game_lose()
  check_game_win()
  pygame.display.update()
# pygame窗口无法关闭时加上下面这句   
pygame.quit()
sys.exit()

这里我们也加入了敌机模块,让敌机随机速度左右移动当跑完这一行的所有位置就向我机方向下移一个方向,这样碰到敌机游戏结束,要是我们打完敌机则宣布胜利获得100

姿势判断函数的编写

mian.py

import cv2
from HandTrackingModule import HandDetector
import numpy as np
import math
# Webcam
cap = cv2.VideoCapture(0)

cap.set(3, 1280)
cap.set(4, 720)

def angle(p1,p2,p3):
    # 计算二维坐标平面上的夹角
    # 定义组成一个角的三个点 point2是中间点
    point1 = np.array(p1)  # 组成一个角的起始点
    point2 = np.array(p2)  # 组成一个角的中间点
    point3 = np.array(p3)  # 组成一个角的终止点
    vector1 = point1 - point2  # 从中间点开始的第一条直线的向量
    vector2 = point3 - point2  # 从中间点开始的第二条直线的向量
    # 根据夹角公式计算该角的余弦值
    cos_theta = np.dot(vector1, vector2) / (
                ((vector1[0] ** 2 + vector1[1] ** 2) ** 0.5) * ((vector2[0] ** 2 + vector2[1] ** 2) ** 0.5))
    theta = np.arccos(cos_theta)  # 弧度
    degree = math.degrees(theta)  # 角度
    return degree
# Hand Detector
detector = HandDetector(detectionCon=0.75, maxHands=2)
# Loop
while True:
    success, img = cap.read()
    hands, img = detector.findHands(img)  # with draw
    if hands:
        # Hand 1
        hand1 = hands[0]
        lmList1 = hand1["lmList"]  # List of 21 Landmark points
        bbox1 = hand1["bbox"]  # Bounding box info x,y,w,h
        centerPoint1 = hand1['center']  # center of the hand cx,cy
        handType1 = hand1["type"]  # Handtype Left or Right
        fingers1 = detector.fingersUp(hand1)
        if len(hands) == 2:
            hand2 = hands[1]
            lmList2 = hand2["lmList"]  # List of 21 Landmark points
            bbox2 = hand2["bbox"]  # Bounding box info x,y,w,h
            centerPoint2 = hand2['center']  # center of the hand cx,cy
            handType2 = hand2["type"]  # Hand Type "Left" or "Right"
            fingers2 = detector.fingersUp(hand2)
            # 连线中指关节处
            cv2.line(img, lmList1[9], lmList2[9], (0, 0, 255), 5, cv2.LINE_AA, 0)
            length, info = detector.findDistance(lmList1[9], lmList2[9])# info 点一点二 中间点
            du1 = angle(lmList1[4], lmList1[9], lmList2[9])
            du2 = angle(lmList2[4], lmList2[9], lmList1[9])
            cv2.putText(img, str(int(du1)), (lmList1[9][0]-100,lmList1[9][1]),
                        cv2.FONT_HERSHEY_COMPLEX_SMALL,2, (255, 0, 0),4)
            cv2.putText(img, str(int(du2)), (lmList2[9][0]+100, lmList1[9][1]),
                        cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, (255, 0, 0), 4)
            cv2.line(img, lmList1[9], lmList1[4], (0, 0, 255), 5, cv2.LINE_AA, 0)
            cv2.line(img, lmList2[9], lmList2[4], (0, 0, 255), 5, cv2.LINE_AA, 0)
            if du1 <= 15:
                cv2.putText(img, str("Fire"), (lmList1[9][0], lmList2[9][1]),
                            cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, (255, 0, 0), 4)
            if du2 >= 100:
                cv2.putText(img, str("Right"), (lmList2[9][0]+200, lmList1[9][1]),
                            cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, (255, 0, 0), 4)
            if du1 >= 100:
                cv2.putText(img, str("Left"), (lmList1[9][0]-200,lmList1[9][1]),
                            cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, (255, 0, 0), 4)
    # Display
    cv2.imshow("Image", img)
    cv2.moveWindow("Image", 0, 0)

    if cv2.waitKey(1) == 27:
        break
cap.release()
cv2.destroyAllWindows()

显示人物左右手并且在左右手4,9关键点上连线,显示左右手,计算夹角,根据夹角判断是否输出指令,在基础的mian.py调试的过程我只加了一个角度小于15度开火,然后哪边手势的角度大于100度就向哪边偏转。

角度公式计算代码

def angle(p1,p2,p3):
    # 计算二维坐标平面上的夹角
    # 定义组成一个角的三个点 point2是中间点
    point1 = np.array(p1)  # 组成一个角的起始点
    point2 = np.array(p2)  # 组成一个角的中间点
    point3 = np.array(p3)  # 组成一个角的终止点
    vector1 = point1 - point2  # 从中间点开始的第一条直线的向量
    vector2 = point3 - point2  # 从中间点开始的第二条直线的向量
    # 根据夹角公式计算该角的余弦值
    cos_theta = np.dot(vector1, vector2) / (
                ((vector1[0] ** 2 + vector1[1] ** 2) ** 0.5) * ((vector2[0] ** 2 + vector2[1] ** 2) ** 0.5))
    theta = np.arccos(cos_theta)  # 弧度
    degree = math.degrees(theta)  # 角度
    return degree


首先计算出AB线段与X轴的夹角,将AB线段进行分解,如下图:
其中角度的计算公式为

◬=arctan(dy/dx)
同理求得CD线段与X轴的角度,这里存在一个问题,由于线段不是向量,因此角度可能为60°,也可能为360°-60°=300°,因此后续合并两个角度的时候需要解决这个问题.
合并的原理也比较简单,由于用python的math.atan2(y,x)函数计算线段与X轴夹角,返回的角度在[-180,180],也就是说在1,2象限为正数,3,4象限为负数,在合并两个夹角时考虑正负号,计算完成后再对180°求余即可.
这里使用两个向量做角度计算考虑到人的手掌结构大拇指角度一般在15-100左右再要扩大角度就要配合另一只手了

 if len(hands) == 
import pygame
from plane_sprites import *


class PlaneGame(object):
    ‘‘‘飞机大战主游戏‘‘‘

    def __init__(self):
        print("游戏初始化")

        # 1,创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)
        # 2,创建游戏的时钟
        self.clock = pygame.time.Clock()
        # 3,调用私有方法,精灵和精灵组的
        self.__create_sprites()
        # 4,设置定时器事件---创建敌机
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)

    def __create_sprites(self):
        # 创建背景精灵和精灵组
        bg1 = Backgroud()
        bg2 = Backgroud(True)
        self.back_group = pygame.sprite.Group(bg1, bg2)
        # 创建敌机的精灵组
        self.enemy_group = pygame.sprite.Group()
        # 创建英雄的精灵,和精灵组
        self.hero = Hero()
        self.hero_group = pygame.sprite.Group(self.hero)


    def start_game(self):
        print("游戏开始...")

        while True:
            # 1,设置刷新频率
            self.clock.tick(FRAME_PRE_SEC)
            # 2,事件监听
            self.__event_hander()
            # 3,碰撞检测
            self.__check_collide()
            # 4,跟新/绘制精灵组
            self.__update_sprites()
            # 5,更新显示
            pygame.display.update()

    def __event_hander(self):

        for event in pygame.event.get():

            # 判断是否退出
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()

            elif event.type == CREATE_ENEMY_EVENT:

                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵,添加到敌机精灵组
                self.enemy_group.add(enemy)
                # print("敌机出场...")
            elif event.type == HERO_FIRE_EVENT:
                self.hero.fire()

            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
                # print("向右移动")
        # 使用键盘提供的方法,获取键盘按键 --- 按键元组
        keys_pressed = pygame.key.get_pressed()
        # 判断元组中对应的按键索引值1
        if keys_pressed[pygame.K_RIGHT]:
            self.hero.speed = 2
        elif keys_pressed[pygame.K_LEFT]:
            self.hero.speed = -2
        else:
            self.hero.speed = 0

    def __check_collide(self):

        # 1,子弹摧毁敌机
        pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
        # 2,敌机撞毁英雄
        enemise = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)

        # 3,判断列表是否有内容,若有,
        if len(enemise) > 0:
            self.hero.kill()
            # 结束游戏
            PlaneGame.__game_over()

    def __update_sprites(self):
        self.back_group.update()
        self.back_group.draw(self.screen)

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

        self.hero_group.update()
        self.hero_group.draw(self.screen)

        self.hero.bullets.update()
        self.hero.bullets.draw(self.screen)

    @staticmethod
    def __game_over():
        print("游戏结束。。")
        pygame.quit()
        exit()


if __name__ == __main__:
    # 游戏初始化
    game = PlaneGame()

    # 启动游戏
    game.start_game()
import random
import pygame


# 定义模块大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 定义刷新帧率
FRAME_PRE_SEC = 60
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 英雄发射子弹时间
HERO_FIRE_EVENT = pygame.USEREVENT + 1


class GameScript(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):
        # 调用父类的初始化方法
        super().__init__()
        # 定义对象属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self):
        # 在屏幕上的垂直方向上移动
        self.rect.y += self.speed


class Backgroud(GameScript):
    """游戏背景精灵"""

    def __init__(self, is_alt=False):
        # 1,调用父类方法,实现精灵的创建(images/background.png
        super().__init__("./images/background.png")

        # 2,判断是否是交替图像,如果是,需要设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):
        # 1,调用父类的方法实现
        super().update()

        # 2,判断是否移出屏幕,如果移出屏幕,将图像设置到上方
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height


class Enemy(GameScript):
    """敌机精灵"""

    def __init__(self):

        # 1,调用父类的方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 2,指定敌机的初始随机速度
        self.speed = random.randint(2, 4)
        # 3,指定敌机的初识随机位置
        # self.rect.y = -self.rect.height 等同于下面这个语句
        self.rect.bottom = 0
        max_x = SCREEN_RECT.width - self.rect.width
        self.rect.x = random.randint(0, max_x)

    def update(self):
        # 1,调用父类方法,保持垂直方向的飞行
        super().update()
        # 2,判读是否飞出屏幕,如果是,需要从精灵组删除敌机
        if self.rect.y >= SCREEN_RECT.height:
            print("飞出屏幕,需要从精灵组中删除")
            # kill方法,可以将精灵从精灵 组中移出,精灵就会被销毁
            self.kill()

    def __del__(self):
        print("敌机挂了 %s" % self.rect)


class Hero(GameScript):
    """英雄精灵"""
    def __init__(self):
        # 1,调用父类方法,设置images
        super().__init__("./images/me1.png")
        self.speed = 0
        # 2,设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.bottom - 120

        # 3,创建子弹精灵组
        self.bullets = pygame.sprite.Group()

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

        # 控制英雄不能离开屏幕
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

    def fire(self):
        print("发射子弹...")
        for i in (1, 2, 3):
            # 1,创建子弹精灵
            bullet = Bullet()
            # 2,设置子弹精灵的初始位置
            bullet.rect.centerx = self.rect.centerx
            bullet.rect.bottom = self.rect.y - i * 20
            # 3,将精灵添加到精灵组
            self.bullets.add(bullet)


class Bullet(GameScript):
    ‘‘‘子弹精灵‘‘‘
    def __init__(self):
        # 调用父类方法,设置子弹图片,设置初始速度
        super().__init__("./images/bullet1.png", -2)

    def update(self):
        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()
        # 判断子弹是否飞出屏幕
        if self.rect.bottom < 0:
            self.kill()

这是该项目的1.0版本,项目不是特别的完善,需要在改进,推出版本2.0




以上是关于opencv飞机大战年度巨制的主要内容,如果未能解决你的问题,请参考以下文章

已知相机参数时的OpenCV图像拼接

飞机大战的操作指南

求一款飞机大战游戏(很经典)

如何用用python写飞机大战?

怎么用python学飞机大战?

全民飞机大战第二十三关怎么过 过关攻略