在Delphi源码有个叫BitmapData的一个找图找色组件性能非常好效果也很给力.一直想用C#实现但是最初实现的性能实在太糟糕了. 找个图片基本都是6 秒以上. 后来经过自己改进开unsafe 用指针后速度有了比较大的提升基本满足了做模拟外挂或者做UI测试的时候图像判断。现在公布出来给大家参考下 如果有更好的性能提升优化请在讨论下面留言 不胜感激。
目前只支持 24位的bmp 其他的就不支持了 源码在下面有时间可以自己改下 我机器配置如下:e31230 8G 找图片 在 1980 1080的图找 20*20的图片没设置背景透明色的情况下 100ms以内 算是满足做找图需求了。
使用如下:
//定义色差范围值
BGR bgr = new BGR();
bgr.B = 1;
BitmapDataFinder big = new BitmapDataFinder("C:\\b.bmp");
big.Name = "母图";
BitmapDataFinder targ = new BitmapDataFinder("C:\\a1.bmp");
targ.Name = "子图";
可以对图片设置背景色.有时候子图 是有透明的地方的,这里用到 BackColor 设置一个颜色为透明色 颜色类型为BGR
targ.BackColor=;
Stopwatch watch = new Stopwatch();
int x = 0, y = 0;
watch.Start();
bool b = big.Find(targ, bgr, ref x, ref y);
watch.Stop();
MessageBox.Show(watch.ElapsedMilliseconds.ToString() + " " + x.ToString() + "," + y.ToString());
/// <summary> /// 三色分量 /// </summary> public struct BGR { public byte B { get; set; } public byte G { get; set; } public byte R { get; set; } public static bool operator ==(BGR c1, BGR c2) { return c1.R == c2.R && c1.B == c1.B && c1.G == c2.G; } public static bool operator !=(BGR c1, BGR c2) { return !(c1.R == c2.R && c1.B == c1.B && c1.G == c2.G); } } /// <summary> /// 图片快速搜索类(大图找小图) /// </summary> public class BitmapDataFinder { const int BD_BYTECOUNT = 3; /// <summary> /// 名字 /// </summary> public string Name { get; set; } /// <summary> /// 位图宽度(象素) /// </summary> public int Width { get; private set; } /// <summary> /// 位图高度(象素) /// </summary> public int Height { get; private set; } private BGR _backColor; /// <summary> /// 背景颜色(BGR格式) /// </summary> public BGR BackColor { get { return _backColor; } set { _backColor = value; IsEnableBackColor = true; } } /// <summary> /// 对齐后每行数据宽度(字节) /// </summary> public int LineWidth { get; private set; } /// <summary> /// 对齐后每行数据多余宽度(字节) /// </summary> public int OffsetWidth { get; private set; } /// <summary> /// 位图数据长度 /// </summary> public int Length { get; private set; } /// <summary> /// 缓冲区实际长度(目前未用到,和位图数据长度一致) /// </summary> public int BufSize { get; private set; } private byte[] _bits; /// <summary> /// 位图数据缓冲区 /// </summary> public byte[] Bits { get { return _bits; } private set { _bits = value; } } /// <summary> /// 是否开启了背景色 /// </summary> public bool IsEnableBackColor { get; set; } public BitmapDataFinder(string path) { if (!File.Exists(path)) throw new Exception(path + " 文件无法找到!"); IniteData(new Bitmap(path)); } public BitmapDataFinder(Bitmap bmp) { IniteData(bmp); } /// <summary> /// 初始化数据 /// </summary> /// <param name="bmp"></param> private void IniteData(Bitmap bmp) { if (bmp.PixelFormat != PixelFormat.Format24bppRgb) { throw new Exception("错误颜色格式只支持24位bmp"); } BitmapData bitmapdata = bmp.LockBits(new Rectangle(new System.Drawing.Point(), bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); try { Width = bitmapdata.Width; Height = bitmapdata.Height; LineWidth = bitmapdata.Stride; OffsetWidth = bitmapdata.Stride - bitmapdata.Width * BD_BYTECOUNT; //对齐后每行数据多余宽度(字节) Length = bitmapdata.Stride * Height; //位图数据长度 BufSize = Length; //缓冲区实际长度 int length = Length; Bits = new byte[length]; Marshal.Copy(bitmapdata.Scan0, Bits, 0, length); } finally { bmp.UnlockBits(bitmapdata); bmp.Dispose(); } } /// <summary> /// 获取指定坐标点颜色 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public BGR this[int x, int y] { get { BGR c = new BGR(); if (x < 0 || (x >= this.Width) || (y < 0) || (y >= this.Height)) { throw new Exception("索引越界错误"); } else { // c.B = Bits[(((this.Height - y - 1) * this.LineWidth) + x * BD_BYTECOUNT)]; int offset = y * this.LineWidth + x * BD_BYTECOUNT; c.B = Bits[offset]; c.G = Bits[offset + 1]; c.R = Bits[offset + 2]; } return c; } } /// <summary> /// 比对大图中某坐标是否存在小图 /// </summary> /// <param name="bmp"></param> /// <param name="range"></param> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public unsafe bool Compare(BitmapDataFinder bmp, BGR range, byte* b1, byte* b2, int Left, int Top) { bool result; if (((Left + bmp.Width) > this.Width) || ((Top + bmp.Height) > this.Height)) { return false; } result = true; int offset1 = 0, offset2 = 0; for (int y = 0; y < bmp.Height; y++) { for (int x = 0; x < bmp.Width; x++) { offset1 = (Top + y) * this.LineWidth + (Left + x) * BD_BYTECOUNT; offset2 = y * bmp.LineWidth + x * BD_BYTECOUNT; //如果背景颜色启用而且当前颜色等于背景色,则继续看下一个点 if (bmp.IsEnableBackColor && (*(b2 + offset2) == bmp.BackColor.B && *(b2 + offset2 + 1) == bmp.BackColor.G && *(b2 + offset2 + 2) == bmp.BackColor.R)) continue; //颜色比较 if (!BGRCompareColor(*(b1 + offset1), *(b1 + offset1 + 1), *(b1 + offset1 + 2), *(b2 + offset2), *(b2 + offset2 + 1), *(b2 + offset2 + 2), range)) { result = false; break; } } if (!result) break; } return result; } public unsafe bool Compare(BitmapDataFinder bmp, byte* b1, byte* b2, int Left, int Top) { bool result; if (((Left + bmp.Width) > this.Width) || ((Top + bmp.Height) > this.Height)) { return false; } result = true; int offset1 = 0, offset2 = 0; for (int y = 0; y < bmp.Height; y++) { for (int x = 0; x < bmp.Width; x++) { offset1 = (Top + y) * this.LineWidth + (Left + x) * BD_BYTECOUNT; offset2 = y * bmp.LineWidth + x * BD_BYTECOUNT; //如果背景颜色启用而且当前颜色等于背景色,则继续看下一个点 if (bmp.IsEnableBackColor && (*(b2 + offset2) == bmp.BackColor.B && *(b2 + offset2 + 1) == bmp.BackColor.G && *(b2 + offset2 + 2) == bmp.BackColor.R)) continue; //颜色比较 if (*(b2 + offset2) != *(b1 + offset1) || *(b2 + offset2 + 1) != *(b1 + offset1 + 1) || *(b2 + offset2 + 2) != *(b1 + offset1 + 2)) { result = false; break; } } if (!result) break; } return result; } /// <summary> /// 颜色比较 /// </summary> /// <param name="c1"></param> /// <param name="c2"></param> /// <param name="range"></param> /// <returns></returns> public bool BGRCompareColor(BGR c1, BGR c2, BGR range) { return ((Math.Abs(c1.R - c2.R) <= range.R) && (Math.Abs(c1.G - c2.G) <= range.G) && (Math.Abs(c1.B - c2.B) <= range.B)); } public bool BGRCompareColor(byte c1B, byte c1G, byte c1R, byte c2B, byte c2G, byte c2R, BGR range) { //B int C = c1B - c2B; if ((C > range.B) || (C < -range.B)) return false; //G C = c1G - c2G; if ((C > range.G) || (C < -range.G)) return false; ; //R C = c1R - c2R; if ((C > range.R) || (C < -range.R)) return false; ; // return true; } /// <summary> /// 找图 /// </summary> /// <param name="bmp"></param> /// <param name="range"></param> /// <param name="Left"></param> /// <param name="Top"></param> /// <returns></returns> public unsafe bool Find(BitmapDataFinder bmp, BGR range, ref int Left, ref int Top) { bool result = false; int x = 0; int y = 0; fixed (byte* b1 = &Bits[0]) { fixed (byte* b2 = &bmp.Bits[0]) { for (y = 0; y + bmp.Height < this.Height; y++) { for (x = 0; x + bmp.Width < this.Width; x++) { if (Compare(bmp, range, b1, b2, x, y)) { result = true; break; } } if (result) { break; } } } } if (result) { Left = x; Top = y; } else { Left = -1; Top = -1; } return result; } public bool Find(BitmapDataFinder bmp, byte range) { int Left = 0, Top = 0; BGR b = new BGR(); b.B = range; b.G = range; b.R = range; return Find(bmp, b, ref Left, ref Top); } public bool Find(BitmapDataFinder bmp, byte range, ref Point p) { int Left = 0, Top = 0; BGR b = new BGR(); b.B = range; b.G = range; b.R = range; bool re = Find(bmp, b, ref Left, ref Top); p.X = Left; p.Y = Top; return re; } public bool Find(BitmapDataFinder bmp, BGR range) { int Left = 0, Top = 0; return Find(bmp, range, ref Left, ref Top); } public bool Find(BitmapDataFinder bmp, byte r, byte g, byte b) { int Left = 0, Top = 0; BGR range = new BGR(); range.B = b; range.G = g; range.R = r; return Find(bmp, range, ref Left, ref Top); } public unsafe bool Find(BitmapDataFinder bmp, ref int Left, ref int Top) { bool result = false; int x = 0; int y = 0; fixed (byte* b1 = &Bits[0]) { fixed (byte* b2 = &bmp.Bits[0]) { for (y = 0; y + bmp.Height < this.Height; y++) { for (x = 0; x + bmp.Width < this.Width; x++) { if (Compare(bmp, b1, b2, x, y)) { result = true; break; } } if (result) { break; } } } } if (result) { Left = x; Top = y; } else { Left = -1; Top = -1; } return result; } /// <summary> /// 枚举子图所有可能点 /// </summary> /// <param name="bmp"></param> /// <param name="range"></param> /// <returns></returns> public unsafe List<Point> EnumImage(BitmapDataFinder bmp, BGR range) { List<Point> list = new List<Point>(); int x = 0; int y = 0; fixed (byte* b1 = &Bits[0]) { fixed (byte* b2 = &bmp.Bits[0]) { for (y = 0; y + bmp.Height < this.Height; y++) { for (x = 0; x + bmp.Width < this.Width; x++) { if (Compare(bmp, range, b1, b2, x, y)) { list.Add(new Point(x, y)); } } } } } return list; } /// <summary> /// 保存为图片文件 /// </summary> /// <param name="path"></param> public void SaveToBmp(string path) { SaveToBmp().Save(path); } /// <summary> /// 保存图片 /// </summary> /// <returns></returns> public Bitmap SaveToBmp() { Bitmap bmp = new Bitmap(this.Width, this.Height); BitmapData bitmapdata = bmp.LockBits(new Rectangle(new System.Drawing.Point(), bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); try { Marshal.Copy(Bits, 0, bitmapdata.Scan0, Length); } finally { bmp.UnlockBits(bitmapdata); } return bmp; } /// <summary> /// 屏幕截图 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <returns></returns> public static BitmapDataFinder CopyScreen(int x, int y, int width, int height) { //根据屏幕大小建立位图 Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb); using (Graphics g = Graphics.FromImage(bitmap)) { g.CopyFromScreen(x, y, 0, 0, new Size(width, height)); } return new BitmapDataFinder(bitmap); } }