improve the speed of matching bigmap

This commit is contained in:
辉鸭蛋
2024-06-30 17:12:50 +08:00
parent cde2046202
commit 2cd71ff4a1
10 changed files with 188 additions and 33 deletions

View File

@@ -49,7 +49,8 @@ public partial class MainWindow : Window
{
// KeyPointMatchTest.Test();
// EntireMapTest.Test();
EntireMapTest.Storage();
// EntireMapTest.Storage();
BigMapMatchTest.Test();
}
private void MapDrawTeleportPoint(object sender, RoutedEventArgs e)

View File

@@ -0,0 +1,46 @@
using BetterGenshinImpact.Core.Recognition.OpenCv.FeatureMatch;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.Helpers;
using OpenCvSharp;
using OpenCvSharp.Detail;
using System.Diagnostics;
using System.Windows.Forms;
using BetterGenshinImpact.Core.Recognition.OpenCv;
namespace BetterGenshinImpact.Test.Simple.AllMap;
public class BigMapMatchTest
{
public static void Test()
{
SpeedTimer speedTimer = new();
// var mainMap100BlockMat = new Mat(@"D:\HuiPrograming\Projects\CSharp\MiHoYo\BetterGenshinImpact\BetterGenshinImpact\Assets\Map\mainMap100Block.png", ImreadModes.Grayscale);
var map2048 = MapAssets.Instance.MainMap2048BlockMat.Value;
var mainMap100BlockMat = ResizeHelper.Resize(map2048, 1d / (4 * 2));
Cv2.ImWrite(@"E:\HuiTask\更好的原神\地图匹配\有用的素材\mainMap128Block.png", mainMap100BlockMat);
var surfMatcher = new FeatureMatcher(mainMap100BlockMat);
var queryMat = new Mat(@"E:\HuiTask\更好的原神\地图匹配\比较\Clip_20240321_000329.png", ImreadModes.Grayscale);
speedTimer.Record("初始化特征");
queryMat = ResizeHelper.Resize(queryMat, 1d / 4);
Cv2.ImShow("queryMat", queryMat);
var pArray = surfMatcher.Match(queryMat);
speedTimer.Record("匹配1");
if (pArray != null)
{
var rect = Cv2.BoundingRect(pArray);
Debug.WriteLine($"Matched rect 1: {rect}");
Cv2.Rectangle(mainMap100BlockMat, rect, Scalar.Red, 2);
Cv2.ImShow(@"b1", mainMap100BlockMat);
}
else
{
Debug.WriteLine("No match 1");
}
speedTimer.DebugPrint();
}
}

View File

