回想起四年前,自己大二刚开始学C#时,发现Bitmap类中有GetPixel方法的时候一阵狂喜。因为那时我玩过一款QQ游戏——大家来找茬,这个游戏是从画幅图中找出不一样的地方。如果可以获取到图片的每个像素值,只要发现其像素值不一样,即可判断图的这个地方不一样了(当然,这得假设腾讯没有对图片进行一些小的处理,比如,一个图的像素值RGB都加1,这时在肉眼是看不出区别的,但可以防止我用这样的方法做出外挂。而实际上,腾讯没有做这样的处理,所以~~~)。再用其它的一些办法(我的方法是自己再创建一个透明窗体,覆盖在一个图片上,不同的像素点用红色标记出来),将这些不同的像素点,显示给我们看,在玩游戏时就可以一下子找到所有的不同。(注意,这里是是标出所有的不同像素点,而还是得我们自己手动去点击)
如下所示,将一幅图中所有不同的地方都用红色标记出来,然后我们只需要点击。
然后,因为要实现上面我所说的这些功能,自己学会了用C#调用win32API截屏,并且也发现可以用win32API获取其它窗体的位置、区域之类的信息,可以说这个QQ找茬的外挂开启了自己学习win32API之路。
这个C#版的程序没有多大的难度。
今年上半年时,自己用c++也实现了同样的功能,不过为了和四年前有所不一样,准备完善聚类分析。找茬游戏中只有5处不一样的地方,而用像素找出来的不一样点将非常多,如果程序可以自动将所有这些点正确地聚类成5类,那么程序就可以自动点击了。其实这个功能自己当初就想过,可当时自己能力、视野都有限,没能完成。不过,现在自己在图像处理上小有所获后,再来看这个问题,觉得可以实现了。
首先,自己尝试用了openCV中的cvKMeans2方法,其使用K均值聚类。但实现效果不好,错误率很高。
现在本人想到的方法是,在那些有差别的像素点上找连通区域,最后只取5个最大的连通区域。如果图片中的5个不同区域不彼此靠近,那么就可以完美地解决问题,但实际情况有些是有几个块会连在一起,这样就得用另外的方法重新考虑了。不过,先将简单的情况实现了再说。而找区域连通性的方法自己以前就有实现过,那么剩下的工作就非常少了。于是就有了下面的效果(非常惊艳)——
(我将分成的5个分别用不同的颜色标识出来了) 这时就可以操作鼠标去自动点击了,5个不同的点可以瞬间找出来。以至于在游戏中测试这个程序时,其他的玩家一盘下来就都跑了~~
上面用的区域检测算法有参照《图像编程精髓:从开发自己的Photoshop开始》,实现的思路不是很复杂,在这就直接贴出代码,就不去仔细说明了。
1 namespace image 2 { 3 using namespace std; 4 namespace //名字空间内的私有函数 5 { 6 typedef struct 7 { 8 int Sign; //标志 9 int Area; //面积 10 }AreaInfo; //用于统计面积的大小与标记的关系 11 12 inline bool AreaSortFun(const AreaInfo &a1,const AreaInfo &a2) 13 { 14 return a1.Area>a2.Area; 15 } 16 inline void ReplaceSign(TwoDimesionArray<int> &signs,int bottom,int srcSign,int dstSign) 17 { 18 for (int i=0;i<signs.GetWidth();++i) 19 { 20 for (int j=0;j<=bottom;++j) 21 { 22 if(signs.GetValue(i,j)==srcSign) 23 signs.SetValue(i,j,dstSign); 24 } 25 } 26 } 27 28 29 }//end namespace 30 31 inline int IsHaveAreaTag(vector<AreaInfo> &areas,int tag) 32 { 33 for (int i=0;i<areas.size();++i) 34 { 35 if(areas[i].Sign ==tag) 36 return i; 37 } 38 return -1; 39 } 40 41 inline TwoDimesionArray<int> GetAreaInfo(TwoDimesionArray<bool> bs, vector<AreaInfo> &areaInfos) 42 { 43 //vector<vector<int>> sameTag; 44 TwoDimesionArray<int> signs(bs.GetWidth(),bs.GetHeight()); 45 signs.SetAllValue(0); 46 int signNo=1; 47 for (int x=0;x<bs.GetWidth();++x) //先处理顶行 48 { 49 if(!bs.GetValue(x,0)) 50 { 51 continue; 52 } 53 while(x<bs.GetWidth() && bs.GetValue(x,0)) 54 { 55 signs.SetValue(x,0,signNo); 56 ++x; 57 } 58 ++signNo; 59 } 60 for (int j=1;j<bs.GetHeight();++j) //处理最左和最右列 61 { 62 if(bs.GetValue(0,j)) //最左列 63 { 64 if(bs.GetValue(0,j-1)) 65 { 66 signs.SetValue(0,j,signs.GetValue(0,j-1)); 67 } 68 else 69 { 70 signs.SetValue(0,j,signNo++); 71 } 72 } 73 if(bs.GetValue(bs.GetWidth()-1,j)) //最右列 74 { 75 if(bs.GetValue(bs.GetWidth()-1,j-1)) 76 { 77 signs.SetValue(bs.GetWidth()-1,j,signs.GetValue(bs.GetWidth()-1,j-1)); 78 } 79 else 80 { 81 signs.SetValue(bs.GetWidth()-1,j,signNo++); 82 } 83 } 84 } 85 for (int j=1;j<bs.GetHeight();++j) 86 { 87 for (int i=1;i<bs.GetWidth()-1;++i) 88 { 89 if(!bs.GetValue(i,j)) 90 continue; 91 if(bs.GetValue(i+1,j-1)) //右上 92 { 93 int sign=signs.GetValue(i+1,j-1); 94 signs.SetValue(i,j,sign); 95 if(bs.GetValue(i-1,j) && signs.GetValue(i-1,j) != sign) //右上与左前不同标记 96 { 97 //AddTag(sameTag,signs.GetValue(i-1,j),tag); 98 ReplaceSign(signs,j,signs.GetValue(i-1,j),sign); 99 } 100 else if(bs.GetValue(i-1,j-1) && signs.GetValue(i-1,j-1) != sign) //右上与左上不同标记 101 { 102 //AddTag(sameTag,signs.GetValue(i-1,j-1),tag); 103 ReplaceSign(signs,j,signs.GetValue(i-1,j-1),sign); 104 } 105 } 106 else if(bs.GetValue(i,j-1)) //正上 107 { 108 signs.SetValue(i,j,signs.GetValue(i,j-1)); 109 } 110 else if(bs.GetValue(i-1,j-1)) //左上 111 { 112 signs.SetValue(i,j,signs.GetValue(i-1,j-1)); 113 } 114 else if(bs.GetValue(i-1,j)) //左前 115 { 116 signs.SetValue(i,j,signs.GetValue(i-1,j)); 117 } 118 else 119 { 120 signs.SetValue(i,j,signNo++); 121 } 122 } 123 } //end two for 124 125 for (int i=0;i<signs.GetWidth();++i) 126 { 127 for (int j=0;j<signs.GetHeight();++j) 128 { 129 //find(areas.begin(),areas.end()) 130 //if() 131 int t=IsHaveAreaTag(areaInfos,signs.GetValue(i,j)); 132 if (t!=-1) 133 { 134 ++areaInfos[t].Area; 135 } 136 else 137 { 138 AreaInfo info; 139 info.Sign=signs.GetValue(i,j); 140 info.Area=1; 141 areaInfos.push_back(info); 142 } 143 } 144 }//end for 145 return signs; 146 }//end function 147 148 149 }//end namespace image
可运行的程序在这 FindDifference
源程序在SVN上 :svn://svn.jundie.net/FindDifference
这个程序中自己用的是CxImage图像库,这个库自己应该快用了一年多的时间了,现在越来越觉得这个库太不灵活了,与MFC的结合也不是很好。准备以后彻底转向openCV的学习。
这里总结出一个经验:掌握知识最好的文法就是尝试着用它,改进它,并且创新~~