準備材料:
1. adb工具(版本新點)比如 platform-tools-latest-windows.zip
2.opencv基於C++ 的開發環境
3.一部裝有最新微信的安卓智能機
實現原理:根據分析跳一跳界面截圖,可以獲知幾個重要信息
1.背景單調,但是漸變色 會自動變色
2.棋子形狀固定,不隨時間變化
3.棋子跳轉的距離與按壓的時間成線性關係
4.每步跳轉的傾斜角是30°。
5.下一步的物塊必定比其它物塊高
6.將每步截圖以中心建立座標系,發現一個重要特點
(1)棋子只會在左下與右下區域
(2)棋子如果在左下區域 下一步必定在右上區域;反之亦然
(3)每步中心點連線必過整個截圖的中心
根據如此多的特點 我們便可以着手進行輔助開發,首先要熟悉一下安卓的調試工具ADB,其具體指令方式不做過多介紹,給出網址有全面的介紹 https://github.com/mzlogin/awesome-adb 程序中只用了兩個指令
截圖:adb exec-out screencap -p >sc1.png 這個指令是直接截圖保存到電腦端
觸摸按壓:adb shell input swipe 500 500 500 500 500 這個其實是滑動指令 這5個500 分別是 按壓的起始位置x y 和鬆開的結束位置 x y 最後一個參數是按壓的時間單位 ms
具體手機與PC的連接不做過多介紹,明顯是使用開發者選項,打開USB調試模式
接下來便是程序的重點
1.獲取一張屏幕截圖,並讀取。根據ADB指令讀取到一幀圖像,讀入程序。根據這個遊戲的工作方式我們很清楚的瞭解到輔助工具只要計算出來兩點的距離 然後轉換成跳躍的按壓時間就行了。因此我們就其實只需要獲取兩個座標點: 棋子 與下一步
2.如何獲取棋子的座標點: 我根據毛星雲書本附帶例程中的模板匹配 很輕鬆的就穩定的獲取到了棋子座標
Mat g_resultImage;
Mat Mode = imread("mode.png");//讀取棋子模板 用作模板匹配
Point findmode(Mat &SrcImg)
{
//Mat SrcImg = imread("G:\\src4.png");
int g_nMatchMethod = 0;
matchTemplate(SrcImg, Mode, g_resultImage, g_nMatchMethod);
normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());
//【4】通過函數 minMaxLoc 定位最匹配的位置
double minValue; double maxValue; Point minLocation; Point maxLocation;
Point matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
//cout << minValue << maxValue << endl;
//【5】對於方法 SQDIFF 和 SQDIFF_NORMED, 越小的數值有着更高的匹配結果. 而其餘的方法, 數值越大匹配效果越好
if (g_nMatchMethod == CV_TM_SQDIFF || g_nMatchMethod == CV_TM_SQDIFF_NORMED)
{
matchLocation = minLocation;
}
else
{
matchLocation = maxLocation;
}
//【6】繪製出矩形,並顯示最終結果
rectangle(SrcImg, matchLocation, Point(matchLocation.x + Mode.cols, matchLocation.y + Mode.rows), Scalar(0, 0, 255), 2, 8, 0);
matchLocation.x = matchLocation.x + Mode.cols / 2;
matchLocation.y = matchLocation.y + Mode.rows;
return matchLocation;
//rectangle(g_resultImage, matchLocation, Point(matchLocation.x + Mode.cols, matchLocation.y + Mode.rows), Scalar(0, 0, 255), 2, 8, 0);
}
獲取到棋子座標比較容易,但是下一步的座標中心點 一直沒有很好的注意獲取, 這裏就不說我的程序的發展歷程,直接展示V3.0版實現的方式,根據重要信息5 我們可以考慮逐行遍歷的方式來獲得物塊的最高點 這個點肯定接近物塊中心,其實還可以發現這個點的橫座標與目標點x座標幾乎是一樣的。
但是怎麼遍歷呢 ,這裏有兩種方案,(1)根據分析背景可知,背景是漸變色,每行的背景色數值是一樣的 ,所以我們可以嵌套for進行判斷 如果下一個像素點與這個點不一樣 ,那麼它就是物塊上的點 ,找到物塊最高點座標 尋找結束
( 2)方案2使用漫水填充將背景填充0(即去除), 然後同樣遍歷像素點找到有數據的座標 ,此座標即爲物塊最高點
做到這步其實已經可以勉強使這個工具工作了,但是每次的目標點不是很準,拿不到高分,想拿到連續跳到中點的高分 就要繼續利用發現到的重要信息 4 和 6.3
目標點與棋子當前座標是成30°的 那麼我在剛剛已經獲取到了目標點的x座標,利用初中的一元一次方程知識 我不就可以獲知到經過棋子座標點獲得這個函數 利用已知座標x 即可求出更加準確的目標點的y 座標
注意事項:棋子的當前座標不一定是中心點 以棋子座標列方程 數據有點不準 ,所以我 直接以截圖的中心點列方程,經過實驗觀察,數據非常穩定。
同時我爲了減少程序的運算量,將截圖的以中心分成的4個區域只選取其中有目標點的 進行數據分析
Point findNextStep(Mat &SrcImg)
{
Point NextStep;
Rect rec;
//blur(SrcImg, SrcImg,Size(3,3));
floodFill(SrcImg, Point(1, 1), Scalar(0, 0, 0),&rec, Scalar(5,5,5),
Scalar(5,5,5));
imshow("DEBUG", SrcImg);
for(int i = 0; i < SrcImg.rows;i++)//列遍歷
{
for(int j = 0; j < SrcImg.cols; j++)//行遍歷
{
if (SrcImg.at < Vec3b >(i,j) != Vec3b(0,0,0) && j>10)
{
NextStep = Point(j,i);
return NextStep;
}
}
}
//
return NextStep;
}
這樣兩個座標點獲取後 再進行距離計算,以像素點爲單位
center1 = findmode(SrcImg);//獲取到棋子座標
if (center1.x > 540)//棋子在右半側
{
TLImg = SrcImg(Range(400, 960), Range(0, 540)).clone();
center2=findNextStep(TLImg)+Point(0,400);
}
else
{
TRImg = SrcImg(Range(400, 960), Range(540, 1080)).clone();
center2 = findNextStep(TRImg) + Point(540, 400);
}
center2.y = CenterPoint.y - 0.5*abs(center2.x - CenterPoint.x);//路徑傾斜30°
center1 = center2 - center1;
int length = sqrt(pow(center1.x, 2) + pow(center1.y, 2));
獲取距離後便調試 距離與按壓時間的轉換比例 ,以我手機1080x1920分辨率爲例 在截圖不壓縮的情況下 距離與時間的換算比是 1.35.至此程序設計完畢。昨日程序運行的最高得分8915 ,可以說是非常穩定了。
雖然也發現一些小問題需要完善 但是出於時間與精力 ,未作調整
已知問題: 距離太近則可能造成無法找到目標點;跳速太快 會使 跳到中心點產生的 波紋對目標點的造成距離誤判。 計算速度略慢
完整代碼供參考學習:
// main.cpp : 定義控制檯應用程序的入口點。
//openCV3.1.0模板
#include "stdafx.h"
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
char PC[] = "adb exec-out screencap -p >sc1.png";
int UpDisplay = 1;
String WINDOW_NAME = "NOKIA6_jump";
RNG rng;
float bili = 1.35;
char Jump[50] = "adb shell input swipe 500 500 500 500 500";
int GetLength(Mat &Srcimg);
Mat SrcImg1;
vector<Vec3f> circles;
Point center1, center2,CenterPoint(540,960);
Mat SrcImg,SrcImgC,desImg,TLImg,TRImg,MaskImg;
Point findmode(Mat &SrcImg);//尋找棋子
Point findNextStep(Mat &SrcImg);//尋找下一步
int _tmain(int argc, _TCHAR* argv[])
{
bool state = 1, jumpstat = 1;
int num=0;
while (state)
{
system(PC);//獲取一張截圖
//system("cls");//CMD清屏
SrcImg = imread("sc1.png");//讀取截取的圖像
//SrcImg = imread("C:\\Users\\cheng\\Desktop\\QQ圖片20180217232802.jpg");
if (SrcImg.empty())//檢測是否讀到
{
cout << "Can't read image " << endl;
waitKey();
return -1;
}
if (SrcImg.size().width != 1080){ resize(SrcImg, SrcImg, Size(1080, 1920)); }//統一尺寸 方便計算距離
center1 = findmode(SrcImg);//獲取到棋子座標
if (center1.x > 540)//棋子在右半側
{
TLImg = SrcImg(Range(400, 960), Range(0, 540)).clone();
center2=findNextStep(TLImg)+Point(0,400);
}
else
{
TRImg = SrcImg(Range(400, 960), Range(540, 1080)).clone();
center2 = findNextStep(TRImg) + Point(540, 400);
}
center2.y = CenterPoint.y - 0.5*abs(center2.x - CenterPoint.x);//路徑傾斜30°
circle(SrcImg, center2, 10, Scalar(255, 255, 0), -1);
resize(SrcImg, SrcImg, SrcImg.size() / 4);//重設尺寸爲了顯示
//circle(SrcImg, Point(SrcImg.cols, SrcImg.rows) / 2, 5, Scalar(0, 0, 0), 2);
line(SrcImg, Point(0, SrcImg.rows / 2), Point(SrcImg.cols, SrcImg.rows / 2), Scalar(0));
line(SrcImg, Point(SrcImg.cols / 2, 0), Point(SrcImg.cols / 2, SrcImg.rows), Scalar(0));
center1 = center2 - center1;
int length = sqrt(pow(center1.x, 2) + pow(center1.y, 2));
if (length < 150 || center2.x==300)length = 222;//距離太近會造成錯誤檢測
printf("第%03d次跳躍。",num++);
std::cout << "GetLength" << length << endl;//輸出距離長度(調試需要)
sprintf_s(Jump, "adb shell input swipe %d %d %d %d %d", rng.uniform(400, 700), rng.uniform(1500, 1600), rng.uniform(400, 700), rng.uniform(1500, 1700), (int)(length * bili));
cout <<"目標點"<< center2 << endl;
system(Jump);//發送按壓指令
imshow("SrcImg", SrcImg);//顯示圖像
switch (waitKey(rng.uniform(1000, 1500)))//隨機演示等待
{
case 27:
state = !state;
break;
case ' ':
cout << "暫停" << endl;
if (27 == waitKey(0))state = !state;
cout << "開始運行" << endl;
break;
default:
break;
}
}
cout << "程序結束" << endl;
//system("adb shell kill-server");//結束adb進程
return 0;
}
Mat g_resultImage;
Mat Mode = imread("mode.png");//讀取棋子模板 用作模板匹配
Point findmode(Mat &SrcImg)
{
//Mat SrcImg = imread("G:\\src4.png");
int g_nMatchMethod = 0;
matchTemplate(SrcImg, Mode, g_resultImage, g_nMatchMethod);
normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());
//【4】通過函數 minMaxLoc 定位最匹配的位置
double minValue; double maxValue; Point minLocation; Point maxLocation;
Point matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
//cout << minValue << maxValue << endl;
//【5】對於方法 SQDIFF 和 SQDIFF_NORMED, 越小的數值有着更高的匹配結果. 而其餘的方法, 數值越大匹配效果越好
if (g_nMatchMethod == CV_TM_SQDIFF || g_nMatchMethod == CV_TM_SQDIFF_NORMED)
{
matchLocation = minLocation;
}
else
{
matchLocation = maxLocation;
}
//【6】繪製出矩形,並顯示最終結果
rectangle(SrcImg, matchLocation, Point(matchLocation.x + Mode.cols, matchLocation.y + Mode.rows), Scalar(0, 0, 255), 2, 8, 0);
matchLocation.x = matchLocation.x + Mode.cols / 2;
matchLocation.y = matchLocation.y + Mode.rows;
return matchLocation;
//rectangle(g_resultImage, matchLocation, Point(matchLocation.x + Mode.cols, matchLocation.y + Mode.rows), Scalar(0, 0, 255), 2, 8, 0);
}
Point findNextStep(Mat &SrcImg)
{
Point NextStep;
Rect rec;
//blur(SrcImg, SrcImg,Size(3,3));
floodFill(SrcImg, Point(1, 1), Scalar(0, 0, 0),&rec, Scalar(5,5,5),
Scalar(5,5,5));
imshow("DEBUG", SrcImg);
for(int i = 0; i < SrcImg.rows;i++)//列遍歷
{
for(int j = 0; j < SrcImg.cols; j++)//行遍歷
{
if (SrcImg.at < Vec3b >(i,j) != Vec3b(0,0,0) && j>10)
{
NextStep = Point(j,i);
return NextStep;
}
}
}
//
return NextStep;
}
第一次寫博客,希望大家支持,完整示例程序見鏈接文件。在1080x1920手機上能夠完美運行,一般跑500分以上完全沒問題。
附完整示例程序:http://download.csdn.net/download/qq_34901073/10254240