opencv飞机大战年度巨制
我写在最前面了希望各路神仙给我点c币吧参考资源
Opencv体感飞机大战
一、项目背景
(1) 项目简介:
利用基于OpenCv-python 的现有姿态识别模块实现对手部姿势的识别,来控制基于pygame库编写的小游戏
(2) 背景介绍:
- 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的支持 - 姿态检测背景:
感知手的形状和运动的能力是改进各种技术领域和平台上的用户体验的重要组成部分。例如,它可以形成手语理解和手势控制的基础,也可以使数字内容和信息叠加在增强现实的物理世界之上。尽管对人类来说很自然,但强大的实时手部感知无疑是一项具有挑战性的计算机视觉任务,因为双手经常会遮挡自己或彼此(例如手指/手掌遮挡和手部抖动),而且缺乏高对比度模式。
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) == 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
那么就看一下初代吧
手撸飞机
在基于前面的main.py的输出,进行优化,移动的时候开火的话一只手的角度一定是大于100度的在这个基础上用另一只手来做开火指令判断这样一来符合我们的用手习惯,也优化了判断的模式,如果两只手都小于20度我们让飞机停下来。在飞机上发射的子弹我也做了修改。
详细演示在视频中都有体现
项目总结
1.项目中用到的知识点
项目中用到了许多的数学知识例如
对角度的计算,子弹和敌机之间发生坐标重合之后的判断,该用多大的判断块,子弹的坐标与敌机的坐标的判断块,加上速度之后更加有难度。
手部关键点的连线对整个判断的影响需要考虑实际问题。
2.程序有待改进的地方
(1)项目后期需要优化应该更加轻量最好界面也给优化了,比如部署在本地浏览器上,把两个窗口整合。
(2)判断方式还可以更加优化,比如加入更多的元素来模拟出真实的飞行员在操控飞机打追击战的时候的快感。
(3)游戏模式可以多增加几个,比如生存模式,该模式不设分数上限。
3、收获和感想
这次的项目灵感取自b站的案例分析,在结合的过程中可能会吃瘪,但是即使走直线走不通,就走曲线,先达到目的然后对其优化,经过这次的项目磨合不仅打开了我的思路拓宽了我的视野,更加明白了交互的意义,感谢老师的倾囊相授,感谢同学的帮助。
全部代码资源也放在这个平台了
我写在最后面了希望各路神仙给我点c币吧参考资源
本文作者:Yang-blackSun
本文链接:https://www.cnblogs.com/Yang-blackSun/p/18025365
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
本文作者:Yang-blackSun
本文链接:https://www.cnblogs.com/Yang-blackSun/p/18025365
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步