128 lines
4.3 KiB
C#
128 lines
4.3 KiB
C#
using OpenCvSharp;
|
|
using Serilog;
|
|
|
|
namespace Poe2Trade.Navigation;
|
|
|
|
/// <summary>
|
|
/// Detects minimap icons (doors, checkpoints) via template matching.
|
|
/// Loads RGBA templates once, converts to grayscale for matching.
|
|
/// </summary>
|
|
internal class IconDetector : IDisposable
|
|
{
|
|
private readonly Mat _doorTemplate;
|
|
private readonly Mat _checkpointOffTemplate;
|
|
private readonly Mat _checkpointOnTemplate;
|
|
|
|
private const double DoorThreshold = 0.65;
|
|
private const double CheckpointThreshold = 0.75;
|
|
|
|
public IconDetector(string assetsDir)
|
|
{
|
|
_doorTemplate = LoadGray(Path.Combine(assetsDir, "door.png"));
|
|
_checkpointOffTemplate = LoadGray(Path.Combine(assetsDir, "checkpoint-off.png"));
|
|
_checkpointOnTemplate = LoadGray(Path.Combine(assetsDir, "checkpoint-on.png"));
|
|
|
|
Log.Information("IconDetector loaded: door={DW}x{DH} cpOff={OW}x{OH} cpOn={NW}x{NH}",
|
|
_doorTemplate.Width, _doorTemplate.Height,
|
|
_checkpointOffTemplate.Width, _checkpointOffTemplate.Height,
|
|
_checkpointOnTemplate.Width, _checkpointOnTemplate.Height);
|
|
}
|
|
|
|
private static Mat LoadGray(string path)
|
|
{
|
|
var bgra = Cv2.ImRead(path, ImreadModes.Unchanged);
|
|
if (bgra.Empty())
|
|
throw new FileNotFoundException($"Icon template not found: {path}");
|
|
|
|
var gray = new Mat();
|
|
if (bgra.Channels() == 4)
|
|
{
|
|
using var bgr = new Mat();
|
|
Cv2.CvtColor(bgra, bgr, ColorConversionCodes.BGRA2BGR);
|
|
Cv2.CvtColor(bgr, gray, ColorConversionCodes.BGR2GRAY);
|
|
}
|
|
else if (bgra.Channels() == 3)
|
|
{
|
|
Cv2.CvtColor(bgra, gray, ColorConversionCodes.BGR2GRAY);
|
|
}
|
|
else
|
|
{
|
|
gray = bgra.Clone();
|
|
}
|
|
|
|
bgra.Dispose();
|
|
return gray;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detect all door icons in the frame. Returns center points in frame coords.
|
|
/// </summary>
|
|
public List<Point> DetectDoors(Mat grayFrame)
|
|
=> Detect(grayFrame, _doorTemplate, DoorThreshold);
|
|
|
|
/// <summary>
|
|
/// Detect all inactive checkpoint icons. Returns center points in frame coords.
|
|
/// </summary>
|
|
public List<Point> DetectCheckpointsOff(Mat grayFrame)
|
|
=> Detect(grayFrame, _checkpointOffTemplate, CheckpointThreshold);
|
|
|
|
/// <summary>
|
|
/// Detect all active checkpoint icons. Returns center points in frame coords.
|
|
/// </summary>
|
|
public List<Point> DetectCheckpointsOn(Mat grayFrame)
|
|
=> Detect(grayFrame, _checkpointOnTemplate, CheckpointThreshold);
|
|
|
|
/// <summary>
|
|
/// Greedy multi-match: find best match, record center, zero out neighborhood, repeat.
|
|
/// </summary>
|
|
private static List<Point> Detect(Mat grayFrame, Mat template, double threshold)
|
|
{
|
|
var results = new List<Point>();
|
|
|
|
if (grayFrame.Width < template.Width || grayFrame.Height < template.Height)
|
|
return results;
|
|
|
|
using var result = new Mat();
|
|
Cv2.MatchTemplate(grayFrame, template, result, TemplateMatchModes.CCoeffNormed);
|
|
|
|
var tw = template.Width;
|
|
var th = template.Height;
|
|
|
|
while (true)
|
|
{
|
|
Cv2.MinMaxLoc(result, out _, out var maxVal, out _, out var maxLoc);
|
|
if (maxVal < threshold)
|
|
break;
|
|
|
|
// Record center point of the matched region
|
|
var centerX = maxLoc.X + tw / 2;
|
|
var centerY = maxLoc.Y + th / 2;
|
|
results.Add(new Point(centerX, centerY));
|
|
|
|
// Zero out neighborhood to prevent re-detection
|
|
var zeroX = Math.Max(0, maxLoc.X - tw / 2);
|
|
var zeroY = Math.Max(0, maxLoc.Y - th / 2);
|
|
var zeroW = Math.Min(tw * 2, result.Width - zeroX);
|
|
var zeroH = Math.Min(th * 2, result.Height - zeroY);
|
|
if (zeroW > 0 && zeroH > 0)
|
|
{
|
|
using var roi = new Mat(result, new Rect(zeroX, zeroY, zeroW, zeroH));
|
|
roi.SetTo(Scalar.Black);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the template size for doors (used to stamp wall regions).
|
|
/// </summary>
|
|
public Size DoorSize => new(_doorTemplate.Width, _doorTemplate.Height);
|
|
|
|
public void Dispose()
|
|
{
|
|
_doorTemplate.Dispose();
|
|
_checkpointOffTemplate.Dispose();
|
|
_checkpointOnTemplate.Dispose();
|
|
}
|
|
}
|