Files
better-genshin-impact/BetterGenshinImpact/GameTask/Common/Map/Maps/Base/SceneBaseMap.cs

206 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Recognition.OpenCv.FeatureMatch;
using OpenCvSharp;
namespace BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
/// <summary>
/// 独立地图
/// </summary>
public abstract class SceneBaseMap : ISceneMap
{
public MapTypes Type { get; set; }
/// <summary>
/// 地图大小
/// 当前只用于切割特征点
/// </summary>
public Size MapSize { get; set; }
/// <summary>
/// 地图原点位置 (在图像坐标系中)
/// </summary>
public Point2f MapOriginInImageCoordinate { get; set; }
/// <summary>
/// 特征地图图像的块大小
/// 2048 或者 1024
/// </summary>
public Point2f MapImageBlockWidth { get; set; }
/// <summary>
/// 特征点拆分行数
/// </summary>
public readonly int SplitRow;
/// <summary>
/// 特征点拆分列数
/// </summary>
public readonly int SplitCol;
/// <summary>
/// 特征地图图像的块大小 / 1024 的值,用于坐标系转换
/// </summary>
private readonly float _mapImageBlockWidthScale = 0;
// ReSharper disable once ConvertToPrimaryConstructor
protected SceneBaseMap(MapTypes type, Size mapSize, Point2f mapOriginInImageCoordinate, int mapImageBlockWidth, int splitRow, int splitCol)
{
Type = type;
MapSize = mapSize;
MapOriginInImageCoordinate = mapOriginInImageCoordinate;
_mapImageBlockWidthScale = mapImageBlockWidth / 1024f;
SplitRow = splitRow;
SplitCol = splitCol;
}
/// <summary>
/// 分层地图特征列表
/// 0 是主地图
/// </summary>
public List<BaseMapLayer> Layers { get; set; } = [];
protected BaseMapLayer MainLayer => Layers[0];
protected readonly Feature2D SiftMatcher = Feature2DFactory.Get(Feature2DType.SIFT);
protected void ExtractAndSaveFeature(string basePath)
{
var fileName = Path.GetFileNameWithoutExtension(basePath);
var folder = Path.GetDirectoryName(basePath)!;
string trainKeyPointsPath = Path.Combine(folder, $"{fileName}_SIFT.kp.bin");
string trainDescriptorsPath = Path.Combine(folder, $"{fileName}_SIFT.mat.png");
if (File.Exists(trainKeyPointsPath) && File.Exists(trainDescriptorsPath))
{
return;
}
SiftMatcher.SaveFeatures(basePath, trainKeyPointsPath, trainDescriptorsPath);
}
public virtual Point2f GetBigMapPosition(Mat greyBigMapMat)
{
return SiftMatcher.Match(MainLayer.TrainKeyPoints, MainLayer.TrainDescriptors, greyBigMapMat);
}
public virtual Rect GetBigMapRect(Mat greyBigMapMat)
{
return SiftMatcher.KnnMatchRect(MainLayer.TrainKeyPoints, MainLayer.TrainDescriptors, greyBigMapMat);
}
public virtual Point2f GetMiniMapPosition(Mat greyMiniMapMat)
{
// 从表到里逐层匹配
foreach (var layer in Layers)
{
try
{
var result = SiftMatcher.KnnMatch(layer.TrainKeyPoints, layer.TrainDescriptors, greyMiniMapMat);
if (result != default)
{
return result;
}
}
catch (Exception e)
{
Debug.WriteLine($"地图{Type}层数{layer.Floor},特征匹配失败:{e.Message}");
}
}
return default;
}
public virtual Point2f GetMiniMapPosition(Mat greyMiniMapMat, float prevX, float prevY)
{
if (prevX <= 0 && prevY <= 0)
{
return GetMiniMapPosition(greyMiniMapMat);
}
foreach (var layer in Layers)
{
try
{
var (keyPoints, descriptors) = (layer.TrainKeyPoints, layer.TrainDescriptors);
if (SplitRow > 0 || SplitCol > 0)
{
(keyPoints, descriptors) = layer.ChooseBlocks(prevX, prevY);
}
var result = SiftMatcher.KnnMatch(keyPoints, descriptors, greyMiniMapMat, null, DescriptorMatcherType.BruteForce);
if (result != default)
{
return result;
}
}
catch (Exception e)
{
Debug.WriteLine($"地图{Type}层数{layer.Floor},特征匹配失败:{e.Message}");
}
}
return default;
}
#region
public Point2f? ConvertImageCoordinatesToGenshinMapCoordinates(Point2f imageCoordinates)
{
if (imageCoordinates.X == 0 && imageCoordinates.Y == 0)
{
return null;
}
// 原神坐标系是 1024 级别的,当图像坐标系不是 1024 级别的时候要做转换
return new Point2f((MapOriginInImageCoordinate.X - imageCoordinates.X) / _mapImageBlockWidthScale,
(MapOriginInImageCoordinate.Y - imageCoordinates.Y) / _mapImageBlockWidthScale);
}
public Rect? ConvertImageCoordinatesToGenshinMapCoordinates(Rect rect)
{
if (rect.X == 0 && rect.Y == 0 && rect.Width == 0 && rect.Height == 0)
{
return null;
}
var center = rect.GetCenterPoint();
var nullablePoint = ConvertImageCoordinatesToGenshinMapCoordinates(new Point2f(center.X, center.Y));
if (nullablePoint is Point2f p)
{
return new Rect((int)(p.X - rect.Width / 2f / _mapImageBlockWidthScale), (int)(p.Y - rect.Height / 2f / _mapImageBlockWidthScale),
(int)(rect.Width / _mapImageBlockWidthScale), (int)(rect.Height / _mapImageBlockWidthScale));
}
return null;
}
public Point2f ConvertGenshinMapCoordinatesToImageCoordinates(Point2f? genshinMapCoordinates)
{
if (genshinMapCoordinates is Point2f p)
{
return new Point2f(MapOriginInImageCoordinate.X - p.X * _mapImageBlockWidthScale,
MapOriginInImageCoordinate.Y - p.Y * _mapImageBlockWidthScale);
}
return default;
}
public Rect ConvertGenshinMapCoordinatesToImageCoordinates(Rect? genshinMapRect)
{
if (genshinMapRect is Rect rect)
{
var (x, y) = ConvertGenshinMapCoordinatesToImageCoordinates(rect.GetCenterPoint());
return new Rect((int)Math.Round(x - rect.Width / 2f * _mapImageBlockWidthScale),
(int)Math.Round(y - rect.Height / 2f * _mapImageBlockWidthScale),
(int)Math.Round(rect.Width * _mapImageBlockWidthScale),
(int)Math.Round(rect.Height * _mapImageBlockWidthScale));
}
return default;
}
#endregion
}