mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-20 08:29:50 +08:00
206 lines
6.6 KiB
C#
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
|
|
}
|