反省
- 最初はコメントをつけていたものの途中から少なくなり可読性が低くなった。自分でも理解に時間がかかるレベル。
- 起動部分、GUIとロジック部分のクラスは分けるべき。
- 部品化できていない部分が多数あり冗長なコードが見られる。(特に動画モードと配信モードの部分)
- 論理型の変数が多数あり管理できていない。まとめて扱う方が良かった。
- OBSクラス自体いらない。
- いまだにスレッドの停止の仕方がわからない。
- これから実装の可能性もあるが、今がどの段階かを判定し認識箇所を減らして処理速度向上を狙ってもいいかも。
ソースコード
__init__.py
起動部分、GUI関連とメインロジックの部分
import datetime from email.mime import image from lib2to3.fixer_util import String from lib2to3.tests.data.infinite_recursion import blkcnt_t from logging import _startTime import os import re import shutil import sys import threading import time import time import tkinter import win32gui import cv2 import numpy as np from PIL import Image from PIL import ImageGrab from future.moves.tkinter import messagebox from obswebsocket import requests from obswebsocket.core import obsws from main import Obs, imageRecognition, screenshot from main.Obs import Obs from main.screenshot import Screenshot from main.thread import mainThread labelX = 30 txtboxY = 30 txtboxDY = 30 txtboxX = 100 txtboxWidth = 80 miniTxtboxWidth = 20 buttonX = 100 buttonDX = 50 buttonY = 210 kidouX = 400 blKidou = False ws = Obs() capName = "kakusi.bmp" def createGui(frm): frm.geometry('600x260') frm.title('ぱにぱにツール ver 1.4') txtList = readConfig() ''' 画面配置(テキストボックス) ''' sourceLabel = tkinter.Label(text="認識ソース") sceneLabel = tkinter.Label(text="シーン") windowLabel = tkinter.Label(text="表示ソース") sourceLabel.place(x=labelX, y=txtboxY) sceneLabel.place(x=labelX, y=txtboxY + txtboxDY) windowLabel.place(x=labelX, y=txtboxY + txtboxDY * 2) portLabel = tkinter.Label(text="ポート") passwordLabel = tkinter.Label(text="パスワード") portLabel.place(x=labelX, y=txtboxY + txtboxDY * 3) passwordLabel.place(x=txtboxX * 3 - labelX * 2, y=txtboxY + txtboxDY * 3) timeLabel = tkinter.Label(text="対戦時間") timeLabel.place(x=labelX, y=txtboxY+txtboxDY * 5) sourceTxt = tkinter.Entry(width=txtboxWidth) sourceTxt.place(x=txtboxX, y=txtboxY) sourceTxt.insert(tkinter.END, txtList[0]) sceneTxt = tkinter.Entry(width=txtboxWidth) sceneTxt.place(x=txtboxX, y=txtboxY + txtboxDY) sceneTxt.insert(tkinter.END, txtList[1]) windowTxt = tkinter.Entry(width=txtboxWidth) windowTxt.place(x=txtboxX, y=txtboxY + txtboxDY * 2) windowTxt.insert(tkinter.END, txtList[2]) portTxt = tkinter.Entry(width=miniTxtboxWidth) portTxt.place(x=txtboxX, y=txtboxY + txtboxDY * 3) portTxt.insert(tkinter.END, txtList[3]) passwordTxt = tkinter.Entry(width=miniTxtboxWidth) passwordTxt.place(x=txtboxX * 3, y=txtboxY + txtboxDY * 3) passwordTxt.insert(tkinter.END, txtList[4]) timeTxt = tkinter.Entry(width=miniTxtboxWidth) timeTxt.place(x=txtboxX, y=txtboxY+txtboxDY * 5) timeTxt.insert(tkinter.END, txtList[5]) kidochuuLabel = tkinter.Label(text="起動中") modeLabel = tkinter.Label(text="モード") modeLabel.place(x=labelX, y=txtboxY + txtboxDY * 4) var = tkinter.IntVar() var.set(txtList[6]) rdo1 = tkinter.Radiobutton(frm,value=0,variable=var, text='動画') rdo1.place(x=txtboxX, y=txtboxY + txtboxDY * 4) rdo2 = tkinter.Radiobutton(frm,value=1,variable=var, text='配信') rdo2.place(x=txtboxX*2, y=txtboxY + txtboxDY * 4) ''' タプルにテキストボックスの情報を入れて、スレッドインスタンスの作成 ''' guiInfo = (sourceTxt, sceneTxt, windowTxt, portTxt, passwordTxt,timeTxt) ''' 画面配置(ボタン) ''' thread = threading.Thread() startButton = tkinter.Button(text="起動", command=lambda:clickKidou(kidochuuLabel, thread, guiInfo,var.get())) stopButton = tkinter.Button(text="停止", command=lambda:clickTeisi(kidochuuLabel, thread, guiInfo)) startButton.place(x=buttonX, y=buttonY) stopButton.place(x=buttonX + buttonDX, y=buttonY) saveButton = tkinter.Button(text="設定保存", command=lambda:saveConfig(guiInfo,var.get())) saveButton.place(x=buttonX + buttonDX * 2, y=buttonY) screenShotButton = tkinter.Button(text="スクショ", command=lambda:copyScreenShot()) screenShotButton.place(x=buttonX + buttonDX * 3 + 25, y=buttonY) def copyScreenShot(): filename='./screenShot/' + datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S') + '.bmp' shutil.copy(capName,filename) def clickKidou(label, thread, guiInfo,mode): global blKidou if not checkRegexpNum(guiInfo[5].get()): messagebox.showerror('エラー', '対戦時間には半角整数を入力してください') return blKidou = True ws.connect("localhost", guiInfo[4].get(), guiInfo[3].get()) label.place(x=kidouX, y=buttonY) print(mode) if mode==0: thread = threading.Thread(target=lambda:beginMainDouga(ws, guiInfo)) thread.start() elif mode==1: thread = threading.Thread(target=lambda:beginMain(ws, guiInfo)) thread.start() def checkRegexpNum(str): pattern='[0-9]+' return re.match(pattern, str) def clickTeisi(label, thread, guiInfo): global blKidou label.place_forget() blKidou = False time.sleep(0.5) thread.clear() ws.disconnect() def createMyPoke2(beforeCount): global capName count=0 sensyutuPoke=[-1,-1,-1,-1] for banme in range(4): banmeResult=imageRecognition.imageRecognition.sensyutuNumRecognition(banme+1, capName) if banmeResult>=0: count=count+1 else: break sensyutuPoke[banme]=banmeResult if beforeCount != count: imageRecognition.imageRecognition.createMyPokeImage(sensyutuPoke,returnNotCapName(capName),count) return count def beginMainDouga(ws,guiInfo): ''' ・相手ポケモンのキャプ ・自分の選出 ・時間表示 ''' global capName startTime=datetime.datetime.now() nokoriTime=datetime.datetime.now() blCountTime=True #対戦時間機能を使うかどうか blCap = False # キャプが必要かどうか blTime =False #時間計測の最初かどうか blbeforeCap = False # blChangeCapName=False #そのループでkakusi.bmpを切り替えるかどうか beforeResult2 = False #選出中の1個前 sensyutuResult = False #選出前かどうか sensyutuResult2 = False #選出中かどうか beforeCount=0 #前の選択ポケモン数 blCap2=False Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi.bmp") Obs.changeRender(ws, False, guiInfo[1].get(), guiInfo[2].get()) Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi2.bmp") Obs.changeRender(ws, False, guiInfo[1].get(), "sensyutu.png") print(guiInfo[5].get()) if guiInfo[5].get() =="0": blCountTime=False if blCountTime: Obs.changeRender(ws, True, guiInfo[1].get(), "time") else: Obs.changeRender(ws, False, guiInfo[1].get(), "time") while blKidou: Obs.takeScreenshot(ws, guiInfo[0].get(), capName) ''' 時間計測 ''' if blTime and blCountTime: nokoriTime=datetime.timedelta(minutes=int(guiInfo[5].get()))-abs(startTime-datetime.datetime.now()) minutes=str((nokoriTime.seconds//60)).zfill(2) seconds=str((nokoriTime.seconds%60)).zfill(2) strTime=(minutes+":"+seconds) Obs.setText(ws,"time", guiInfo[1].get(),strTime) time.sleep(0.1) sensyutuResult = imageRecognition.imageRecognition.sensyutu1Recognition(capName) # 選出前 sensyutuResult2 = imageRecognition.imageRecognition.sensyutu3Recognition(capName) # 選出中 if sensyutuResult or sensyutuResult2 or blbeforeCap or beforeResult2: blTime=False ''' 対戦を始めますの画面 ''' if sensyutuResult: blCap = True blbeforeCap = True else: if blCap: sensyutuResult3 = imageRecognition.imageRecognition.sensyutu2Recognition(capName, blCap) if sensyutuResult3: capName = returnNotCapName(capName) blCap = False if sensyutuResult2: blbeforeCap = False if imageRecognition.imageRecognition.sensyutu4Recognition(capName): blCap2=True else: intResult = imageRecognition.imageRecognition.commandRecognition(capName,blTime) if blTime==False and blCountTime: if intResult == 1: startTime = datetime.datetime.now() blTime=True ''' 勝負が終わった時 ''' if intResult == 5: blTaoreta = False blTime=False if blCap2: beforeCount=createMyPoke2(beforeCount) blCap2=False def beginMain(ws, guiInfo): ''' ・スクリーンショット ・画像認識 ・OBSの操作 ''' global capName blCountTime=True #対戦時間機能を使うかどうか blCap = False # キャプが必要かどうか blTime =False #時間計測の最初かどうか blbeforeCap = False # blTaoreta = False #倒れたの判定 blTonbo = False #戻っていくの判定 blRenderSentaku=False #選択中の文字とかの表示非表示 blRenderkakusi1=False #kakusi.bmpの表示非表示 blRenderkakusi2=False #kakusi2.bmpの表示非表示 blRenderSensyutu=False #sensyutu.pngの表示非表示 blSensyutuCheck=False #選出チェックが必要かどうか beforeResult=False #1つ前の判定 beforebeforeResult=False #2つ前の判定 beforeResult2 = False #選出中の1個前 sensyutuResult = False #選出前かどうか sensyutuResult2 = False #選出中かどうか sumtime=63 #処理にかかっている時間を合計したもの startTime=datetime.datetime.now() nokoriTime=datetime.datetime.now() taoretaCount=0 #試合中何回倒れたか beforeIntResult=0 #1つ前の判定 intResult = 0 #技選択のぶぶんの判定 result=False #技選択中かどうか blsumTime=False #合計時間がたくさん経ったかどうか #blChangeCapName=False #そのループでkakusi.bmpを切り替えるかどうか Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi.bmp") Obs.changeRender(ws, False, guiInfo[1].get(), guiInfo[2].get()) Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi2.bmp") Obs.changeRender(ws, False, guiInfo[1].get(), "sensyutu.png") if guiInfo[5].get() ==0: blCountTime=False if blCountTime: Obs.changeRender(ws, True, guiInfo[1].get(), "time") else: Obs.changeRender(ws, False, guiInfo[1].get(), "time") timeNow = datetime.datetime.now() while blKidou: Obs.takeScreenshot(ws, guiInfo[0].get(), capName) if blTime: nokoriTime=datetime.timedelta(minutes=int(guiInfo[5].get()))-abs(startTime-datetime.datetime.now()) minutes=str((nokoriTime.seconds//60)).zfill(2) seconds=str((nokoriTime.seconds%60)).zfill(2) strTime=(minutes+":"+seconds) Obs.setText(ws,"time", guiInfo[1].get(),strTime) beforeResult2 = sensyutuResult2 time.sleep(0.05) sensyutuResult = imageRecognition.imageRecognition.sensyutu1Recognition(capName) # 選出前 sensyutuResult2 = imageRecognition.imageRecognition.sensyutu3Recognition(capName) # 選出中 if sensyutuResult or sensyutuResult2 or blbeforeCap or beforeResult2: taoretaCount=0 blTaoreta = False if blTime and blCountTime: blTime=False Obs.setText(ws,"time", guiInfo[1].get(),"00:00") ''' 対戦を始めますの画面 ''' if sensyutuResult: capName = returnNotCapName(capName) blCap = True blSensyutuCheck=True blbeforeCap = True else: if blCap: sensyutuResult3 = imageRecognition.imageRecognition.sensyutu2Recognition(capName, blCap) if sensyutuResult3: Obs.setOptions(ws,capName) capName = returnNotCapName(capName) blCap = False elif blSensyutuCheck: sensyutuResult3 = imageRecognition.imageRecognition.sensyutu2Recognition(returnNotCapName(capName), blCap) if not sensyutuResult3: sensyutuResult3 = imageRecognition.imageRecognition.sensyutu2Recognition(capName, blCap) if sensyutuResult3: capName = returnNotCapName(capName) if sensyutuResult2: blbeforeCap = False if capName == "kakusi.bmp": if blRenderkakusi1==True: Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi.bmp") blRenderkakusi1=False if blRenderkakusi2==False: Obs.changeRender(ws, True, guiInfo[1].get(), "kakusi2.bmp") blRenderkakusi2=True else: if blRenderkakusi1==False: Obs.changeRender(ws, True, guiInfo[1].get(), "kakusi.bmp") blRenderkakusi1=True if blRenderkakusi2==True: Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi2.bmp") blRenderkakusi2=False if blRenderSensyutu ==False: Obs.changeRender(ws, True, guiInfo[1].get(), "sensyutu.png") blRenderSensyutu=True else: beforeIntResult=intResult intResult = 1 intResult = imageRecognition.imageRecognition.commandRecognition(capName,blTime) print(sumtime) if blTime==False and blCountTime: if intResult == 1: startTime = datetime.datetime.now() blTime=True ''' 合計時間が多すぎる時はリセット ''' if sumtime>80: blsumTime=False if sumtime >10000: sumtime=80 else: blsumTime=True ''' たたかうの画面の時、隠す画面の更新を行う。 ''' if (not beforeResult) and intResult == 1 and sumtime >= 80: print(sumtime) capName = returnNotCapName(capName) ''' たたかうの画面、技選択、ポケモン交代画面の時 ''' if intResult == 1: sumtime=-30 if intResult == 1 or intResult == 2 or intResult == 6: blTaoreta = False result = True else: result = False ''' 技選択、ポケモン交代画面の時 ''' if intResult == 2 or intResult == 6: blTonbo = False if intResult == 6: sumtime=-60 ''' 倒れた時、3回以上倒れた時は除く ''' if intResult == 3 and taoretaCount<3: capName = returnNotCapName(capName) blTaoreta = True if beforeIntResult!=3: taoretaCount=taoretaCount+1 ''' とんぼ、ボルチェンの時 ''' if intResult == 4: capName = returnNotCapName(capName) blTonbo = True ''' 勝負が終わった時 ''' if intResult == 5: blTaoreta = False if blCountTime: blTime=False Obs.setText(ws,"time", guiInfo[1].get(),"00:00") ''' 技選択中の場合、合計時間を図りだす ''' if result and sumtime>0: sumtime=0 sumtime=sumtime+(datetime.datetime.now() - timeNow).microseconds / 10000 timeNow = datetime.datetime.now() ''' 表示非表示の切り替え ''' if blRenderSentaku !=((result or beforeResult or blsumTime or blTaoreta or blTonbo)) : Obs.changeRender(ws, (result or beforeResult or blsumTime or blTaoreta or blTonbo), guiInfo[1].get(), guiInfo[2].get()) blRenderSentaku=(result or beforeResult or blsumTime or blTaoreta or blTonbo) if capName == "kakusi.bmp": if blRenderkakusi1==True: Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi.bmp") blRenderkakusi1=False if blRenderkakusi2!=(result or beforeResult or blsumTime or blTaoreta or blTonbo): Obs.changeRender(ws, (result or beforeResult or blTaoreta or blTonbo), guiInfo[1].get(), "kakusi2.bmp") blRenderkakusi2=(result or beforeResult or blsumTime or blTaoreta or blTonbo) else: if blRenderkakusi1!=(result or beforeResult or blsumTime or blTaoreta or blTonbo): Obs.changeRender(ws, (result or beforeResult or blsumTime or blTaoreta or blTonbo), guiInfo[1].get(), "kakusi.bmp") blRenderkakusi1=(result or beforeResult or blsumTime or blTaoreta or blTonbo) if blRenderkakusi2==True: Obs.changeRender(ws, False, guiInfo[1].get(), "kakusi2.bmp") blRenderkakusi2=False if blRenderSensyutu ==True: Obs.changeRender(ws, False, guiInfo[1].get(), "sensyutu.png") blRenderSensyutu=False beforeResult = result beforeResult2 = False def outputNow(): timeNow = datetime.datetime.now() print(timeNow) def readConfig(): list = [] count=0 with open("config.txt") as f: for s_line in f: count=count+1 list.append(s_line.rstrip('\n')) if count ==5: list.append('0') list.append('0') return list def saveConfig(guiInfo,mode): os.remove("config_bk.txt") os.rename("config.txt", "config_bk.txt") with open("config.txt", "w") as f: for i in range(6): f.write(str(guiInfo[i].get()) + "\n") f.write(str(mode) + "\n") def returnNotCapName(capName): if capName == "kakusi.bmp": return"kakusi2.bmp" if capName == "kakusi2.bmp": return "kakusi.bmp" return "" def main(): frm = tkinter.Tk() createGui(frm) frm.mainloop() if __name__ == '__main__': main()
OBS.py
OBS制御に関する部分
''' Created on 2019/11/04 @author: panipani ''' import os import time from obswebsocket import requests from obswebsocket.core import obsws from main.screenshot import Screenshot class Obs(object): ''' classdocs ''' def connect(self,server,obsId,obsPass): self.ws = obsws(server, obsPass, obsId) self.ws.connect() def changeRender(self,bl,obsScene,obsSource): ''' 画像の表示非表示の変更 ''' self.ws.call(requests.SetSceneItemRender(obsSource,bl,obsScene)) def takeScreenshot(self,obsSource,capName): self.ws.call(requests.TakeSourceScreenshot(obsSource, saveToFilePath=os.getcwd()+"\\" + capName, width=1920, height=1080)) def disconnect(self): self.ws.disconnect() def setOptions(self,sourceName): self.ws.call(requests.SetSourceSettings(sourceName, {"unload": False})) ''' source=ソース名 scene=シーン名 str=テキスト ''' def setText(self,source,scene,str): self.ws.call(requests.SetTextGDIPlusProperties(source,scene,text=str)) def __init__(self): ''' Constructor ''' self.ws=obsws()
ImageRecognition.py
画像認識に関する部分
''' Created on 2019/11/02 @author: panipani ''' import sys from PIL import Image import cv2 from numpy.random.bounded_integers import np class imageRecognition(): ''' 画像認識に関するクラスs ''' @staticmethod def isExistImage(imgName,tempImgName,accuracy,recTop,recButtom,recLeft,recRight): result=False img=cv2.imread(imgName) img1 = img[recTop : recButtom, recLeft: recRight] temp=cv2.imread(tempImgName) if temp is None: print(imgName+"が見つかりません") sys.exit(1) gray= cv2.cvtColor(img1,cv2.COLOR_RGB2GRAY) temp= cv2.cvtColor(temp,cv2.COLOR_RGB2GRAY) match=cv2.matchTemplate(gray,temp,cv2.TM_CCOEFF_NORMED) loc = np.where( match >=accuracy) for pt in zip(*loc[::-1]): result=True return result @staticmethod def createMyPokeImage(sensyutuPoke,img,count): imgImg=Image.open(img) if count==0: outputImg = imgImg.crop((310, 192, 311, 193)) outputImg.save('test/myPoke2.bmp',quality=95) else: dst = Image.new('RGB', (408*count, 120)) i=0 for num in sensyutuPoke: outputImg = imgImg.crop((305, 187+120*num, 713, 299+120*num)) dst.paste(outputImg,(0+408*i,0)) i=i+1 dst.save('test/myPoke2.bmp',quality=95) @staticmethod def capturePokemon(imgName): img=Image.open(imgName) outputImg = img.crop((1230, 200, 1320, 900)) outputImg.save('test/enemyPoke.bmp', quality=95) outputImg = img.crop((330, 200, 420, 900)) outputImg.save('test/myPoke.bmp', quality=95) @staticmethod def commandRecognition(imgName,blTime): if imageRecognition.isExistImage(imgName, "test/Temp4.jpg",0.85,880,1040,160,1750) and (not imageRecognition.isExistImage(imgName, "test/aite.bmp",0.85,880,1040,170,1750)) : return 3 if imageRecognition.isExistImage(imgName, "test/temp.jpg",0.85,470,530,1660,1900): if blTime: return 1 elif imageRecognition.isExistImage(imgName, "test/hpGauge.jpg",0.85,75,130,1430,1510): return 1 if imageRecognition.isExistImage(imgName, "test/katimake.bmp",0.85,880,1040,160,380): return 5 if imageRecognition.isExistImage(imgName, "test/kousan.bmp",0.85,880,1040,160,370): return 5 if imageRecognition.isExistImage(imgName, "test/tonbo.bmp",0.85,880,1040,160,1750) and (not imageRecognition.isExistImage(imgName, "test/aite.bmp",0.85,880,1040,170,1750)): return 4 if imageRecognition.isExistImage(imgName, "test/temp2.jpg",0.85,520,580,1660,1900): return 2 if imageRecognition.isExistImage(imgName, "test/temp3.jpg",0.85,0,200,0,1000): return 6 if imageRecognition.isExistImage(imgName, "test/tojiru.bmp",0.85,470,530,1660,1900): return 2 return 0 @staticmethod def sensyutu1Recognition(imgName): if imageRecognition.isExistImage(imgName, "test/searchtemp.jpg",0.85,860,1070,510,1400): return True if imageRecognition.isExistImage(imgName, "test/friendTemp.bmp",0.85,890,990,540,620): return True return False @staticmethod def sensyutu2Recognition(imgName,bl): if imageRecognition.isExistImage(imgName, "test/sensyututemp.jpg",0.85,70,330,480,1250): if bl: imageRecognition.capturePokemon(imgName) return True return False @staticmethod def sensyutu3Recognition(imgName): if imageRecognition.isExistImage(imgName, "test/sensyututemp2.jpg",0.85,0,90,0,600): return True return False @staticmethod def sensyutu4Recognition(imgName): if imageRecognition.isExistImage(imgName, "test/sensyututemp4.bmp",0.85,65,125,300,360): return True return False @staticmethod def sensyutuNumRecognition(banme,imgName): for num in range(6): if imageRecognition.isExistImage(imgName, "test/ban" + str(banme) +".bmp",0.85,105+num*145,175+num*145,1300,1400): return num return -1 def __init__(self, params): ''' Constructor '''