
用 OpenCV+TensorFlow 和 AI 玩石头剪刀布
发布日期:2021-05-06 23:44:00
浏览次数:53
分类:精选文章
本文共 8479 字,大约阅读时间需要 28 分钟。
文章目录
项目介绍
在此项目中,我们将使用三个基于 OpenCV 和 TensorFlow 的 py 文件来实现石头剪刀布游戏,这些代码来源于大神,它们的作用分别是:
- 1、使用 OpenCV 收集图片数据用于训练手势分类器;
- 2、通过迁移学习使用 DenseNet 训练网络用于对不同手势进行分类;
- 3、开启摄像头进行游戏并判断最终赢家。
这篇文章会对这些代码进行详细讲解。
代码实现
一、getData.py 文件
这个文件的作用是调用摄像头来获取图片数据的,这样就可以制作自己的数据集了。其中,所有的图片尺寸都是 300x300 的。
import numpy as npimport matplotlib.pyplot as pltimport cv2import osimport syscap = cv2.VideoCapture(0) # 打开摄像头shape = ['rock', 'paper', 'scissor'] # 三种手势# 为每个手势建立一个文件夹,分别为 './Images/rock','./Images/paper','./Images/scissor'dir_name = ['Images/'+shape[i] for i in range(len(shape))]ct = 0 # 初始化计数器maxCt = 4 # 每个手势保存 maxCt 张图片print("Hit Space to Capture Image")for i in range(3): # 如果目录下没有此文件夹,则建立一个 try: os.mkdir('./'+dir_name[i]) except FileExistsError: pass # 逐帧读取摄像头记录的视频 while True: ret, frame = cap.read(0) cv2.imshow('Get Data : '+dir_name[i], frame[50:350,100:400]) if cv2.waitKey(1) & 0xFF == ord(' '): cv2.imwrite('./'+dir_name[i]+'/'+shape[i]+'{}.jpg'.format(ct), frame[50:350,100:400]) # 将此帧图片保存到相应文件夹 print(label[i]+'/'+shape[i]+'{}.jpg Captured'.format(ct)) ct+=1 # 计数器加一 if ct >= maxCt: # 如果计数达到 maxCt ct = 0 # 重置计数器 break # 退出 while 循环 if i == 2: # 当三个手势的图片都保存完,退出 for 循环 breakcap.release() # 释放摄像头cv2.destroyAllWindows() # 关闭窗口
运行 getData.py 后,在同一文件夹下将会出现一个新的文件夹 Images,在这个文件夹下又存在 3 个文件夹名为 rock,paper 和 scissor,分别存储着三种不同的手势图片。
二、train.py 文件
此文件旨在对制作的数据集进行数据增强并用这些数据来训练模型。
import tensorflow as tfimport numpy as npimport cv2import matplotlib.pyplot as pltfrom skimage import ioimport osimport pickleimport sysimport tensorflow as tffrom tensorflow.keras.models import Sequential,load_modelfrom tensorflow.keras.layers import Dense,MaxPool2D,Dropout,Flatten,Conv2D,GlobalAveragePooling2D,Activationfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStoppingfrom tensorflow.keras.optimizers import Adamfrom tensorflow.keras.applications.densenet import DenseNet121"""处理数据"""DATA_PATH = './Images' # 含有所有图片的总文件夹# shape_to_label 字典:键为不同手势,值为不同手势对应的独热编码shape_to_label = { 'rock':np.array([1.,0.,0.]),'paper':np.array([0.,1.,0.]),'scissor':np.array([0.,0.,1.])}# arr_to_shape 字典:键为不同手势的代号,值为不同手势的名称 --> {0: 'rock', 1: 'paper', 2: 'scissor'}arr_to_shape = { np.argmax(shape_to_label[x]):x for x in shape_to_label.keys()}imgData = list() # 初始化图片特征labels = list() # 初始化图片标签for dr in os.listdir(DATA_PATH): # 遍历总文件夹下的所有子文件夹('rock','paper','scissor' 等) if dr not in ['rock','paper','scissor']: # 不属于 'rock','paper','scissor' 的文件夹被跳过 continue lb = shape_to_label[dr] # 判断该文件夹中图片对应的独热编码 i = 0 # 用 i 记录含有该手势的图片数量 for pic in os.listdir(os.path.join(DATA_PATH, dr)): # 遍历该文件夹中的所有图片 path = os.path.join(DATA_PATH, dr+'/'+pic) # 图片路径 img = cv2.imread(path) # 导入图片 imgData.append([img,lb]) # 原始图片+对应的独热编码 # 数据增强 imgData.append([cv2.flip(img, 1),lb]) # 水平翻转图片+对应的独热编码 imgData.append([cv2.resize(img[50:250,50:250],(300,300)),lb]) # 剪裁图片+对应的独热编码 i+=3 # 循环一次,imgData 中增加 3 张图片imgData = list() # 初始化图片特征labels = list() # 初始化图片标签for dr in os.listdir(DATA_PATH): # 遍历总文件夹下的所有子文件夹('rock','paper','scissor' 等) if dr not in ['rock','paper','scissor']: # 不属于 'rock','paper','scissor' 的文件夹被跳过 continue lb = shape_to_label[dr] # 判断该文件夹中图片对应的独热编码 i = 0 # 用 i 记录含有该手势的图片数量 for pic in os.listdir(os.path.join(DATA_PATH, dr)): # 遍历该文件夹中的所有图片 path = os.path.join(DATA_PATH, dr+'/'+pic) # 图片路径 img = cv2.imread(path) # 导入图片 imgData.append([img,lb]) # 原始图片+对应的独热编码 # 数据增强 imgData.append([cv2.flip(img, 1),lb]) # 水平翻转图片+对应的独热编码 imgData.append([cv2.resize(img[50:250,50:250],(300,300)),lb]) # 剪裁图片+对应的独热编码 i+=3 # 循环一次,imgData 中增加 3 张图片"""建立模型"""# 迁移学习导入 DenseNet 模型densenet = DenseNet121(include_top=False, weights='imagenet', classes=3,input_shape=(300,300,3))densenet.trainable=Truedef genericModel(base): model = Sequential() model.add(base) model.add(MaxPool2D()) model.add(Flatten()) model.add(Dense(3,activation='softmax')) model.compile(optimizer=Adam(),loss='categorical_crossentropy',metrics=['acc']) return modeldnet = genericModel(densenet)# 设立时间点用来保存权值checkpoint = ModelCheckpoint( 'model.h5', monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=True, mode='auto')es = EarlyStopping(patience = 3)# 训练模型history = dnet.fit( x=imgData, y=labels, batch_size = 16, epochs=30, callbacks=[checkpoint,es], validation_split=0.2)# 保存模型权值dnet.save_weights('model.h5')# 将模型写入 json 文件with open("model.json", "w") as json_file: json_file.write(dnet.to_json())
运行 getData.py 后,在同一文件夹下将会出现 model.h5 文件和 model.json 文件,其中包含着模型的权值。
三、play.py 文件
此文件是最终的游戏文件,通过将 OpenCV 捕捉到的图片输入上一个文件建造的模型中来预测玩家手势,进而判断玩家和电脑的胜负情况。
from tensorflow.keras.models import model_from_jsonimport numpy as npfrom skimage import ioimport cv2import random# 将图片尺寸 resize 成 300x300,然后将其 reshape 成 (1,300,300,3)def prepImg(pth): return cv2.resize(pth,(300,300)).reshape(1,300,300,3)# 通过判断玩家手势 play 和电脑手势 bplay 来更新胜负情况 p 和 bdef updateScore(play,bplay,p,b): winRule = { 'rock':'scissor','scissor':'paper','paper':'rock'} if play == bplay: # 玩家和电脑所出手势相同 return p,b elif bplay == winRule[play]: # 玩家胜 return p+1,b else: # 电脑胜 return p,b+1# 读取 json 文件和 h5 文件来获取模型权重(导入网络)with open('model.json', 'r') as f: loaded_model_json = f.read()loaded_model = model_from_json(loaded_model_json)loaded_model.load_weights("model.h5")print("Loaded model from disk")shape_to_label = { 'rock':np.array([1.,0.,0.]),'paper':np.array([0.,1.,0.]),'scissor':np.array([0.,0.,1.])}arr_to_shape = { np.argmax(shape_to_label[x]):x for x in shape_to_label.keys()}options = ['rock','paper','scissor']winRule = { 'rock':'scissor','scissor':'paper','paper':'rock'}rounds = 0 # 进行的轮数botScore = 0 # 电脑的得分playerScore = 0 # 玩家的得分NUM_ROUNDS = 3 # 总轮数cap = cv2.VideoCapture(0) # 打开摄像头ret,frame = cap.read()loaded_model.predict(prepImg(frame[50:350,100:400]))bplay = ""while True: ret , frame = cap.read() # 按空格进入游戏 frame = cv2.putText(frame,"Press Space to start",(160,200),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) cv2.imshow('Rock Paper Scissor',frame) if cv2.waitKey(1) & 0xff == ord(' '): breakfor rounds in range(NUM_ROUNDS): pred = "" for i in range(90): # 每轮游戏在 90 次循环内完成 ret,frame = cap.read() # 倒计时,此时玩家考虑出什么手势 if i//20 < 3 : # 循环执行次数 i < 60 frame = cv2.putText(frame,str(i//20+1),(320,100),cv2.FONT_HERSHEY_SIMPLEX,3,(250,250,0),2,cv2.LINE_AA) # 预测玩家手势 elif i/20 < 3.5: # 60 =< 循环执行次数 i < 70 pred = arr_to_shape[np.argmax(loaded_model.predict(prepImg(frame[50:350,100:400])))] # 得到电脑所出手势 elif i/20 == 3.5: # 循环执行次数 i = 70 bplay = random.choice(options) print(pred,bplay) # 打印出玩家所出手势和电脑所出手势 # 更新玩家和电脑的得分 elif i//20 == 4: # 80 < 循环执行次数 i < 89 playerScore,botScore = updateScore(pred,bplay,playerScore,botScore) break cv2.rectangle(frame, (50, 100), (350, 400), (255, 255, 255), 2) # 玩家需要在这个矩形框中比出手势 frame = cv2.putText(frame,"Player : {} Bot : {}".format(playerScore,botScore),(120,400),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) # 玩家得分,电脑得分 frame = cv2.putText(frame,pred,(150,140),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) # 预测的玩家手势 frame = cv2.putText(frame,"Bot Played : {}".format(bplay),(300,140),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) # 电脑手势 cv2.imshow('Rock Paper Scissor',frame) if cv2.waitKey(1) & 0xff == ord('q'): # 按 q 退出游戏 breakif playerScore > botScore: # 玩家胜 winner = "You Won!!"elif playerScore == botScore: # 平局 winner = "Its a Tie"else: # 电脑胜 winner = "Bot Won.." while True: ret,frame = cap.read() frame = cv2.putText(frame,winner,(230,150),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) # 显示胜者 frame = cv2.putText(frame,"Press q to quit",(190,200),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) # 显示'按 q 退出游戏' frame = cv2.putText(frame,"Player : {} Bot : {}".format(playerScore,botScore),(120,400),cv2.FONT_HERSHEY_SIMPLEX,1,(250,250,0),2,cv2.LINE_AA) # 显示玩家得分和电脑得分 cv2.imshow('Rock Paper Scissor',frame) if cv2.waitKey(1) & 0xff == ord('q'): breakcap.release()cv2.destroyAllWindows()
发表评论
最新留言
留言是一种美德,欢迎回访!
[***.207.175.100]2025年04月16日 20时30分47秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
vue3 template refs dom的引用、组件的引用、获取子组件的值
2021-05-09
深入浅出mybatis
2021-05-09
Zookeeper快速开始
2021-05-09
882. Reachable Nodes In Subdivided Graph
2021-05-09
402. Remove K Digits
2021-05-09
375. Guess Number Higher or Lower II
2021-05-09
650. 2 Keys Keyboard
2021-05-09
764. Largest Plus Sign
2021-05-09
214. Shortest Palindrome
2021-05-09
916. Word Subsets
2021-05-09
869. Reordered Power of 2
2021-05-09
1086 Tree Traversals Again
2021-05-09
1127 ZigZagging on a Tree
2021-05-09
1062 Talent and Virtue
2021-05-09
1045 Favorite Color Stripe
2021-05-09
B. Spreadsheets(进制转换,数学)
2021-05-09
等和的分隔子集(DP)
2021-05-09
基础练习 十六进制转八进制(模拟)
2021-05-09
L - Large Division (大数, 同余)
2021-05-09
39. Combination Sum
2021-05-09