Effect
Operating environment
Anaconda 22.9.0
Visual Studio Code
Python 3.9.12
PyQT 5.2
Detailed process
Preparation
1. Create a folder and give it a random name to represent the name of the game.
2. Create another folder in this folder, and you can put the materials into the new folder
3. Create a py file and you’re ready to go
4. We give priority to the main part of the game, and then slowly improve it
1. Import the required libraries
import cv2 import time import cvzone from cvzone.HandTrackingModule import HandDetector from winsound import Beep import numpy as np import random
2. Turn on the camera and set the page size
cap = cv2.VideoCapture(0) cap.set(3, 1280) cap.set(4, 720)
3. Read material
imgBackground = cv2.imread("picture\Background.png")#Background board imgBall = cv2.imread("picture\Ball.png", cv2.IMREAD_UNCHANGED)#小ball imgBat1 = cv2.imread("picture\Bat1.png", cv2.IMREAD_UNCHANGED)#bat imgBat2 = cv2.imread("picture\Bat2.png", cv2.IMREAD_UNCHANGED)# racket
4.Initialization data
1. Initialize the ball and scores
# Random direction of the ball speed = [-20,20] # The initial position of the ball is in the center of the table ballPos = [600, 320] # Random initial speed of the ball speedX = random.choice(speed) speedY = random.choice(speed) # Set game to start gameOver = False #Initial score score = [0, 0]
2. Import hand recognition
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. Logic implementation
5.1. Two-hand mode
# If the hand is in the camera if hands: for hand in hands: x, y, w, h = hand['bbox'] # Return the height and width of the racket h1, w1 = imgBat1.shape[0:2] y1 = y - h1 // 2 # Specify the upper and lower range of the racket y1 = np.clip(y1, 20, 520) # If detected as left hand if hand['type'] == "Left": # Place the board on the background, the abscissa does not move, and the ordinate moves up and down according to the position of the hand img = cvzone.overlayPNG(img, imgBat1, (60, y1)) # If the ball hits the board if 70 < ballPos[0] < 70 + w1 and y1-h1//3 < ballPos[1] < y1 + h1: # Reverse the lateral speed, that is, rebound speedX = -speedX ballPos[0] + = 30 score[0] + = 1 #crash Beep(1046,50) #If detected as right hand 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
Real-time score display
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)
End interface display
# The score on the left is high 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) # The score on the right is high 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) # Tie display elif score[0]==score[1]: cv2.putText(img, str('DRAW'), (500, 650), cv2.FONT_HERSHEY_COMPLEX, 3, (192, 44, 56), 5)
5.2. One-handed mode
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 the ball goes over the board if ballPos[0] < 40: # game over gameOver=True # If the ball hits the right wall if ballPos[0] >= 1200: speedX = -speedX # Display real-time results 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 the ball goes over the board if ballPos[0] > 1160: # game over gameOver=True # If the ball hits the left wall if ballPos[0] <= 20: # bounce speedX = -speedX cv2.putText(img,str(score[1]),(900,650),cv2.FONT_HERSHEY_COMPLEX,3,(255, 255, 255), 5) # If no hands are detected and the ball exceeds the left and right boundaries (the core of seamlessly connecting left and right hands) if not hands and (ballPos[0] < 40 or ballPos[0] > 1160): # game over gameOver = True
5.3 Game end interface
if gameOver: imgGameOver = cv2.imread("picture\Gameover.png") img = imgGameOver cvzone.putTextRect(img, f"'R' begin or 'ESC' end", (400,100))
5.4 frame rate screen
pTime = 0 cTime = 0 Write the calculation process and display into the while loop 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 Moving the ball
ballPos[0] + = speedX ballPos[1] + = speedY # Display the ball trajectory (background, ball, coordinates) img = cvzone.overlayPNG(img, imgBall, ballPos)
5.6 Restart or exit the game
if key == ord('R'): # Restore central position ballPos = [600, 320] # The ball returns to a random initial speed speedX = random.choice(speed) speedY = random.choice(speed) # Close game over screen gameOver = False #Restore initialization results score = [0, 0] elif key == 27: # quit break
Complete code
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("Fun Table Tennis") 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">\ " "<html><head><meta name="qrichtext" content="1" /><style type="text/css">\ " "p, li { white-space: pre-wrap; }\ " "</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">\ " "<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>\ " "<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>\ " "<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>\ " "<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;">Fun table tennis</span></p>\ " "<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;">Fried Chicken is a fun gesture decompression game, easy to use</span></p>\ " "<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;">Game Rules</span><span style=" font-size:20pt; color:#550000;\ ">:</span><span style=" font-size:16pt; color:#550000;">The racket hits the ball to score points. The ball will bounce when it hits the upper and lower sides. The game ends when the ball goes out of bounds. Press </span>< span style=" font-size:16pt; color:#00ff00;">ESC to exit</span><span style=" font-size:16pt; color:#550000;">Game or press</span> span><span style=" font-size:16pt; color:#ff5500;">R restart</span><span style=" font-size:16pt; color:#550000;">Game </span></p>\ " "<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>\ " "<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;"> One-handed mode:</span><span style=" font-size:16pt; color:#000000 ;">When one side of the racket appears, the opposite boundary can allow the ball to bounce</span></p>\ " "<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;"> Two-handed mode:</span><span style=" font-size:16pt; color:#000000; ">Only the up and down bounces, the left and right rackets need to be controlled</span></p>\ " "<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", "One-handed mode")) self.Button2.setText(_translate("Game", "Two-Handed Mode"))
Materials
End
This is my first time blogging, and to be honest, I quite like this game. In the first version, mistakes were often made, such as the racket failing to hit the ball, etc. The one-handed mode later came out, which was also quite fun. If you like it, you can click it to favorites and follow it. If there is anything wrong, please point it out.
Reference
https://blog.csdn.net/weixin_42506516/article/details/125393153