minimap working much better
This commit is contained in:
parent
7fd80b1645
commit
07fe46c596
2 changed files with 18 additions and 64 deletions
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue