fixes for noise
This commit is contained in:
parent
d2dab86544
commit
b9b9a41c3c
3 changed files with 61 additions and 71 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue