-
Notifications
You must be signed in to change notification settings - Fork 0
/
camara_rps.py
206 lines (188 loc) · 9.8 KB
/
camara_rps.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
from contextlib import contextmanager
from keras.models import load_model # TensorFlow is required for Keras to work
import cv2 # Requires opencv-python
import numpy as np
import random # import random module to use choice() method
import time
class RPS:
'''
This class represents an instance of the computer vision rock-paper-scissors game.
Attributes:
----------
computer_wins: integer representing the number of rounds the computer has won
user_wins: integer representing the number of rounds the user has won
cap: opencv VideoCapture class, used for capturing images from computer webcam
image: image captured from webcam using VideoCapture read() method
game_started: boolean representing the status of the game
timer: integer used for counting down each round of the game
model: keras machine learning model class
class_names: list representation of machine learning model class labels
'''
def __init__(self):
'''Initialises an instance of the RPS class'''
self.computer_wins = 0
self.user_wins = 0
self.cap = cv2.VideoCapture(0)
self.image = []
self.game_started = False
self.timer = 0
self.model = load_model("keras_model.h5", compile=False)
self.class_names = open("labels.txt", "r").readlines()
def get_computer_choice(self):
'''Returns a random choice as a string - either 'rock', 'paper', or 'scissors'.'''
return random.choice(['rock', 'paper', 'scissors'])
def get_winner(self, computer_choice, user_choice):
'''
Takes two strings, compares them and evaluates who the winner is based on the rules of rock-paper-scissors.
Parameters
----------
computer_choice : str
A string generated from running the function get_computer_choice
user_choice : str
A string generated from running the function get_prediction
Returns
-------
result : str
A string representing the winner - either the user or the computer, or a tie
'''
print(f"\nYou chose {user_choice}.")
print(f"The computer chose {computer_choice}.")
# check if user didn't make gesture
if user_choice == 'nothing':
print("No gesture was detected, round is null")
return "nothing"
# check all three conditions whereby computer can win
elif (computer_choice == 'rock' and user_choice == 'scissors') or \
(computer_choice == 'paper' and user_choice == 'rock') or \
(computer_choice == 'scissors' and user_choice == 'paper'):
# if any of these are met, print losing message to console and return winner as string
print("You lost this round")
return "computer"
# check if user_choice and computer_choice are identical
elif computer_choice == user_choice:
print("This round was a tie!")
return "tie"
else:
print("You won this round!")
return "user"
@contextmanager
def get_video(self):
'''
Shows the video image from the computer webcam.
Uses the cv2 VideoCapture class method read() to generate an image from webcam video, then flips the image.
Utilises @contextmanager decorator to enable this function to wrap other lines of code.
'''
# Grab the webcamera's image.
ret, image = self.cap.read()
# Flip the image along the vertical axis as most people are used to seeing mirror image
self.image = cv2.flip(image,1)
# crop image to roughly match the shape of the capture area from Teachable Machine
self.image = self.image[:, 300:-300]
# try any code accompanying this function in with statement
try:
yield
finally:
cv2.imshow("Webcam Image", self.image)
def get_prediction(self):
'''
Uses a machine learning model to classify the content of the webcam image and returns that classification.
Takes the class attribute image (generated from webcam), resizes it to match ML model, turns it into a numpy array
and normalises it before predicting which gesture the user made and returning the prediction.
Returns
-------
class_name: str
string representing the classname predicted from the ML model
'''
# Resize the raw image into (224-height,224-width) pixels
gesture = cv2.resize(self.image, (224, 224), interpolation=cv2.INTER_AREA)
# Make the image a numpy array and reshape it to the models input shape.
gesture = np.asarray(gesture, dtype=np.float32).reshape(1, 224, 224, 3)
# Normalise the image array
gesture = (gesture / 127.5) - 1
# Classify the gesture using the model
prediction = self.model.predict(gesture, verbose=0)
# Get the index of the class with the highest prediction score
index = np.argmax(prediction)
# Use the index to get the string representation of that class
class_name = self.class_names[index]
# Return a sliced version of the string removing the class number and trailing newline, and converting to lowercase
return class_name[2:-1].lower()
def put_text_intro(self):
'''Uses cv2.putText() to place a series of messages on the screen at various positions'''
cv2.putText(self.image, "Rock, Paper, Scissors!", (100, 150), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
cv2.putText(self.image, "Show your gesture to the webcam.", (50, 200), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
cv2.putText(self.image, "When the countdown ends, your", (50, 250), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
cv2.putText(self.image, "choice is captured and the round", (50, 300), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
cv2.putText(self.image, "winner is declared in the terminal.", (50, 350), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
cv2.putText(self.image, "Press 'q' between rounds to quit.", (50, 400), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
cv2.putText(self.image, "Press 'c' to continue...", (50, 450), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
def display_prompt(self):
'''Displays either the game intro screen or the continue prompt depending on whether the game has started or not.'''
# check if the game has started
if self.game_started == False:
with self.get_video():
self.put_text_intro()
else:
with self.get_video():
cv2.putText(self.image, "Press 'c' to continue...", (50, 400), cv2.FONT_HERSHEY_DUPLEX, 1, (21, 35, 189), 2, cv2.LINE_AA)
def play_round(self):
'''
Plays a round of rock-paper-scissors.
Displays a countdown timer to the screen - when the timer reaches zero, an image is captured and the user's gesture
is predicted. A random computer gesture is generated and the two are compared. A round winner is declared based on
the rules of the game and the class attributes are updated to keep score for the game overall. Round result and
current game score are displayed in the terminal.
'''
self.timer = 5
prev_time = time.time()
while self.timer > 0:
curr_time = time.time()
# compare the time now to the time when the round started, if more than one second has elapsed...
if curr_time - prev_time >= 1:
# reset the previous time
prev_time = curr_time
# decrease timer by one
self.timer -= 1
# start the video and write the timer to the screen
with self.get_video():
cv2.putText(self.image, str(self.timer), (300, 300), cv2.FONT_HERSHEY_DUPLEX, 4, (21, 35, 189), 4, cv2.LINE_AA)
cv2.waitKey(1) # wait for input needed to keep video open
# once the timer reaches zero get a new video image without text
with self.get_video():
result = self.get_winner(self.get_computer_choice(), self.get_prediction())
if result == 'computer':
self.computer_wins += 1
elif result == 'user':
self.user_wins += 1
print(f"Current score: Computer: {self.computer_wins} | User: {self.user_wins}")
def play(self):
'''
Enables user to play a full game of rock-paper-scissors against the computer.
Loops over successive rounds of the game, calling the necessary functions until one of the players has reached
three wins (i.e. best of five rounds).
'''
# set up loop that ends once either player reaches three wins
while self.computer_wins < 3 and self.user_wins < 3:
self.display_prompt()
# wait for user input...
keystroke = cv2.waitKey(1)
# check user input - if user presses 'c'...
if keystroke == ord('c'):
self.game_started = True
self.play_round()
# otherwise, game can be ended between rounds by pressing 'q' key
elif keystroke == ord('q'):
break
# check which player reached three wins first and display winner to the terminal
if self.computer_wins == 3:
print("\nThe computer won the game\n")
elif self.user_wins == 3:
print("\nYou won the game!\n")
else:
print("\nYou ended the game before three round wins were achieved.\n")
# clean up the video capture object and close the image window
self.cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
game = RPS()
game.play()