work on well of souls and yolo detection
This commit is contained in:
parent
3456e0d62a
commit
40d30115bf
41 changed files with 3031 additions and 148 deletions
|
|
@ -1,6 +1,10 @@
|
|||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using Poe2Trade.Core;
|
||||
using OpenCvSharp.Extensions;
|
||||
using Serilog;
|
||||
using Region = Poe2Trade.Core.Region;
|
||||
|
||||
namespace Poe2Trade.Screen;
|
||||
|
||||
|
|
@ -178,6 +182,144 @@ public class ScreenReader : IScreenReader
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// -- Nameplate diff OCR --
|
||||
|
||||
public Bitmap CaptureRawBitmap() => ScreenCapture.CaptureOrLoad(null, null);
|
||||
|
||||
public Task<OcrResponse> NameplateDiffOcr(Bitmap reference, Bitmap current)
|
||||
{
|
||||
int w = Math.Min(reference.Width, current.Width);
|
||||
int h = Math.Min(reference.Height, current.Height);
|
||||
|
||||
var refData = reference.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
var curData = current.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
byte[] refPx = new byte[refData.Stride * h];
|
||||
byte[] curPx = new byte[curData.Stride * h];
|
||||
Marshal.Copy(refData.Scan0, refPx, 0, refPx.Length);
|
||||
Marshal.Copy(curData.Scan0, curPx, 0, curPx.Length);
|
||||
int stride = refData.Stride;
|
||||
reference.UnlockBits(refData);
|
||||
current.UnlockBits(curData);
|
||||
|
||||
// Build a binary mask of pixels that got significantly brighter (nameplates are bright text)
|
||||
const int brightThresh = 30;
|
||||
bool[] mask = new bool[w * h];
|
||||
Parallel.For(0, h, y =>
|
||||
{
|
||||
int rowOff = y * stride;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int i = rowOff + x * 4;
|
||||
int brighter = (curPx[i] - refPx[i]) + (curPx[i + 1] - refPx[i + 1]) + (curPx[i + 2] - refPx[i + 2]);
|
||||
if (brighter > brightThresh)
|
||||
mask[y * w + x] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Find connected clusters via row-scan: collect bounding boxes of bright regions
|
||||
var boxes = FindBrightClusters(mask, w, h, minWidth: 40, minHeight: 10, maxGap: 8);
|
||||
Log.Information("NameplateDiff: found {Count} bright clusters", boxes.Count);
|
||||
|
||||
if (boxes.Count == 0)
|
||||
return Task.FromResult(new OcrResponse { Text = "", Lines = [] });
|
||||
|
||||
// OCR each cluster crop, accumulate results with screen-space coordinates
|
||||
var allLines = new List<OcrLine>();
|
||||
var allText = new List<string>();
|
||||
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
// Pad the crop slightly
|
||||
int pad = 4;
|
||||
int cx = Math.Max(0, box.X - pad);
|
||||
int cy = Math.Max(0, box.Y - pad);
|
||||
int cw = Math.Min(w - cx, box.Width + pad * 2);
|
||||
int ch = Math.Min(h - cy, box.Height + pad * 2);
|
||||
|
||||
using var crop = current.Clone(new Rectangle(cx, cy, cw, ch), PixelFormat.Format32bppArgb);
|
||||
var ocrResult = _pythonBridge.OcrFromBitmap(crop);
|
||||
|
||||
// Offset word coordinates to screen space
|
||||
foreach (var line in ocrResult.Lines)
|
||||
{
|
||||
foreach (var word in line.Words)
|
||||
{
|
||||
word.X += cx;
|
||||
word.Y += cy;
|
||||
}
|
||||
allLines.Add(line);
|
||||
allText.Add(line.Text);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(new OcrResponse
|
||||
{
|
||||
Text = string.Join("\n", allText),
|
||||
Lines = allLines,
|
||||
});
|
||||
}
|
||||
|
||||
private static List<Rectangle> FindBrightClusters(bool[] mask, int w, int h, int minWidth, int minHeight, int maxGap)
|
||||
{
|
||||
// Row density
|
||||
int[] rowCounts = new int[h];
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
if (mask[y * w + x]) rowCounts[y]++;
|
||||
|
||||
// Find horizontal bands of bright rows
|
||||
int rowThresh = 3;
|
||||
var bands = new List<(int Top, int Bottom)>();
|
||||
int bandStart = -1, lastActive = -1;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
if (rowCounts[y] >= rowThresh)
|
||||
{
|
||||
if (bandStart < 0) bandStart = y;
|
||||
lastActive = y;
|
||||
}
|
||||
else if (bandStart >= 0 && y - lastActive > maxGap)
|
||||
{
|
||||
if (lastActive - bandStart + 1 >= minHeight)
|
||||
bands.Add((bandStart, lastActive));
|
||||
bandStart = -1;
|
||||
}
|
||||
}
|
||||
if (bandStart >= 0 && lastActive - bandStart + 1 >= minHeight)
|
||||
bands.Add((bandStart, lastActive));
|
||||
|
||||
// For each band, find column extents to get individual nameplate boxes
|
||||
var boxes = new List<Rectangle>();
|
||||
foreach (var (top, bottom) in bands)
|
||||
{
|
||||
int[] colCounts = new int[w];
|
||||
for (int y = top; y <= bottom; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
if (mask[y * w + x]) colCounts[x]++;
|
||||
|
||||
int colThresh = 1;
|
||||
int colStart = -1, lastCol = -1;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
if (colCounts[x] >= colThresh)
|
||||
{
|
||||
if (colStart < 0) colStart = x;
|
||||
lastCol = x;
|
||||
}
|
||||
else if (colStart >= 0 && x - lastCol > maxGap)
|
||||
{
|
||||
if (lastCol - colStart + 1 >= minWidth)
|
||||
boxes.Add(new Rectangle(colStart, top, lastCol - colStart + 1, bottom - top + 1));
|
||||
colStart = -1;
|
||||
}
|
||||
}
|
||||
if (colStart >= 0 && lastCol - colStart + 1 >= minWidth)
|
||||
boxes.Add(new Rectangle(colStart, top, lastCol - colStart + 1, bottom - top + 1));
|
||||
}
|
||||
|
||||
return boxes;
|
||||
}
|
||||
|
||||
public void Dispose() => _pythonBridge.Dispose();
|
||||
|
||||
// -- OCR text matching --
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue