added filter for text

This commit is contained in:
Boki 2026-02-13 13:27:46 -05:00
parent dab5735c80
commit 7fd80b1645
3 changed files with 36 additions and 8 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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()