【QT】找茬外挂制作
找茬外挂制作
找茬游戏大家肯定都很熟悉吧,两张类似的图片,找里面的不同。在下眼神不大好,经常瞪图片半天也找不到区别。于是乎决定做个辅助工具来解放一下自己的双眼。
一、使用工具
- Qt:主要是用来做界面的
- OpenCV: 用于图像处理
- C++: 基本实现语言
Qt中OpenCV的配置在【QT】OpenCV配置中讲过了,不会配置的可以看看。
二、实现方案
我要做一个通用的找茬辅助工具,即可以在所有PC找茬游戏中使用。这意味着我们不能通过获取游戏窗口句柄来定位游戏界面。那怎么办呢?灵光一闪,我想到了截图。把有游戏画面的区域截下来,再根据图片的内容作分析,切割出两张画面,进行对比。
实现界面和使用过程中的效果如下图:
 
  
 
可以看到,有四个按钮。
锁定按钮:是用来截取游戏画面位置和找出不同点的。当点击了锁定后,用鼠标像截图一样截取游戏的图片,就像第二张图那样,覆盖住两个待对比的画面。之后软件自动分析图片位置,实现切割查找。结果如第三张图显示的那样。
找茬按钮:大家会觉得,锁定按钮都可以找出不同了为什么还有一个找茬按钮。主要是因为,锁定中截取游戏画面太过耗时了,经常要瞄准半天,要是每张图都这么截取窗口会让人有点烦。好在一般游戏过程中,我们基本不会改变游戏窗口的位置。所以,如果我们之前锁定过了,就不需要再再次截取位置了,只要根据之前的位置来截取就好了。所以找茬按钮就是根据之前的图片截取位置来获取游戏画面,然后分析给出结果的。当然,如果说窗口位置变化了,那么就只好再次用锁定按钮了。
帮助、退出:就是使用说明和退出功能。
2.1 图片截取
图片截取我自己之前也没做过,于是从网上找了一个截图的源码。但具体来源不记得了...截图的大体思路是:首先,截取全屏,用一个Label全屏显示这张截图,再用事件过滤器在这张大截图上截取小块区域。
全屏获取:Qt中有一个QScreen类,里面的grabWindow函数可以实现截取全屏的功能。
void MainWindow::grapWindowScreen()
{
if (!fullScreenLabel)
{
fullScreenLabel = new QLabel();
} //获取全屏截图fullScreenPixmap,并将其放入fullScreenLabel
QScreen *screen = QGuiApplication::primaryScreen();
fullScreenPixmap = screen->grabWindow(QApplication::desktop()->winId());
fullScreenLabel->setPixmap(fullScreenPixmap); //label全屏显示
fullScreenLabel->showFullScreen();
}
获取区域截图:采用事件过滤器。其中利用了QRubberBand类(橡皮筋窗口)来实现区域选取中的掩膜版的功能。事件过滤器我自己也是第一次用,感觉挺强大的。
//事件过滤器,在全屏的截图上截取想要的部分
bool MainWindow::eventFilter(QObject *o, QEvent *e)
{
if (o != fullScreenLabel)
{
return MainWindow::eventFilter(o, e);
} QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (e); //true 鼠标左键按下且按键还未弹起
if ((mouseEvent->button() == Qt::LeftButton)
&& (mouseEvent->type() == QEvent::MouseButtonPress))
{
//鼠标左键标志位按下
leftMousePress = true; //获取鼠标点
origin = mouseEvent->pos(); if (!rubberBand)
{
rubberBand = new QRubberBand(QRubberBand::Rectangle, fullScreenLabel);
} rubberBand->setGeometry(QRect(origin,QSize()));
rubberBand->show(); return true;
} //true 鼠标左键按下并拖动
if ((mouseEvent->type() == QEvent::MouseMove)
&& (leftMousePress))
{
if (rubberBand)
{
rubberBand->setGeometry(QRect(origin, mouseEvent->pos()).normalized());
} return true;
} //鼠标左键松开
if ((mouseEvent->button() == Qt::LeftButton)
&& (mouseEvent->type() == QEvent::MouseButtonRelease))
{
//鼠标标志位弹起
leftMousePress = false; if (rubberBand)
{
//获取橡皮筋框的终止坐标
termination = mouseEvent->pos();
rect = QRect(origin, termination); rubberBand->hide(); //把橡皮筋窗口隐藏 //根据橡皮筋框截取全屏上的信息,并将其放入shotScreenLabel
shotScreenPixmap = fullScreenPixmap.grabWidget(fullScreenLabel,
rect.x(),
rect.y(),
rect.width(),
rect.height()); shotScreenPixmap.save("tmp.jpg"); //不会QPixmap转QImage或Mat只好出此下策 fullScreenLabel->hide(); findDifference(); //找不同的处理过程 if(!bNeedLocation) //不需要重新锁定表示结果有效,这时显示结果
{
//将shotScreenLabel的用户区大小固定为所截图片大小
shotScreenLabel->setFixedSize(showDifferenceImage.width(), showDifferenceImage.height());
shotScreenLabel->setPixmap(QPixmap::fromImage(showDifferenceImage));
shotScreenLabel->show();
}
} return true;
} return false;
}
2.2 图片定位
我们截取的图片基本上是下面的样子:
 
 
区别就是图片是横向排列还是纵向排列的。两张图片在水平或者垂直维度上像素点是对应的。我们先假设图片是两张横向排列的(就像第一张图一样),如果遇到了纵向排列的,我们把图旋转90即可。
图片定位分下面几个步骤:
1. 找到两张图片对应列的横向距离
2. 找到两张图片的左右界限
3. 找到两张图片的上下界限
2.2.1找到两张图片对应列的横向距离
就是如图所示的距离

方法是在图片的左半部分找N条线,我取的是在图片从左边开始的1/10到4/10的范围内以均匀的间隔取8条线。然后从4/10到图片最右边依次扫描每一列像素,找到像素点差异最小的那一列作为相应的匹配位置。两个列坐标相减就是所求的距离。取8条线是为了增加容错的能力。在取位置时,我们用出现次数最多的那个距离。
注意:再判断两个像素是否相同时要考虑噪声。我这里认为三个通道的像素差异加起来超过10的才算是不一样的点。
查找匹配线的代码:
//查找垂直的匹配线
void findFitLineVertical(Mat src, int y, int& fit_y)
{
fit_y = y; //初始化为y
Mat src2;
src.copyTo(src2);
int different_num = src.size().height;
for(int c = src.size().width * 0.4 + ; c < src.size().width; c++) //遍历垂直方向的线条
{
int different_num_tmp = ;
for(int r = ; r < src.size().height; r++) //遍历线条上的每个点
{
int d = ;
for(int k = ; k < ; k++)
{
d += abs(src.at<Vec3b>(r, c)[k] - src.at<Vec3b>(r, y)[k]);
}
if(d > ) //很关键,要允许有少量的噪声 否则误差很大
different_num_tmp++;
}
if(different_num_tmp < different_num)
{
different_num = different_num_tmp;
fit_y = c;
//line( src2, Point(c, 0), Point(c, src.size().height - 1), Scalar(0,255,0), 3, 8 );
//imshow("pic2", src2);
//cvWaitKey(15);
}
} return;
}
获取相应距离的代码:
const int N = ; //需要匹配的线条数
int s = src.size().width *0.1; //测试线条的开始列
int e = src.size().width * 0.4; //测试线条的结束列
int gap = (e - s + ) / (N - ); //测试线条取样间隔
int dis = ; //两幅图的横向间隔
int disnum = ;
for(int i = ; i < N; i++)
{
int dis_tmp = ;
int y = s + i * gap;
//line( src2, Point(y, 0), Point(y, src.size().height - 1), Scalar(255,0,0), 3, 8 );
//imshow("pic1", src2);
int fit_y;
findFitLineVertical(src, y, fit_y); //下面这段通过典型的找数组中出现超过一半的数字的算法来找两幅图的横向间隔
dis_tmp = fit_y - y;
if(disnum == )
{
dis = dis_tmp;
disnum++;
}
else
{
if(dis_tmp == dis)
disnum++;
else
disnum--;
} //printf("%d, %d, %d\n", y, fit_y, fit_y - y);
//line( src2, Point(fit_y, 0), Point(fit_y, src.size().height - 1), Scalar(0,255,0), 3, 8 );
//imshow("pic1", src2);
//cvWaitKey(0);
}
2.2.2找到两张图片的左右界限
有了对应点距离后,找左右界限就容易多了。左边界就从图片的最左边开始扫描,看看理论的对应位置上不用的点是否超过了阈值,超过了就表示这不是图片的起始位置,继续扫描下一列,直到找到左边界。右边界同理,就是从右往左找。
2.2.3找到两张图片的上下界限
有了左右界限后,找上下界限也容易了。找上边界时把图片1左右边界范围内的像素和相应行图片2左右边界范围内的像素做比较,相同点超过阈值就表示是对应的边界。下边界同理。
注意:这种找出来的边界可能比实际的图片大一些,不过不影响结果。
2.2.4当图片方向不对时旋转图片
我们第一次找左右边界时,如果没有找到匹配线,这时我们就旋转图片再次找左右边界。如果还没有可能就是图截取的不好了。这时候就需要重新锁定了。
// Load an image
Mat srcRGB = imread("tmp.jpg");
Mat srcRotate;
//imshow("src", srcRGB); bool bRotate= false; //图片是否经过旋转 //int left1, left2, right1, right2, up, down;
bool b1 = findLeftRightBoard(srcRGB, left1, right1, left2, right2);
if(!b1) //第一次没有找到匹配线 有可能是因为两张图是上下存放的 我们把图片旋转一下 再试试
{
srcRotate = rotateImage(srcRGB);
srcRGB.release();
srcRotate.copyTo(srcRGB);
bRotate = true;
}
b1 = findLeftRightBoard(srcRGB, left1, right1, left2, right2);
bNeedLocation |= (!b1);
if(bNeedLocation) //如果两个方向都失败了 需要重新锁定
{
QMessageBox::warning(this, tr("error"), tr("Please locate again!\n图片不理想,请重新锁定!"));
return;
}
findUpDownBoard(srcRGB, left1, right1, left2, right2, up, down);
2.2.5图片类型转换
在上述处理过程中需要各种图片类型的转换。抓屏获取的是QPixmap类型,图片处理用的是Mat类型,图片显示用的是QImage类型。
QPixmap转Mat : 在网上找了很久都没找到。只好先把图片保存在当前目录,再用Mat类型读出。
Mat转QImage: 我直接在网上找的代码
QImage const copy_mat_to_qimage(cv::Mat const &mat, QImage::Format format)
{
QImage image(mat.cols, mat.rows, format);
for (int i = ; i != mat.rows; ++i)
{
memcpy(image.scanLine(i), mat.ptr(i), image.bytesPerLine() );
} return image;
} QImage const mat_to_qimage_cpy(cv::Mat &mat)
{
if(mat.type() == CV_8UC3)
{
cv::cvtColor(mat, mat, CV_BGR2RGB); return copy_mat_to_qimage(mat, QImage::Format_RGB888);
} if(mat.type() == CV_8U)
{
return copy_mat_to_qimage(mat, QImage::Format_Indexed8);
} return QImage();
}
2.3查找不同
当我们得到两张待对比的图片后,我们通过逐点对比获取不同点。把两张图片像素点差异超过30的取出来,做二值化。然后腐蚀膨胀去噪声。最后通过连通区域提取来把不同的区域取出并画出来。
这部分直接看代码非常直接:
Mat mSub = abs((img1 - img2)) > ;
Mat gray;
cvtColor(mSub, gray, CV_RGB2GRAY);
gray = gray > ;
erode(gray, gray, Mat());
dilate(gray, gray, Mat());
erode(gray, gray, Mat());
dilate(gray, gray, Mat());
//连通区域提取相关
vector<vector<Point> > contours;
vector<Vec4i> hierarchy; //连通区域提取
findContours(gray, contours, hierarchy, CV_RETR_CCOMP , CV_CHAIN_APPROX_NONE );
for(int i = ; i < contours.size(); i++)
{
if(contours[i].size() >= ) // 点数大于等于 的连通区域才考虑
{
Rect r = boundingRect(contours[i]);
rectangle(img1, r, Scalar(,,), ); //画出连通区域
}
}//结果转换为QImage
if(!bRotate)
showDifferenceImage = mat_to_qimage_cpy(img1);
else //如果旋转过,需要再转270恢复原来的方向
{
img1 = rotateImage(img1);
img1 = rotateImage(img1);
img1 = rotateImage(img1);
showDifferenceImage = mat_to_qimage_cpy(img1);
}
2.4界面美化
在做的时候我争取做的漂亮一点。Qt的界面加图片很容易,所以我就找或者制作了一些图片。效果在上面显示过了。
背景图片是在网上找的。
按钮的字是在随便找的字体网站上做的http://www.diyiziti.com/katong。做了几个版本。本来打算做按钮点击时候效果的,后来懒得做了,只用了其中一版。
 
  
 
 
  
 
 
  
 
