Files
better-genshin-impact/BetterGenshinImpact/GameTask/Common/Map/MiniMap/CameraOrientationCalculator.cs
Juemin Lin 4b5f229006 分层地图的模板匹配坐标识别以及相机视角识别优化和小地图预处理 (#1730)
* 小地图预处理和视角识别算法优化

* 模板匹配的相关类,包括快速带遮罩的SqDiff模板匹配,模板匹配归一化类,简易亚像素模板匹配实现,小地图匹配相关配置,和小地图匹配上下文。

* 实现小地图的分层地图模板匹配,修改 SceneBaseMap 的 GetMiniMapPosition 为 virtual 以便继承覆盖。
2025-06-24 10:39:16 +08:00

107 lines
4.6 KiB
C#

using System.Linq;
using OpenCvSharp;
using System;
namespace BetterGenshinImpact.GameTask.Common.Map.MiniMap;
using static MiniMapPreprocessorUtils;
public class CameraOrientationCalculator : IDisposable
{
private const int TplOutRad = 78;
private const int TplInnRad = 19;
private const int RLength = 60;
private const int ThetaLength = 180;
private const int FLength = 256;
private readonly Mat _rotationRemapDataX = new Mat();
private readonly Mat _rotationRemapDataY = new Mat();
private readonly Mat _alphaMask2Remap;
public CameraOrientationCalculator()
{
float[] rArray = LinearSpaced(TplInnRad, TplOutRad, RLength);
float[] thetaArray = LinearSpaced(0, 360, ThetaLength, false);
using (var r = Mat.FromPixelData(1, RLength, MatType.CV_32F, rArray))
using (var theta = Mat.FromPixelData(ThetaLength, 1, MatType.CV_32F, thetaArray))
using (var rMat = r.Repeat(ThetaLength, 1))
using (var thetaMat = theta.Repeat(1, RLength))
{
Cv2.PolarToCart(rMat, thetaMat, _rotationRemapDataX, _rotationRemapDataY, true);
}
Cv2.Add(_rotationRemapDataX, Size / 2, _rotationRemapDataX);
Cv2.Add(_rotationRemapDataY, Size / 2, _rotationRemapDataY);
using (var alphaMask2Row = Mat.FromPixelData(1, RLength, MatType.CV_32F, rArray.Select(v => (float)(111.7 + 1.836 * v)).ToArray()))
{
_alphaMask2Remap = alphaMask2Row.Repeat(ThetaLength, 1);
}
}
public (float, float) PredictRotation(Mat outMat)
{
using var remap = new Mat();
using var hImg = new Mat();
using var faImg = new Mat();
using var fbImg = new Mat();
using var histA = new Mat();
using var histB = new Mat();
using var result = Mat.Zeros(1, ThetaLength, MatType.CV_32FC1).ToMat();
using var resultShift = new Mat();
using var mask = new Mat();
using var maskSum = new Mat();
Cv2.Remap(outMat, remap, _rotationRemapDataX, _rotationRemapDataY, InterpolationFlags.Nearest);
Cv2.InRange(remap, new Scalar(0, 0, 0), new Scalar(1, 1, 1), mask);
Cv2.BitwiseNot(mask,mask);
Cv2.Reduce(mask, maskSum, ReduceDimension.Column, ReduceTypes.Sum, MatType.CV_32F);
Cv2.Transpose(maskSum, maskSum);
BgrToHue(remap, hImg, faImg);
faImg.CopyTo(fbImg);
ApplyMask(fbImg, _alphaMask2Remap, new Scalar(255.0f));
int[] channels = [0, 1];
int[] histSize = [ThetaLength, FLength];
Rangef[] ranges = [new(0, 180), new(0, 256)];
Cv2.CalcHist([hImg, faImg], channels, null, histA, 2, histSize, ranges);
Cv2.CalcHist([hImg, fbImg], channels, null, histB, 2, histSize, ranges);
unsafe
{
var hImgPtr = (float*)hImg.Data;
var faImgPtr = (float*)faImg.Data;
var fbImgPtr = (float*)fbImg.Data;
var histAPtr = (float*)histA.Data;
var histBPtr = (float*)histB.Data;
var resultPtr = (float*)result.Data;
var totalElements = ThetaLength * RLength;
for (var i = 0; i < totalElements; i++)
{
float h = hImgPtr[i];
float fa = faImgPtr[i];
float fb = fbImgPtr[i];
if (h is < 0 or >= 180 || fa is < 0 or >= 256 || fb is < 0 or >= 256) continue;
float ha = histAPtr[(int)(h / 180 * ThetaLength) * FLength + (int)(fa / 256 * FLength)];
float hb = histBPtr[(int)(h / 180 * ThetaLength) * FLength + (int)(fb / 256 * FLength)];
resultPtr[i / RLength] += (ha > hb) ? 0.0f :
(Math.Abs(ha - hb) < 0.0001) ? 60.0f :
255.0f;
}
}
Cv2.Divide(result, maskSum, result);
Cv2.Resize(result, result, new Size(result.Width * 2, 1), 0, 0, InterpolationFlags.Cubic);
var peakWidth = ThetaLength / 2 + 1;
RightShiftCv(result, resultShift, peakWidth);
var peakRegionSum = Cv2.Sum(resultShift[0, 1, 0, peakWidth]).Val0;
Cv2.Subtract(result, resultShift, resultShift);
Cv2.Integral(resultShift, result);
Cv2.MinMaxLoc(result, out _, out var maxVal, out _, out var maxLoc);
var degree = (float)(maxLoc.X - 1) / ThetaLength * 180 - 45;
var rotationConfidence = (float)(maxVal + peakRegionSum) / (peakWidth * RLength * 255);
return (degree, rotationConfidence);
}
public void Dispose()
{
_rotationRemapDataX.Dispose();
_rotationRemapDataY.Dispose();
_alphaMask2Remap.Dispose();
}
}