从大图中查找其中小图片的较快的方法
我想到的一个较快的从大图片中查找局部小图的方法。比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 }
示例程序运行如下图: