最近微信的跳一跳很火,无意间在QQ空间看见有人用单片机控制舵机测量距离实现微信跳一跳,于是我雅兴大发,自己也用Opencv实现一个视觉控制的微信跳一跳,百度一下,果然很多,但大多数都是python实现的,于是我自己写了一个C++实现的微信跳一跳脚本。
我是利用360手机助手中 有一个360演示,图标如下
控制手机在电脑上进行运行。那只是这样的
一、对区域截图
一开始需要截取改界面的图片加以运算,这里使用windows API对C++开放的接口,获取界面所在的位置,由于python截取屏幕简单方便程序远远超过C++,所以在截图这一块,我用C++调用了python,代码如下:
void ScreenShot(void)
{
RECT rect;
HWND hwnd = ::FindWindow(NULL, L"实时演示"); //获取360演示的句柄
GetWindowRect(hwnd, &rect); //获取图像的位置
char sScreenShot[64];
sprintf(sScreenShot, "grab.exe %d %d %d %d", rect.left, rect.top+135, rect.right, rect.bottom-50);//组成字符串
system(sScreenShot); //调用python截取图片
}
python截图图片如下:
from PIL import ImageGrab
import argparse
parser = argparse.ArgumentParser() #初始化准备传参
parser.add_argument('integers', metavar='N', type=int, nargs='+',
help='location for top left and bottom right')
args = parser.parse_args()
print(args.integers) #打印出参数
def grab():
ImageGrab.grab(bbox=(args.integers[0],args.integers[1],args.integers[2],args.integers[3])).convert("L").save(".\\helloworld.jpg")#截图 保存为helloworld.jpg
grab() #进行截图
二、获取黑子的位置
接下来就是获取黑子所在的位置,这个可以使用Opencv提供的模板匹配matchTemplate,事先,将黑子截图保存好,之后和图像进行对比,找到最相近的区域。
C++代码如下:
Point GetNowPoint(Mat& srcImage, Mat& Tem_img)
{
cv::Mat image_matched;
matchTemplate(srcImage, Tem_img, image_matched, CV_TM_SQDIFF);// 匹配黑棋子
double minVal, maxVal;
Point minLoc, maxLoc, matchLoc;
minMaxLoc(image_matched, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
matchLoc = minLoc; //matchLoc是最佳匹配的区域左上角点
return Point(matchLoc.x + Tem_img.cols*0.5, matchLoc.y + Tem_img.rows);
}
三、寻找目标位置
接下来就是检测需要跳到的位置检测,我们需要对图像做一些处理,对图像进行高斯滤波减少噪声,之后我们对他进行Canny边缘检测,实现的效果图如下
我们要找到两个点,第一个是图像最高点(即Y方向最低点),第二个是图像最靠右边点(即X方向大点)
C++代码实现如下:
Point GetNextPoint(Mat& srcImage)
{
cv::Point point1;
cv::Point point2;
cv::GaussianBlur(srcImage, srcImage, cv::Size(5, 5), 0); //高斯滤波,降低噪声
// cv::threshold(srcImage, srcImage, 0, 255, 8);
Canny(srcImage, srcImage, 20, 30); //进行边缘检测
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(srcImage, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point()); //找到关键的角点
//遍历每一个轮廓,把多余的轮廓去掉
std::vector<std::vector<cv::Point> >::const_iterator it = contours.begin();
while (it != contours.end()) {
if (it->size()<150)
it = contours.erase(it);
else
++it;
}
int nYMin = srcImage.rows;
int nXMin = srcImage.cols;
int nYMax = 0;
int nXMax = 0;
int nIdY = 0;
for (int i = 0; i < contours.size(); i++)
{
//contours[i]代表的是第i个轮廓,contours[i].size()代表的是第i个轮廓上所有的像素点数
for (int j = 0; j < contours[i].size(); j++)
{
if (contours[i][j].y < nYMin)
{
nYMin = contours[i][j].y; //找到最低的y值
point1 = contours[i][j]; //记录 y值最低点坐标
nIdY = i; //记录哪个区域内的
}
}
}
int minY = srcImage.cols;
for (int j = 0; j < contours[nIdY].size(); j++) //在哪个区域内继续变量 找到x最大值
{
if (contours[nIdY][j].x >nXMax)
{
nXMax = contours[nIdY][j].x;
}
}
for (int j = 0; j < contours[nIdY].size(); j++)
{//找到x中最大值上的最小值
if (contours[nIdY][j].x == nXMax && contours[nIdY][j].y < minY)
{
point2 = contours[nIdY][j];
minY = contours[nIdY][j].y; //记录X点的最大值
}
}
return cv::Point(point1.x, point2.y); //返回中点坐标
}
四、计算距离
通过原点坐标和目标点坐标算出距离,这个就很简单了:
int GetDistance(Point& first_point, Point& next_point)
{
int A = first_point.x - next_point.x;
int B = first_point.y - (next_point.y + 50);
return int(pow(pow(A, 2) + pow(B, 2), 0.5));
}
五、计算跳跃时间
算出距离后,下面就是计算按下位置和时间,就可以了
void Jump(int&g_distance)
{
cout << "distance:" << g_distance << endl;
int time = g_distance*4+330;
RECT rect;
HWND hwnd = ::FindWindow(NULL, L"实时演示");
GetWindowRect(hwnd, &rect);
::SetCursorPos(rect.left+100, rect.bottom-500);
INPUT Input = { 0 }; //鼠标按下
Input.type = INPUT_MOUSE;
Input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
SendInput(1, &Input, sizeof(INPUT));
Sleep(time);//鼠标下的时间
Input = { 0 }; //鼠标松开
Input.type = INPUT_MOUSE;
Input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
SendInput(1, &Input, sizeof(INPUT));
}
整个的工程已经描述完毕了,包括我的想法和设计,如果有更好的,欢迎交流。整个工程的下载在这里。