基于采样的快速找图以及实现方式

来源:互联网 发布:vm共享文件夹mac 编辑:程序博客网 时间:2024/05/16 14:49
  • 按键精灵是很多人都用过的东西,但是毕竟它只是VB脚本,功能很有限,开发和调试都是一大头疼事,于是我就想自己弄一套按键精灵的复刻版类库以便自己能够在C#里面试用,再加上VS的强大调试功能和众多.NET运行库,比使用脚本不知道强了多少倍。

    相信不少人用过按键精灵的 找图 的功能,实现它的方式也有很多种,但我们最注重的一个东西就是效率。因为找图的瓶颈当然就在于效率了。我也研究了几天的找图的功能,今天早上重新翻相关的内容的时候终于因为一个看过N次的帖子的老兄的一句话而茅塞顿开,于是就有了下面的内容。

     

    下面我就直入主题了。

    首先理论:

    因为找图的时候按照每一个像素点来判断的话效率是非常非常低的,遍历大图的次数决定了关键效率,当然其中也有截图的消耗。基于采样的算法最低只需要遍历一次大图就可以完成查找,当然为了保证正确性,也可以遍历多次,但这是可以掌控的。

    我们首先在小图中记录下一些特征点的颜色值,然后就可以进行匹配了,但这里有一个最摸不着头脑的问题,从哪里开始找起?仔细想的话有点像是爱因斯坦的相对论的感觉。所以啊,我们就需要一个绝对的点来作为参照。假设,这个点是可以在大图中找到的,那么定位其他的采样点就容易得多啦,我们只需要遍历一次大图,判断每一个像素是否是参照点,如果是,就判断采样点的颜色值,如果采样点的颜色值都相同,那么我们就可以认为是找到小图了。有些人可能有疑问了,如果小图中白色底色,就一个黑色的汉字,那么正确性是不是会降低呢?答案肯定是会,这样,你就需要增加采样点的个数或者取一些特征点来判断了。这一切都是基于参照点的位置的,如果连参照点都没有找到,那么说其他的,也就是白扯,所以参照点就显得至关重要了,为了保证参照点的正确性,我们可以使用多个参照点来保证找图的正确性和稳定性。 理论就这么一点点,这根本也不是什么高深的东西,对吧www.it165.net?

     

    代码开整:

     

    我要实现的目标是按键精灵 区域找图(FindPic)这个函数,首先列出我们要实现的函数的原型

     

    FindPic(IntPrt hwnd,Rectangle rect,Image serchImg,float h,Point[] points)

    参数:

    hwnd:要找图的窗口句柄,如果为0则表示获取的是桌面截图,这里需要用这个hwnd来截图

    rect:需要在大图找的区域和大小

    serchImg:小图

    h:相似度,取值范围为0-1 1为完全相同

    points:采样点

    这里最重要的参数就是采样点,采样点的意思就是,在小图里面取一些个别的点,如果这些个别的点匹配成功,那么就可以认为是找到了serchImg的位置

    为了保证正确性,采样点可以使用百分比的方式或者试用指定个数的方式来随机获取,至于使用百分比或者指定点的个数由你来定。个人认为百分比的方式比较稳定,因为采样点的个数不会太影响性能,至少不是影响性能的首要因素。

     

    view sourceprint?
    01./// <summary>
    02./// 随机生成采样点
    03./// </summary>
    04./// <param name="img">图片</param>
    05./// <param name="h">采样比率,范围为0-1,决定图片像素的采样百分比,大于等于1为采样点个数</param>
    06./// <returns></returns>
    07.public static Point[] GetImgTestPoints(Image img, float h)
    08.{
    09.Random random = new Random(DateTime.Now.Second);
    10. 
    11.int width_max = img.Width;
    12.int height_max = img.Height;
    13. 
    14.int count = 1;
    15.if (h > 1)
    16.{
    17.count = (int)h;
    18.}
    19.else
    20.{
    21.count = (int)(width_max * height_max * h);//采样点的个数
    22.}
    23. 
    24.List<Point> points = new List<Point>();//采样点的集合
    25. 
    26.for (int i = 0; i < count; i++)
    27.{
    28.Point point = new Point();
    29.point.X = random.Next(width_max);
    30.point.Y = random.Next(height_max);
    31. 
    32.points.Add(point);
    33.}
    34.return points.ToArray();
    35.}

    好了,FindPic的所有参数都解决了。剩下就是这个方法的实现了。

    FindPic需要使用EqImage EqTestColor 和一个叫做EqualsRGB的扩展函数接下来我会一个个讲解这三个函数

    首先是 EqualsRGB

     

    view sourceprint?
    01./// <summary>
    02./// 扩展函数,比较图片的 RGB 是否相同
    03./// </summary>
    04./// <param name="color1"></param>
    05./// <param name="color2"></param>
    06./// <returns></returns>
    07.public static bool EqualsRGB(this Color color1, Color color2)
    08.{
    09.if (color1.R == color2.R && color1.B == color2.B && color1.G == color2.G)
    10.{
    11.return true;
    12.}
    13.else
    14.{
    15.return false;
    16.}
    17.}

     大家可能要问了,这个函数不是可有可无的吗? 其实不是。因为试用 BitBlt(WIN32 API)截图后是没有Alpha 通道的,所以啊,我就写了这么一个函数来对比颜色值是否相同了,大家也能够理解了吧?

    EqTestColor :这个方法是来判断采样点的

     

    view sourceprint?
    01./// <summary>
    02./// 给定一个原点,判断采样点的颜色和小图片的颜色是否相同
    03./// </summary>
    04./// <param name="bigImg">大图片</param>
    05./// <param name="centerPoint">小图片的原点</param>
    06./// <param name="getCenterPoint">搜索到的原点</param>
    07./// <param name="points">采样点集合</param>
    08./// <param name="pointsColors">采样点颜色集合</param>
    09./// <param name="h">相似度</param>
    10./// <returns></returns>
    11.private static bool EqTestColor(Bitmap bigImg, Point centerPoint, Point getCenterPoint, Point[] points, List<Color> pointsColors,float h)
    12.{
    13.bool result = false;
    14.int count = 0;
    15. 
    16.for (int i = 0; i < points.Length; i++)
    17.{
    18.Point testPoint = points[i];
    19.//距离GetCenterPoint的相对距离
    20.testPoint.X = testPoint.X - centerPoint.X + getCenterPoint.X;
    21.testPoint.Y = testPoint.Y - centerPoint.Y + getCenterPoint.Y;
    22. 
    23.if (testPoint.X < 0 || testPoint.Y < 0 ||
    24.testPoint.X >= bigImg.Width || testPoint.Y >= bigImg.Height)
    25.{
    26.break;
    27.}
    28. 
    29.//获取颜色比较
    30.Color bigColor = bigImg.GetPixel(testPoint.X, testPoint.Y);
    31.Color smailColor = pointsColors[i];
    32. 
    33.if (bigColor.EqualsRGB(smailColor) == true)
    34.{
    35.count++;
    36.}
    37.}
    38. 
    39.if (((float)count / (float)points.Length) >= h)
    40.{
    41.result = true;
    42.}
    43.else
    44.{
    45.result = false;
    46.}
    47. 
    48.return result;
    49.}

    这个方法也很容易理解,我就不多说了。

    接下来是EqImage  :



    view sourceprint?
    01./// <summary>
    02./// 比较2张图片是否相等
    03./// </summary>
    04./// <param name="bigImg">bigImg</param>
    05./// <param name="smailImg">smailImg</param>
    06./// <param name="h">相似度</param>
    07./// <param name="centerPoint">采样原点</param>
    08./// <param name="points">所有采样点</param>
    09./// <returns></returns>
    10.public static bool EqImage(Bitmap bigImg, Bitmap smailImg, float h, Point centerPoint, Point[] points, out Point point)
    11.{
    12.bool result = false;
    13. 
    14.point = new Point(-1, -1);
    15. 
    16.int x = 0;
    17.int y = 0;
    18.int max_x = bigImg.Width;
    19.int max_y = bigImg.Height;
    20. 
    21.//原点的颜色
    22.Color centerPointColor = smailImg.GetPixel(centerPoint.X, centerPoint.Y);
    23.//采样点的颜色集合
    24.List<Color> pointsColors = new List<Color>();
    25.//获取采样点的颜色
    26.for (int i = 0; i < points.Length; i++)
    27.{
    28.pointsColors.Add(smailImg.GetPixel(points[i].X, points[i].Y));
    29.}
    30. 
    31.for (x = 0; x < max_x; x++)
    32.{
    33.for (y = 0; y < max_y; y++)
    34.{
    35.Point getCenterPoint = new Point(x, y);
    36.Color getCenterPointColor = bigImg.GetPixel(x, y);
    37. 
    38.//判断原点是否相同
    39.if (centerPointColor.EqualsRGB(getCenterPointColor) == true)
    40.{
    41.//判断采样点颜色
    42.if (EqTestColor(bigImg, centerPoint, getCenterPoint, points, pointsColors, h) == true)
    43.{//如果相同就跳出
    44.result = true;
    45. 
    46.//计算相对于参照点的0点
    47.point.X = x - centerPoint.X;
    48.point.Y = y - centerPoint.Y;
    49. 
    50.break;
    51.}
    52.}
    53.}
    54.if (result == true)
    55.{
    56.break;
    57.}
    58.}
    59. 
    60.return result;
    61.}

    这里的centerPoint就是我们设定的参照点,方法中先找到参照点的位置,然后调用EqTestColor 方法来判断参照点是否相同,如果相同就返回是否找到,并用一个输出参数来返回找到的位置。

    最后就是FindPic了

     

    view sourceprint?
    01./// <summary>
    02./// 区域找图
    03./// </summary>
    04./// <param name="hwnd">要找的窗口句柄</param>
    05./// <param name="rect">大图的区域</param>
    06./// <param name="serchBmp">小图</param>
    07./// <param name="h">相似度</param>
    08./// <returns></returns>
    09.public static Point FindPic(IntPtr hwnd, Rectangle rect, Bitmap serchBmp, float h, Point[] points)
    10.{
    11.Point point = new Point(-1, -1);
    12. 
    13.Bitmap bmp = GetWindowPic(hwnd, rect);//截图
    14. 
    15.//采样点集合
    16.Point[] centerPoints = GetImgTestPoints(serchBmp, 5, false);
    17. 
    18.for (int i = 0; i < centerPoints.Length; i++)
    19.{
    20.Point centerPoint = centerPoints[i];
    21. 
    22.//根据原点判断采样点的颜色值是否相同
    23.if (EqImage(bmp, serchBmp, h, centerPoint, points, out point) == true)
    24.{
    25.break;
    26.}
    27.}
    28. 
    29.//合成相对于窗口的坐标
    30.if (point.X >= 0 && point.Y >= 0)
    31.{
    32.point.X += rect.X;
    33.point.Y += rect.Y;
    34.}
    35. 
    36.serchBmp.Dispose();
    37.return point;
    38.}

    FindPic里面我做了一个循环,用来判断多个参照点,以便增加正确性。获得0点位置后,记住啊,这个0点是相对于区域的位置,我们需要把它转换成相对于窗口的位置。

    最后提一下 GetWindowPic 是我自己定义的截图函数,我就不贴了。

    需要注意的地方:Win7下截图有个毛病,不知道大家清楚不,就是截取桌面的时候是有Alpha 通道的,也就是能正确截取,但按照窗口句柄截图会出现Alpha 通道消失的情况,也就是如果截图桌面,背景会是纯黑色,我给出下面2张对比图,大家一看便知

    正常桌面:

     

    \
     

    按桌面截图:

     

    \
     

    按照句柄截图:

     

    \
     

    这是win7中的需要注意的地方,当然也可能只是我自己截图有问题,大家可以修改我的代码试用自己的截图函数。

    修改FindPic中 这句就可以了。

    ?Bitmap bmp = GetWindowPic(hwnd, rect);//截图<BR><BR><BR>

    不知道我说的够不够清楚?大家赶紧自己试试吧. ^Y^

    有错误或者有疏漏或者有改进的意见欢迎指出。

     

0 0
原创粉丝点击