我自己还设计了个中国风的Logo:

“茬”就表示找茬游戏,Help表示辅助工具,整体的意思就是找茬辅助工具。“茬”字跟上面一样都是网站声成的。毛笔圈是我在百度上找的图片,然后用美图秀秀抠图把毛笔圈扣成透明的,加在“茬”字外面。HELP在美图秀秀上的文字添加上去。
程序左上角的图标可以通过在cpp文件中加入下面语句实现:
setWindowIcon(QIcon(":/pic/chaH.jpg"));
程序的exe图标需要先自己制作一个ico图标文件,可以用iconEditor软件制作。然后把图标放在工程目录下面,再在pro文件里加入
RC_ICONS = chaH2.ico
其中chaH2.ico是自己的图标名称。
三、实现过程中的问题
3.1截图过程中的问题
QT自带的截图似乎噪声很多,也许是我保存成图片再读取的过程中像素发生了变化。之前实验的时候用的是QQ截图,然后做处理,噪声很少,效果也不错。改成QT的截图后效果变差了很多,为了容错不得不增大阈值。结果导致有些图片区别被当做噪声去掉了。
3.2阈值选取问题
多大的阈值合适?这个可能需要进一步分析。就拿下面的图来说,里面有5处不同,那一缕头发,一个手纹的真心跟噪声似的。阈值调小了,这里面的差异倒是出来了,可是换成别的图就是一堆噪点。这里顺带说一句,QQ的大家来找茬,不同时一块一块的,即图片基本都是整块不同,比较好找。而网上其他杂七杂八的,很可能使用photoshop在某个边角抹了抹,很难识别。

3.3图片截取的失败方案:Hough变换
Hough变换是用来检测图片中直线的。我开始想,直接用霍夫变换提取直线,然后找相同大小的区域就可以了。可是实现后我发现霍夫变换的效果很差,那些很明显的直线都不能完整得到。
3.4定位窗口
开始想做针对QQ大家来找茬的外挂。我用按键精灵确定了游戏窗口的名字是“大家来找茬”但是我在QT上通过这个名字却无法获取有效的窗口。为什么呢?
实验时的代码如下,有人帮我看看么?
#include <windows.h>
#include "mainwindow.h"
#include <QApplication>int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show(); HWND p = FindWindowA(NULL, "大家来找茬");
qDebug("p = %d\n",(int)p); // 获取屏幕鼠标坐标
POINT pt;
GetCursorPos(&pt);
qDebug("%d %d\n",pt.x,pt.y); HWND h = WindowFromPoint(pt);
qDebug("%d\n",h); //wchar_t text[200];
//GetWindowText(h,text,200);
//qDebug("%s\n",text); return a.exec();
}
四、应用程序即源码下载
http://yun.baidu.com/share/link?shareid=782947870&uk=2757788903
【QT】找茬外挂制作的更多相关文章
- C#编写QQ找茬外挂
		QQ找茬外挂,用C#代码编写. 使用方法 这个工具的主要运行流程很简单:游戏截图->比较图片->标记图片不同点.实现代码 截图的处理类ScreenCapture: /// /// 提供全屏 ... 
- 用Python实现QQ找茬游戏外挂工具
		源地址:http://cpiz.net/blog/2012/03/a_qq_zhaocha_assistant_by_python/ (原创作品,转载请注明出处)好久没写技术相关的博文,这次写篇有意思 ... 
- 项目实战:Qt+OpenCV大家来找茬(Qt抓图,穿透应用,识别左右图区别,框选区别,微调位置)
		前言 本项目的出现理由只是笔者的一个念头,于是利用专业Qt和Opencv相关的知识开发一个辅助工具,本文章仅用于Qt和Opencv结合的学习. Demo演示效果 运行包下载地 ... 
- C#外挂QQ找茬辅助源码,早期开发
		这是一款几年前开发的工具,当年作为一民IT纯屌,为了当年自己心目中的一位女神熬夜开发完成.女神使用后找茬等级瞬间从眼明手快升级为三只眼...每次看到这个就会想起那段屌丝与女神的回忆.今天特地把代码更新 ... 
- 对"QQGame-大家来找茬"的辅助工具的改进
		[前言]最近在博客园首页上看到有“大家来找茬”这个游戏(此游戏为找出两个相近图片的不同点)外挂的相关帖子,所以这里我也翻看了我之前(2009年5月)的写的一个简单的辅助程序(采用 VC6 开发的).我 ... 
- 2014中秋节,用java为QQ游戏美女找茬写辅助
		引子 今年中秋闲在家,总要找点事做. 前几天开始学python,很早之前就有计划拿下这门语言了,可惜一直拖到现在……不可否认,我也是个拖沓症患者.在学习python的过程中 ... 
- 比较不错的一个ios找茬游戏源码
		找茬游戏源码 ,这个是一款非常不错的ios找茬游戏源码,该游戏的兼容性非常好的,并且还可以支持ipad和iphone,UI界面设计得也很漂亮,游戏源码真的是一款非常完美,而且又很完整的一款休闲类的游戏 ... 
- MFC版美女找茬
		今天心情:捡了个闲暇. 前几天工作出了个漏洞,电话会议时候怎么都是忽大忽小的声音,实在没听清电话会议的内容,完了依据想象交了一个设计方案,之后便是赋闲. 进入正题,美女找茬实现不难,没有设计上的难度, ... 
- 社交系统/社群系统“ThinkSNS+”H5及PC端终于来了!一起来“找茬”
		[什么是TS+?] ThinkSNS(简称TS),一款全平台综合性社交系统,为国内外大中小企业和创业者提供社会化软件研发及技术解决方案,目前最新版本为ThinkSNS+,简称TS+. 还记得2017年 ... 
随机推荐
- 【J2EE】Hibernate
			Hibernate是面向Java环境的对象/关系数据库映射工具,管理Java应用和数据库之间的映射关系,提供数据查询和获取数据的方法,可以大幅减少使用JDBC处理数据持久化的时间. 使用Eclipse ... 
- linux命令行下的ftp 多文件下载和目录下载(转)
			目标ftp服务器是一个非标准端口的ftp 1.通过shell登录 #ftp //shell下输入ftp命令,进入到ftp提示符 >open IP PORT //IP ,PORT对 ... 
- Android--获取短信的内容,截取短信
			1.首先我们要写一个广播接收器,当我们的手机收到短信时,系统会自动发送一个广播,我们只需要接收到这条广播就可以了 2.在广播里面,我们重写的onReceive()方法,通过里面的Intent写到的Bu ... 
- Windows Phone中Wallet钱包的使用
			前言 Windows Phone 8中加入了钱包Wallet这个功能,这个功能非常的有意思,开发者可以通过Wallet提供的API创建获取Wallet中的商品.统一管理用户的收集优惠券.信用卡.成员资 ... 
- [转]coredump简介与coredump原因总结
			[转]coredump简介与coredump原因总结 http://blog.sina.com.cn/s/blog_54f82cc201013srb.html 什么是coredump? 通常情况下co ... 
- Labview实现单边带信号调制(SSB)[滤波法]
			Labview实现单边带信号调制(SSB)[滤波法] 首先用信号仿真器得到一个被调制信号m(t),以及载波信号,该实验选择正弦信号作为载波信号. 根据调制器模型 得到一个结果信号. 其中,H(w)的选 ... 
- C++异常机制知识点
			在这里总结一下,C++中的异常机制,以及如何使用异常的知识点 C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决 ... 
- 思维认知-读mindhacks杂记
			1. 导语 无意中浏览知乎,搜索到了mindhacks.cn这个个人geek的网址.mindhacks博主本人是牛人程序员一枚,但他的博客主题涵盖的主要内容确是思维改变生活. 博客链接地址:http: ... 
- C6011 正在取消对 null 指针的引用
- 程序开发心理学阅读笔记——第II篇
			作为社会行为的软件开发程序开发组->程序开发团队->程序开发项目1.要判断程序员的某个集体是否构成一支团队,要看其中的成员以何种方式相互协作,以共同开发软件产品.2.健康的团队要始终能够保 ... 
