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 MinimapConfig _config;
private readonly IScreenCapture _backend; private readonly IScreenCapture _backend;
private readonly Queue<Mat> _frameBuffer = new();
public MinimapCapture(MinimapConfig config) public MinimapCapture(MinimapConfig config)
{ {
@ -68,28 +67,25 @@ public class MinimapCapture : IDisposable
var playerOffset = FindCentroid(playerMask); var playerOffset = FindCentroid(playerMask);
// --- 3. Wall mask: bright OR saturated → structure lines --- // --- 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); 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 --- // Raw walls used for everything: dedup, matching, and stitching.
var smoothed = TemporalSmooth(classified); // classified goes into ring buffer // 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) --- // --- 5. Gray for optical flow tracking (player zeroed) ---
var stableWallMask = new Mat();
Cv2.Compare(smoothed, new Scalar((byte)MapCell.Wall), stableWallMask, CmpType.EQ);
// --- 7. Gray for optical flow tracking (player zeroed) ---
var grayForCorr = new Mat(); var grayForCorr = new Mat();
Cv2.CvtColor(bgr, grayForCorr, ColorConversionCodes.BGR2GRAY); Cv2.CvtColor(bgr, grayForCorr, ColorConversionCodes.BGR2GRAY);
grayForCorr.SetTo(Scalar.Black, playerMask); grayForCorr.SetTo(Scalar.Black, playerMask);
return new MinimapFrame( return new MinimapFrame(
GrayMat: grayForCorr, GrayMat: grayForCorr,
WallMask: stableWallMask, WallMask: wallMask, // raw — for matching + dedup
ClassifiedMat: smoothed, ClassifiedMat: classified, // raw — for stitching (confidence filters noise)
PlayerOffset: playerOffset, PlayerOffset: playerOffset,
Timestamp: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() 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> /// <summary>
/// Capture a single frame and return the requested pipeline stage as PNG bytes. /// Capture a single frame and return the requested pipeline stage as PNG bytes.
/// </summary> /// </summary>
@ -294,7 +258,5 @@ public class MinimapCapture : IDisposable
public void Dispose() public void Dispose()
{ {
_backend.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 confRoi = new Mat(_confidence, dstRect);
var confInc = (short)_config.ConfidenceInc; var confInc = (short)_config.ConfidenceInc;
var confDec = (short)_config.ConfidenceDec;
var confThreshold = (short)_config.ConfidenceThreshold; var confThreshold = (short)_config.ConfidenceThreshold;
var confMax = (short)_config.ConfidenceMax; 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 row = 0; row < h; row++)
for (var col = 0; col < w; col++) for (var col = 0; col < w; col++)
{ {
var srcVal = srcRoi.At<byte>(row, col); var srcVal = srcRoi.At<byte>(row, col);
if (srcVal != (byte)MapCell.Wall) continue;
var conf = confRoi.At<short>(row, col); var conf = confRoi.At<short>(row, col);
conf = boosted
if (srcVal == (byte)MapCell.Wall) ? confMax
{ : Math.Min((short)(conf + confInc), confMax);
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);
}
confRoi.Set(row, col, conf); confRoi.Set(row, col, conf);
if (conf >= confThreshold) if (conf >= confThreshold)
dstRoi.Set(row, col, (byte)MapCell.Wall); 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 // Mark explored area: circle around player, only overwrite Unknown