work on minimap

This commit is contained in:
Boki 2026-02-13 12:30:13 -05:00
parent a152a5cead
commit 6bc3fb6972
3 changed files with 39 additions and 66 deletions

View file

@ -70,15 +70,11 @@ public class MinimapCapture : IDisposable
// --- 3. Wall mask: bright OR saturated → structure lines ---
using var wallMask = BuildWallMask(satChan, valueChan, playerMask);
// --- 4. Explored mask: brightness above fog-of-war, minus walls/player ---
using var exploredMask = BuildExploredMask(valueChan, wallMask, playerMask);
// --- 5. Build raw classified mat ---
// --- 4. Build classified mat (walls only — explored is tracked by WorldMap) ---
var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC1, Scalar.Black);
classified.SetTo(new Scalar((byte)MapCell.Explored), exploredMask);
classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask);
// --- 6. Temporal smoothing: majority vote ---
// --- 5. Temporal smoothing: majority vote on walls ---
var smoothed = TemporalSmooth(classified); // classified goes into ring buffer
// --- 7. Gray for phase correlation (player zeroed — it stays centered, walls shift with map) ---
@ -118,23 +114,6 @@ public class MinimapCapture : IDisposable
return wallMask;
}
private Mat BuildExploredMask(Mat valueChan, Mat wallMask, Mat playerMask)
{
// Explored = any pixel above fog-of-war darkness, minus walls and player
var exploredMask = new Mat();
Cv2.Threshold(valueChan, exploredMask, _config.FloorMinValue, 255, ThresholdTypes.Binary);
using var notWall = new Mat();
Cv2.BitwiseNot(wallMask, notWall);
Cv2.BitwiseAnd(exploredMask, notWall, exploredMask);
using var notPlayer = new Mat();
Cv2.BitwiseNot(playerMask, notPlayer);
Cv2.BitwiseAnd(exploredMask, notPlayer, exploredMask);
return exploredMask;
}
private static void FilterSmallComponents(Mat mask, int minArea)
{
if (minArea <= 0) return;
@ -172,31 +151,21 @@ public class MinimapCapture : IDisposable
var size = classified.Size();
using var wallCount = new Mat(size, MatType.CV_8UC1, Scalar.Black);
using var exploredCount = 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);
using var isExplored = new Mat();
Cv2.Compare(frame, new Scalar((byte)MapCell.Explored), isExplored, CmpType.EQ);
Cv2.Add(exploredCount, new Scalar(1), exploredCount, isExplored);
}
// Apply vote thresholds
// Apply vote threshold
using var wallPass = new Mat();
Cv2.Threshold(wallCount, wallPass,
_config.WallTemporalThreshold - 0.5, 255, ThresholdTypes.Binary);
using var exploredPass = new Mat();
Cv2.Threshold(exploredCount, exploredPass,
_config.ExploredTemporalThreshold - 0.5, 255, ThresholdTypes.Binary);
// Build smoothed result (explored wins over wall)
var result = new Mat(size, MatType.CV_8UC1, Scalar.Black);
result.SetTo(new Scalar((byte)MapCell.Wall), wallPass);
result.SetTo(new Scalar((byte)MapCell.Explored), exploredPass);
return result;
}
@ -236,12 +205,8 @@ public class MinimapCapture : IDisposable
using var wallMask = BuildWallMask(satChan, valueChan, playerMask);
if (stage == MinimapDebugStage.Walls) return EncodePng(wallMask);
using var exploredMask = BuildExploredMask(valueChan, wallMask, playerMask);
if (stage == MinimapDebugStage.Explored) return EncodePng(exploredMask);
// Classified
// Classified (walls + player only — explored is tracked by WorldMap)
using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black);
classified.SetTo(new Scalar(104, 64, 31), exploredMask);
classified.SetTo(new Scalar(26, 45, 61), wallMask);
classified.SetTo(new Scalar(0, 165, 255), playerMask);
return EncodePng(classified);
@ -275,25 +240,22 @@ public class MinimapCapture : IDisposable
Cv2.InRange(hsv, _config.PlayerLoHSV, _config.PlayerHiHSV, playerMask);
using var wallMask = BuildWallMask(satChan, valueChan, playerMask);
using var exploredMask = BuildExploredMask(valueChan, wallMask, playerMask);
// Colorized classified
// Colorized classified (walls + player)
using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black);
classified.SetTo(new Scalar(104, 64, 31), exploredMask); // blue
classified.SetTo(new Scalar(26, 45, 61), wallMask); // brown
classified.SetTo(new Scalar(0, 165, 255), playerMask); // orange
Cv2.ImWrite(Path.Combine(dir, "01-raw.png"), bgr);
Cv2.ImWrite(Path.Combine(dir, "02-walls.png"), wallMask);
Cv2.ImWrite(Path.Combine(dir, "03-explored.png"), exploredMask);
Cv2.ImWrite(Path.Combine(dir, "04-player.png"), playerMask);
Cv2.ImWrite(Path.Combine(dir, "05-classified.png"), classified);
Cv2.ImWrite(Path.Combine(dir, "03-player.png"), playerMask);
Cv2.ImWrite(Path.Combine(dir, "04-classified.png"), classified);
// HSV channels
var channels = Cv2.Split(hsv);
Cv2.ImWrite(Path.Combine(dir, "06-hue.png"), channels[0]);
Cv2.ImWrite(Path.Combine(dir, "07-sat.png"), channels[1]);
Cv2.ImWrite(Path.Combine(dir, "08-val.png"), channels[2]);
Cv2.ImWrite(Path.Combine(dir, "05-hue.png"), channels[0]);
Cv2.ImWrite(Path.Combine(dir, "06-sat.png"), channels[1]);
Cv2.ImWrite(Path.Combine(dir, "07-val.png"), channels[2]);
foreach (var c in channels) c.Dispose();
Log.Information("Debug minimap images saved to {Dir}", Path.GetFullPath(dir));

View file

@ -29,7 +29,6 @@ public enum MinimapDebugStage
WorldMap,
Raw,
Walls,
Explored,
Player,
Classified,
Hue,
@ -73,16 +72,15 @@ public class MinimapConfig
public int WallMinValue { get; set; } = 200;
public int WallMinSat { get; set; } = 150;
// Floor detection: minimum V to distinguish explored floor from fog-of-war
public int FloorMinValue { get; set; } = 40;
// Connected components: minimum area to keep (kills speckle)
public int WallMinArea { get; set; } = 30;
// Temporal smoothing: majority vote over ring buffer
// Explored radius: pixels around player position to mark as explored on world map
public int ExploredRadius { get; set; } = 100;
// Temporal smoothing: majority vote over ring buffer (walls only)
public int TemporalFrameCount { get; set; } = 5;
public int WallTemporalThreshold { get; set; } = 3;
public int ExploredTemporalThreshold { get; set; } = 2;
// Capture rate (~30 fps)
public int CaptureIntervalMs { get; set; } = 33;

View file

@ -36,21 +36,34 @@ public class WorldMap : IDisposable
var srcRoi = new Mat(classifiedMat, srcRect);
var dstRoi = new Mat(_canvas, dstRect);
// Only paste non-Unknown pixels; don't overwrite Explored with Wall
// Paste wall pixels (walls always win)
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);
if (srcVal == (byte)MapCell.Unknown) continue;
var dstVal = dstRoi.At<byte>(row, col);
// Don't overwrite Explored with Wall (Explored is more reliable)
if (dstVal == (byte)MapCell.Explored && srcVal == (byte)MapCell.Wall)
continue;
var srcVal = srcRoi.At<byte>(row, col);
if (srcVal == (byte)MapCell.Wall)
dstRoi.Set(row, col, srcVal);
}
}
// Mark explored area: circle around player, only overwrite Unknown
var pcx = (int)Math.Round(position.X);
var pcy = (int)Math.Round(position.Y);
var r = _config.ExploredRadius;
var r2 = r * r;
var y0 = Math.Max(0, pcy - r);
var y1 = Math.Min(_config.CanvasSize - 1, pcy + r);
var x0 = Math.Max(0, pcx - r);
var x1 = Math.Min(_config.CanvasSize - 1, pcx + r);
for (var y = y0; y <= y1; y++)
for (var x = x0; x <= x1; x++)
{
var dx = x - pcx;
var dy = y - pcy;
if (dx * dx + dy * dy > r2) continue;
if (_canvas.At<byte>(y, x) == (byte)MapCell.Unknown)
_canvas.Set(y, x, (byte)MapCell.Explored);
}
}