首先寫這個輔助的想法來源于之前微信小游戲“跳一跳”的輔助,使用到了adb
來獲得手機的截圖等,而直播答題碰到不會的題遇到的困難主要就是沒時間去打字搜索,如果用同樣的方法得到了題目相關的文字信息自動去搜索,成功率不就大大提高了嘛!再有很自然想到的一點就是一道題就3個選項,3臺手機不就肯定能保證對一道嗎?因此如何快速操作多個手機模擬器也需要考慮。
Ref
思路
開啟多個手機模擬器,題目出現時啟動Python程序,截圖并進行OCR識別出文字,獲得各個選項,接著對問題以及各個選項分別進行搜索,獲得前幾條搜索結果或者頁面數量等進行判斷。
手機模擬器
在這里我用的是夜神手機模擬器,需要用到一個分辨率、DPI較高的模擬器用于OCR識別,剩下的配置盡量調低降低硬件開銷。
adb
由于需要操作多個模擬器,我們首先需要知道如何連接各個模擬器。下載好adb包之后將其配置到環境變量中,然后adb devices
查詢在線的模擬器即可。然而有時候模擬器抽風,這個命令不起作用,這個時候需要先adb connect
之后adb devices
才有用(神設定),查看對應安裝目錄,Nox\bin\debug.bat
發現查找對應模擬器的端口的方法是通過查找配置文件的5555端口實現的,依葫蘆畫瓢,來到Nox\bin\BignoxVMS
查看各個副本的配置文件即可得到端口。這里總結一下用到的adb命令
//輸入adb可查看相關命令用法
//存在多個模擬器時需要用-s 指定操作的目標
adb -s 127.0.0.1:62001 shell screencap -p /sdcard/n.png //截屏
adb -s 127.0.0.1:62001 pull /sdcard/n.png ./n.png //傳到電腦
//輸入adb shell input可查看相關命令用法
adb -s 127.0.0.1:62001 shell input tap x y //點擊(x,y)位置 用以點擊問題的選項
確定題目與選項位置
經過測試題目選項出現的位置是固定的,所以這里的問題主要是將分辨率較高的模擬器上選項的位置對應到低分辨率模擬器上。
從以上3張圖可以得到,分辨率相同的話,題目區域大小與DPI成正比;DPI相同的話,整個題目占用的像素點是相同的,題目區域大小與分辨率成反比。所以假如在480X800 DPI 160
的模擬器上位置為(x,y)
,在240X400 DPI 60
的模擬器上位置為(x,y*2*60/160)=(x,0.75y)
OCR
從固定位置中裁剪出問題的圖像,再OCR識別為文字。這里用到的是tesseract-ocr,同樣需要配置環境變量。
系統變量下添加變量TESSDATA_PREFIX
值為E:\Tesseract-OCR\tessdata
PATH
下添加E:\Tesseract-OCR
還需要下載一個中文的數據包chi_sim.traineddata
放到E:\Tesseract-OCR\tessdata
cmd下輸入tesseract test.png out.txt -l chi_sim
生成的out.txt即為test.png的識別結果。需要注意這個test.png需要裁剪出待識別的區域,否則效果很差。
以上步驟成功后就可以用Python的pytesseract
庫來返回結果了。
# 保存問題
cropped_img = im[y_start:y_end, x_start:x_end]
cv2.imwrite("total.bmp" , cropped_img)
# 這里直接使用cropped_img不行,需要保存后再用PIL.Image打開
text_result=pytesseract.image_to_string(Image.open("total.bmp"), lang="chi_sim"a
搜索策略
文本拿到就很好做了。預處理一下,然后用jieba
獲取問題的關鍵詞,再進行搜索就行啦。這里我用的是根據搜索結果的數目來判斷的。這里請參考Wikipedia ——Pointwise mutual information
divided=text.replace(" ","").split("\n")
for i in divided:
if (i=="" or i=="\n"):
divided.remove(i)
question=divided[0]
del divided[0]
print("Question: %s"%question)
keywords=jieba.analyse.extract_tags(question,topK=2,withWeight=True)
print("Keywords:")
for i in keywords:
print i
totalweight=0
print ("Options: ")
print("\n".join(divided))
至于說獲得搜索結果的數量,用一個Xpath來提取就好啦,相信大家寫過爬蟲之后很容易搞定的。
def get_search_nums(word):
# url="https://www.baidu.com/s?wd="+unicode(word,"utf-8")
#需要先轉為utf8才能進行quote
url="https://www.baidu.com/s?wd="+urllib2.quote(word.encode("utf8"))
myheaders = {
'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} # 瀏覽器請求頭
request=urllib2.Request(url,headers=myheaders)
response=urllib2.urlopen(request)
source=response.read()
html=etree.HTML(source)
result=html.xpath('''//*[@id="container"]/div[2]/div/div[2]/text()''')
result=result[0]
pattern=re.compile(u".*約([\d,]+)個")
#?。。∽⒁膺@里編碼和網頁返回的編碼要對應才可以進行查找!!大坑!
num=re.findall(pattern,result)
num=int(num[0].replace(",",""))
return num
結果
嗯…用這種PMI算法得到的結果還是不太靠譜,按照整個問題來搜索AC選項差異不大,按照“電影”這個關鍵詞應該選B……(正確答案是C)所以還是需要把搜索結果的前幾條給展示出來的。獲得正確答案之后朝著各個模擬器發送一個對應位置的adb shell input
去點擊選項即可。
改進
- OCR效果太差導致搜索出錯。如上圖的“第六藝術”識別為“第大藝術”
(啥玩意兒啊)在Ref中的第二篇文章中用到了百度的OCR,不知道效果如何。 - 搜索多個頁面由于網絡速度的限制還是比較費時間的。
- “跳一跳”中曾出現直接抓包分析并提交結果的方法,不知道在西瓜視頻里面能否用上。
- 使用多個模擬器測試的過程中發現各個問題在模擬器上的出現是有時差的,延后出現對作答是有利的,能否在可接受的范圍內加大這個時差?