赤裸裸的來蹭下熱點。 微信跳一跳小遊戲,風格簡約,忍不住動心思自動跳一跳。代碼閱讀起來太費勁,決定寫一篇文章描述一下本身的代碼。javascript
僅供練習nodejs技能,勿討論做弊手段。vue
使用的開箱即用工具java
遊戲目標分析node
設備數據(藉助別人github repo,非ADB)python
圖像處理webpack
技能點:git
遊戲中,小人蓄力時長決定彈跳距離,成功跳到下一個墩子,即加分。
目標即獲取小人位置,獲取目標點位置而後計算距離。
在作的過程當中,發現,人物彈跳方向爲斜向30度,未跳到中心點的狀況下,偏移位置彷佛不會致使遊戲失敗。
因而遊戲目標簡化爲搜索小人位置,與搜索墩子中心點橫座標。
墩子中心點橫座標,與墩子頂點橫座標基本一致,只有一個長方形墩子不一致。
小人的圓形頭部圖像不變,使用opencv模板識別,直接可以準確搜索到人頭位置。 因此遊戲目標再簡化爲:github
將openstf/minicap
,openstf/minitouch
部署到安卓設備,而後經過adb啓動socket,再經過adb鏈接socket,後續請求與發送數據不須要再次建立adb鏈接,實時性較好。 web
async function startMinicap
:
...
let command = util.format(
'LD_LIBRARY_PATH=%s exec %s %s',
path.dirname('/data/local/tmp/minicap.so'),
'/data/local/tmp/minicap',
`-P 1080x1920@360x640/${orientation} -S -Q ${quality}`
)
// `-P 540x960@360x640/${orientation} -S -Q ${quality}`
status.tryingStart = true
let stdout = await client.shell(device.id, command)
...
複製代碼
stdout 爲標準輸出的socket對象,後續加一個200ms內無錯誤即resolve的Promise,令startMinicap可正確await。
鏈接Socket,獲取Stream : /src/renderer/util/getStream.js#L6 async function liveStream
:算法
...
var { err, stream } = await client
.openLocal(device.id, 'localabstract:minicap')
.timeout(10000)
.then(out => ({ stream: out }))
.catch(err => ({ err }))
...
複製代碼
獲取stream ,而後使用on readable 事件取屏幕每幀圖片,格式爲jpeg壓縮。
...
stream.on('readable', tryRead)
...
複製代碼
function tryRead #L50,其邏輯爲解析stream每次讀取到的buffer,按條件拼成jpeg raw buffer 。
此處可簡單作限圖像刷新頻率處理 #L154
顯示圖像,能夠方便的反饋判別結果。
上一步的socket,能夠在electron中輕鬆import,並能夠方便的將每個framebuffer 賦值給 vm.screendata 。 使用vue監聽screendata,便可實時將screendata顯示到canvas中。
這裏用到 vue 的 directives 。
<canvas v-screen='screendata' id='screen' :width="canvasWidth" :height="canvasHeight" :style="canvasStyle"></canvas>
複製代碼
...
directives: {
screen(el, binding, vNode) {
// console.info('[canvas Screen]')
if (!binding.value) return
// console.info('render an image ---- ', +new Date())
let BLANK_IMG = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
var g = el.getContext('2d')
var blob = new Blob([binding.value], { type: 'image/jpeg' })
var URL = window.URL || window.webkitURL
var img = new Image()
img.onload = () => {
vNode.context.canvasWidth = img.width
vNode.context.canvasHeight = img.height
g.drawImage(img, 0, 0)
// firstImgLoad = true
img.onload = null
img.src = BLANK_IMG
img = null
u = null
blob = null
}
var u = URL.createObjectURL(blob)
img.src = u
},
...
}
...
複製代碼
使用 URL.createObjectURL
爲img生成一個src地址,而後將img畫到canvas中。 定義 directives
時,vNode
須要手動傳入,不能直接用this
。
【
此處,僞裝一個動態GIF:
stream.on('readable',function tryRead(){
...
framedata = chunk.read()
callback(framedata)
...
})
function callback (framedata){
vm.screendata = framedata
}
每個framedata 賦給 vm.screendata, Canvas上顯示的圖像刷新一下。
】
複製代碼
代碼中一樣使用directives
作了一個輔助線層,用來顯示輔助線,以及找到的點。
設備觸摸事件發送
按照屏幕stream的方式,取得minitouch的socket,對socket按照minitouch README中格式進行write,便可完成觸摸事件的模擬。
觸摸時長的控制,經過控制touchdown與touchup的時間長度調節。兼容設備觸摸事件,設定每超過200ms,進行原地touchmove一下。代碼MirrorScreen.vue#L221
時間調節,經過async / await 實現。標準的api應用,彷佛沒什麼可說的。
敲下地面
到此,準備好的工具,可以提供給我截圖,畫點,精確ms時長蓄力,因而我採集到了一些數據:
X = [0,50,100,150,200,250,300,700,1000]
Y = [0,33, 69, 90,144,177,207,516, 753]
複製代碼
f(x) = -6.232e-08 x^3 + 0.0001559 x^2 + 0.6601 x - 0.7638
複製代碼
首先, open4nodejs 的使用。 opencv4nodejs 的README講得挺全的。
最開始搜索node版opencv時,發現有2.4版本有3.0版本。這個repo使用的3.0版本,安裝起來也很順利。
README中,不一樣通道數的圖像,根據座標獲取圖像的顏色信息,建立一個形狀等,描述的都很清楚。
找頂點的方式,想到了使用漫水法填充背景色,而後二值化+反色取到最靠上的頂點。
實際過程當中會遇到:
而後,用此灰度圖像,對背景進行漫水填充,閩值40,使用 BINARY_INV 方式,處理獲得二值圖。而後逐行搜索,找到頂點所在行。而後用數組方法,根據方差,對該行元素進行簡易分類,獲得最長連續像素範圍,取中間值,即爲頂點橫座標。
一樣方式能夠識別小藥瓶:
使用opencv的templateMatch方法,可快速獲得結果 findTarget2.js#L11:
...
let ballMat = cv.imread(path.resolve(__dirname, '..', 'ball.jpg'), 0) # 小人頭部爲固定圖片
...
let { maxLoc: ballPoint } = colorMat
.bgrToGray()
.matchTemplate(ballMat, 3)
.minMaxLoc()
...
複製代碼
結果中取maxLoc便可獲得小人底座位置存入變量ballPoint
。每次取小球位置太準確了,以致於沒有寫異常捕捉。
new Promise(r=>{cachedArray.push(r)}).then(...)
的方式,變種使用promise,完成socket返回數據以後繼續執行代碼邏輯。實現先蓄力,而後 n 毫秒以後返回處理結果,再斷定彈跳時間。這個輔助應用,是本身把所瞭解的技能連續堆積完成的,比demo大了。
此工具徹底非開箱即用: electron 部分、opencv部分。
不足
總結
熟練了socket的使用、buffer的操做,熟悉了opencv的基本使用、vue directives的使用。嘗試了使用python。
最後。
實時性效果,坊一個之前的沒有opencv的自動極速變色龍的視頻:
youtu.be/7YSpqiYZJ0w