之前有看到微信小程序《跳一跳》別人用python實現自動運行,后來看到別人用hash碼實現《加減大師》的自動答題領取娃娃,最近一直在研究深度學習,為啥不用機器學習實現呢?不就是一個分類問題嗎
如何實現自動答題微信小游戲《加減大師》?
思考:
- 圖像識別嗎?
- 如何建立特征工程?
- 選用什么算法?
一、圖像特征工程
如何獲取手機游戲上的圖片?
- 使用adb命令截取手機屏幕;
- 在PC端和手機端同時運行APowerMirror軟件,將手機投屏到電腦上,然后使用Pillow包中的截圖方法截取電腦上對應手機屏幕的
區域。 - 在PC端和手機端同時運行APowerMirror軟件,將手機投屏到電腦上,然后使用Python調用windows的原生API截取電腦上對應手機屏幕的區域。
實驗結果: 三種截屏方式花費的時間差異很大,第一種每次截屏需要0.7s左右,第二種0.3s左右,第三種0.04s左右。
當然選擇第3種咯,下載地址[https://www.apowersoft.cn/phone-mirror],一個好的軟件是成功的關鍵(夠清晰)。
獲取訓練樣本
相關步驟:
1.
util.py
中的shotByWinAPI
函數:首先利用window自帶api獲取全屏圖片,然后自定義config.py
的相關參數。
# 從PC端截屏時,截取區域左上角相對桌面的x坐標
'projection_x': 32,
# 從PC端截屏時,截取區域左上角相對桌面的y坐標
'projection_y': 278,
# 從PC端截屏時,截取區域的寬度
'projection_width': 482,
# 從PC端截屏時,截取區域的高度
'projection_height': 854,
可以用window命令鍵PrtScSysRq
(F12的右邊),然后復制到畫圖中(1920x1080)。
用畫圖的放大鏡放大,圖中紅色框的小方塊位置(32x278)projection_x
即32,projection_y
即278。
在畫圖中計算出截圖的寬度和高度,即projection_width
和projection_height
(482x854)
2.
img_tool.py
函數介紹:主要是通過all(img, filename)
函數進行圖片分割
srcImg = cv2.imread(os.path.join("ScreenShotForTrain", f), 0)
上述代碼是為了將彩色圖片灰度模式加載

def all(img, filename):
"""封裝對圖片的所有操作"""
img = cropImg(img)
img = binaryImg(img)
img1, img2 = cropAgain(img)
imgs = cutImg(img1, filename + '_1') + cutImg(img2, filename + '_2')
return imgs
def cropImg(img):
"""裁剪原始截圖"""
height = img.shape[0]
img2 = img[int(config.config['exp_area_top_rate'] * height):int(config.config['exp_area_bottom_rate'] * height),:]
#print('裁剪完畢')
return img2
cropImg(img)
函數主要是為了裁剪含有數字的區域,通過設置參數
#表達式區域的頂部處於整張圖片的位置(307/854=0.359)
'exp_area_top_rate': 0.36,
#表達式區域的底部處於整張圖片的位置(478/854=0.559)
'exp_area_bottom_rate': 0.56,
如果覺得設置比例太麻煩,可以直接寫死位置(img2 = img[int(307):int(478),:]
)。得到如下圖:

def binaryImg(img):
"""二值化圖片"""
ret, thresh1 = cv2.threshold(img, config.config['binary_threshold'], 255, cv2.THRESH_BINARY)
# ret, thresh1 = cv2.threshold(img, config.config['binary_threshold'], 255, cv2.THRESH_BINARY_INV)
#print('二值化完畢')
return thresh1
binaryImg(img)
函數主要是為了將圖片二值化,可以參考
Python+OpenCV教程6:閾值分割。得到的圖片如下圖:
def cropAgain(img):
"""再次裁剪"""
height = img.shape[0]
img1 = img[0:int(0.5 * height), :]
img2 = img[int(0.5 * height):height, :]
#print('再次裁剪完畢')
return img1, img2
cropAgain(img)
函數主要是為了將圖片分成上下兩部分
def cutImg(img, filename):
"""水平分割圖片"""
sb = np.array(img)
print(sb.shape)
sum_list = np.array(img).sum(axis=0)
start_index = -1
res = []
names = []
index = 0
for sum in sum_list:
if sum > 255 * 4:
if start_index == -1:
start_index = index
else:
if start_index != -1:
if config.config['type'] == 0:
sigleCharWidth = config.config['abd_single_char_width']
else:
sigleCharWidth = config.config['pc_single_char_width']
#為了防止字符粘連,需要在此處寬度進行判斷
if index - start_index > sigleCharWidth * 2:
res.append((start_index,start_index + (index - start_index) // 2))
res.append((start_index + (index - start_index) // 2, index))
else:
res.append((start_index, index))
start_index = -1
index += 1
imgs = []
count = 0
for single_char in res:
start = single_char[0]
end = single_char[1]
sub_img = img[:, start:end]
sub_img = cv2.resize(sub_img, (120, 240), interpolation=cv2.INTER_CUBIC)
#cv2.imwrite('SingleChar/%s_%d.png' % (filename, count), sub_img)
#names.append('%s_%d.png' % (filename, count))
# cv2.imshow(str(count), sub_img)
imgs.append(sub_img)
count += 1
# cv2.waitKey()
#print('分割,重新設置大小 %s 完畢' %filename)
return imgs
設置pc_single_char_width
參數值,得到如下圖:
c = 0
def v_cut(img):
global c
"""豎直方向切割圖片"""
sb1 = np.array(img)
print(sb1.shape)
sum_list = np.array(img).sum(axis=1)
start_index = -1
end = -1
index = 0
for sum in sum_list:
if sum > 255 * 2:
start_index = index
break
index += 1
for i in range(1, len(sum_list) + 1):
if sum_list[-i] > 255 * 2:
end = len(sum_list) + 1 - i
break
img = img[start_index:end, :]
img = cv2.resize(img, (30, 60), interpolation=cv2.INTER_CUBIC)
#cv2.imwrite('SingleChar/%d.png' %c, img)
c += 1
return img
重新固定圖片的大小(30x60),得到如下圖:
二、訓練模型,建立LR分類器
相關代碼請看ml.py
,這里不過多介紹,直接利用python包from sklearn.linear_model import LogisticRegression
LogisticRegression(class_weight='balanced')
sklearn邏輯回歸(Logistic Regression,LR)類庫使用小結
三、自動答題模式開啟
實現原理
-
1.截取游戲界面,本項目中提供了三種方案。
在PC端和手機端同時運行APowerMirror軟件,將手機投屏到電腦上,然后使用Python調用windows的原生API截取電腦上對應手機屏幕的區域。 -
2.提取截屏圖片中的表達式區域並進行文字識別,得到表達式字符串。
由於圖片中的表達式區域固定,而且字符規整,因此這一步不是很困難,我僅僅訓練了一個簡單的邏輯回歸模型就得到了非常高的識別正確率。 -
3.根據第二步得到的表達式,調用Python的eval()函數,得到表達式結果的正誤,然后點擊手機屏幕的相應區域。當截圖使用投屏的方案時,點擊手機屏幕通過代碼點擊
電腦上手機的對應區域。
首次操作,生成分類器模型
1.借用投屏軟件,利用畫圖工具配置相關參數
config.py
,可以參考上面的“圖像特征工程”
2.對於新的手機(我用的是honor8),必須重新訓練模型,設置
config.py
中的debug
參數為True,打開“加減大師”,然后運行main.py
,這里必須手動答題,盡可能多答對一些題,目的為了擴充訓練樣本。
3.步驟2會產生一個
SingleCharForTrain
文件夾,剔除重復樣本和無關樣本。
4.運行
img_tool.py
文件,會生成一個SingleCharForTrain
文件夾。
5.將步驟4得到的文件夾中的字符進行人工分類,保存至
TrainChar
文件夾。
6.運行
ml.py
文件,生成分類器模型lr.pickle
。
注意桌面上不要有東西遮擋到手機的投影區域
根據分類器模型自動答題
1.修改
config.py
中的debug
參數為False及其他相關參數。
#使用PC進行截圖時點擊手機屏幕正確區域的x坐標
'pc_tap_true_x':117,
#使用PC進行截圖時點擊手機屏幕錯誤區域的x坐標
'pc_tap_false_x':365,
#使用PC進行截圖時點擊手機屏幕正確和區域的y坐標
'pc_tap_y':760,
配置正確和錯誤選擇的橫縱坐標,橫坐標不一樣,縱坐標相同(在同一高度)
2.打開加減大師,直接運行
main.py
即可。
遇到的問題
Q1: 跑到200步左右就停了?
A1: 如果是誤判的話,把出錯的那張圖重新截圖,將得到的字符添加到
TrainChar
文件夾中,重新訓練模型
A1: 如果是上一張圖和這張圖相同,再跑一次唄,不相信你運氣會那么差
Q2: 刷到1000分,結果小程序上不了分
A2: 剛開始以為是答題時間沒有設置隨機的問題,設置
main.py
中
one_tap(res)
# 設置隨機睡眠時間,隨機性防止微信后台檢測
if (count < 100):
time.sleep(0.1 * (random.randint(0, 9)))
elif (count <200):
time.sleep(0.05 * (random.randint(0, 9)))
elif (count <300):
time.sleep(0.01 * (random.randint(0, 9)))
elif (count < 400):
time.sleep(0.01 * (random.randint(0, 9)))
elif (count < 500):
# 可以控制到這一關gg
if (count == 455):
time.sleep(3)
然而並沒有軟用,估計是后台設置(個人認為,當天的分數不能超過第一名太多),反正是前500都能獲得小卡片,你可以嘗試設置比第一名多個幾分或少幾分。
四、源代碼地址
記得給哥們的github打♥啊
上代碼:https://github.com/Yiutto/WechatGame_jjds
有問題私聊我yiutto@qq.com
最后放出我的娃娃來,手機上顯示的是這樣的
到手的時候卻是這樣的(本來以為沒戲了,等了將近一個星期)
最后,祝大家都能拿到娃娃!!!