@@ -14,7 +14,10 @@ public class FeatureMatcher
{
private readonly double _threshold = 100; // SURF 100
private readonly Feature2D _feature2D;
private readonly Mat _trainMat; // 大图本身
// private readonly Mat _trainMat; // 大图本身
private readonly Size _trainMatSize; // 大图大小
private readonly Mat _trainRet = new(); // 大图特征描述子
private readonly KeyPoint[] _trainKeyPoints;
@@ -23,10 +26,19 @@ public class FeatureMatcher
private readonly int _splitCol = MapCoordinate.GameMapCols * 2; // 特征点拆分列数
private KeyPointFeatureBlock? _lastMergedBlock; // 上次合并的特征块
/// <summary>
/// 从图像 or 特征点加载
/// 大图不建议使用此构造函数加载,速度很慢
/// </summary>
/// <param name="trainMat"></param>
/// <param name="featureStorage"></param>
/// <param name="type"></param>
/// <param name="threshold"></param>
/// <exception cref="Exception"></exception>
public FeatureMatcher(Mat trainMat, FeatureStorage? featureStorage = null, Feature2DType type = Feature2DType.SIFT, double threshold = 100)
{
_threshold = threshold;
_trainMat = trainMat;
_trainMatSize = trainMat.Size();
if (Feature2DType.SURF == type)
{
_feature2D = SURF.Create(_threshold, 4, 3, false, true);
@@ -54,11 +66,51 @@ public class FeatureMatcher
_trainRet = featureStorage.LoadDescMat() ?? throw new Exception("加载特征描述矩阵失败");
}
}
else
{
_feature2D.DetectAndCompute(trainMat, null, out _trainKeyPoints, _trainRet);
}
Debug.WriteLine("被匹配的图像生成初始化KeyPoint完成");
Stopwatch sw = new();
sw.Start();
_blocks = KeyPointFeatureBlockHelper.SplitFeatures(_trainMat, _splitRow, _splitCol, _trainKeyPoints, _trainRet);
_blocks = KeyPointFeatureBlockHelper.SplitFeatures(_trainMatSize, _splitRow, _splitCol, _trainKeyPoints, _trainRet);
sw.Stop();
Debug.WriteLine($"切割特征点耗时: {sw.ElapsedMilliseconds}ms");
}
/// <summary>
/// 直接从特征点加载
/// </summary>
/// <param name="trainMatSize"></param>
/// <param name="featureStorage"></param>
/// <param name="type"></param>
/// <param name="threshold"></param>
/// <exception cref="Exception"></exception>
public FeatureMatcher(Size trainMatSize, FeatureStorage featureStorage, Feature2DType type = Feature2DType.SIFT, double threshold = 100)
{
_threshold = threshold;
_trainMatSize = trainMatSize;
if (Feature2DType.SURF == type)
{
_feature2D = SURF.Create(_threshold, 4, 3, false, true);
}
else
{
_feature2D = SIFT.Create();
}
featureStorage.TypeName = type.ToString();
Debug.WriteLine("尝试从磁盘加载特征点");
var kpFromDisk = featureStorage.LoadKeyPointArray();
_trainKeyPoints = kpFromDisk ?? throw new Exception("特征点不存在");
_trainRet = featureStorage.LoadDescMat() ?? throw new Exception("加载特征描述矩阵失败");
Debug.WriteLine("被匹配的图像生成初始化KeyPoint完成");
Stopwatch sw = new();
sw.Start();
_blocks = KeyPointFeatureBlockHelper.SplitFeatures(_trainMatSize, _splitRow, _splitCol, _trainKeyPoints, _trainRet);
sw.Stop();
Debug.WriteLine($"切割特征点耗时: {sw.ElapsedMilliseconds}ms");
}
@@ -83,7 +135,7 @@ public class FeatureMatcher
/// <returns></returns>
public Point2f[]? Match(Mat queryMat, int prevX, int prevY, Mat? queryMatMask = null)
{
var (cellRow, cellCol) = KeyPointFeatureBlockHelper.GetCellIndex(_trainMat, _splitRow, _splitCol, prevX, prevY);
var (cellRow, cellCol) = KeyPointFeatureBlockHelper.GetCellIndex(_trainMatSize, _splitRow, _splitCol, prevX, prevY);
Debug.WriteLine($"当前坐标({prevX},{prevY})在特征块({cellRow},{cellCol})中");
if (_lastMergedBlock == null || _lastMergedBlock.MergedCenterCellRow != cellRow || _lastMergedBlock.MergedCenterCellCol != cellCol)
{

View File

@@ -9,7 +9,7 @@ namespace BetterGenshinImpact.Core.Recognition.OpenCv.FeatureMatch;
public class KeyPointFeatureBlockHelper
{
public static KeyPointFeatureBlock[][] SplitFeatures(Mat originalImage, int rows, int cols, KeyPoint[] keyPoints, Mat matches)
public static KeyPointFeatureBlock[][] SplitFeatures(Size originalImage, int rows, int cols, KeyPoint[] keyPoints, Mat matches)
{
var matchesCols = matches.Cols; // SURF 64 SIFT 128
// Calculate grid size
@@ -68,7 +68,7 @@ public class KeyPointFeatureBlockHelper
return splitKeyPoints;
}
public static (int, int) GetCellIndex(Mat originalImage, int rows, int cols, int x, int y)
public static (int, int) GetCellIndex(Size originalImage, int rows, int cols, int x, int y)
{
// Calculate grid size
int cellWidth = originalImage.Width / cols;

View File

@@ -10,22 +10,23 @@ public class ResizeHelper
/// </summary>
/// <param name="src"></param>
/// <param name="scale"></param>
/// <param name="interpolation"></param>
/// <returns></returns>
public static Mat Resize(Mat src, double scale)
public static Mat Resize(Mat src, double scale, InterpolationFlags interpolation = InterpolationFlags.Linear)
{
if (Math.Abs(scale - 1) > 0.00001)
{
return Resize(src, scale, scale);
return Resize(src, scale, scale, interpolation);
}
return src;
}
public static Mat Resize(Mat src, double widthScale, double heightScale)
public static Mat Resize(Mat src, double widthScale, double heightScale, InterpolationFlags interpolation = InterpolationFlags.Linear)
{
if (Math.Abs(widthScale - 1) > 0.00001 || Math.Abs(heightScale - 1) > 0.00001)
{
var dst = new Mat();
Cv2.Resize(src, dst, new Size(src.Width * widthScale, src.Height * heightScale));
Cv2.Resize(src, dst, new Size(src.Width * widthScale, src.Height * heightScale), 0, 0, interpolation);
return dst;
}

View File

@@ -66,7 +66,7 @@ public class AutoTrackPathTask
var json = File.ReadAllText(Global.Absolute(@"GameTask\AutoTrackPath\Assets\tp.json"));
_tpPositions = JsonSerializer.Deserialize<List<GiWorldPosition>>(json, ConfigService.JsonOptions) ?? throw new Exception("tp.json deserialize failed");
var wayJson = File.ReadAllText(Global.Absolute(@"log\way\yl3.json"));
var wayJson = File.ReadAllText(Global.Absolute(@"log\way\way2.json"));
_way = JsonSerializer.Deserialize<GiPath>(wayJson, ConfigService.JsonOptions) ?? throw new Exception("way json deserialize failed");
}
@@ -526,9 +526,10 @@ public class AutoTrackPathTask
using var mapScaleButtonRa = ra.Find(QuickTeleportAssets.Instance.MapScaleButtonRo);
if (mapScaleButtonRa.IsExist())
{
var rect = EntireMap.Instance.GetBigMapPositionByFeatureMatch(ra.SrcGreyMat);
var rect = BigMap.Instance.GetBigMapPositionByFeatureMatch(ra.SrcGreyMat);
Debug.WriteLine("识别大地图在全地图位置矩形:" + rect);
return MapCoordinate.Main2048ToGame(rect);
const int s = 4 * 2; // 相对1024做4倍缩放
return MapCoordinate.Main2048ToGame(new Rect(rect.X * s, rect.Y * s, rect.Width * s, rect.Height * s));
}
else
{

View File

@@ -9,10 +9,13 @@ public class MapAssets : BaseAssets<MapAssets>
{
public Lazy<Mat> MainMap100BlockMat { get; } = new(() => new Mat(Global.Absolute(@"Assets\Map\mainMap100Block.png")));
public Lazy<Mat> MainMap1024BlockMat { get; } = new(() => new Mat(@"E:\HuiTask\更好的原神\地图匹配\有用的素材\mainMap1024Block.png", ImreadModes.Grayscale));
// public Lazy<Mat> MainMap1024BlockMat { get; } = new(() => new Mat(@"E:\HuiTask\更好的原神\地图匹配\有用的素材\mainMap1024Block.png", ImreadModes.Grayscale));
public Lazy<Mat> MainMap2048BlockMat { get; } = new(() => new Mat(@"E:\HuiTask\更好的原神\地图匹配\有用的素材\mainMap2048Block.png", ImreadModes.Grayscale));
public Lazy<Mat> MainMap128BlockMat { get; } = new(() => new Mat(@"E:\HuiTask\更好的原神\地图匹配\有用的素材\mainMap128Block.png", ImreadModes.Grayscale));
public Lazy<Mat> MainMap256BlockMat { get; } = new(() => new Mat(@"E:\HuiTask\更好的原神\地图匹配\有用的素材\mainMap256Block.png", ImreadModes.Grayscale));
// 每个地区点击后处于的中心位置
// 2048 区块下,存在传送点的最大面积,识别结果比这个大的话,需要点击放大

View File

@@ -0,0 +1,44 @@
using System;
using System.Diagnostics;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Recognition.OpenCv.FeatureMatch;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.Model;
using OpenCvSharp;
namespace BetterGenshinImpact.GameTask.Common.Map;
/// <summary>
/// 专门用于大地图的识别
/// 图像缩小了8倍
/// </summary>
public class BigMap : Singleton<BigMap>
{
// 直接从图像加载特征点
private readonly FeatureMatcher _featureMatcher = new(MapAssets.Instance.MainMap256BlockMat.Value, new FeatureStorage("mainMap256Block"));
/// <summary>
/// 基于特征匹配获取地图位置 全部匹配
/// </summary>
/// <param name="greyMat">传入的大地图图像会缩小8倍</param>
/// <returns></returns>
public Rect GetBigMapPositionByFeatureMatch(Mat greyMat)
{
try
{
greyMat = ResizeHelper.Resize(greyMat, 1d / 4);
var pArray = _featureMatcher.Match(greyMat);
if (pArray == null || pArray.Length < 4)
{
throw new InvalidOperationException();
}
return Cv2.BoundingRect(pArray);
}
catch
{
Debug.WriteLine("Feature Match Failed");
return Rect.Empty;
}
}
}

View File

@@ -49,7 +49,8 @@ public class EntireMap : Singleton<EntireMap>
// Mat grey = new();
// Cv2.CvtColor(_mainMap100BlockMat, grey, ColorConversionCodes.BGR2GRAY);
// _featureMatcher = new FeatureMatcher(MapAssets.Instance.MainMap1024BlockMat.Value, new FeatureStorage("mainMap1024Block"));
_featureMatcher = new FeatureMatcher(MapAssets.Instance.MainMap2048BlockMat.Value, new FeatureStorage("mainMap2048Block"));
// 只从特征点加载
_featureMatcher = new FeatureMatcher(new Size(28672, 26624), new FeatureStorage("mainMap2048Block"));
}
/// <summary>

View File

@@ -9,6 +9,8 @@ using System.Linq;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Point = OpenCvSharp.Point;
using Size = OpenCvSharp.Size;
@@ -96,25 +98,29 @@ public class TestTrigger : ITaskTrigger
//}
// 小地图匹配测试
var tar = ElementAssets.Instance.PaimonMenuRo.TemplateImageGreyMat!;
var p = MatchTemplateHelper.MatchTemplate(content.CaptureRectArea.SrcGreyMat, tar, TemplateMatchModes.CCoeffNormed, null, 0.9);
if (p.X == 0 || p.Y == 0)
{
return;
}
var miniMapMat = new Mat(content.CaptureRectArea.SrcGreyMat, new Rect(p.X + 24, p.Y - 15, 210, 210));
var mask = new Mat(new Size(miniMapMat.Width, miniMapMat.Height), MatType.CV_8UC1, Scalar.Black);
Cv2.Circle(mask, new Point(miniMapMat.Width / 2, miniMapMat.Height / 2), 90, Scalar.White, -1);
var res = new Mat();
Cv2.BitwiseAnd(miniMapMat, miniMapMat, res, mask);
EntireMap.Instance.GetMapPositionAndDrawByFeatureMatch(res);
Cv2.ImWrite(Global.Absolute(@"log\minimap.png"), res);
// var tar = ElementAssets.Instance.PaimonMenuRo.TemplateImageGreyMat!;
// var p = MatchTemplateHelper.MatchTemplate(content.CaptureRectArea.SrcGreyMat, tar, TemplateMatchModes.CCoeffNormed, null, 0.9);
// if (p.X == 0 || p.Y == 0)
// {
// return;
// }
//
// var miniMapMat = new Mat(content.CaptureRectArea.SrcGreyMat, new Rect(p.X + 24, p.Y - 15, 210, 210));
// var mask = new Mat(new Size(miniMapMat.Width, miniMapMat.Height), MatType.CV_8UC1, Scalar.Black);
// Cv2.Circle(mask, new Point(miniMapMat.Width / 2, miniMapMat.Height / 2), 90, Scalar.White, -1);
// var res = new Mat();
// Cv2.BitwiseAnd(miniMapMat, miniMapMat, res, mask);
// EntireMap.Instance.GetMapPositionAndDrawByFeatureMatch(res);
// Cv2.ImWrite(Global.Absolute(@"log\minimap.png"), res);
// 大地图测试
// var mat = content.CaptureRectArea.SrcGreyMat;
// // mat = mat.Resize(new Size(240, 135));
// EntireMap.Instance.GetMapPositionAndDrawByFeatureMatch(mat);
var mat = content.CaptureRectArea.SrcGreyMat;
// mat = mat.Resize(new Size(240, 135));
Rect rect = BigMap.Instance.GetBigMapPositionByFeatureMatch(mat);
var s = 2.56;
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(this, "UpdateBigMapRect", new object(),
new System.Windows.Rect(rect.X / s, rect.Y / s, rect.Width / s, rect.Height / s)));
// Bv.BigMapIsUnderground(content.CaptureRectArea);
}