屏幕图像搜索脚本运行耗时过长,寻求性能优化方案
图像搜索脚本优化方案
原代码的核心性能问题
- 错误的循环边界:内层循环误用
searchIn.Width作为y轴遍历上限,实际应为searchIn.Height,这会导致无效循环甚至越界风险 GetPixel的性能开销:该方法每次调用都要对位图进行锁定/解锁操作,循环中频繁调用会大幅拖慢运行速度- 冗余日志输出:循环内的
Log语句每执行一次就触发IO操作,直接导致运行时间指数级增长 - 多余的边界判断:循环本身已通过
x < searchIn.Width、y < searchIn.Height限制范围,额外的y != searchIn.Height && x != searchIn.Width等判断完全无意义
优化措施及代码实现
关键优化点
- 改用
BitmapData直接操作内存,规避GetPixel的频繁开销 - 修正循环边界,只遍历有效区域(避免越界且减少无效遍历)
- 移除循环内的日志输出,消除IO开销
- 优化像素匹配逻辑,直接操作字节数组对比
优化后的代码
using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; public bool FindImageOnScreen(Bitmap searchFor, Bitmap searchIn) { // 提前处理边界情况:搜索图比屏幕图大直接返回false if (searchFor.Width > searchIn.Width || searchFor.Height > searchIn.Height) return false; // 锁定位图到内存,直接操作字节数据 using (var searchInData = searchIn.LockBits(new Rectangle(0, 0, searchIn.Width, searchIn.Height), ImageLockMode.ReadOnly, searchIn.PixelFormat)) using (var searchForData = searchFor.LockBits(new Rectangle(0, 0, searchFor.Width, searchFor.Height), ImageLockMode.ReadOnly, searchFor.PixelFormat)) { int pixelByteSize = Image.GetPixelFormatSize(searchIn.PixelFormat) / 8; int searchInStride = searchInData.Stride; int searchForStride = searchForData.Stride; // 将位图数据复制到字节数组 byte[] searchInBytes = new byte[searchInStride * searchIn.Height]; byte[] searchForBytes = new byte[searchForStride * searchFor.Height]; Marshal.Copy(searchInData.Scan0, searchInBytes, 0, searchInBytes.Length); Marshal.Copy(searchForData.Scan0, searchForBytes, 0, searchForBytes.Length); // 预存搜索图的左上角和中心像素字节数据 int centerX = searchFor.Width / 2; int centerY = searchFor.Height / 2; byte[] topLeftPixel = GetPixelBytes(searchForBytes, 0, 0, searchForStride, pixelByteSize); byte[] centerPixel = GetPixelBytes(searchForBytes, centerX, centerY, searchForStride, pixelByteSize); // 遍历屏幕图的有效区域(避免越界) int maxX = searchIn.Width - searchFor.Width; int maxY = searchIn.Height - searchFor.Height; for (int x = 0; x <= maxX; x++) { for (int y = 0; y <= maxY; y++) { // 快速匹配左上角像素 if (ComparePixels(searchInBytes, x, y, searchInStride, pixelByteSize, topLeftPixel)) { // 匹配中心像素 if (ComparePixels(searchInBytes, x + centerX, y + centerY, searchInStride, pixelByteSize, centerPixel)) { return true; } } } } } return false; } // 获取指定坐标的像素字节数据 private byte[] GetPixelBytes(byte[] imageBytes, int x, int y, int stride, int pixelByteSize) { int startIndex = y * stride + x * pixelByteSize; byte[] pixel = new byte[pixelByteSize]; Array.Copy(imageBytes, startIndex, pixel, 0, pixelByteSize); return pixel; } // 对比两个像素的字节数据是否一致 private bool ComparePixels(byte[] imageBytes, int x, int y, int stride, int pixelByteSize, byte[] targetPixel) { int startIndex = y * stride + x * pixelByteSize; for (int i = 0; i < pixelByteSize; i++) { if (imageBytes[startIndex + i] != targetPixel[i]) { return false; } } return true; }
额外优化建议
- 哈希预计算:预先计算搜索图的感知哈希/平均哈希,对屏幕图候选区域快速计算哈希过滤,再进行精确匹配
- 多线程并行遍历:用
Parallel.For将屏幕图划分为多个区域并行处理,进一步提升速度 - 匹配逻辑调整:若允许误差,可加入颜色阈值匹配;若需更高准确率,应在两点匹配通过后执行全图像素验证
内容的提问来源于stack exchange,提问作者King Tkay
相关产品推荐
相关产品推荐

