diff --git a/src/Poe2Trade.Navigation/MinimapCapture.cs b/src/Poe2Trade.Navigation/MinimapCapture.cs index 511f900..8289e4d 100644 --- a/src/Poe2Trade.Navigation/MinimapCapture.cs +++ b/src/Poe2Trade.Navigation/MinimapCapture.cs @@ -103,9 +103,23 @@ public class MinimapCapture : IDisposable using var highS = new Mat(); Cv2.Threshold(satChan, highS, _config.WallMinSat, 255, ThresholdTypes.Binary); + // Exclude nameplate text: very bright (V > 230) AND unsaturated (S < 40) + // Nameplates are pure white text; minimap walls are colored (have saturation) + using var nameplateBright = new Mat(); + Cv2.Threshold(valueChan, nameplateBright, _config.NameplateMinValue, 255, ThresholdTypes.Binary); + using var nameplateLowSat = new Mat(); + Cv2.Threshold(satChan, nameplateLowSat, _config.NameplateMaxSat, 255, ThresholdTypes.BinaryInv); + using var nameplateMask = new Mat(); + Cv2.BitwiseAnd(nameplateBright, nameplateLowSat, nameplateMask); + var wallMask = new Mat(); Cv2.BitwiseOr(highV, highS, wallMask); + // Subtract nameplates + using var notNameplate = new Mat(); + Cv2.BitwiseNot(nameplateMask, notNameplate); + Cv2.BitwiseAnd(wallMask, notNameplate, wallMask); + // Dilate to connect wall line fragments using var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)); Cv2.Dilate(wallMask, wallMask, kernel); diff --git a/src/Poe2Trade.Navigation/NavigationTypes.cs b/src/Poe2Trade.Navigation/NavigationTypes.cs index 42179f4..de81472 100644 --- a/src/Poe2Trade.Navigation/NavigationTypes.cs +++ b/src/Poe2Trade.Navigation/NavigationTypes.cs @@ -74,6 +74,10 @@ public class MinimapConfig public int WallMinValue { get; set; } = 200; public int WallMinSat { get; set; } = 150; + // Nameplate filter: exclude bright + unsaturated pixels (white text like "SALVAGE BENCH") + public int NameplateMinValue { get; set; } = 230; + public int NameplateMaxSat { get; set; } = 40; + // Connected components: minimum area to keep (kills speckle) public int WallMinArea { get; set; } = 30; @@ -98,7 +102,7 @@ public class MinimapConfig public int MatchSearchRadius { get; set; } = 100; // Template matching: minimum correlation confidence to accept a match - public double MatchConfidence { get; set; } = 0.3; + public double MatchConfidence { get; set; } = 0.15; // Wall confidence (canvas-level): per-pixel counters to filter transient noise public int ConfidenceInc { get; set; } = 3; diff --git a/src/Poe2Trade.Navigation/WorldMap.cs b/src/Poe2Trade.Navigation/WorldMap.cs index 31933da..746079a 100644 --- a/src/Poe2Trade.Navigation/WorldMap.cs +++ b/src/Poe2Trade.Navigation/WorldMap.cs @@ -11,6 +11,7 @@ public class WorldMap : IDisposable private readonly Mat _confidence; // CV_16SC1: per-pixel wall confidence counter private MapPosition _position; private int _frameCount; + private int _consecutiveMatchFails; private Mat? _prevWallMask; // for frame deduplication public MapPosition Position => _position; @@ -68,11 +69,13 @@ public class WorldMap : IDisposable if (matched == null) { - Log.Information("MatchAndStitch: dedup={Dedup:F1}ms match={Match:F1}ms (FAILED) total={Total:F1}ms", - dedupMs, matchMs, sw.Elapsed.TotalMilliseconds); - return _position; // skip stitching entirely on failed match + _consecutiveMatchFails++; + Log.Information("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 } + _consecutiveMatchFails = 0; _position = matched; var stitchStart = sw.Elapsed.TotalMilliseconds; StitchWithConfidence(classifiedMat, _position, boosted: false); @@ -115,8 +118,13 @@ public class WorldMap : IDisposable // Check if canvas has enough walls to match against var canvasWallCount = Cv2.CountNonZero(canvasWalls); + var frameWallCount = Cv2.CountNonZero(wallMask); if (canvasWallCount < 50) + { + Log.Information("Match fail: too few canvas walls ({CanvasWalls}) frame walls={FrameWalls}", + canvasWallCount, frameWallCount); return null; + } // Template match: find where frame's walls best align with canvas walls using var result = new Mat(); @@ -125,7 +133,8 @@ public class WorldMap : IDisposable if (maxVal < _config.MatchConfidence) { - Log.Debug("Map match low confidence: {Conf:F3}", maxVal); + Log.Information("Match fail: low confidence {Conf:F3} (need {Min:F2}) canvas={CanvasWalls} frame={FrameWalls}", + maxVal, _config.MatchConfidence, canvasWallCount, frameWallCount); return null; } @@ -134,8 +143,8 @@ public class WorldMap : IDisposable var matchX = sx0 + maxLoc.X + frameSize / 2.0; var matchY = sy0 + maxLoc.Y + frameSize / 2.0; - Log.Debug("Map match: ({X:F1}, {Y:F1}) conf={Conf:F3} walls={Walls}", - matchX, matchY, maxVal, canvasWallCount); + Log.Debug("Map match: ({X:F1}, {Y:F1}) conf={Conf:F3} canvas={CanvasWalls} frame={FrameWalls}", + matchX, matchY, maxVal, canvasWallCount, frameWallCount); return new MapPosition(matchX, matchY); } @@ -177,7 +186,7 @@ public class WorldMap : IDisposable if (srcVal == (byte)MapCell.Wall) { if (boosted) - conf = confThreshold; // warmup: immediately at threshold + conf = confMax; // warmup: max confidence so walls survive initial movement else conf = Math.Min((short)(conf + confInc), confMax); } @@ -312,6 +321,7 @@ public class WorldMap : IDisposable _prevWallMask = null; _position = new MapPosition(_config.CanvasSize / 2.0, _config.CanvasSize / 2.0); _frameCount = 0; + _consecutiveMatchFails = 0; } public void Dispose()