getting closer
This commit is contained in:
parent
40f013d07e
commit
bb8e75a2f5
2 changed files with 70 additions and 10 deletions
|
|
@ -95,11 +95,21 @@ public class MinimapConfig
|
||||||
public int CanvasSize { get; set; } = 4000;
|
public int CanvasSize { get; set; } = 4000;
|
||||||
|
|
||||||
// Template matching: search radius around current position estimate (pixels)
|
// Template matching: search radius around current position estimate (pixels)
|
||||||
public int MatchSearchRadius { get; set; } = 50;
|
public int MatchSearchRadius { get; set; } = 100;
|
||||||
|
|
||||||
// Template matching: minimum correlation confidence to accept a match
|
// Template matching: minimum correlation confidence to accept a match
|
||||||
public double MatchConfidence { get; set; } = 0.3;
|
public double MatchConfidence { get; set; } = 0.3;
|
||||||
|
|
||||||
|
// Wall confidence (canvas-level): per-pixel counters to filter transient noise
|
||||||
|
public int ConfidenceInc { get; set; } = 3;
|
||||||
|
public int ConfidenceDec { get; set; } = 1;
|
||||||
|
public int ConfidenceThreshold { get; set; } = 8;
|
||||||
|
public int ConfidenceMax { get; set; } = 30;
|
||||||
|
public int WarmupFrames { get; set; } = 5;
|
||||||
|
|
||||||
|
// Frame dedup: min changed pixels to process a frame (skip near-identical minimap frames)
|
||||||
|
public int FrameChangeThreshold { get; set; } = 50;
|
||||||
|
|
||||||
// Stuck detection
|
// Stuck detection
|
||||||
public double StuckThreshold { get; set; } = 2.0;
|
public double StuckThreshold { get; set; } = 2.0;
|
||||||
public int StuckFrameCount { get; set; } = 5;
|
public int StuckFrameCount { get; set; } = 5;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ public class WorldMap : IDisposable
|
||||||
{
|
{
|
||||||
private readonly MinimapConfig _config;
|
private readonly MinimapConfig _config;
|
||||||
private readonly Mat _canvas;
|
private readonly Mat _canvas;
|
||||||
|
private readonly Mat _confidence; // CV_16SC1: per-pixel wall confidence counter
|
||||||
private MapPosition _position;
|
private MapPosition _position;
|
||||||
private int _frameCount;
|
private int _frameCount;
|
||||||
|
private Mat? _prevWallMask; // for frame deduplication
|
||||||
|
|
||||||
public MapPosition Position => _position;
|
public MapPosition Position => _position;
|
||||||
|
|
||||||
|
|
@ -16,6 +18,7 @@ public class WorldMap : IDisposable
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_canvas = new Mat(config.CanvasSize, config.CanvasSize, MatType.CV_8UC1, Scalar.Black);
|
_canvas = new Mat(config.CanvasSize, config.CanvasSize, MatType.CV_8UC1, Scalar.Black);
|
||||||
|
_confidence = new Mat(config.CanvasSize, config.CanvasSize, MatType.CV_16SC1, Scalar.Black);
|
||||||
_position = new MapPosition(config.CanvasSize / 2.0, config.CanvasSize / 2.0);
|
_position = new MapPosition(config.CanvasSize / 2.0, config.CanvasSize / 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,19 +30,37 @@ public class WorldMap : IDisposable
|
||||||
{
|
{
|
||||||
_frameCount++;
|
_frameCount++;
|
||||||
|
|
||||||
// First frame: just stitch at center
|
// Frame deduplication: skip if minimap hasn't scrolled yet
|
||||||
if (_frameCount <= 1)
|
if (_prevWallMask != null && _frameCount > 1)
|
||||||
{
|
{
|
||||||
Stitch(classifiedMat, _position);
|
using var xor = new Mat();
|
||||||
|
Cv2.BitwiseXor(wallMask, _prevWallMask, xor);
|
||||||
|
var changedPixels = Cv2.CountNonZero(xor);
|
||||||
|
if (changedPixels < _config.FrameChangeThreshold)
|
||||||
|
{
|
||||||
|
Log.Debug("Frame dedup: {Changed} changed pixels, skipping", changedPixels);
|
||||||
|
return _position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store current wall mask for next frame's dedup check
|
||||||
|
_prevWallMask?.Dispose();
|
||||||
|
_prevWallMask = wallMask.Clone();
|
||||||
|
|
||||||
|
// Warmup: stitch at center with boosted confidence to bootstrap canvas
|
||||||
|
if (_frameCount <= _config.WarmupFrames)
|
||||||
|
{
|
||||||
|
StitchWithConfidence(classifiedMat, _position, boosted: true);
|
||||||
return _position;
|
return _position;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match wallMask against canvas to find best position
|
// Match wallMask against canvas to find best position
|
||||||
var matched = MatchPosition(wallMask, _position);
|
var matched = MatchPosition(wallMask, _position);
|
||||||
if (matched != null)
|
if (matched == null)
|
||||||
_position = matched;
|
return _position; // skip stitching entirely on failed match
|
||||||
|
|
||||||
Stitch(classifiedMat, _position);
|
_position = matched;
|
||||||
|
StitchWithConfidence(classifiedMat, _position, boosted: false);
|
||||||
return _position;
|
return _position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +121,7 @@ public class WorldMap : IDisposable
|
||||||
return new MapPosition(matchX, matchY);
|
return new MapPosition(matchX, matchY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Stitch(Mat classifiedMat, MapPosition position)
|
private void StitchWithConfidence(Mat classifiedMat, MapPosition position, bool boosted)
|
||||||
{
|
{
|
||||||
var halfSize = _config.CaptureSize / 2;
|
var halfSize = _config.CaptureSize / 2;
|
||||||
var canvasX = (int)Math.Round(position.X) - halfSize;
|
var canvasX = (int)Math.Round(position.X) - halfSize;
|
||||||
|
|
@ -121,14 +142,38 @@ public class WorldMap : IDisposable
|
||||||
|
|
||||||
var srcRoi = new Mat(classifiedMat, srcRect);
|
var srcRoi = new Mat(classifiedMat, srcRect);
|
||||||
var dstRoi = new Mat(_canvas, dstRect);
|
var dstRoi = new Mat(_canvas, dstRect);
|
||||||
|
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;
|
||||||
|
|
||||||
// Paste wall pixels (walls always win)
|
|
||||||
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);
|
||||||
|
var conf = confRoi.At<short>(row, col);
|
||||||
|
|
||||||
if (srcVal == (byte)MapCell.Wall)
|
if (srcVal == (byte)MapCell.Wall)
|
||||||
dstRoi.Set(row, col, srcVal);
|
{
|
||||||
|
if (boosted)
|
||||||
|
conf = confThreshold; // warmup: immediately at threshold
|
||||||
|
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);
|
||||||
|
|
||||||
|
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
|
// Mark explored area: circle around player, only overwrite Unknown
|
||||||
|
|
@ -243,6 +288,9 @@ public class WorldMap : IDisposable
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_canvas.SetTo(Scalar.Black);
|
_canvas.SetTo(Scalar.Black);
|
||||||
|
_confidence.SetTo(Scalar.Black);
|
||||||
|
_prevWallMask?.Dispose();
|
||||||
|
_prevWallMask = null;
|
||||||
_position = new MapPosition(_config.CanvasSize / 2.0, _config.CanvasSize / 2.0);
|
_position = new MapPosition(_config.CanvasSize / 2.0, _config.CanvasSize / 2.0);
|
||||||
_frameCount = 0;
|
_frameCount = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -250,5 +298,7 @@ public class WorldMap : IDisposable
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_canvas.Dispose();
|
_canvas.Dispose();
|
||||||
|
_confidence.Dispose();
|
||||||
|
_prevWallMask?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue