最近用OpenCV做外挂实现别踩白块小游戏,用的知识不是很难,所以写文章记录当时遇到过的问题,OpenCV版本3.3.0。
程序运行的视频网址:http://v.youku.com/v_show/id_XMzI5MTc4MDM5Ng==.html?spm=a2h3j.8428770.3416059.1
首先就是要实现截取当前屏幕的图像,网上查了很多,结果发现了这篇有价值的文章:http://blog.csdn.net/sinat_36219858/article/details/70877677,
但这篇文章写的比较久远,用的是OpenCV1.0的函数,所以参考这篇文章的代码,将它改成OpenCV2.0以后的。具体的代码如下:
void get_screen(Mat &result)
{
LPVOID screenCaptureData = NULL;//NULL为获取全屏
HBITMAP hBitmap;
HDC hDDC;
HDC hCDC;
int nWidth = GetSystemMetrics(SM_CXSCREEN);//得到屏幕的分辨率的x
int nHeight = GetSystemMetrics(SM_CYSCREEN);//得到屏幕分辨率的y
screenCaptureData = new char[nWidth*nHeight * 4];
memset(screenCaptureData, 0, nWidth);
// Get desktop DC, create a compatible dc, create a comaptible bitmap and select into compatible dc.
hDDC = GetDC(GetDesktopWindow());//得到屏幕的dc
hCDC = CreateCompatibleDC(hDDC);//
hBitmap = CreateCompatibleBitmap(hDDC, nWidth, nHeight);//得到位图
SelectObject(hCDC, hBitmap);
BitBlt(hCDC, 0, 0, nWidth, nHeight, hDDC, 0, 0, SRCCOPY);
GetBitmapBits(hBitmap, nWidth*nHeight * 4, screenCaptureData);//得到位图的数据,并存到screenCaptureData数组中。
/**************************************************************
Mat img(Size(nWidth, nHeight), CV_8UC4, Scalar(0));//创建一个4通道图像rgba
**************************************************************/
Mat img(Size(nWidth, nHeight), CV_8UC4, Scalar(0));//创建一个4通道图像rgba
memcpy((uchar*)img.data, screenCaptureData, nWidth*nHeight * 4);
Mat img2(Size(nWidth, nHeight), CV_8UC4, Scalar(0));
cvtColor(img, img2, CV_BGRA2BGR);
result = img2;
}
中间遇到的问题就是这句话:
memcpy((uchar*)img.data, screenCaptureData, nWidth*nHeight * 4);
这句话一直出错,试了好多的方法,结果是这句话的问题:
Mat img(Size(nWidth, nHeight), CV_8UC4, Scalar(0));
以前一直是CV_8UC3,后来试了一下,改成了CV_8UC4,这样才没报错。应该是得到的图像是4通道的。
剩下的就没有什么问题了。
经过百度,得到了这篇有价值的文章:http://blog.sina.com.cn/s/blog_161af74b60102wmyl.html,
经过参考文章里的程序,得到了我们需要用到的函数:
//鼠标操作
void mouse_press(int x, int y)
{
POINT p;
p.x = x;
p.y = y;
SetCursorPos(p.x, p.y); //设置鼠标位置
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //按下鼠标
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); //松开鼠标
}
//按键操作
void key_press(int Ascii)
{
keybd_event(Ascii, 0, 0, 0); //68是D的ASCII码,按下按键‘a’
keybd_event(Ascii, 0, KEYEVENTF_KEYUP, 0); //松开按键
}
最后一部就是图像分析了,用的知识简单,只是简单实现了功能,以后还需改进。代码如下:
#include
#include
#include
#include
using namespace cv;
using namespace std;
//获取屏幕图像
void get_screen(Mat &result)
{
LPVOID screenCaptureData = NULL;//NULL为获取全屏
HBITMAP hBitmap;
HDC hDDC;
HDC hCDC;
int nWidth = GetSystemMetrics(SM_CXSCREEN);//得到屏幕的分辨率的x
int nHeight = GetSystemMetrics(SM_CYSCREEN);//得到屏幕分辨率的y
screenCaptureData = new char[nWidth*nHeight * 4];
memset(screenCaptureData, 0, nWidth);
hDDC = GetDC(GetDesktopWindow());//得到屏幕的dc
hCDC = CreateCompatibleDC(hDDC);
hBitmap = CreateCompatibleBitmap(hDDC, nWidth, nHeight);//得到位图
SelectObject(hCDC, hBitmap);
BitBlt(hCDC, 0, 0, nWidth, nHeight, hDDC, 0, 0, SRCCOPY);
GetBitmapBits(hBitmap, nWidth*nHeight * 4, screenCaptureData);//得到位图的数据,并存到screenCaptureData数组中。
Mat img(Size(nWidth, nHeight), CV_8UC4, Scalar(0));//创建一个4通道图像rgba
memcpy((uchar*)img.data, screenCaptureData, nWidth*nHeight * 4);
Mat img2(Size(nWidth, nHeight), CV_8UC4, Scalar(0));
cvtColor(img, img2, CV_BGRA2BGR);
result = img2;
}
//鼠标操作
void mouse_press(int x, int y)
{
////获取鼠标事实位置
//while (1)
//{
// POINT point;
// GetCursorPos(&point);
// cout << "x=" << point.x << " y=" << point.y << endl;
//}
POINT p;
p.x = x;
p.y = y;
SetCursorPos(p.x, p.y); //设置鼠标位置
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //按下鼠标
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); //松开鼠标
}
//按键操作
void key_press(int Ascii)
{
keybd_event(Ascii, 0, 0, 0); //68是D的ASCII码,按下按键‘a’
keybd_event(Ascii, 0, KEYEVENTF_KEYUP, 0); //松开按键
}
//二值化函数
void mythreshold(Mat &img, uchar T)
{
int n1 = img.rows;
int nc = img.cols * img.channels();
if (img.isContinuous())//判断图像是否连续
{
nc = nc * n1;
n1 = 1;
}
for (int i = 0; i < n1; i++)
{
uchar *p = img.ptr(i);
for (int j = 0; j < nc; j++)
{
if (p[j] < T)
p[j] = 0;
else p[j] = 255;
}
}
}
int main()
{
system("pause");
mouse_press(400, 500);
while (1)
{
Mat img;
//img = imread("test_img.jpg"); //1536x864
get_screen(img); //得到当前屏幕图像
//cout << "img.width = " << img.cols << "img.height = " << img.rows << endl;
Mat img_ROI = img(Range(320, 590), Range(500, 1110)); //通过预估,确定游戏界面的大体位置
//Mat img_show; //用来显示
//img_ROI.copyTo(img_show);
cvtColor(img_ROI, img_ROI, COLOR_BGR2GRAY);
mythreshold(img_ROI, 100);
//先膨胀后腐蚀 闭运算 排除小型黑洞
//先腐蚀后膨胀 开运算 在纤细除分离物体
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
dilate(img_ROI, img_ROI, element, Point(-1, -1), 1);
//erode(temp, temp, element, Point(-1, -1), 1);//腐蚀3次
Point point_press(500, 320 + 130); //鼠标点击位置坐标
vector < int > value;
//预估,确定遍历的纵坐标
//只要存在边界,就记录它的坐标
uchar *p = img_ROI.ptr(130);
for (int i = 0; i < img_ROI.cols - 1; i++)
{
if (p[i] == 255)
{
if (p[i + 1] == 0)
{
value.push_back(i);
}
}
if (p[i] == 0)
{
if (p[i + 1] == 255)
{
value.push_back(i);
}
}
}
//分析坐标:黑块在中间的两个位置
if (value.size() == 4)
{
cout << "already find!" << endl;
point_press.x = value[1] + 40;
float num = float(value[1] - value[0]) / 100.0; //每个格子宽度大概是100
cout << "num = " << num << endl;
if (num > 0.7 && num < 1.3)
key_press(70); //按下F
else if (num > 1.7 && num < 2.3)
key_press(74); //按下J
else
break;
}
//分析坐标:黑块在旁边的两个位置
else if (value.size() == 2)
{
cout << "already find!" << endl;
point_press.x = value[1] + 40;
float num = float(value[0]) / 100.0;
cout << "num = " << num << endl;
if (num > 1.7 && num < 2.3)
key_press(68); //按下D
else if (num > 0.7 && num < 1.3)
key_press(75); //按下K
else
break;
}
else
{
cout << "not found!" << endl;
break;
}
Sleep(60); //延时控制速度
}
return 0;
}
别踩白块用的是4399小游戏上的:http://www.4399.com/flash/135255_3.htm
注意:运行的时候浏览器必须全屏。
如果有什么需要改进的地方,下方评论指出或与我联系:
QQ:1570553025