计算机视觉 利用OpenCV做一个乒乓球手势游戏

效果

运行环境

Anaconda 22.9.0

Visual Studio Code

Python 3.9.12

PyQT 5.2

具体过程

准备

1.创建一个文件夹,随便起个名,代表这个游戏的名字

2.在这个文件夹里面再创建一个文件夹,可以将素材放入新建的文件夹

3.创建一个py文件,就可以开始了

4.我们优先做游戏主体部分,然后再慢慢完善

1.导入需要的库

import cv2 
import time
import cvzone
from cvzone.HandTrackingModule import HandDetector
from winsound import Beep
import numpy as np
import random

2.打开摄像头,设置页面大小

cap = cv2.VideoCapture(0)
cap.set(3, 1280)
cap.set(4, 720)

3.读取素材

imgBackground = cv2.imread("picture\Background.png")#背景板
imgBall = cv2.imread("picture\Ball.png", cv2.IMREAD_UNCHANGED)#小球
imgBat1 = cv2.imread("picture\Bat1.png", cv2.IMREAD_UNCHANGED)#球拍
imgBat2 = cv2.imread("picture\Bat2.png", cv2.IMREAD_UNCHANGED)#球拍

4.初始化数据

 1.初始化小球和成绩

# 小球随机方向
speed = [-20,20]
# 小球的初始位置在球桌中央
ballPos = [600, 320]
# 小球随机初始速度
speedX = random.choice(speed)
speedY = random.choice(speed)
# 设置游戏为开始
gameOver = False
# 初始分数
score = [0, 0]

2.导入手部识别

detector = HandDetector(detectionCon=0.8, maxHands=2)
hands, img = detector.findHands(img)
img = cv2.flip(img, 1)
hands, img = detector.findHands(img, flipType=False)

5.逻辑实现

5.1.双手模式

# 如果手在镜头内
if hands:
    for hand in hands:
       x, y, w, h = hand['bbox']
       # 返回球拍的高度,宽度
       h1, w1 = imgBat1.shape[0:2]
       y1 = y - h1 // 2
       # 规定球拍的上下范围
       y1 = np.clip(y1, 20, 520)
       # 如果检测为左手
       if hand['type'] == "Left":
       	# 将板放在背景上,横坐标不动,纵坐标根据手的位置来上下移动
           img = cvzone.overlayPNG(img, imgBat1, (60, y1))
           # 如果球碰到了板
           if 70 < ballPos[0] < 70 + w1 and y1-h1//3 < ballPos[1] < y1 + h1:
           # 将横向速度反转,也就是反弹
           	speedX = -speedX
              ballPos[0] += 30
              score[0] += 1
              # 撞击声
              Beep(1046,50)
#如果检测为右手
       if hand['type'] == "Right":
      		img = cvzone.overlayPNG(img, imgBat2, (1190, y1))
       	if 1140 - w1 < ballPos[0] < 1140 and y1-h1//3 < ballPos[1] < y1 + h1:
          speedX = -speedX
          ballPos[0] -= 30 
          score[1] += 1
          Beep(1046,50)
if ballPos[0] < 40 or ballPos[0] > 1160:
gameOver = True

实时成绩显示 

cv2.putText(img,str(score[0]),(300,650),cv2.FONT_HERSHEY_COMPLEX,3,(255,255,255),5)
cv2.putText(img,str(score[1]),(900,650),cv2.FONT_HERSHEY_COMPLEX,3,(255,255, 255),5)

结束界面显示

