从大图中查找其中小图片的较快的方法

我想到的一个较快的从大图片中查找局部小图的方法。比AForge.Imaging速度快多了,且不象它占用大量内存。

示例程序中查找小图片只用了100毫秒左右。

I found a quick way to find the location of a small picture in a large picture that contains it!

有关源代码和示例程序已放在github.com/Dotfirez/PictureFinder 

以下部分核心代码,供有兴趣的朋友交流。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Drawing;
  4 using System.Drawing.Imaging;
  5 using System.Linq;
  6 using System.Runtime.InteropServices;
  7 
  8 namespace Dotfire
  9 {
 10     public class PictureFinder
 11     {
 12         /// <summary>
 13         /// 查找局部图片的方法
 14         /// </summary>
 15         /// <param name="FindResulte">返回查找的结果,相似度大于设定的FoundRate比例</param>
 16         /// <param name="Source">原始大图片</param>
 17         /// <param name="Smallpic">原始大图片的局部小图片</param>
 18         /// <param name="OriginalFindResulte">返回原始的查找结果</param>
 19         /// <param name="FindAll">是否相同所有相同的多个子图片</param>
 20         /// <param name="FoundRate">查找到相同像素的百分比条件,1表示100%完全相同,默认值为0.90f表示90%的相似度</param>
 21         /// <param name="ExcludeColor">默认排除查找的颜色为黑色0xFF和白色0xFFFFFFFF(BGRA)</param>
 22         /// <param name="error">颜色的容错值,0~255,0表示没有色差,255表示最大色差</param>
 23         /// <param name="step">查找像素的步长,默认为1</param>
 24         /// <param name="deeppercent">取出多少百分比相同颜色进行比较,以提高速度,0~1f</param>
 25         /// <returns>是否找到了局部小图片</returns>
 26         public static bool FindSmallPic(out Dictionary<Point, float> FindResulte, Bitmap Source, Bitmap Smallpic, out Dictionary<Point, float> OriginalFindResulte, bool FindAll = false, float FoundRate = 0.90f, uint[] ExcludeColor = default, byte error = 20, int step = 1, float deeppercent = 0.1f)
 27         {
 28             int w1 = Source.Width;
 29             int h1 = Source.Height;
 30             int w = Smallpic.Width;
 31             int h = Smallpic.Height;
 32             if (step == default || step < 1)
 33                 step = 1;
 34 
 35             if (ExcludeColor == default)
 36                 ExcludeColor = new uint[] { 0xFF, 0xFFFFFFFF };//黑色和白色,最后一个字节是255表示是透明度
 37             Dictionary<uint, uint> colorcountsmall;
 38             Dictionary<uint, uint> colorcountbig;
 39             Dictionary<uint, List<Point>> ColorPointListSmall = GetBitmapColorPointList(Smallpic, out colorcountsmall);
 40             Dictionary<uint, List<Point>> ColorPointListBig = GetBitmapColorPointList(Source, out colorcountbig);
 41             ColorPointListBig = ColorPointListBig.Where(x => ColorPointListSmall.ContainsKey(x.Key) && !ExcludeColor.Contains(x.Key)).ToDictionary(p => p.Key, p => p.Value);
 42             //有时小图不一定是原大图中的,要进行颜色的相互筛选
 43             ColorPointListSmall = ColorPointListSmall.Where(x => ColorPointListBig.ContainsKey(x.Key)).ToDictionary(p => p.Key, p => p.Value);
 44             Dictionary<uint, double> count = new Dictionary<uint, double>();
 45             foreach (var v in ColorPointListSmall.Keys)
 46             {
 47                 double vsmall = Math.Sqrt(colorcountsmall[v]);
 48                 double vbig = Math.Sqrt(colorcountbig[v]);
 49                 double cunt = vsmall * vbig;
 50                 count.Add(v, cunt);
 51             }
 52             if (count.Count == 0)
 53             {
 54                 FindResulte = null;
 55                 OriginalFindResulte = null;
 56                 //GC.Collect();
 57                 return false;
 58             }
 59             colorcountsmall = null;
 60             colorcountbig = null;
 61             count = count.OrderBy(x => x.Value).ToDictionary(p => p.Key, p => p.Value);
 62             int deep = (int)(count.Count* deeppercent);// (int)(count.Last().Value * deeppercent);
 63             deep=deep<1 ? 1 : deep;
 64             uint[] corlortofind = count.OrderBy(x => x.Value).Take(deep).Select(x => x.Key).ToArray();
 65 
 66             count = null;
 67             ColorPointListBig = ColorPointListBig.Where(x => corlortofind.Contains(x.Key)).ToDictionary(p => p.Key, p => p.Value);
 68             ColorPointListSmall = ColorPointListSmall.Where(x => corlortofind.Contains(x.Key)).ToDictionary(p => p.Key, p => p.Value);
 69             corlortofind = null;
 70             Dictionary<Point, List<Point>> PointsFound = new Dictionary<Point, List<Point>>();
 71             foreach (var vv in ColorPointListSmall)
 72             {
 73                 foreach (var v in vv.Value)
 74                 {
 75                     List<Point> points = ColorPointListBig.Where(x => x.Key == vv.Key).Select(x => x.Value).First();
 76                     PointsFound.Add(v, points);
 77                 }
 78             }
 79             ColorPointListBig = null;
 80             ColorPointListSmall = null;
 81             FindResulte = new Dictionary<Point, float>();
 82 
 83             foreach (var vp in PointsFound)
 84             {
 85                 foreach (var p in vp.Value)
 86                 {
 87                     float percent = 0;
 88                     float BeFind = 0;
 89                     int startY = p.Y - vp.Key.Y;
 90                     int startX = p.X - vp.Key.X;
 91                     if (startX < 0 || startY < 0 || startX > w1 - w || startY > h1 - h)
 92                         continue;
 93                     Point start = new Point(startX, startY);
 94                     if (FindResulte.ContainsKey(start))
 95                     {
 96                         continue;
 97                     }
 98                     float AllDot = 0;
 99                     for (int j = 0; j < h; j += step)
100                     {
101                         for (int i = 0; i < w; i += step)
102                         {
103                             AllDot++;
104                             Color vsmal = Smallpic.GetPixel(i, j);
105                             Color vsource = Source.GetPixel(i + startX, j + startY);
106                             if (ColorSimilarity(vsmal, vsource) <= error)
107                             {
108                                 BeFind++;
109                             }
110                         }
111                     }
112                     percent = BeFind / AllDot;
113                     FindResulte.Add(start, percent);
114                 }
115             }
116 
117             OriginalFindResulte = FindResulte.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
118             FindResulte = OriginalFindResulte.Where(x => x.Value >= FoundRate).ToDictionary(x => x.Key, x => x.Value);
119             bool ret = FindResulte.Count > 0;
120             PointsFound = null;
121             //GC.Collect();
122             return ret;
123         }
124         /// <summary>
125         /// 两颜色的RGB值的差的平均值
126         /// </summary>
127         /// <param name="a">颜色a</param>
128         /// <param name="b">颜色b</param>
129         /// <returns>两颜色的RGB值的差平均值</returns>
130         private static byte ColorSimilarity2(Color a, Color b)
131         {
132             //int Ea = (a.A - b.A);
133             byte Er = (byte)Math.Abs(a.R - b.R);
134             byte Eg = (byte)Math.Abs(a.G - b.G);
135             byte Eb = (byte)Math.Abs(a.B - b.B);
136             byte e = (byte)((Er + Eg + Eb) / 3);
137             return e;
138         }
139         private static byte ColorSimilarity(Color a, Color b)
140         {
141             const double Max= 2167.470702927d;
142             //int Ea = (a.A - b.A);
143             long Er = a.R - b.R;
144             long Eg = a.G - b.G;
145             long Eb = a.B - b.B;
146             long DR = (a.R + b.R)/2;
147             double e = Math.Sqrt((((512+DR)*Er*Er)>>8)+4*Eg*Eg+(((767-DR)*Eb*Eb)>>8));
148             byte ret = (byte)(e/Max*255);
149             return ret;
150         }
151 
152         /// <summary>
153         /// 查找局部图片的方法.
154         /// </summary>
155         /// <param name="FindResulte">返回查找的结果,相似度大于设定的FoundRate比例</param>
156         /// <param name="Source">原始大图片</param>
157         /// <param name="Smallpic">原始大图片的局部小图片</param>
158         /// <param name="FoundRate">查找到相同像素的百分比条件,1表示100%完全相同,默认值为0.90f表示90%的相似度</param>
159         /// <param name="ExcludeColor">默认排除查找的颜色为白色0xFFFFFFFF</param>
160         /// <returns>是否找到了局部小图片</returns>
161         public static bool FindSmallPic(out Dictionary<Point, float> FindResulte, Bitmap Source, Bitmap Smallpic, float FoundRate = 0.90f, uint[] ExcludeColor = default)
162         {
163             int WidthBig = Source.Width;
164             int HeightBig = Source.Height;
165             int WidthSmall = Smallpic.Width;
166             int HeightSmall = Smallpic.Height;
167             //查找的对比像素的步长,越大越快,但会降低精度,速度提升并不明显。
168             int Step = 1;
169             //比对多少种颜色,越少越快
170             int ColorDeep = 1;
171             if (ExcludeColor == default)
172                 ExcludeColor = new uint[] { 0xFFFFFFFF };
173             Dictionary<uint, uint> colorcountsmall;
174             Dictionary<uint, uint> colorcountbig;
175             Dictionary<uint, List<Point>> ColorPointListSmall = GetBitmapColorPointList(Smallpic, out colorcountsmall);
176             Dictionary<uint, List<Point>> ColorPointListBig = GetBitmapColorPointList(Source, out colorcountbig);
177             ColorPointListBig = ColorPointListBig.Where(x => ColorPointListSmall.ContainsKey(x.Key) && !ExcludeColor.Contains(x.Key)).ToDictionary(p => p.Key, p => p.Value);
178             //有时小图不一定是原大图中的,要进行颜色的相互筛选
179             ColorPointListSmall = ColorPointListSmall.Where(x => ColorPointListBig.ContainsKey(x.Key)).ToDictionary(p => p.Key, p => p.Value);
180             Dictionary<uint, double> count = new Dictionary<uint, double>();
181             foreach (var v in ColorPointListSmall.Keys)
182             {
183                 double vsmall = Math.Sqrt(colorcountsmall[v]);
184                 double vbig = Math.Sqrt(colorcountbig[v]);
185                 double allcount = vsmall * vbig;
186                 count.Add(v, allcount);
187             }
188             if (count.Count == 0)
189             {
190                 FindResulte = null;
191                 return false;
192             }
193 
194             colorcountsmall = null;
195             colorcountbig = null;
196             count = count.OrderBy(x => x.Value).ToDictionary(p => p.Key, p => p.Value);
197 
198             uint[] corlortofind = count.OrderBy(x => x.Value).Take(ColorDeep).Select(x => x.Key).ToArray();
199             count = null;
200             ColorPointListBig = ColorPointListBig.Where(x => corlortofind.Contains(x.Key)).ToDictionary(p => p.Key, p => p.Value);
201             ColorPointListSmall = ColorPointListSmall.Where(x => corlortofind.Contains(x.Key)).ToDictionary(p => p.Key, p => p.Value);
202             Dictionary<Point, List<Point>> PointsFound = new Dictionary<Point, List<Point>>();
203             foreach (var vv in ColorPointListSmall)
204             {
205                 foreach (var v in vv.Value)
206                 {
207                     List<Point> points = ColorPointListBig.Where(x => x.Key == vv.Key).Select(x => x.Value).First();
208                     PointsFound.Add(v, points);
209                 }
210             }
211             ColorPointListBig = null;
212             ColorPointListSmall = null;
213             FindResulte = new Dictionary<Point, float>();
214             Dictionary<Point, Rectangle> RectanglesFound = new Dictionary<Point, Rectangle>();
215             foreach (var vp in PointsFound)
216             {
217                 foreach (var p in vp.Value)
218                 {
219                     float percent = 0;
220                     float BeFind = 0;
221                     int startY = p.Y - vp.Key.Y;
222                     int startX = p.X - vp.Key.X;
223                     if (startX < 0 || startY < 0 || startX > WidthBig - WidthSmall || startY > HeightBig - HeightSmall)
224                         continue;
225                     Point start = new Point(startX, startY);
226                     if (FindResulte.ContainsKey(start))
227                     {
228                         continue;
229                     }
230                     float AllDot = 0;
231                     for (int j = 0; j < HeightSmall; j += Step)
232                     {
233                         for (int i = 0; i < WidthSmall; i += Step)
234                         {
235                             AllDot++;
236                             int vsmal = Smallpic.GetPixel(i, j).ToArgb();
237                             int vsource = Source.GetPixel(i + startX, j + startY).ToArgb();
238                             if (vsmal == vsource)
239                             {
240                                 BeFind++;
241                             }
242                         }
243                     }
244                     percent = BeFind / AllDot;
245                     FindResulte.Add(start, percent);
246                 }
247             }
248             Dictionary<Point, float> vlist, vlist2;
249             vlist = FindResulte.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
250             vlist2 = vlist.Where(x => x.Value >= FoundRate).ToDictionary(x => x.Key, x => x.Value);
251             FindResulte = vlist2;
252             vlist=null; vlist2=null;
253             //GC.Collect();
254             return FindResulte.Count > 0;
255         }
256         public static Dictionary<uint, List<Point>> GetBitmapColorPointList(Bitmap obitmap, out Dictionary<uint, uint> ColorCount)
257         {
258             Bitmap bitmap = (Bitmap)obitmap.Clone();
259             int parWidth = bitmap.Width;
260             int parHeight = bitmap.Height;
261             var parData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
262             var byteArraryPar = new byte[parData.Stride * parData.Height];
263             Marshal.Copy(parData.Scan0, byteArraryPar, 0, parData.Stride * parData.Height);
264             var iMax = parData.Height;//
265             var jMax = parData.Width;//
266             int Depth = 4;//Format32bppArgb的颜色深度
267             Dictionary<uint, List<Point>> dir = new Dictionary<uint, List<Point>>();
268             ColorCount = new Dictionary<uint, uint>();
269             for (int i = 0; i < iMax - 1; i++)
270             {
271                 for (int j = 0; j < jMax - 1; j++)
272                 {
273                     //大图x,y坐标处的颜色值
274                     //int x = j, y = i;
275                     int parIndex = i * parWidth * Depth + j * Depth;
276                     uint c = 0;
277                     byte a = byteArraryPar[parIndex + 3];
278                     byte r = byteArraryPar[parIndex + 2];
279                     byte g = byteArraryPar[parIndex + 1];
280                     byte b = byteArraryPar[parIndex + 0];
281                     c = (uint)((b << 24) | (g << 16) | (r << 8) | a);
282                     if (dir.ContainsKey(c))
283                     {
284                         dir[c].Add(new Point(j, i));
285                         ColorCount[c]++;
286                     }
287                     else
288                     {
289                         List<Point> list = new List<Point>
290                         {
291                             new Point(j, i)
292                         };
293                         dir.Add(c, list);
294                         ColorCount.Add(c, 1);
295                     }
296                 }
297             }
298             bitmap.UnlockBits(parData);
299             bitmap = null;
300             //GC.Collect();
301             return dir;
302         }
303 
304         public static Bitmap DrawRectangle(Bitmap d, List<Rectangle> recs, Color color = default, float penwidth = 1f)
305         {
306             if (color == default) color = Color.Red;
307             Bitmap vb = (Bitmap)d.Clone();
308             Graphics g = Graphics.FromImage(vb);
309             Pen pen = new Pen(color, penwidth);
310             pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
311             foreach (var v in recs)
312             {
313                 g.DrawRectangle(pen, v);
314             }
315             g.Dispose();
316             return vb;
317         }
318         public static Bitmap DrawRectangle(Bitmap d, Rectangle rec, Color color = default)
319         {
320             if (color == default) color = Color.Red;
321             Bitmap vb = (Bitmap)d.Clone();
322             Pen pen = new Pen(color, 0.1f);
323             Graphics g = Graphics.FromImage(vb);
324             pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
325             g.DrawRectangle(pen, rec);
326             g.Dispose();
327             return vb;
328         }
329     }
330 }

 

示例程序运行如下图:

 

posted @ 2023-04-20 20:34  dotfirer  阅读(187)  评论(1编辑  收藏  举报