用 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()
上一篇:使用 OpenCV 对车道进行实时检测
下一篇:用OpenCV检测行人移动方向

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2025年04月16日 20时30分47秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章