Files
sendou.ink/scripts/cv/dump-crops.ts
Kalle a8f8dd9a43 CV housekeeping: biome, knip, docs
- biome sweep over app/features/cv + scripts/cv (tabs, a11y button types,
  CLI scripts get the repo's noConsole ignore convention); fixture corpus
  excluded from biome
- knip: vendored scripts/dicts ignored (knip's vite plugin started
  analyzing it once scripts/cv/vite-node.config.ts appeared), dead
  exports pruned
- app/features/cv/README.md carries the detector/atlas/fixture/gotcha
  documentation; AGENTS.md points to it and pins the fixture ground-truth
  and id-types rules
- assets/fonts/ gitignored (proprietary atlas-builder fonts)
2026-07-19 14:22:06 +03:00

94 lines
2.6 KiB
TypeScript

/** biome-ignore-all lint/suspicious/noConsole: CLI script output */
/**
* ROI calibration helper: normalize a frame to 1080p, then dump crops
* (optionally scaled up) and/or a grid overlay for visual inspection.
*
* Usage:
* vite-node -c scripts/cv/vite-node.config.ts scripts/cv/dump-crops.ts <image> <outdir> grid
* vite-node -c scripts/cv/vite-node.config.ts scripts/cv/dump-crops.ts <image> <outdir> x,y,w,h[,scale][:label] ...
*/
import { mkdirSync } from "node:fs";
import { join } from "node:path";
import { loadOpenCV } from "../../app/features/cv/core/cv";
import {
cropRoi,
matToFrameData,
normalizeFrame,
toMat,
} from "../../app/features/cv/core/image";
import { readImage, writePng } from "../../app/features/cv/node/image-io";
const [imagePath, outDir, ...specs] = process.argv.slice(2);
if (!imagePath || !outDir || specs.length === 0) {
console.error(
"usage: vite-node -c scripts/cv/vite-node.config.ts scripts/cv/dump-crops.ts <image> <outdir> (grid | x,y,w,h[,scale][:label])...",
);
process.exit(1);
}
const cv = await loadOpenCV();
mkdirSync(outDir, { recursive: true });
const src = toMat(await readImage(imagePath));
const frame = normalizeFrame(src);
src.delete();
for (const spec of specs) {
if (spec === "grid") {
const overlay = frame.clone();
for (let x = 0; x < overlay.cols; x += 20) {
const major = x % 100 === 0;
cv.line(
overlay,
new cv.Point(x, 0),
new cv.Point(x, overlay.rows),
new cv.Scalar(255, 0, major ? 0 : 255, 255),
major ? 2 : 1,
);
}
for (let y = 0; y < overlay.rows; y += 20) {
const major = y % 100 === 0;
cv.line(
overlay,
new cv.Point(0, y),
new cv.Point(overlay.cols, y),
new cv.Scalar(255, 0, major ? 0 : 255, 255),
major ? 2 : 1,
);
}
writePng(join(outDir, "grid.png"), matToFrameData(overlay));
overlay.delete();
continue;
}
const [rect = "", label] = spec.split(":");
const [x, y, w, h, scale = 1] = rect.split(",").map(Number);
if ([x, y, w, h].some((v) => !Number.isFinite(v))) {
console.error(`bad spec: ${spec}`);
continue;
}
const cx = Math.max(0, Math.min(x!, frame.cols - 1));
const cy = Math.max(0, Math.min(y!, frame.rows - 1));
const crop = cropRoi(frame, {
x: cx,
y: cy,
w: Math.min(w!, frame.cols - cx),
h: Math.min(h!, frame.rows - cy),
});
const out = new cv.Mat();
cv.resize(
crop,
out,
new cv.Size(0, 0),
Number(scale),
Number(scale),
cv.INTER_NEAREST,
);
const name = label ?? `crop-${x}-${y}-${w}x${h}`;
writePng(join(outDir, `${name}.png`), matToFrameData(out));
crop.delete();
out.delete();
}
frame.delete();
console.info(`wrote crops to ${outDir}`);