better
This commit is contained in:
parent
2565028ad0
commit
23c581cff9
6 changed files with 41 additions and 54 deletions
|
|
@ -14,7 +14,7 @@ internal class IconDetector : IDisposable
|
|||
private readonly Mat _checkpointOnTemplate;
|
||||
|
||||
private const double DoorThreshold = 0.65;
|
||||
private const double CheckpointThreshold = 0.60;
|
||||
private const double CheckpointThreshold = 0.75;
|
||||
|
||||
public IconDetector(string assetsDir)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ public class MinimapCapture : IFrameConsumer, IDisposable
|
|||
|
||||
public MinimapMode DetectedMode => _detectedMode;
|
||||
public MinimapFrame? LastFrame => _lastFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Atomically take ownership of the last frame (sets _lastFrame to null).
|
||||
/// Caller is responsible for disposing the returned frame.
|
||||
/// </summary>
|
||||
public MinimapFrame? TakeFrame() => Interlocked.Exchange(ref _lastFrame, null);
|
||||
|
||||
public event Action<MinimapMode>? ModeChanged;
|
||||
|
||||
public MinimapCapture(MinimapConfig config, IScreenCapture backend, string? assetsDir = null)
|
||||
|
|
@ -259,6 +266,22 @@ public class MinimapCapture : IFrameConsumer, IDisposable
|
|||
Cv2.BitwiseNot(playerMask, notPlayer);
|
||||
Cv2.BitwiseAnd(wallMask, notPlayer, wallMask);
|
||||
|
||||
// Overlay mode: exclude game text labels bleeding through the minimap.
|
||||
// Text color #E0E0F6 → HSV(120, 23, 246): same blue hue as walls but
|
||||
// much lower saturation. AA edges blend with the background pushing S
|
||||
// above the wall floor. Detect the text core, dilate to cover the AA
|
||||
// fringe, then subtract from wall mask before morphology amplifies it.
|
||||
if (!isCorner)
|
||||
{
|
||||
using var textMask = new Mat();
|
||||
Cv2.InRange(hsv, new Scalar(0, 0, 200), new Scalar(180, 50, 255), textMask);
|
||||
using var textKernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(11, 11));
|
||||
Cv2.Dilate(textMask, textMask, textKernel);
|
||||
using var notText = new Mat();
|
||||
Cv2.BitwiseNot(textMask, notText);
|
||||
Cv2.BitwiseAnd(wallMask, notText, wallMask);
|
||||
}
|
||||
|
||||
if (sample && !isCorner)
|
||||
_colorTracker.SampleFrame(hsv, wallMask);
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ public class NavigationExecutor : IDisposable
|
|||
// 1. Capture + process via pipeline (single full-screen capture)
|
||||
SetState(NavigationState.Capturing);
|
||||
await _pipeline.ProcessOneFrame();
|
||||
var frame = _capture.LastFrame;
|
||||
using var frame = _capture.TakeFrame();
|
||||
if (frame == null)
|
||||
{
|
||||
Log.Warning("Failed to capture minimap frame");
|
||||
|
|
@ -489,7 +489,7 @@ public class NavigationExecutor : IDisposable
|
|||
{
|
||||
Log.Warning(ex, "Pipeline capture failed");
|
||||
}
|
||||
var frame = _capture.LastFrame;
|
||||
using var frame = _capture.TakeFrame();
|
||||
var captureMs = sw.Elapsed.TotalMilliseconds - captureStart;
|
||||
|
||||
if (frame == null)
|
||||
|
|
|
|||
|
|
@ -141,11 +141,11 @@ public class MinimapConfig
|
|||
public double MatchConfidence { get; set; } = 0.25;
|
||||
|
||||
// Wall confidence (canvas-level): per-pixel counters to filter transient noise
|
||||
// Walls need ceil(Threshold/Inc) = 2 frames of reinforcement to appear.
|
||||
// Walls need ceil(Threshold/Inc) = 3 frames of reinforcement to appear.
|
||||
// Block noise filter handles glow; confidence just smooths frame-to-frame jitter.
|
||||
public int ConfidenceInc { get; set; } = 6;
|
||||
public int ConfidenceInc { get; set; } = 5;
|
||||
public int ConfidenceDec { get; set; } = 1;
|
||||
public int ConfidenceThreshold { get; set; } = 10;
|
||||
public int ConfidenceThreshold { get; set; } = 13;
|
||||
public int ConfidenceMax { get; set; } = 30;
|
||||
public int WarmupFrames { get; set; } = 5;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ namespace Poe2Trade.Navigation;
|
|||
/// </summary>
|
||||
internal record BfsResult(
|
||||
List<Point> Path, // canvas coords: player → target frontier (subsampled)
|
||||
List<Point> BestFrontier, // frontier cells in the chosen direction (canvas coords)
|
||||
List<Point> OtherFrontier, // frontier cells in other directions (canvas coords)
|
||||
double DirX, double DirY, // chosen direction (unit vector)
|
||||
int PlayerCx, int PlayerCy // player position on canvas
|
||||
);
|
||||
|
|
@ -130,8 +128,6 @@ internal class PathFinder
|
|||
var bestClusterScore = -1.0;
|
||||
var bestEntryGx = -1;
|
||||
var bestEntryGy = -1;
|
||||
List<Point>? bestFrontierPts = null;
|
||||
var otherFrontier = new List<Point>();
|
||||
var clusterQueue = new Queue<(int gx, int gy)>(256);
|
||||
|
||||
foreach (var (fgx, fgy) in frontierCells)
|
||||
|
|
@ -179,27 +175,13 @@ internal class PathFinder
|
|||
var cost = (int)minDist;
|
||||
var score = gain / (cost + 1.0);
|
||||
|
||||
// Convert cluster cells to canvas coords
|
||||
var pts = new List<Point>(gain);
|
||||
foreach (var (cgx, cgy) in clusterCells)
|
||||
pts.Add(new Point(cx + (cgx - rr) * step, cy + (cgy - rr) * step));
|
||||
|
||||
if (score > bestClusterScore)
|
||||
{
|
||||
// Demote previous best to "other"
|
||||
if (bestFrontierPts != null)
|
||||
otherFrontier.AddRange(bestFrontierPts);
|
||||
|
||||
bestClusterScore = score;
|
||||
bestClusterGain = gain;
|
||||
bestClusterCost = cost;
|
||||
bestEntryGx = entryGx;
|
||||
bestEntryGy = entryGy;
|
||||
bestFrontierPts = pts;
|
||||
}
|
||||
else
|
||||
{
|
||||
otherFrontier.AddRange(pts);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +219,7 @@ internal class PathFinder
|
|||
dirY /= len;
|
||||
|
||||
// Step F: Store result for visualization
|
||||
LastResult = new BfsResult(path, bestFrontierPts!, otherFrontier, dirX, dirY, cx, cy);
|
||||
LastResult = new BfsResult(path, dirX, dirY, cx, cy);
|
||||
|
||||
Log.Debug("BFS: {ClusterCount} clusters, best={Gain} cells cost={Cost} score={Score:F1}, dir=({Dx:F2},{Dy:F2}), path={PathLen} waypoints",
|
||||
clusterCount, bestClusterGain, bestClusterCost, bestClusterScore, dirX, dirY, path.Count);
|
||||
|
|
@ -358,8 +340,8 @@ internal class PathFinder
|
|||
dirX /= len;
|
||||
dirY /= len;
|
||||
|
||||
// Store result for visualization (reuse BfsResult — frontiers empty for target mode)
|
||||
LastResult = new BfsResult(path, [], [], dirX, dirY, cx, cy);
|
||||
// Store result for visualization
|
||||
LastResult = new BfsResult(path, dirX, dirY, cx, cy);
|
||||
|
||||
Log.Debug("BFS target: path={PathLen} waypoints to ({TX},{TY}), dir=({Dx:F2},{Dy:F2})",
|
||||
path.Count, target.X, target.Y, dirX, dirY);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public class WorldMap : IDisposable
|
|||
if (wallCountAfter < 50)
|
||||
{
|
||||
_frameCount--;
|
||||
Log.Information("Warmup waiting for minimap ({Ms:F1}ms)", sw.Elapsed.TotalMilliseconds);
|
||||
Log.Debug("Warmup waiting for minimap ({Ms:F1}ms)", sw.Elapsed.TotalMilliseconds);
|
||||
return _position;
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ public class WorldMap : IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Information("Warmup frame {N}/{Total}: walls={WallsBefore}→{WallsAfter}(filtered) frameSize={FS} stitch={Ms:F1}ms",
|
||||
Log.Debug("Warmup frame {N}/{Total}: walls={WallsBefore}→{WallsAfter}(filtered) frameSize={FS} stitch={Ms:F1}ms",
|
||||
_frameCount, _config.WarmupFrames, wallCountBefore, wallCountAfter,
|
||||
wallMask.Width, sw.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ public class WorldMap : IDisposable
|
|||
{
|
||||
_consecutiveMatchFails++;
|
||||
LastMatchSucceeded = false;
|
||||
Log.Information("MatchAndStitch: dedup={Dedup:F1}ms match={Match:F1}ms (FAILED x{Fails}) total={Total:F1}ms",
|
||||
Log.Debug("MatchAndStitch: dedup={Dedup:F1}ms match={Match:F1}ms (FAILED x{Fails}) total={Total:F1}ms",
|
||||
dedupMs, matchMs, _consecutiveMatchFails, sw.Elapsed.TotalMilliseconds);
|
||||
return _position; // don't stitch — wrong position would corrupt the canvas
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ public class WorldMap : IDisposable
|
|||
var cleanFraction = FilterNoisyBlocks(wallMask, classifiedMat);
|
||||
if (cleanFraction < 0.25)
|
||||
{
|
||||
Log.Information("Noise filter: {Clean:P0} clean, skipping ({Ms:F1}ms)",
|
||||
Log.Debug("Noise filter: {Clean:P0} clean, skipping ({Ms:F1}ms)",
|
||||
cleanFraction, sw.Elapsed.TotalMilliseconds);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -228,14 +228,14 @@ public class WorldMap : IDisposable
|
|||
var frameWallCount = Cv2.CountNonZero(wallMask);
|
||||
if (frameWallCount < 50)
|
||||
{
|
||||
Log.Information("Match fail: too few frame walls ({FrameWalls}) frameSize={FS}",
|
||||
Log.Debug("Match fail: too few frame walls ({FrameWalls}) frameSize={FS}",
|
||||
frameWallCount, frameSize);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (canvasWallCount < 50)
|
||||
{
|
||||
Log.Information("Match fail: too few canvas walls ({CanvasWalls}) frame walls={FrameWalls} frameSize={FS} searchROI={SW}x{SH}",
|
||||
Log.Debug("Match fail: too few canvas walls ({CanvasWalls}) frame walls={FrameWalls} frameSize={FS} searchROI={SW}x{SH}",
|
||||
canvasWallCount, frameWallCount, frameSize, sw, sh);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -247,7 +247,7 @@ public class WorldMap : IDisposable
|
|||
|
||||
if (maxVal < _config.MatchConfidence)
|
||||
{
|
||||
Log.Information("Match fail: low confidence {Conf:F3} (need {Min:F2}) frameSize={FS} searchROI={SW}x{SH} canvas={CanvasWalls} frame={FrameWalls}",
|
||||
Log.Debug("Match fail: low confidence {Conf:F3} (need {Min:F2}) frameSize={FS} searchROI={SW}x{SH} canvas={CanvasWalls} frame={FrameWalls}",
|
||||
maxVal, _config.MatchConfidence, frameSize, sw, sh, canvasWallCount, frameWallCount);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ public class WorldMap : IDisposable
|
|||
|
||||
var deltaX = matchX - estimate.X;
|
||||
var deltaY = matchY - estimate.Y;
|
||||
Log.Information("Match OK: conf={Conf:F3} pos=({X:F1},{Y:F1}) delta=({Dx:F1},{Dy:F1}) frameSize={FS} searchROI={SW}x{SH} canvas={CanvasWalls} frame={FrameWalls}",
|
||||
Log.Debug("Match OK: conf={Conf:F3} pos=({X:F1},{Y:F1}) delta=({Dx:F1},{Dy:F1}) frameSize={FS} searchROI={SW}x{SH} canvas={CanvasWalls} frame={FrameWalls}",
|
||||
maxVal, matchX, matchY, deltaX, deltaY, frameSize, sw, sh, canvasWallCount, frameWallCount);
|
||||
|
||||
return new MapPosition(matchX, matchY);
|
||||
|
|
@ -467,28 +467,10 @@ public class WorldMap : IDisposable
|
|||
colored.Set(r, c, new Vec3b(120, 70, 40));
|
||||
}
|
||||
|
||||
// BFS overlay: frontier cells + direction line
|
||||
// BFS overlay: direction arrow + path line
|
||||
var bfs = _pathFinder.LastResult;
|
||||
if (bfs != null)
|
||||
{
|
||||
// Other frontier directions (dim cyan)
|
||||
foreach (var pt in bfs.OtherFrontier)
|
||||
{
|
||||
var vx = pt.X - x0;
|
||||
var vy = pt.Y - y0;
|
||||
if (vx >= 0 && vx < viewSize && vy >= 0 && vy < viewSize)
|
||||
colored.Set(vy, vx, new Vec3b(100, 80, 0));
|
||||
}
|
||||
|
||||
// Best frontier direction (bright green)
|
||||
foreach (var pt in bfs.BestFrontier)
|
||||
{
|
||||
var vx = pt.X - x0;
|
||||
var vy = pt.Y - y0;
|
||||
if (vx >= 0 && vx < viewSize && vy >= 0 && vy < viewSize)
|
||||
colored.Set(vy, vx, new Vec3b(0, 220, 0));
|
||||
}
|
||||
|
||||
// Direction line from player
|
||||
var px2 = bfs.PlayerCx - x0;
|
||||
var py2 = bfs.PlayerCy - y0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue