Files
2026-10-24 13:02:19 +08:00

76 lines
2.9 KiB
C#

using OpenCvSharp;
using System.Drawing.Imaging;
namespace anjianjingling.Vision
{
/// <summary>
/// 找图:OpenCV 模板匹配(TM_CCOEFF_NORMED),支持相似度阈值。
/// </summary>
public sealed class ImageFinder : IDisposable
{
private readonly Dictionary<string, Mat> _templateCache = new(StringComparer.OrdinalIgnoreCase);
private static Mat BitmapToMat(Bitmap bmp)
{
using var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Png);
return Cv2.ImDecode(ms.ToArray(), ImreadModes.Color);
}
private Mat LoadTemplate(string path)
{
if (_templateCache.TryGetValue(path, out var cached)) return cached;
if (!File.Exists(path))
throw new FileNotFoundException($"模板图片不存在:{path}");
var mat = Cv2.ImRead(path, ImreadModes.Color);
if (mat.Empty())
throw new InvalidDataException($"模板图片无法读取:{path}");
_templateCache[path] = mat;
return mat;
}
/// <summary>
/// 在 haystack 位图中查找模板。
/// </summary>
/// <param name="haystack">被查找的画面(通常是区域截图)</param>
/// <param name="templatePath">模板图路径</param>
/// <param name="threshold">相似度阈值 0~1</param>
/// <param name="offsetX">结果坐标要加上的屏幕偏移 X</param>
/// <param name="offsetY">结果坐标要加上的屏幕偏移 Y</param>
/// <returns>匹配中心屏幕坐标与相似度;未达阈值返回 (-1,-1,score)</returns>
public (int X, int Y, double Score) Find(Bitmap haystack, string templatePath,
double threshold, int offsetX = 0, int offsetY = 0)
{
using var hay = BitmapToMat(haystack);
using var tpl = LoadTemplate(templatePath);
if (tpl.Width > hay.Width || tpl.Height > hay.Height)
throw new ArgumentException($"模板图({tpl.Width}x{tpl.Height})大于查找区域({hay.Width}x{hay.Height})");
using var result = new Mat();
Cv2.MatchTemplate(hay, tpl, result, TemplateMatchModes.CCoeffNormed);
Cv2.MinMaxLoc(result, out _, out double maxVal, out _, out OpenCvSharp.Point maxLoc);
if (maxVal >= threshold)
{
int cx = offsetX + maxLoc.X + tpl.Width / 2;
int cy = offsetY + maxLoc.Y + tpl.Height / 2;
return (cx, cy, maxVal);
}
return (-1, -1, maxVal);
}
/// <summary>清除模板缓存(模板文件更新后调用)。</summary>
public void ClearCache()
{
foreach (var m in _templateCache.Values) m.Dispose();
_templateCache.Clear();
}
public void Dispose()
{
ClearCache();
}
}
}