zoukankan      html  css  js  c++  java
  • C#实现按键精灵的'找图' '找色' '找字'的功能

    背景:游戏辅助功能通常使用按键精灵编写脚本,按键精灵的最大卖点就是能够找到画面中字,图,色,这对于模拟用户鼠标操作至关重要,这能找到道具,找到血量,实现自动打怪,自动补血,自动买卖道具,博主闲来无聊,看到一款按键精灵实现的辅助,于是乎想用WPF也写一款辅助工具,实现其核心的找图找色等功能。博主测试,对于背景复杂多变的画面,找不变图的成功率达到100%,找带透明的图,比如文字,能达到90%以上。默认您已经知道一个颜色值由argb构成,每个值范围都是0~255。网上发现不少人询问过该问题,几乎没有比较全面的解答,今天本博主自己写了这些功能的代码,C#同学们,以后可以用这几个函数实现你的游戏辅助了哦(⊙o⊙)

     找色:

     
    /// <summary>
            /// 找颜色
            /// </summary>
            /// <param name="parPic">查找的图片的绝对路径</param>
            /// <param name="searchColor">查找的16进制颜色值,如#0C5FAB</param>
            /// <param name="searchRect">查找的矩形区域范围内</param>
            /// <param name="errorRange">容错</param>
            /// <returns></returns>
            System.Drawing.Point FindColor(string parPic, string searchColor, System.Drawing.Rectangle searchRect, byte errorRange = 10)
            {
                var colorX = System.Drawing.ColorTranslator.FromHtml(searchColor);
                var parBitmap = new Bitmap(parPic);
                var parData = parBitmap.LockBits(new System.Drawing.Rectangle(0, 0, parBitmap.Width, parBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                var byteArraryPar = new byte[parData.Stride * parData.Height];
                Marshal.Copy(parData.Scan0, byteArraryPar, 0, parData.Stride * parData.Height);
                if (searchRect.IsEmpty)
                {
                    searchRect = new System.Drawing.Rectangle(0, 0, parBitmap.Width, parBitmap.Height);
                }
                var searchLeftTop = searchRect.Location;
                var searchSize = searchRect.Size;
                var iMax = searchLeftTop.Y + searchSize.Height;//行
                var jMax = searchLeftTop.X + searchSize.Width;//列
                int pointX = -1; int pointY = -1;
                for (int m = searchRect.Y; m < iMax; m++)
                {
                    for (int n = searchRect.X; n < jMax; n++)
                    {
                        int index = m * parBitmap.Width * 4 + n * 4;
                        var color = System.Drawing.Color.FromArgb(byteArraryPar[index + 3], byteArraryPar[index + 2], byteArraryPar[index + 1], byteArraryPar[index]);
                        if (ColorAEqualColorB(color, colorX, errorRange))
                        {
                            pointX = n;
                            pointY = m;
                            goto END;
                        }
                    }
                }
            END:
                parBitmap.UnlockBits(parData);
                return new System.Drawing.Point(pointX, pointY);
            }
            #endregion
    

      

    方法中的容错范围,默认设置为10。R、G、B三者的范围都是0~255,容错为10,就表示每个范围都可以在10上下波动,下面还会有容错的概念

      找图:

    在一张大图中截取一个矩形小图,然后在任意包含该小图的图片中找到该小图的坐标位置

     
    #region 找图
    
            /// <summary>
            /// 查找图片,不能镂空
            /// </summary>
            /// <param name="subPic"></param>
            /// <param name="parPic"></param>
            /// <param name="searchRect">如果为empty,则默认查找整个图像</param>
            /// <param name="errorRange">容错,单个色值范围内视为正确0~255</param>
            /// <param name="matchRate">图片匹配度,默认90%</param>
            /// <param name="isFindAll">是否查找所有相似的图片</param>
            /// <returns>返回查找到的图片的中心点坐标</returns>
            List<System.Drawing.Point> FindPicture(string subPic, string parPic, System.Drawing.Rectangle searchRect, byte errorRange, double matchRate = 0.9, bool isFindAll = false)
            {
                List<System.Drawing.Point> ListPoint = new List<System.Drawing.Point>();
                var subBitmap = new Bitmap(subPic);
                var parBitmap = new Bitmap(parPic);
                int subWidth = subBitmap.Width;
                int subHeight = subBitmap.Height;
                int parWidth = parBitmap.Width;
                int parHeight = parBitmap.Height;
                if (searchRect.IsEmpty)
                {
                    searchRect = new System.Drawing.Rectangle(0, 0, parBitmap.Width, parBitmap.Height);
                }
    
                var searchLeftTop = searchRect.Location;
                var searchSize = searchRect.Size;
                System.Drawing.Color startPixelColor = subBitmap.GetPixel(0, 0);
                var subData = subBitmap.LockBits(new System.Drawing.Rectangle(0, 0, subBitmap.Width, subBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                var parData = parBitmap.LockBits(new System.Drawing.Rectangle(0, 0, parBitmap.Width, parBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                var byteArrarySub = new byte[subData.Stride * subData.Height];
                var byteArraryPar = new byte[parData.Stride * parData.Height];
                Marshal.Copy(subData.Scan0, byteArrarySub, 0, subData.Stride * subData.Height);
                Marshal.Copy(parData.Scan0, byteArraryPar, 0, parData.Stride * parData.Height);
    
                var iMax = searchLeftTop.Y + searchSize.Height - subData.Height;//行
                var jMax = searchLeftTop.X + searchSize.Width - subData.Width;//列
    
                int smallOffsetX = 0, smallOffsetY = 0;
                int smallStartX = 0, smallStartY = 0;
                int pointX = -1; int pointY = -1;
                for (int i = searchLeftTop.Y; i < iMax; i++)
                {
                    for (int j = searchLeftTop.X; j < jMax; j++)
                    {
                        //大图x,y坐标处的颜色值
                        int x = j, y = i;
                        int parIndex = i * parWidth * 4 + j * 4;
                        var colorBig = System.Drawing.Color.FromArgb(byteArraryPar[parIndex + 3], byteArraryPar[parIndex + 2], byteArraryPar[parIndex + 1], byteArraryPar[parIndex]);
                        ;
                        if (ColorAEqualColorB(colorBig, startPixelColor, errorRange))
                        {
                            smallStartX = x - smallOffsetX;//待找的图X坐标
                            smallStartY = y - smallOffsetY;//待找的图Y坐标
                            int sum = 0;//所有需要比对的有效点
                            int matchNum = 0;//成功匹配的点
                            for (int m = 0; m < subHeight; m++)
                            {
                                for (int n = 0; n < subWidth; n++)
                                {
                                    int x1 = n, y1 = m;
                                    int subIndex = m * subWidth * 4 + n * 4;
                                    var color = System.Drawing.Color.FromArgb(byteArrarySub[subIndex + 3], byteArrarySub[subIndex + 2], byteArrarySub[subIndex + 1], byteArrarySub[subIndex]);
    
                                    sum++;
                                    int x2 = smallStartX + x1, y2 = smallStartY + y1;
                                    int parReleativeIndex = y2 * parWidth * 4 + x2 * 4;//比对大图对应的像素点的颜色
                                    var colorPixel = System.Drawing.Color.FromArgb(byteArraryPar[parReleativeIndex + 3], byteArraryPar[parReleativeIndex + 2], byteArraryPar[parReleativeIndex + 1], byteArraryPar[parReleativeIndex]);
                                    if (ColorAEqualColorB(colorPixel, color, errorRange))
                                    {
                                        matchNum++;
                                    }
                                }
                            }
                            if ((double)matchNum / sum >= matchRate)
                            {
                                Console.WriteLine((double)matchNum / sum);
                                pointX = smallStartX + (int)(subWidth / 2.0);
                                pointY = smallStartY + (int)(subHeight / 2.0);
                                var point = new System.Drawing.Point(pointX, pointY);
                                if (!ListContainsPoint(ListPoint, point, 10))
                                {
                                    ListPoint.Add(point);
                                }
                                if (!isFindAll)
                                {
                                    goto FIND_END;
                                }
                            }
                        }
                        //小图x1,y1坐标处的颜色值
                    }
                }
            FIND_END:
                subBitmap.UnlockBits(subData);
                parBitmap.UnlockBits(parData);
                subBitmap.Dispose();
                parBitmap.Dispose();
                GC.Collect();
                return ListPoint;
            }
            #endregion
    

      

    private void Button_Click_Pic(object sender,RoutedEventArgs e)
            {
                string subPic = @"E:2.png";
                string parPic = @"E:1.png";
                ;
                List<System.Drawing.Point> List =FindPicture(subPic, parPic, System.Drawing.Rectangle.Empty, 100);
                foreach(System.Drawing.Point p in List)
                {
                    Console.WriteLine(p.X.ToString());
                    Console.WriteLine(p.Y.ToString());
                }
                Console.WriteLine("finish");
            }
    

      

      找字:

    找字比较困难了呢,因为文字是一种镂空的图像,不像上述找的是非镂空图像,代码:

    定义结构体:

     
    struct NumBody
            {
                public int num;//数字
                public int matchNum;//匹配的个数
                public int matchSum;
                public double matchRate;//匹配度
                public System.Drawing.Point point;
                public List<System.Drawing.Point> bodyCollectionPoint;//该数字所有像素在大图中的坐标
            }
    

      

     
    #region 找字
    
            /// <summary>
            /// 找文字,镂空的图片文字
            /// </summary>
            /// <param name="subPic"></param>
            /// <param name="parPic"></param>
            /// <param name="searchRect"></param>
            /// <param name="errorRange"></param>
            /// <param name="matchRate"></param>
            /// <param name="isFindAll"></param>
            /// <returns></returns>
            List<NumBody> FindText(string subPic, string parPic, System.Drawing.Rectangle searchRect, byte errorRange, double matchRate = 0.9, bool isFindAll = false)
            {
    
                List<NumBody> ListPoint = new List<NumBody>();
                var subBitmap = new Bitmap(subPic);
                var parBitmap = new Bitmap(parPic);
                int subWidth = subBitmap.Width;
                int subHeight = subBitmap.Height;
                int parWidth = parBitmap.Width;
                int parHeight = parBitmap.Height;
                var bgColor = subBitmap.GetPixel(0, 0);//背景红色
                if (searchRect.IsEmpty)
                {
                    searchRect = new System.Drawing.Rectangle(0, 0, parBitmap.Width, parBitmap.Height);
                }
                var searchLeftTop = searchRect.Location;
                var searchSize = searchRect.Size;
                var subData = subBitmap.LockBits(new System.Drawing.Rectangle(0, 0, subBitmap.Width, subBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                var parData = parBitmap.LockBits(new System.Drawing.Rectangle(0, 0, parBitmap.Width, parBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                var byteArrarySub = new byte[subData.Stride * subData.Height];
                var byteArraryPar = new byte[parData.Stride * parData.Height];
                Marshal.Copy(subData.Scan0, byteArrarySub, 0, subData.Stride * subData.Height);
                Marshal.Copy(parData.Scan0, byteArraryPar, 0, parData.Stride * parData.Height);
                var iMax = searchLeftTop.Y + searchSize.Height - subData.Height;//行
                var jMax = searchLeftTop.X + searchSize.Width - subData.Width;//列
                System.Drawing.Color startPixelColor = System.Drawing.Color.FromArgb(0, 0, 0);
                int smallOffsetX = 0, smallOffsetY = 0;
                int smallStartX = 0, smallStartY = 0;
                int pointX = -1; int pointY = -1;
    
    
                for (int m = 0; m < subHeight; m++)
                {
                    for (int n = 0; n < subWidth; n++)
                    {
                        smallOffsetX = n;
                        smallOffsetY = m;
                        int subIndex = m * subWidth * 4 + n * 4;
                        var color = System.Drawing.Color.FromArgb(byteArrarySub[subIndex + 3], byteArrarySub[subIndex + 2], byteArrarySub[subIndex + 1], byteArrarySub[subIndex]);
                        if (!ColorAEqualColorB(color, bgColor, errorRange))
                        {
                            startPixelColor = color;
                            goto END;
                        }
                    }
                }
    
            END:
                for (int i = searchLeftTop.Y; i < iMax; i++)
                {
                    for (int j = searchLeftTop.X; j < jMax; j++)
                    {
                        //大图x,y坐标处的颜色值
                        int x = j, y = i;
                        int parIndex = i * parWidth * 4 + j * 4;
                        var colorBig = System.Drawing.Color.FromArgb(byteArraryPar[parIndex + 3], byteArraryPar[parIndex + 2], byteArraryPar[parIndex + 1], byteArraryPar[parIndex]);
                        ;
    
                        List<System.Drawing.Point> myListPoint = new List<System.Drawing.Point>();
                        if (ColorAEqualColorB(colorBig, startPixelColor, errorRange))
                        {
                            smallStartX = x - smallOffsetX;//待找的图X坐标
                            smallStartY = y - smallOffsetY;//待找的图Y坐标
                            int sum = 0;//所有需要比对的有效点
                            int matchNum = 0;//成功匹配的点
                            for (int m = 0; m < subHeight; m++)
                            {
                                for (int n = 0; n < subWidth; n++)
                                {
                                    int x1 = n, y1 = m;
                                    int subIndex = m * subWidth * 4 + n * 4;
                                    var color = System.Drawing.Color.FromArgb(byteArrarySub[subIndex + 3], byteArrarySub[subIndex + 2], byteArrarySub[subIndex + 1], byteArrarySub[subIndex]);
                                    if (color != bgColor)
                                    {
                                        sum++;
                                        int x2 = smallStartX + x1, y2 = smallStartY + y1;
                                        int parReleativeIndex = y2 * parWidth * 4 + x2 * 4;//比对大图对应的像素点的颜色
                                        var colorPixel = System.Drawing.Color.FromArgb(byteArraryPar[parReleativeIndex + 3], byteArraryPar[parReleativeIndex + 2], byteArraryPar[parReleativeIndex + 1], byteArraryPar[parReleativeIndex]);
                                        if (ColorAEqualColorB(colorPixel, color, errorRange))
                                        {
                                            matchNum++;
                                        }
                                        myListPoint.Add(new System.Drawing.Point(x2, y2));
                                    }
                                }
                            }
    
                            double rate = (double)matchNum / sum;
                            if (rate>= matchRate)
                            {
                                Console.WriteLine((double)matchNum / sum);
                                pointX = smallStartX + (int)(subWidth / 2.0);
                                pointY = smallStartY + (int)(subHeight / 2.0);
                                var point = new System.Drawing.Point(pointX, pointY);
                                if (!ListTextBodyContainsPoint(ListPoint, point, 1))
                                {
                                    ListPoint.Add(new NumBody() { point = point, matchNum = matchNum,matchSum=sum, matchRate = rate, bodyCollectionPoint = myListPoint });
                                }
                                SearchNumbersByMatchNum(ref ListPoint);
                                if (!isFindAll)
                                {
                                    goto FIND_END;
                                }
                            }
                        }
                        //小图x1,y1坐标处的颜色值
                    }
                }
            FIND_END:
                subBitmap.UnlockBits(subData);
                parBitmap.UnlockBits(parData);
                subBitmap.Dispose();
                parBitmap.Dispose();
                GC.Collect();
                return ListPoint;
            }
    

      

    特别注意:有了这个方法还是不能找到你要的文字的。要先处理文字,下面举例:

    例如在这张图片上找到朋友的朋字的坐标位置:

    1:打开你的PS,先将图片放大,看到像素方块为止,然后将朋字的范围圈选住,注意稍微比字圈选的大一点,像这样:

    2:按住CTRL+C,然后CTRL+N,出现对话框:(教教大家使用PS^_^)

    3:将背景内容选择透明,按确定,再按CTRL+V复制图像

    4:将这个图片放大到看到像素为止,将所有非字体的位置全部用铅笔工具涂上同一种颜色,

    5:涂完了之后将这张图片保存下来,这张图片就是我们要查找的“朋”字,图片是这样的

    6:我们需要的就是第五步的图片和第一张底图,下面见证奇迹的时刻到了。

    1          string str1 = @"C:UsersJimmyBrightDesktop1.png";
    2             string str2 = @"C:UsersJimmyBrightDesktop2.png";
    3             var xx = FindText(str2,str1,new System.Drawing.Rectangle(0, 0, 400, 600),10);

    str1是我们的底图,str2是第五步的那张处理后的文字图片,xx就是我们最后需要的文字的位置坐标,我们运行看看。下面截图运行结果:

    显然最后我们查找的文字在图片中的坐标为(224,286),大家可以下载那张图片验证

    找数:

      你以为找到文字就算完了吗?No,找数字才是最困难的,为什么呢?有人会问,数字难道不也是文字吗,不也可以通过PS处理数字达到查找其位置的目的吗?对的,数字也是文字,我们将需要查找的数字0~9全部PS处理,就能查到它们的位置了。但是有一个问题啊,游戏中用数字表示的地方通常是一连串的数字,这些数字里面包含0~9的任意组合。所以我们需要这样处理:

      我们从0~9依次查找指定区域,记录每次查找的结果,没查到的数字不必记录,对查到结果的数字再按照X坐标排序,因为在X坐标越小,数字越靠左边。

    还有一个严重的问题,例如38,14,这样的数字会很讨厌,为什么呢,我们会再8当中查找3,在4当中查找到1,这会对我们的数字识别产生重大误差,所以下面我也写了一个方法对这个问题做了处理,代码:

    复制代码
     1     #region 查找数字
     2         
     3         /// <summary>
     4         /// 在指定区域里面查找数字
     5         /// </summary>
     6         /// <param name="numDic"></param>
     7         /// <param name="parPic"></param>
     8         /// <param name="searchRect"></param>
     9         /// <param name="errorRange"></param>
    10         /// <returns></returns>
    11         int FindNumbers(Dictionary<int, string> numDic, string parPic, System.Drawing.Rectangle searchRect, byte errorRange=8, double matchRate = 0.9)
    12         {
    13             //同一个区域找到多个相同的图片
    14             List<NumBody> ListBody = new List<NumBody>();
    15             foreach (var item in numDic)
    16             {
    17                 var listPoint = FindText(item.Value, parPic, searchRect, errorRange, matchRate, true);
    18                 foreach (var point in listPoint)
    19                 {
    20                     ListBody.Add(new NumBody() { num = item.Key,matchNum=point.matchNum,matchSum=point.matchSum, matchRate=point.matchRate, point = point.point, bodyCollectionPoint = point.bodyCollectionPoint });
    21                 }
    22             }
    23 
    24             SearchNumbersByMatchNum(ref ListBody);
    25             var myList = from body in ListBody orderby body.point.X ascending select body;
    26             string number = "0";
    27             foreach (var item in myList)
    28             {
    29                 number += item.num;
    30             }
    31             int num = Int32.Parse(number);
    32             return num;
    33         }
    34        /// <summary>
    35        /// 搜索同一个数字的时候,出现重叠的地方,用匹配度去过滤掉匹配度低的
    36        /// 比如同样是1,在控制匹配度允许下,一个(83,95)和(84,95)这两个点明显是同一个数字
    37        /// 此时谁的匹配度低过滤掉谁
    38        /// </summary>
    39        /// <param name="ListBody"></param>
    40         void SearchNumbersByMatchNum(ref List<NumBody> ListBody)
    41         {
    42             bool isValid = true;
    43             for (int i = 0; i < ListBody.Count; i++)
    44             {
    45                 var body = ListBody[i];
    46                 
    47                 for (int j = i; j < ListBody.Count; j++)
    48                 {
    49 
    50                     var bodyX = ListBody[j];
    51                     if (!bodyX.Equals(body))
    52                     {
    53                         int sameNum = 0;
    54                         foreach (var item in body.bodyCollectionPoint)
    55                         {
    56                             if (bodyX.bodyCollectionPoint.Contains(item))
    57                             {
    58                                 sameNum++;
    59                             }
    60                         }
    61                         if (sameNum >= 1)//有1个以上点重合,表面图像重叠,删除像素点数少的图像
    62                         {
    63                             isValid = false;
    64 
    65                             //如果某个数字100%匹配,那就不用比较了,这个数字肯定是对的
    66                             double maxRate = 1;
    67                             if (bodyX.matchRate >= maxRate)
    68                             {
    69                                 ListBody.Remove(body);
    70                             }
    71                             else if (body.matchRate>=maxRate)
    72                             {
    73                                 ListBody.Remove(bodyX);
    74                             }
    75                             else
    76                             {
    77                                 if (bodyX.matchNum >= body.matchNum)//图像包含的所有像素个数
    78                                 {
    79                                     ListBody.Remove(body);
    80                                 }
    81                                 else
    82                                 {
    83                                     ListBody.Remove(bodyX);
    84                                 }
    85                             }
    86                             SearchNumbersByMatchNum(ref ListBody);
    87                         }
    88                     }
    89                 }
    90             }
    91             if (isValid)
    92             {
    93                 return;
    94             }
    95         }
    96 
    97         #endregion
    复制代码

    其他方法:

    复制代码
     1    bool ColorAEqualColorB(System.Drawing.Color colorA, System.Drawing.Color colorB, byte errorRange = 10)
     2         {
     3             return colorA.A <= colorB.A + errorRange && colorA.A >= colorB.A - errorRange &&
     4                 colorA.R <= colorB.R + errorRange && colorA.R >= colorB.R - errorRange &&
     5                 colorA.G <= colorB.G + errorRange && colorA.G >= colorB.G - errorRange &&
     6                 colorA.B <= colorB.B + errorRange && colorA.B >= colorB.B - errorRange;
     7           
     8         }
     9         bool ListContainsPoint(List<System.Drawing.Point> listPoint, System.Drawing.Point point, double errorRange = 10)
    10         {
    11             bool isExist = false;
    12             foreach (var item in listPoint)
    13             {
    14                 if (item.X <= point.X + errorRange && item.X >= point.X - errorRange && item.Y <= point.Y + errorRange && item.Y >= point.Y - errorRange)
    15                 {
    16                     isExist = true;
    17                 }
    18             }
    19             return isExist;
    20         }
    21         bool ListTextBodyContainsPoint(List<NumBody> listPoint, System.Drawing.Point point, double errorRange = 10)
    22         {
    23             bool isExist = false;
    24             foreach (var item in listPoint)
    25             {
    26 
    27                 if (item.point.X <= point.X + errorRange && item.point.X >= point.X - errorRange && item.point.Y <= point.Y + errorRange && item.point.Y >= point.Y - errorRange)
    28                 {
    29                     isExist = true;
    30                 }
    31             }
    32             return isExist;
    33         }
    复制代码

    结束语:以上代码本人实现了找颜色,找图片,找文字,找数字的所有功能,希望对朋友们能有所帮助。

    最近找到了以前的代码:

    想看源码的去这里

    https://github.com/ahuchjm/BtnPress.git
  • 相关阅读:
    吴裕雄--天生自然C++语言学习笔记:C++ 文件和流
    吴裕雄--天生自然C++语言学习笔记:C++ 接口(抽象类)
    吴裕雄--天生自然C++语言学习笔记:C++ 数据封装
    吴裕雄--天生自然C++语言学习笔记:C++ 数据抽象
    吴裕雄--天生自然C++语言学习笔记:C++ 多态
    吴裕雄--天生自然C++语言学习笔记:C++ 重载运算符和重载函数
    吴裕雄--天生自然C++语言学习笔记:C++ 继承
    吴裕雄--天生自然C++语言学习笔记:C++ 类 & 对象
    HihoCoder1656 : 前缀后缀查询([Offer收割]编程练习赛39)(字典树+小技巧)
    HihoCoder1655 : 第K小最简真分数([Offer收割]编程练习赛39)(唯一分解+容斥定理+二分)(不错的数学题)
  • 原文地址:https://www.cnblogs.com/microtiger/p/9012397.html
Copyright © 2011-2022 走看看