switched to new way
This commit is contained in:
parent
f22d182c8f
commit
4a65c8e17b
96 changed files with 4991 additions and 10025 deletions
|
|
@ -1,190 +0,0 @@
|
|||
namespace OcrDaemon;
|
||||
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
class DetectGridHandler
|
||||
{
|
||||
public object HandleDetectGrid(Request req)
|
||||
{
|
||||
if (req.Region == null)
|
||||
return new ErrorResponse("detect-grid requires region");
|
||||
|
||||
int minCell = req.MinCellSize > 0 ? req.MinCellSize : 20;
|
||||
int maxCell = req.MaxCellSize > 0 ? req.MaxCellSize : 70;
|
||||
bool debug = req.Debug;
|
||||
|
||||
Bitmap bitmap = ScreenCapture.CaptureOrLoad(req.File, req.Region);
|
||||
int w = bitmap.Width;
|
||||
int h = bitmap.Height;
|
||||
|
||||
var bmpData = bitmap.LockBits(
|
||||
new Rectangle(0, 0, w, h),
|
||||
ImageLockMode.ReadOnly,
|
||||
PixelFormat.Format32bppArgb
|
||||
);
|
||||
byte[] pixels = new byte[bmpData.Stride * h];
|
||||
Marshal.Copy(bmpData.Scan0, pixels, 0, pixels.Length);
|
||||
bitmap.UnlockBits(bmpData);
|
||||
int stride = bmpData.Stride;
|
||||
|
||||
byte[] gray = new byte[w * h];
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int i = y * stride + x * 4;
|
||||
gray[y * w + x] = (byte)((pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3);
|
||||
}
|
||||
|
||||
bitmap.Dispose();
|
||||
|
||||
// ── Pass 1: Scan horizontal bands using "very dark pixel density" ──
|
||||
// Grid lines are nearly all very dark (density ~0.9), cell interiors are
|
||||
// partially dark (0.3-0.5), game world is mostly bright (density ~0.05).
|
||||
// This creates clear periodic peaks at grid line positions.
|
||||
int bandH = 200;
|
||||
int bandStep = 40;
|
||||
const int veryDarkPixelThresh = 12; // pixels below this brightness = "very dark"
|
||||
const double gridSegThresh = 0.25; // density above this = potential grid column
|
||||
|
||||
var candidates = new List<(int bandY, int cellW, double hAc, int hLeft, int hRight)>();
|
||||
|
||||
for (int by = 0; by + bandH <= h; by += bandStep)
|
||||
{
|
||||
// "Very dark pixel density" per column: fraction of pixels below threshold
|
||||
double[] darkDensity = new double[w];
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int count = 0;
|
||||
for (int y = by; y < by + bandH; y++)
|
||||
{
|
||||
if (gray[y * w + x] < veryDarkPixelThresh) count++;
|
||||
}
|
||||
darkDensity[x] = (double)count / bandH;
|
||||
}
|
||||
|
||||
// Find segments where density > gridSegThresh (grid panel regions)
|
||||
var gridSegs = SignalProcessing.FindDarkDensitySegments(darkDensity, gridSegThresh, 200);
|
||||
|
||||
foreach (var (segLeft, segRight) in gridSegs)
|
||||
{
|
||||
// Extract segment and run AC
|
||||
int segLen = segRight - segLeft;
|
||||
double[] segment = new double[segLen];
|
||||
Array.Copy(darkDensity, segLeft, segment, 0, segLen);
|
||||
|
||||
var (period, acScore) = SignalProcessing.FindPeriodWithScore(segment, minCell, maxCell);
|
||||
|
||||
if (period <= 0) continue;
|
||||
|
||||
// FindGridExtent within the segment
|
||||
var (extLeft, extRight) = SignalProcessing.FindGridExtent(segment, period);
|
||||
if (extLeft < 0) continue;
|
||||
|
||||
// Map back to full image coordinates
|
||||
int absLeft = segLeft + extLeft;
|
||||
int absRight = segLeft + extRight;
|
||||
int extent = absRight - absLeft;
|
||||
|
||||
// Require at least 8 cells wide AND 200px absolute minimum
|
||||
if (extent < period * 8 || extent < 200) continue;
|
||||
|
||||
if (debug) Console.Error.WriteLine(
|
||||
$" Band y={by}: seg=[{segLeft}-{segRight}] period={period}, AC={acScore:F3}, " +
|
||||
$"extent={absLeft}-{absRight}={extent}px ({extent / period} cells)");
|
||||
|
||||
candidates.Add((by, period, acScore, absLeft, absRight));
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) Console.Error.WriteLine($"Pass 1: {candidates.Count} candidates");
|
||||
|
||||
// Sort by score = AC * extent (prefer large strongly-periodic areas)
|
||||
candidates.Sort((a, b) =>
|
||||
{
|
||||
double sa = a.hAc * (a.hRight - a.hLeft);
|
||||
double sb = b.hAc * (b.hRight - b.hLeft);
|
||||
return sb.CompareTo(sa);
|
||||
});
|
||||
|
||||
// ── Pass 2: Verify vertical periodicity ──
|
||||
foreach (var cand in candidates.Take(10))
|
||||
{
|
||||
int colSpan = cand.hRight - cand.hLeft;
|
||||
if (colSpan < cand.cellW * 3) continue;
|
||||
|
||||
// Row "very dark pixel density" within the detected column range
|
||||
double[] rowDensity = new double[h];
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int count = 0;
|
||||
for (int x = cand.hLeft; x < cand.hRight; x++)
|
||||
{
|
||||
if (gray[y * w + x] < veryDarkPixelThresh) count++;
|
||||
}
|
||||
rowDensity[y] = (double)count / colSpan;
|
||||
}
|
||||
|
||||
// Find grid panel vertical segment
|
||||
var vGridSegs = SignalProcessing.FindDarkDensitySegments(rowDensity, gridSegThresh, 100);
|
||||
if (vGridSegs.Count == 0) continue;
|
||||
|
||||
// Use the largest segment
|
||||
var (vSegTop, vSegBottom) = vGridSegs.OrderByDescending(s => s.end - s.start).First();
|
||||
int vSegLen = vSegBottom - vSegTop;
|
||||
double[] vSegment = new double[vSegLen];
|
||||
Array.Copy(rowDensity, vSegTop, vSegment, 0, vSegLen);
|
||||
|
||||
var (cellH, vAc) = SignalProcessing.FindPeriodWithScore(vSegment, minCell, maxCell);
|
||||
if (cellH <= 0) continue;
|
||||
|
||||
var (extTop, extBottom) = SignalProcessing.FindGridExtent(vSegment, cellH);
|
||||
if (extTop < 0) continue;
|
||||
|
||||
int top = vSegTop + extTop;
|
||||
int bottom = vSegTop + extBottom;
|
||||
int vExtent = bottom - top;
|
||||
|
||||
// Require at least 3 rows tall AND 100px absolute minimum
|
||||
if (vExtent < cellH * 3 || vExtent < 100) continue;
|
||||
|
||||
if (debug) Console.Error.WriteLine(
|
||||
$" 2D candidate: cellW={cand.cellW}, cellH={cellH}, " +
|
||||
$"region=({cand.hLeft},{top})-({cand.hRight},{bottom}), " +
|
||||
$"vAC={vAc:F3}, extent={vExtent}px ({vExtent / cellH} rows)");
|
||||
|
||||
// ── Found a valid 2D grid ──
|
||||
int gridW = cand.hRight - cand.hLeft;
|
||||
int gridH = bottom - top;
|
||||
int cols = Math.Max(2, (int)Math.Round((double)gridW / cand.cellW));
|
||||
int rows = Math.Max(2, (int)Math.Round((double)gridH / cellH));
|
||||
|
||||
// Snap grid dimensions to exact multiples of cell size
|
||||
gridW = cols * cand.cellW;
|
||||
gridH = rows * cellH;
|
||||
|
||||
if (debug) Console.Error.WriteLine(
|
||||
$" => cols={cols}, rows={rows}, gridW={gridW}, gridH={gridH}");
|
||||
|
||||
return new DetectGridResponse
|
||||
{
|
||||
Detected = true,
|
||||
Region = new RegionRect
|
||||
{
|
||||
X = req.Region.X + cand.hLeft,
|
||||
Y = req.Region.Y + top,
|
||||
Width = gridW,
|
||||
Height = gridH,
|
||||
},
|
||||
Cols = cols,
|
||||
Rows = rows,
|
||||
CellWidth = Math.Round((double)gridW / cols, 1),
|
||||
CellHeight = Math.Round((double)gridH / rows, 1),
|
||||
};
|
||||
}
|
||||
|
||||
if (debug) Console.Error.WriteLine(" No valid 2D grid found");
|
||||
return new DetectGridResponse { Detected = false };
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue