一、想法來由
我最近在玩暗黑3 PTR 測試伺服器
結果發現要達到全身洪荒裝備
有兩個方向
1.用50血岩跟PTR商人換一個【裝備袋】
會掉洪荒裝備沒錯,但是一個箱子中少則6-7件裝備,多則2x件裝備,
一箱子裡基本上有掉一件洪荒就不錯了
而且要剛好出你需要的裝備,這機率又更低啦
2.重鑄裝備
用50血岩跟PTR商人換取一個【PTR寶袋】,
這會給1-5章所有懸賞材料
50血岩可換懸賞材料各100個+拆解裝備材料各1000個
而重鑄一次需要懸賞材料各5個+遺忘之魂50個
所以花50血岩換一次材料可以重鑄20次
於是就展開了重鑄洪荒裝備之旅
但是點著點著....10次,100次,1000次.....手好痠阿
天啊,雖然測試伺服器掉寶率很多
但是要出洪荒,看臉成分很大啊
想想,全身洪荒裝備前,我手就廢了,這可不行
我得想個好辦法來達成這件事情
於是我找到Autohotkey來達成
按某鍵後去點擊【放入材料】再點擊【轉化】
但是是一次性的,所以我手還是得放在鍵盤上按
這還是太麻煩啦,不夠懶人,不夠全自動
於是想想,還是自己寫好了,掌控度也比較高
會用PYTHON的考量是,我不需要視窗,加上PYTHON很多影像辨識的套件可以直接用
於是再次展開懶人追求全自動重鑄之旅
我想到分兩個階段來實現
第一階段(就是先做到Autohotkey做的事情,然後再慢慢加)
- 鍵盤監聽(當按下某按鍵開始,案某按鍵結束)
- 自動重鑄(左鍵點擊放入材料後再點擊轉化)
- 加入While...Loop,實現一直重鑄
- 多線程(同時監聽鍵盤+自動重鑄)
- Screenshot,只擷取角色數值區域
- OCR,圖片轉文字
- 設定目標,判定停止
YA! 要做的事情擬定好後都感覺到那股美好了(都還沒完成呢!)
二、運行環境
- Windows 10 Home
- Python 3.7.2
- opencv 3.4.5.20
- PIL 8.0.0
- pyautogui 0.9.52
- ctypes 1.1.0
- numpy 1.19.2
- pytesseract 0.3.6
- easygui 0.98.1
- pynput 1.6.8
>>> import cv2 >>> cv2.__version__ '3.4.5' >>> import PIL >>> PIL.__version__ '8.0.0' >>> import pyautogui >>> pyautogui.__version__ '0.9.52' >>> import ctypes >>> ctypes.__version__ '1.1.0' >>> import numpy >>> numpy.__version__ '1.19.2' >>> import pkg_resources >>> pkg_resources.working_set.by_key['pytesseract'].version '0.3.6' >>> import pkg_resources >>> pkg_resources.working_set.by_key['easygui'].version '0.98.1' >>> import pkg_resources >>> pkg_resources.working_set.by_key['pynput'].version '1.6.8'
三、Function介紹
1.鍵盤監聽範例(參考自此篇文章)
程式碼
# -*- coding: utf-8 -*- from pynput.keyboard import Controller, Key, Listener from pynput import keyboard #得到鍵入的值 def get_key_name(key): return str(key) # 監聽按壓 def on_press(key): global fun_start,time_interval,index,dict,count,count_dict print("正在按壓:", get_key_name(key)) # 監聽釋放 def on_release(key): global start,fun_start, time_inxterval, index,count,count_dict print("已經釋放:", get_key_name(key)) if key == Key.esc: # 停止監聽 return False # 開始監聽 def start_listen(): with Listener(on_press=on_press, on_release=on_release) as listener: listener.join() if __name__ == '__main__': # 開始監聽,按esc退出監聽 start_listen()
結果
===================== RESTART: D:/D3/Autoreforge/test.py =====================
正在按壓: 'a' 已經釋放: 'a' 正在按壓: 'b' 已經釋放: 'b' 正在按壓: Key.space 已經釋放: Key.space 正在按壓: Key.shift 已經釋放: Key.shift 正在按壓: Key.esc 已經釋放: Key.esc >>>
2.多線程範例
我覺得這篇文章【一文看懂Python多程序與多執行緒程式設計】寫得很詳細,想多了解多線程可以參考這篇。
基本上程式都是由上往下一行一行執行,如何做到同一個時間點,同時執行兩個不同段落的程式碼,就需要使用到多線程的概念。
在此次需求中,我想同時間做鍵盤監控又要控制滑鼠點擊,所以才需要用到多線程。
3.截圖並裁切
我的螢幕解析度為1920*1080
如果是其他解析度,座標要調整一下喔
在切割圖片之後,我把所有非白色的pixel都變黑色(概念來自這篇)
讓圖片呈現黑底白字的狀態
程式碼
# -*- coding: utf-8 -*- import pyautogui image = pyautogui.screenshot()#全螢幕截圖 image_obj = image.crop((1440,240,1600,500)).convert('L')#切割圖片,只留需要的區域 for a in range(image_obj.size[1]): for b in range(image_obj.size[0]): if image_obj.getpixel((b,a)) < 171: image_obj.putpixel((b,a),0) image_obj.save(r"D:\D3\Autoreforge\screenshotDefault20.png")
結果
白話說就是把圖片裡的文字解讀並轉成字串,讓我們可以在程式中使用(加減乘除做比較...等等)。
在做OCR之前,我們還得要對圖片進行前處理(去雜訊/去噪),讓圖片比較容易被電腦判讀。
- 圖片前處理
而plt.imshow()顯示圖片時是RGB,所以我們必須將順序調整一下
不然你的圖片會變得藍藍的
透過這行代碼來做
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
程式碼
# -*- coding: utf-8 -*- import cv2 import matplotlib.pyplot as plt import numpy as np #影像前處理 def get_clearImage(): #原圖 imagex = cv2.imread(r"D:\D3\Autoreforge\screenshotDefault2.png", cv2.IMREAD_COLOR)
imagex = cv2.cvtColor(imagex,cv2.COLOR_BGR2RGB) #只留白色的圖 img = cv2.imread(r"D:\D3\Autoreforge\screenshotDefault8.png", cv2.IMREAD_COLOR)
#轉灰階 coefficients = [0, 1, 1] m = np.array(coefficients).reshape((1, 3)) gray = cv2.transform(img, m) #閾值 maxval:255 142 binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] #並排顯示四張圖 p_1 = plt.subplot(141) plt.imshow(imagex) p_1.set_title('Original') p_2 = plt.subplot(142) plt.imshow(img) p_2.set_title('Only white') p_3 = plt.subplot(143) plt.imshow(gray,cmap ='gray') p_3.set_title('Gray') p_4 = plt.subplot(144) plt.imshow(binary,cmap ='gray') p_4.set_title('Threshold') plt.show() return binary get_clearImage()
結果
- 白底黑字的辨識度 優於 黑底白字
- 如果圖片不是單純的白底黑字,那圖片進OCR之前的前處理很重要
- 根據圖片特徵去做前處理以強化你想要辨識的地方,以我的例子,我要的是白色的數值部分,所以我就去凸顯白色,把非白色都去掉。
四、完整程式碼
# -*- coding: utf-8 -*- by Luca import sys, os from pynput.keyboard import Controller, Key, Listener from pynput import keyboard #version 1.6.8 from datetime import datetime from PIL import ImageGrab, Image #version 8.0.0 import easygui #version 0.98.1 import time import pyautogui #version 0.9.52 import threading import inspect import ctypes #version 1.1.0 import pytesseract import cv2 #version 3.4.5.20 import numpy as np #version 1.19.2 import skimage #0.24.0 flag = False #自動重鑄的執行狀態,True表示重鑄中,False表示閒置中 threads = [] #多線程陣列 condition="DAMAGE:1054000" #目標值 def _async_raise(tid, exctype): """raises the exception, performs cleanup if needed""" tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") #結束多線程 def stop_thread(thread): _async_raise(thread.ident, SystemExit) #Inputbox def call_inputbox(): global condition tmp = easygui.enterbox("請輸入條件","訊息",condition) if tmp != None: condition = tmp print('目前條件為:'+ condition) time.sleep(1) #影像前處理 def get_clearImage(img): #放大照片 #resize image to be 300 pixels wide r = 300.0 / img.shape[1] dim = (300, int(img.shape[0] * r)) bigimg = cv2.resize(img, dim, interpolation=cv2.INTER_AREA) #轉灰階(去除顏色留下黑白) coefficients = [0, 1, 1] m = np.array(coefficients).reshape((1, 3)) gray = cv2.transform(bigimg, m) #定義膨脹核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) #膨脹操作(把白字變粗) dilated = cv2.dilate(gray, kernel, iterations=1) #閾值 maxval:255 142 白轉黑 黑轉白 變成白底黑字 binary = cv2.threshold(dilated, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] return binary #圖片轉文字 def get_charactfromIMG(cond): imgPath =r"D:\D3\Autoreforge\screenshotDefault.png" img = cv2.imread(imgPath, cv2.IMREAD_COLOR) result = [] cong = r'--oem 3 --psm 6 outputbase digits' datas = pytesseract.image_to_string(get_clearImage(img),lang="eng", config=cong)#chi_tra for i in datas.splitlines(): tempval = i.strip().replace('.','') if tempval != "": result.append(tempval) if cond == "STR" and len(result) > 0: #力量 return result[0] elif cond == "AGI" and len(result) > 1: #敏捷 return result[1] elif cond == "INT" and len(result) > 2: #智力 return result[2] elif cond == "VIT" and len(result) > 3: #體能 return result[3] elif cond == "DAMAGE" and len(result) > 4: #傷害 return result[4] else: stop_rebuild() print("不在暗黑畫面中,停止鑄造") #比較條件與數值 def compare_value(): global condition if len(condition) > 0: condType = condition.split(':')[0] condValue = condition.split(':')[1] val1 = get_charactfromIMG(condType) if (len(val1)>1) and (int(val1) >= int(condValue)): return True else: return False else: return False #螢幕截圖 def get_screenshot(): image = pyautogui.screenshot()#全螢幕截圖 image_obj = image.crop((1440,240,1600,500)).convert('L')#切割圖片,只留需要的區域 #非白色都轉黑色,只留下白色數值部分 for a in range(image_obj.size[1]): for b in range(image_obj.size[0]): if image_obj.getpixel((b,a)) < 171: image_obj.putpixel((b,a),0) image_obj.save(r"D:\D3\Autoreforge\screenshotDefault.png") #停止重鑄 def stop_rebuild(): global threads, flag,condition flag = False if len(threads) > 0: stop_thread(threads[0]) threads.clear() condition="" print(datetime.now().strftime("%H:%M:%S") + " : 停止重鑄...") # 自動重鑄 def auto_rebuild(): i=0 while i<1000: #以防萬一程式停不了,跑到1000次時強制停止 get_screenshot()#全螢幕截圖 if compare_value() == False: pyautogui.click(699,839) #放入材料 time.sleep(.200) pyautogui.click(277,834) #轉化 time.sleep(3.500) i+=1 else: break print(datetime.now().strftime("%H:%M:%S") + " : 目標達成,停止重鑄...") stop_rebuild() #得到鍵入的值 def get_key_name(key): return str(key) # 監聽按壓 def on_press(key): global fun_start,time_interval,index,dict,count,count_dict #print("正在按壓:", get_key_name(key)) # 監聽釋放 def on_release(key): global start,fun_start, time_inxterval, index,count,count_dict,flag,threads,condition tmpkey = get_key_name(key) #print("已經釋放:", tmpkey) if tmpkey == "'a'" and flag == False: threads.clear() print(datetime.now().strftime("%H:%M:%S") , " : 開始重鑄...") threads.append(threading.Thread(target = auto_rebuild)) threads[0].start() flag = True if tmpkey == "'b'" and flag == True: stop_rebuild() if tmpkey == "'s'": call_inputbox() if tmpkey == "'x'": # 停止監聽 return False # 開始監聽 def start_listen(): with Listener(on_press=on_press, on_release=on_release) as listener: listener.join() if __name__ == '__main__': # 開始監聽,按esc退出監聽 print('暗黑破壞神3 自動重鑄程式開始執行....') print('按s設定目標值, 按a開始自動重鑄, 按b停止自動重鑄, 按x結束程式') print('力量STR,敏捷AGI,智力INT,體力VIT,條件使用預期目標,當力量>=10000才停止重鑄') print('條件範例: AGI:10000,這條件表示當敏捷大於等於10000會停止重鑄') print('目前條件為:'+ condition) start_listen()
大部分情況下程式都可以正常運行
但在多線程的處理上,還有進步空間,偶有Error會發生
但至少現在可以不用守在電腦前面啦
參考資料
- python實現鍵盤監聽
- Python多執行緒threading模組平行化
- python強制結束線程
- 一文看懂Python多程序與多執行緒程式設計
- 如何優雅地終止python執行緒
- PyAutoGUI:使用Python控制電腦
- Pyautogui screenshot. Where does it go? How to save and find later?
- OCR 辨識繁體中文
- Tesseract-OCR
- Text Detection with OpenCV in Python | OCR using Tesseract (2020)
- python 識別圖片中的文字資訊方法
- Python做的簡單文字識別小程序
- Image Thresholding
- 使用pytesseract进行图像识别字母和数字 (python3.x)
- python 识别图片上的数字(weixin_30335353)
- python 识别图片上的数字(BackingStar)
- [Python] OpenCV & Tesseract 辨識身份證
- EasyGui 學習文件
- 用Python做一個游戲輔助腳本,完整編程思路分享!
- D3非战斗向复合型一键AHK宏
- 用Pyinstaller把Python3.7程式打包成可執行檔案exe
沒有留言:
張貼留言