easyocr work
This commit is contained in:
parent
9f208b0606
commit
735b6f7157
7 changed files with 121 additions and 10 deletions
|
|
@ -105,9 +105,10 @@ static class Daemon
|
|||
private static object HandleDiffOcrPython(OcrHandler ocrHandler, PythonOcrBridge pythonBridge, Request request)
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var p = request.Threshold > 0
|
||||
? new DiffOcrParams { DiffThresh = request.Threshold }
|
||||
: new DiffOcrParams();
|
||||
// Use default params (same wide crop as Tesseract path).
|
||||
// Background subtraction below eliminates stash items from the image.
|
||||
var p = new DiffOcrParams();
|
||||
if (request.Threshold > 0) p.DiffThresh = request.Threshold;
|
||||
|
||||
var cropResult = ocrHandler.DiffCrop(request, p);
|
||||
if (cropResult == null)
|
||||
|
|
@ -115,22 +116,29 @@ static class Daemon
|
|||
|
||||
var (cropped, refCropped, current, region) = cropResult.Value;
|
||||
using var _current = current;
|
||||
using var _refCropped = refCropped;
|
||||
|
||||
// Apply background subtraction to isolate tooltip text.
|
||||
// This removes stash items and game world — only tooltip text remains.
|
||||
// No upscale (upscale=1) to keep the image small for EasyOCR speed.
|
||||
// Hard threshold (softThreshold=false) produces clean binary for OCR.
|
||||
using var processed = ImagePreprocessor.PreprocessWithBackgroundSub(
|
||||
cropped, refCropped, dimPercentile: 40, textThresh: 60, upscale: 1, softThreshold: false);
|
||||
cropped.Dispose();
|
||||
refCropped.Dispose();
|
||||
var diffMs = sw.ElapsedMilliseconds;
|
||||
|
||||
// Save crop to requested path if provided
|
||||
// Save processed crop if path provided
|
||||
if (!string.IsNullOrEmpty(request.Path))
|
||||
{
|
||||
var dir = Path.GetDirectoryName(request.Path);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
cropped.Save(request.Path, ImageUtils.GetImageFormat(request.Path));
|
||||
processed.Save(request.Path, ImageUtils.GetImageFormat(request.Path));
|
||||
}
|
||||
|
||||
// Send crop to Python via base64 over pipe (no temp file I/O)
|
||||
// Send processed image to Python OCR via base64
|
||||
sw.Restart();
|
||||
var ocrResult = pythonBridge.OcrFromBitmap(cropped, request.Engine!);
|
||||
cropped.Dispose();
|
||||
var ocrResult = pythonBridge.OcrFromBitmap(processed, request.Engine!);
|
||||
var ocrMs = sw.ElapsedMilliseconds;
|
||||
|
||||
Console.Error.WriteLine($" diff-ocr-python: diff={diffMs}ms ocr={ocrMs}ms total={diffMs + ocrMs}ms crop={region.Width}x{region.Height}");
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ class OcrHandler(TesseractEngine engine)
|
|||
int rowRangeLen = bestRowEnd - bestRowStart + 1;
|
||||
if (rowRangeLen <= 200)
|
||||
{
|
||||
// Small range: serial is faster than Parallel overhead
|
||||
for (int y = bestRowStart; y <= bestRowEnd; y++)
|
||||
{
|
||||
int rowOffset = y * stride;
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 397 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 157 KiB |
104
tools/test-easyocr.js
Normal file
104
tools/test-easyocr.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { spawn } from 'child_process';
|
||||
import { join } from 'path';
|
||||
|
||||
const EXE = join('tools', 'OcrDaemon', 'bin', 'Release', 'net8.0-windows10.0.19041.0', 'OcrDaemon.exe');
|
||||
const TESSDATA = join('tools', 'OcrDaemon', 'bin', 'Release', 'net8.0-windows10.0.19041.0', 'tessdata');
|
||||
const SAVE_DIR = join('tools', 'OcrDaemon', 'bin', 'Release', 'net8.0-windows10.0.19041.0', 'tessdata', 'images');
|
||||
|
||||
const expected = {
|
||||
vertex1: [
|
||||
'The Vertex', 'Tribal Mask', 'Helmet', 'Quality: +20%',
|
||||
'Evasion Rating: 79', 'Energy Shield: 34', 'Requires: Level 33',
|
||||
'16% Increased Life Regeneration Rate', 'Has no Attribute Requirements',
|
||||
'+15% to Chaos Resistance', 'Skill gems have no attribute requirements',
|
||||
'+3 to level of all skills', '15% increased mana cost efficiency',
|
||||
'Twice Corrupted', 'Asking Price:', '7x Divine Orb',
|
||||
],
|
||||
vertex2: [
|
||||
'The Vertex', 'Tribal Mask', 'Helmet', 'Quality: +20%',
|
||||
'Evasion Rating: 182', 'Energy Shield: 77', 'Requires: Level 33',
|
||||
'+29 To Spirit', '+1 to Level of All Minion Skills',
|
||||
'Has no Attribute Requirements', '130% increased Evasion and Energy Shield',
|
||||
'27% Increased Critical Hit Chance', '+13% to Chaos Resistance',
|
||||
'+2 to level of all skills', 'Twice Corrupted', 'Asking Price:', '35x Divine Orb',
|
||||
],
|
||||
};
|
||||
|
||||
function levenshteinSim(a, b) {
|
||||
a = a.toLowerCase(); b = b.toLowerCase();
|
||||
if (a === b) return 1;
|
||||
const la = a.length, lb = b.length;
|
||||
if (!la || !lb) return 0;
|
||||
const d = Array.from({ length: la + 1 }, (_, i) => { const r = new Array(lb + 1); r[0] = i; return r; });
|
||||
for (let j = 0; j <= lb; j++) d[0][j] = j;
|
||||
for (let i = 1; i <= la; i++)
|
||||
for (let j = 1; j <= lb; j++) {
|
||||
const cost = a[i-1] === b[j-1] ? 0 : 1;
|
||||
d[i][j] = Math.min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost);
|
||||
}
|
||||
return 1 - d[la][lb] / Math.max(la, lb);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const proc = spawn(EXE, [], { stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
let buffer = '';
|
||||
let resolveNext;
|
||||
proc.stdout.on('data', (data) => {
|
||||
buffer += data.toString();
|
||||
let idx;
|
||||
while ((idx = buffer.indexOf('\n')) !== -1) {
|
||||
const line = buffer.slice(0, idx).trim();
|
||||
buffer = buffer.slice(idx + 1);
|
||||
if (!line) continue;
|
||||
try { const p = JSON.parse(line); if (resolveNext) { const r = resolveNext; resolveNext = null; r(p); } } catch {}
|
||||
}
|
||||
});
|
||||
proc.stderr.on('data', (data) => process.stderr.write(data));
|
||||
function sendCmd(cmd) { return new Promise((resolve) => { resolveNext = resolve; proc.stdin.write(JSON.stringify(cmd) + '\n'); }); }
|
||||
await new Promise((resolve) => { resolveNext = resolve; });
|
||||
|
||||
const cases = [
|
||||
{ id: 'vertex1', image: 'vertex1.png', snapshot: 'vertex-snapshot.png' },
|
||||
{ id: 'vertex2', image: 'vertex2.png', snapshot: 'vertex-snapshot.png' },
|
||||
];
|
||||
|
||||
for (const tc of cases) {
|
||||
const snapPath = join(TESSDATA, 'images', tc.snapshot);
|
||||
const imgPath = join(TESSDATA, 'images', tc.image);
|
||||
|
||||
// 3 runs: first saves crop, rest just timing
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await sendCmd({ cmd: 'snapshot', file: snapPath });
|
||||
const savePath = i === 0 ? join(SAVE_DIR, `${tc.id}_easyocr_crop.png`) : undefined;
|
||||
const t0 = performance.now();
|
||||
const resp = await sendCmd({ cmd: 'diff-ocr', file: imgPath, engine: 'easyocr', ...(savePath ? { path: savePath } : {}) });
|
||||
const ms = (performance.now() - t0).toFixed(0);
|
||||
const region = resp.region;
|
||||
const lines = (resp.lines || []).map(l => l.text.trim()).filter(l => l.length > 0);
|
||||
|
||||
if (i === 0) {
|
||||
// Accuracy check on first run
|
||||
const exp = expected[tc.id];
|
||||
const used = new Set();
|
||||
let matched = 0, fuzzy = 0, missed = 0;
|
||||
for (const e of exp) {
|
||||
let bestIdx = -1, bestSim = 0;
|
||||
for (let j = 0; j < lines.length; j++) {
|
||||
if (used.has(j)) continue;
|
||||
const sim = levenshteinSim(e, lines[j]);
|
||||
if (sim > bestSim) { bestSim = sim; bestIdx = j; }
|
||||
}
|
||||
if (bestIdx >= 0 && bestSim >= 0.75) { used.add(bestIdx); if (bestSim >= 0.95) matched++; else fuzzy++; }
|
||||
else { missed++; console.log(` MISS: ${e}${bestIdx >= 0 ? ` (best: "${lines[bestIdx]}", sim=${bestSim.toFixed(2)})` : ''}`); }
|
||||
}
|
||||
console.log(`${tc.id}: ${ms}ms crop=${region?.width}x${region?.height} at (${region?.x},${region?.y}) ${matched} OK / ${fuzzy}~ / ${missed} miss lines=${lines.length}${savePath ? ' [saved]' : ''}`);
|
||||
} else {
|
||||
console.log(`${tc.id}: ${ms}ms crop=${region?.width}x${region?.height}`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
proc.stdin.end();
|
||||
proc.kill();
|
||||
}
|
||||
run().catch(console.error);
|
||||
Loading…
Add table
Add a link
Reference in a new issue