minimap working much better

This commit is contained in:
Boki 2026-02-13 13:48:31 -05:00
parent 7fd80b1645
commit 07fe46c596
2 changed files with 18 additions and 64 deletions

View file

@ -9,7 +9,6 @@ public class MinimapCapture : IDisposable
{
private readonly MinimapConfig _config;
private readonly IScreenCapture _backend;
private readonly Queue<Mat> _frameBuffer = new();
public MinimapCapture(MinimapConfig config)
{
@ -68,28 +67,25 @@ public class MinimapCapture : IDisposable
var playerOffset = FindCentroid(playerMask);
// --- 3. Wall mask: bright OR saturated → structure lines ---
using var rawWallMask = BuildWallMask(satChan, valueChan, playerMask);
var wallMask = BuildWallMask(satChan, valueChan, playerMask);
// --- 4. Build classified mat (walls only — explored is tracked by WorldMap) ---
// --- 4. 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), rawWallMask);
classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask);
// --- 5. Temporal smoothing: majority vote on walls ---
var smoothed = TemporalSmooth(classified); // classified goes into ring buffer
// 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.
// --- 6. Extract smoothed wall mask for tracking (filters transient noise) ---
var stableWallMask = new Mat();
Cv2.Compare(smoothed, new Scalar((byte)MapCell.Wall), stableWallMask, CmpType.EQ);
// --- 7. Gray for optical flow tracking (player zeroed) ---
// --- 5. Gray for optical flow tracking (player zeroed) ---
var grayForCorr = new Mat();
Cv2.CvtColor(bgr, grayForCorr, ColorConversionCodes.BGR2GRAY);
grayForCorr.SetTo(Scalar.Black, playerMask);
return new MinimapFrame(
GrayMat: grayForCorr,
WallMask: stableWallMask,
ClassifiedMat: smoothed,
WallMask: wallMask, // raw — for matching + dedup
ClassifiedMat: classified, // raw — for stitching (confidence filters noise)
PlayerOffset: playerOffset,
Timestamp: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
);
@ -157,38 +153,6 @@ public class MinimapCapture : IDisposable
}
}
private Mat TemporalSmooth(Mat classified)
{
// Add raw frame to ring buffer (takes ownership)
_frameBuffer.Enqueue(classified);
while (_frameBuffer.Count > _config.TemporalFrameCount)
_frameBuffer.Dequeue().Dispose();
// Not enough frames yet — return as-is
if (_frameBuffer.Count < 2)
return classified.Clone();
var size = classified.Size();
using var wallCount = new Mat(size, MatType.CV_8UC1, Scalar.Black);
foreach (var frame in _frameBuffer)
{
using var isWall = new Mat();
Cv2.Compare(frame, new Scalar((byte)MapCell.Wall), isWall, CmpType.EQ);
Cv2.Add(wallCount, new Scalar(1), wallCount, isWall);
}
// Apply vote threshold
using var wallPass = new Mat();
Cv2.Threshold(wallCount, wallPass,
_config.WallTemporalThreshold - 0.5, 255, ThresholdTypes.Binary);
var result = new Mat(size, MatType.CV_8UC1, Scalar.Black);
result.SetTo(new Scalar((byte)MapCell.Wall), wallPass);
return result;
}
/// <summary>
/// Capture a single frame and return the requested pipeline stage as PNG bytes.
/// </summary>
@ -294,7 +258,5 @@ public class MinimapCapture : IDisposable
public void Dispose()
{
_backend.Dispose();
while (_frameBuffer.Count > 0)
_frameBuffer.Dequeue().Dispose();
}
}

View file

@ -173,35 +173,27 @@ 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".
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);
if (srcVal == (byte)MapCell.Wall)
{
if (boosted)
conf = confMax; // warmup: max confidence so walls survive initial movement
else
conf = Math.Min((short)(conf + confInc), confMax);
}
else
{
// Pixel is in visible area but not wall — decay confidence
conf = Math.Max((short)(conf - confDec), (short)0);
}
conf = boosted
? confMax
: Math.Min((short)(conf + confInc), confMax);
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, downgrade
}
// Mark explored area: circle around player, only overwrite Unknown