# 左边分数高
if score[0]>score[1]:
    cv2.putText(img, str('WIN'), (250, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255,0,255), 5)
    cv2.putText(img, str('LOSE'), (850, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (34, 148, 83), 5)
# 右边分数高    
elif score[1]>score[0]:
    cv2.putText(img, str('LOSE'), (250, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (34, 148, 83), 5)
    cv2.putText(img, str('WIN'), (850, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255,0,255), 5)
# 平局显示    
elif score[0]==score[1]:
cv2.putText(img, str('DRAW'), (500, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (192, 44, 56), 5)

5.2.单手模式

if hands:
    for hand in hands:
        x, y, w, h = hand['bbox']
        h1, w1 = imgBat1.shape[0:2]
        y1 = y - h1 // 2 
        y1 = np.clip(y1, 20, 520)
        if hand['type'] == "Left":
            img = cvzone.overlayPNG(img, imgBat1, (60, y1))
            if 70 < ballPos[0] < 70 + w1 and y1-h1//3 < ballPos[1] < y1 + h1:
                speedX = -speedX
                ballPos[0] += 30 
                score[0] += 1
                Beep(1046,50)
            # 如果球超过板
            if ballPos[0] < 40 :
                # 游戏结束
                gameOver = True
            # 如果球碰到右边的墙
            if ballPos[0] >= 1200 :
                speedX = -speedX
            # 显示实时成绩
cv2.putText(img,str(score[0]),(300,650),cv2.FONT_HERSHEY_COMPLEX,3,(255, 255, 255), 5)						
        if hand['type'] == "Right":
            img = cvzone.overlayPNG(img, imgBat2, (1190, y1))
            if 1140 - w1 < ballPos[0] < 1140 and y1-h1//3 < ballPos[1] < y1 + h1:
                speedX = -speedX
                ballPos[0] -= 30
                score[1] += 1
                Beep(1046,50)
            # 如果球超过板
            if ballPos[0] > 1160 :
                # 游戏结束
                gameOver = True
            # 如果球碰到左边的墙
            if ballPos[0] <= 20 : 
                # 反弹
                speedX = -speedX
cv2.putText(img,str(score[1]),(900,650),cv2.FONT_HERSHEY_COMPLEX,3,(255, 255, 255), 5)
# 如果没有检测到手并且球超出左右边界(无缝衔接左右手的核心)
if not hands and (ballPos[0] < 40 or ballPos[0] > 1160):
    # 游戏结束
    gameOver = True

5.3游戏结束界面

if gameOver:
    imgGameOver = cv2.imread("picture\Gameover.png")
    img = imgGameOver
    cvzone.putTextRect(img, f"'R' begin or 'ESC' end", (400,100))

5.4帧数画面

pTime = 0
cTime = 0
将计算过程与显示写进while循环里
cTime = time.time()
fps = 1/(cTime-pTime)
pTime = cTime
cv2.putText(img,f"FPS:{int(fps)}",(20,70),cv2.FONT_HERSHEY_PLAIN,3,(255, 255, 0),3)

5.5移动小球

ballPos[0] += speedX
ballPos[1] += speedY
# 显示小球轨迹(背景,小球,坐标)
img = cvzone.overlayPNG(img, imgBall, ballPos)

5.6重新开始或者退出游戏

if key == ord('R'):
# 恢复中央位置
    ballPos = [600, 320]
    # 小球恢复随机初始速度
    speedX = random.choice(speed)
    speedY = random.choice(speed)
    # 关闭游戏结束画面
    gameOver = False
    # 恢复初始化成绩
    score = [0, 0]
elif key == 27:
    # 退出
    break

完整代码

UI.py

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from UI_Set import Ui_Game
import game

class MyMainForm(QMainWindow,Ui_Game):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)
        self.Button1.clicked.connect(game.One_hand)
        self.Button2.clicked.connect(game.Both_hands)    
        self.setWindowTitle("趣味乒乓球")
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    mygame = MyMainForm()
    mygame.setObjectName("MainWindow")
    mygame.show()
    sys.exit(app.exec_())

game.py

import cv2
import time
import cvzone
from cvzone.HandTrackingModule import HandDetector
from winsound import Beep
import numpy as np
import random

imgBackground = cv2.imread("picture\Background.png")
imgBall = cv2.imread("picture\Ball.png", cv2.IMREAD_UNCHANGED)
imgBat1 = cv2.imread("picture\Bat1.png", cv2.IMREAD_UNCHANGED)
imgBat2 = cv2.imread("picture\Bat2.png", cv2.IMREAD_UNCHANGED)

cap = cv2.VideoCapture(0)
cap.set(3, 1280)
cap.set(4, 720)

def Both_hands(self):

    detector = HandDetector(detectionCon=0.8, maxHands=2)
    pTime = 0
    cTime = 0
    speed = [-20,20]
    ballPos = [600, 320]
    speedX = random.choice(speed)
    speedY = random.choice(speed)
    gameOver = False
    score = [0, 0]

    while True:
        imgGameOver = cv2.imread("picture\Gameover.png")
        _, img = cap.read()
        img = cv2.flip(img, 1)
        cTime = time.time()
        fps = 1/(cTime-pTime)
        pTime = cTime
        cv2.putText(img,f"FPS:{int(fps)}",(20,70),cv2.FONT_HERSHEY_PLAIN,3,(255, 255, 0),3)

        hands, img = detector.findHands(img, flipType=False)
        img = cv2.addWeighted(img, 0.2, imgBackground, 0.8, 0)

        if hands:
            for hand in hands:
                x, y, w, h = hand['bbox']
                h1, w1 = imgBat1.shape[0:2]
                y1 = y - h1 // 2
                y1 = np.clip(y1, 20, 520)

                if hand['type'] == "Left":
                    img = cvzone.overlayPNG(img, imgBat1, (60, y1))
                    if 70 < ballPos[0] < 70 + w1 and y1-h1//3 < ballPos[1] < y1 + h1:
                        speedX = -speedX
                        ballPos[0] += 30
                        score[0] += 1
                        Beep(1046,50)

                if hand['type'] == "Right":
                    img = cvzone.overlayPNG(img, imgBat2, (1190, y1))
                    if 1140 - w1 < ballPos[0] < 1140 and y1-h1//3 < ballPos[1] < y1 + h1:
                        speedX = -speedX
                        ballPos[0] -= 30 
                        score[1] += 1
                        Beep(1046,50)

        if ballPos[0] < 40 or ballPos[0] > 1160:
            gameOver = True

        if gameOver:
            img = imgGameOver
            cvzone.putTextRect(img, f"'R' begin or 'ESC' end", (400,100))
            if score[0]>score[1]:
                cv2.putText(img, str('WIN'), (250, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255,0,255), 5)
                cv2.putText(img, str('LOSE'), (850, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (34, 148, 83), 5)   
            elif score[1]>score[0]:
                cv2.putText(img, str('LOSE'), (250, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (34, 148, 83), 5)
                cv2.putText(img, str('WIN'), (850, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255,0,255), 5)    
            elif score[0]==score[1]:
                cv2.putText(img, str('DRAW'), (500, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (192, 44, 56), 5)
            
        else:
            if ballPos[1] >= 600 or ballPos[1] <= 10:
                speedY = -speedY    
            ballPos[0] += speedX
            ballPos[1] += speedY
            img = cvzone.overlayPNG(img, imgBall, ballPos)
            cv2.putText(img, str(score[0]), (300, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255, 255, 255), 5)
            cv2.putText(img, str(score[1]), (900, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255, 255, 255), 5)

        cv2.imshow("Both_hands", img)

        key = cv2.waitKey(1)
        if key == ord('R'):
            ballPos = [600, 320]
            speedX = random.choice(speed)
            speedY = random.choice(speed)
            gameOver = False
            score = [0, 0]

        elif key == 27:
            break

    cv2.destroyAllWindows()




def One_hand(self):

    detector = HandDetector(detectionCon=0.8, maxHands=1)
    pTime = 0
    cTime = 0
    speed = [-15,15]
    ballPos = [600, 320]
    speedX = random.choice(speed)
    speedY = random.choice(speed)
    gameOver = False
    score = [0, 0]

    while True:

        imgGameOver = cv2.imread("picture\Gameover.png")
        _, img = cap.read()
        img = cv2.flip(img, 1)
        cTime = time.time()
        fps = 1/(cTime-pTime)
        pTime = cTime
        cv2.putText(img,f"FPS:{int(fps)}",(20,70),cv2.FONT_HERSHEY_PLAIN,3,(255, 255, 0),3)
        
        hands, img = detector.findHands(img, flipType=False)
        img = cv2.addWeighted(img, 0.2, imgBackground, 0.8, 0)

        if hands:

            for hand in hands:
                x, y, w, h = hand['bbox']
                h1, w1 = imgBat1.shape[0:2]
                y1 = y - h1 // 2 
                y1 = np.clip(y1, 20, 520)

                if hand['type'] == "Left":
                    img = cvzone.overlayPNG(img, imgBat1, (60, y1))
                    if 70 < ballPos[0] < 70 + w1 and y1-h1//3 < ballPos[1] < y1 + h1:
                        speedX = -speedX
                        ballPos[0] += 30 
                        score[0] += 1
                        Beep(1046,50)
                    if ballPos[0] < 40 :
                        gameOver = True
                    if ballPos[0] >= 1200 :
                        speedX = -speedX
                    cv2.putText(img, str(score[0]), (300, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255, 255, 255), 5)

                if hand['type'] == "Right":
                    img = cvzone.overlayPNG(img, imgBat2, (1190, y1))
                    if 1140 - w1 < ballPos[0] < 1140 and y1-h1//3 < ballPos[1] < y1 + h1:
                        speedX = -speedX
                        ballPos[0] -= 30
                        score[1] += 1
                        Beep(1046,50)
                    if ballPos[0] > 1160 :
                        gameOver = True
                    if ballPos[0] <= 20 : 
                        speedX = -speedX
                    cv2.putText(img, str(score[1]), (900, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (255, 255, 255), 5)

        if not hands and (ballPos[0] < 40 or ballPos[0] > 1160):
            gameOver = True

        if gameOver:
            img = imgGameOver
            cvzone.putTextRect(img, f"'R' begin or 'ESC' end", (400,100))
  
        else:
            if ballPos[1] >= 600 or ballPos[1] <= 10:
                speedY = -speedY
            ballPos[0] += speedX
            ballPos[1] += speedY
            img = cvzone.overlayPNG(img, imgBall, ballPos)

        cv2.imshow("One_hand", img)

        key = cv2.waitKey(1)

        if key == ord('R'):
            ballPos = [600, 320]
            speedX = random.choice(speed) 
            speedY = random.choice(speed)
            gameOver = False
            score = [0, 0]
        elif key == 27:
            break

    cv2.destroyAllWindows()

UI_Set.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'e:\my_opencv\pingpong\pingpong5.0\First.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Game(object):
    def setupUi(self, Game):
        Game.setObjectName("Game")
        Game.setEnabled(True)
        Game.resize(854, 716)
        Game.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
        self.textEdit = QtWidgets.QTextEdit(Game)
        self.textEdit.setGeometry(QtCore.QRect(11, 11, 831, 511))
        self.textEdit.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.ArrowCursor))
        self.textEdit.setStyleSheet("background-color: rgb(255, 255, 255,60)")
        self.textEdit.setObjectName("textEdit")
        self.Button1 = QtWidgets.QPushButton(Game)
        self.Button1.setGeometry(QtCore.QRect(20, 550, 231, 131))
        self.Button1.setCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
        self.Button1.setStyleSheet("background-color: rgb(255, 255, 255,60)")
        self.Button1.setObjectName("Button1")
        self.Button2 = QtWidgets.QPushButton(Game)
        self.Button2.setGeometry(QtCore.QRect(590, 550, 241, 131))
        self.Button2.setCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
        self.Button2.setStyleSheet("background-color: rgb(255, 255, 255,60)")
        self.Button2.setObjectName("Button2")

        self.retranslateUi(Game)
        QtCore.QMetaObject.connectSlotsByName(Game)

    def retranslateUi(self, Game):
        _translate = QtCore.QCoreApplication.translate
        Game.setWindowTitle(_translate("Game", "Form"))
        self.textEdit.setHtml(_translate("Game", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:48pt; color:#ff0000;\"><br /></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:48pt; color:#ff0000;\">趣味乒乓球</span></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:18pt; color:#0000ff;\">炒鸡好玩的手势解压小游戏,简单上手</span></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:20pt; color:#000000;\">游戏规则</span><span style=\" font-size:20pt; color:#550000;\">:</span><span style=\" font-size:16pt; color:#550000;\">球拍击打球得分,球碰到上下会反弹,球出界游戏结束,按</span><span style=\" font-size:16pt; color:#00ff00;\">ESC退出</span><span style=\" font-size:16pt; color:#550000;\">游戏或者按</span><span style=\" font-size:16pt; color:#ff5500;\">R重新开始</span><span style=\" font-size:16pt; color:#550000;\">游戏</span></p>\n"
"<p align=\"center\" style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:16pt; color:#550000;\"><br /></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:20pt; color:#000000;\"> 单手模式:</span><span style=\" font-size:16pt; color:#000000;\">出现一侧球拍时,对面的边界可以让球反弹</span></p>\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:20pt; color:#000000;\"> 双手模式:</span><span style=\" font-size:16pt; color:#000000;\">只有上下会反弹,左边右边球拍都需要控制</span></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:24pt; color:#550000;\"><br /></p></body></html>"))
        self.Button1.setText(_translate("Game", "单手模式"))
        self.Button2.setText(_translate("Game", "双手模式"))

素材 

结尾

第一次写博客,说实话,我挺喜欢这个游戏的。第一版的时候经常出错,球拍拍不到球之类的,到后面出的单手模式,也是蛮好玩的。喜欢的可以点个收藏关注,有什么不对的地方请指出。

参考 

https://blog.csdn.net/weixin_42506516/article/details/125393153

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值