fixes for noise

This commit is contained in:
Boki 2026-02-13 14:35:46 -05:00
parent d2dab86544
commit b9b9a41c3c
3 changed files with 61 additions and 71 deletions

View file

@ -53,78 +53,51 @@ public class MinimapCapture : IDisposable
if (bgr == null || bgr.Empty())
return null;
// --- 1. HSV + extract S/V channels ---
using var hsv = new Mat();
Cv2.CvtColor(bgr, hsv, ColorConversionCodes.BGR2HSV);
using var satChan = new Mat();
using var valueChan = new Mat();
Cv2.ExtractChannel(hsv, satChan, 1); // S
Cv2.ExtractChannel(hsv, valueChan, 2); // V
// --- 2. Player mask (orange marker) ---
// Player mask (orange marker)
using var playerMask = new Mat();
Cv2.InRange(hsv, _config.PlayerLoHSV, _config.PlayerHiHSV, playerMask);
var playerOffset = FindCentroid(playerMask);
// --- 3. Wall mask: bright OR saturated → structure lines ---
var wallMask = BuildWallMask(satChan, valueChan, playerMask);
// Wall mask: target #A2AEE5 blue-lavender structure lines
var wallMask = BuildWallMask(hsv, playerMask);
// --- 4. Build classified mat (walls only — for stitching) ---
// Build classified mat (walls only — for stitching)
var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC1, Scalar.Black);
classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask);
// Raw walls used for everything: dedup, matching, and stitching.
// Temporal smoothing kills walls during movement (minimap scrolls between frames,
// 3/5 vote fails). The confidence system is the long-term noise filter instead.
// --- 5. Gray for optical flow tracking (player zeroed) ---
// Gray for correlation tracking (player zeroed)
var grayForCorr = new Mat();
Cv2.CvtColor(bgr, grayForCorr, ColorConversionCodes.BGR2GRAY);
grayForCorr.SetTo(Scalar.Black, playerMask);
return new MinimapFrame(
GrayMat: grayForCorr,
WallMask: wallMask, // raw — for matching + dedup
ClassifiedMat: classified, // raw — for stitching (confidence filters noise)
WallMask: wallMask,
ClassifiedMat: classified,
PlayerOffset: playerOffset,
Timestamp: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
);
}
private Mat BuildWallMask(Mat satChan, Mat valueChan, Mat playerMask)
private Mat BuildWallMask(Mat hsv, Mat playerMask)
{
// Wall = bright OR saturated pixels (minimap structure lines, icons)
using var highV = new Mat();
Cv2.Threshold(valueChan, highV, _config.WallMinValue, 255, ThresholdTypes.Binary);
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);
// Target wall color #A2AEE5 — HSV(115, 75, 229)
// This is map-independent: walls are always blue-lavender, fog is higher-saturation blue
var wallMask = new Mat();
Cv2.BitwiseOr(highV, highS, wallMask);
Cv2.InRange(hsv, _config.WallLoHSV, _config.WallHiHSV, 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);
// Subtract player (bright/saturated marker would otherwise be classified as wall)
// Subtract player marker (orange overlaps blue range slightly on some maps)
using var notPlayer = new Mat();
Cv2.BitwiseNot(playerMask, notPlayer);
Cv2.BitwiseAnd(wallMask, notPlayer, wallMask);
// Dilate to connect thin wall line fragments
using var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));
Cv2.Dilate(wallMask, wallMask, kernel);
FilterSmallComponents(wallMask, _config.WallMinArea);
return wallMask;
}
@ -176,16 +149,11 @@ public class MinimapCapture : IDisposable
return result;
}
using var satChan = new Mat();
using var valueChan = new Mat();
Cv2.ExtractChannel(hsv, satChan, 1);
Cv2.ExtractChannel(hsv, valueChan, 2);
using var playerMask = new Mat();
Cv2.InRange(hsv, _config.PlayerLoHSV, _config.PlayerHiHSV, playerMask);
if (stage == MinimapDebugStage.Player) return EncodePng(playerMask);
using var wallMask = BuildWallMask(satChan, valueChan, playerMask);
using var wallMask = BuildWallMask(hsv, playerMask);
if (stage == MinimapDebugStage.Walls) return EncodePng(wallMask);
// Classified (walls + player only — explored is tracked by WorldMap)
@ -214,15 +182,10 @@ public class MinimapCapture : IDisposable
using var hsv = new Mat();
Cv2.CvtColor(bgr, hsv, ColorConversionCodes.BGR2HSV);
using var satChan = new Mat();
using var valueChan = new Mat();
Cv2.ExtractChannel(hsv, satChan, 1);
Cv2.ExtractChannel(hsv, valueChan, 2);
using var playerMask = new Mat();
Cv2.InRange(hsv, _config.PlayerLoHSV, _config.PlayerHiHSV, playerMask);
using var wallMask = BuildWallMask(satChan, valueChan, playerMask);
using var wallMask = BuildWallMask(hsv, playerMask);
// Colorized classified (walls + player)
using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black);

View file

@ -70,13 +70,10 @@ public class MinimapConfig
public Scalar PlayerLoHSV { get; set; } = new(5, 80, 80);
public Scalar PlayerHiHSV { get; set; } = new(25, 255, 255);
// Wall detection: bright OR saturated pixels (minimap structure lines, icons)
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;
// Wall detection: target #A2AEE5 (blue-lavender structure lines)
// HSV(115, 75, 229) — blue hue, low saturation, bright
public Scalar WallLoHSV { get; set; } = new(100, 20, 150);
public Scalar WallHiHSV { get; set; } = new(130, 140, 255);
// Connected components: minimum area to keep (kills speckle)
public int WallMinArea { get; set; } = 30;
@ -116,6 +113,10 @@ public class MinimapConfig
// Frame dedup: min changed pixels to process a frame (skip near-identical minimap frames)
public int FrameChangeThreshold { get; set; } = 50;
// Noise gate: skip frames where wall pixels exceed this fraction of capture area
// Normal walls ~2-5%, waypoint glow / effects spike to 10%+
public double WallMaxDensity { get; set; } = 0.08;
// Stuck detection
public double StuckThreshold { get; set; } = 2.0;
public int StuckFrameCount { get; set; } = 5;

View file

@ -33,6 +33,17 @@ public class WorldMap : IDisposable
var sw = Stopwatch.StartNew();
_frameCount++;
// Noise gate: skip frames with too many wall pixels (waypoint glow, effects)
var wallPixels = Cv2.CountNonZero(wallMask);
var totalPixels = wallMask.Width * wallMask.Height;
var density = (double)wallPixels / totalPixels;
if (density > _config.WallMaxDensity)
{
Log.Information("Noise gate: {Density:P1} wall density ({Pixels} px), skipping ({Ms:F1}ms)",
density, wallPixels, sw.Elapsed.TotalMilliseconds);
return _position;
}
// Frame deduplication: skip if minimap hasn't scrolled yet
if (_prevWallMask != null && _frameCount > 1)
{
@ -173,27 +184,42 @@ public class WorldMap : IDisposable
var confRoi = new Mat(_confidence, dstRect);
var confInc = (short)_config.ConfidenceInc;
var confDec = (short)_config.ConfidenceDec;
var confThreshold = (short)_config.ConfidenceThreshold;
var confMax = (short)_config.ConfidenceMax;
// Only increment confidence for wall pixels. Don't decay on non-wall pixels because
// temporal smoothing (3/5 vote) kills walls during movement — the minimap scrolls so
// wall pixels shift across frames and fail the vote. "Not wall" in the smoothed mat
// during movement means "couldn't confirm" not "definitely not a wall".
// Wall pixels: increase confidence. Non-wall pixels in visible area: decay confidence.
// Real walls accumulate high confidence (40) and survive brief non-confirmation during
// movement. Transient noise (waypoint glow, effects) only reaches moderate confidence
// and gets removed as it decays.
for (var row = 0; row < h; row++)
for (var col = 0; col < w; col++)
{
var srcVal = srcRoi.At<byte>(row, col);
if (srcVal != (byte)MapCell.Wall) continue;
var conf = confRoi.At<short>(row, col);
conf = boosted
? confMax
: Math.Min((short)(conf + confInc), confMax);
if (srcVal == (byte)MapCell.Wall)
{
conf = boosted
? confMax
: Math.Min((short)(conf + confInc), confMax);
}
else if (conf > 0)
{
// Visible area, not a wall → slow decay
conf = Math.Max((short)(conf - confDec), (short)0);
}
else
{
continue; // nothing to update
}
confRoi.Set(row, col, conf);
if (conf >= confThreshold)
dstRoi.Set(row, col, (byte)MapCell.Wall);
else if (dstRoi.At<byte>(row, col) == (byte)MapCell.Wall)
dstRoi.Set(row, col, (byte)MapCell.Explored); // lost confidence → demote
}
// Mark explored area: circle around player, only overwrite Unknown