Files
nanocode/web-opencv/miniappB/diagnose.js
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

223 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* diagnose.js — 诊断工具:显示中间处理步骤,帮助调参
* 用法: node diagnose.js <image_path>
*/
const { createCanvas, loadImage } = require('canvas');
const fs = require('fs');
const path = require('path');
const { Img, PaperRecognizer, PaperSpatialType, LocatorColor, PARAMS } = require('./paper-recognizer.js');
const OUTPUT_DIR = '/home/ubuntu/nanocode/web-opencv/miniappB/samples/';
function saveDebugImage(label, data, w, h, isGray = false) {
const canvas = createCanvas(w, h);
const ctx = canvas.getContext('2d');
const imgData = ctx.createImageData(w, h);
for (let i = 0; i < w * h; i++) {
const val = data[i];
imgData.data[i * 4] = isGray ? val : val;
imgData.data[i * 4 + 1] = isGray ? val : val;
imgData.data[i * 4 + 2] = isGray ? val : val;
imgData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);
const outPath = path.join(OUTPUT_DIR, label + '.png');
fs.writeFileSync(outPath, canvas.toBuffer('image/png'));
console.log(` [debug] ${label} → ${outPath}`);
}
async function diagnose(imagePath) {
const fname = path.basename(imagePath);
console.log(`诊断: ${fname}`);
const img = await loadImage(imagePath);
const w = img.width, h = img.height;
console.log(`尺寸: ${w}×${h}`);
const canvas = createCanvas(w, h);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, w, h);
const rgba = new Uint8Array(imageData.data.buffer);
// ---- 预处理 ----
const bgr = new Float64Array(w * h * 3);
const hsvArr = new Float64Array(w * h * 3);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const i4 = (y * w + x) * 4;
const i3 = (y * w + x) * 3;
const r = rgba[i4], g = rgba[i4 + 1], b = rgba[i4 + 2];
bgr[i3] = b; bgr[i3 + 1] = g; bgr[i3 + 2] = r;
const [hh, ss, vv] = Img.rgbToHsv(r, g, b);
hsvArr[i3] = hh; hsvArr[i3 + 1] = ss; hsvArr[i3 + 2] = vv;
}
}
// ---- HSV 统计 ----
// Sample center of image to understand typical HSV values
const cx = Math.floor(w / 2), cy = Math.floor(h / 2);
const sampleSize = 50;
let hSum = 0, sSum = 0, vSum = 0, count = 0;
console.log(`\n 中心区域(${sampleSize}×${sampleSize}) HSV 采样:`);
const colorBins = { red: 0, green: 0, blue: 0, other: 0 };
for (let dy = -sampleSize / 2; dy < sampleSize / 2; dy += 2) {
for (let dx = -sampleSize / 2; dx < sampleSize / 2; dx += 2) {
const sx = cx + dx, sy = cy + dy;
if (sx < 0 || sx >= w || sy < 0 || sy >= h) continue;
const i3 = (sy * w + sx) * 3;
hSum += hsvArr[i3]; sSum += hsvArr[i3 + 1]; vSum += hsvArr[i3 + 2];
count++;
// Classify
const hue = hsvArr[i3];
if ((hue >= 0 && hue <= 10) || hue >= 160) colorBins.red++;
else if (hue >= 35 && hue <= 85) colorBins.green++;
else if (hue >= 90 && hue <= 150) colorBins.blue++;
else colorBins.other++;
}
}
console.log(` 平均 HSV: H=${(hSum/count).toFixed(1)} S=${(sSum/count).toFixed(1)} V=${(vSum/count).toFixed(1)}`);
const total = colorBins.red + colorBins.green + colorBins.blue + colorBins.other;
console.log(` 色相分布: 红=${(colorBins.red/total*100).toFixed(1)}% 绿=${(colorBins.green/total*100).toFixed(1)}% 蓝=${(colorBins.blue/total*100).toFixed(1)}% 其他=${(colorBins.other/total*100).toFixed(1)}%`);
// ---- 颜色分割 ----
const redMask1 = Img.hsvInRange(hsvArr, w, h, 0, 10, 70, 255, 50, 255);
const redMask2 = Img.hsvInRange(hsvArr, w, h, 160, 180, 70, 255, 50, 255);
const redMask = new Uint8Array(w * h);
const greenMask = Img.hsvInRange(hsvArr, w, h, 35, 85, 70, 255, 50, 255);
const blueMask = Img.hsvInRange(hsvArr, w, h, 90, 150, 50, 255, 10, 120);
let redCount = 0, greenCount = 0, blueCount = 0;
const colorMask = new Uint8Array(w * h);
for (let i = 0; i < w * h; i++) {
const rv = Math.max(redMask1[i], redMask2[i]);
redMask[i] = rv;
colorMask[i] = Math.max(rv, greenMask[i], blueMask[i]);
if (rv > 0) redCount++;
if (greenMask[i] > 0) greenCount++;
if (blueMask[i] > 0) blueCount++;
}
const totalPixels = w * h;
console.log(`\n 颜色分割 (像素占比):`);
console.log(` 红色像素: ${redCount} (${(redCount/totalPixels*100).toFixed(2)}%)`);
console.log(` 绿色像素: ${greenCount} (${(greenCount/totalPixels*100).toFixed(2)}%)`);
console.log(` 蓝色像素: ${blueCount} (${(blueCount/totalPixels*100).toFixed(2)}%)`);
saveDebugImage('01_colorMask', colorMask, w, h, true);
// ---- 形态学 ----
let mask = Img.morphClose(colorMask, w, h, 5);
saveDebugImage('02_morph', mask, w, h, true);
// ---- 轮廓提取 ----
const contours = Img.findContours(mask, w, h);
console.log(`\n 检测到 ${contours.length} 个轮廓`);
const imageArea = w * h;
let passed = 0;
const candidates = [];
for (const contour of contours) {
const area = Img.contourArea(contour);
if (area < PARAMS.MIN_CONTOUR_AREA) continue;
if (area > imageArea * PARAMS.MAX_AREA_RATIO) continue;
const rect = Img.minAreaRect(contour);
const ww = Math.max(rect.size.width, rect.size.height);
const hh = Math.min(rect.size.width, rect.size.height);
if (hh < 1) continue;
const aspectRatio = ww / hh;
// Square filter
if (aspectRatio >= 0.9 && aspectRatio <= 1.1) continue;
if (aspectRatio > 4.0) continue;
const tolerance = 0.35;
const isBigRatio = Math.abs(aspectRatio - PARAMS.BIG_LOCATOR_RATIO_SHORT) < tolerance ||
Math.abs(aspectRatio - PARAMS.BIG_LOCATOR_RATIO_LONG) < tolerance;
const isSmallRatio = Math.abs(aspectRatio - PARAMS.SMALL_LOCATOR_RATIO) < tolerance;
const rectArea = rect.size.width * rect.size.height;
const rectangularity = area / rectArea;
console.log(` #${passed} area=${area.toFixed(0)} ratio=${aspectRatio.toFixed(2)} rectangularity=${rectangularity.toFixed(2)} ` +
`center=(${rect.center.x.toFixed(0)},${rect.center.y.toFixed(0)}) ` +
`isBig=${isBigRatio} isSmall=${isSmallRatio} size=(${ww.toFixed(0)}×${hh.toFixed(0)})`);
if (!isBigRatio && !isSmallRatio) {
console.log(` → 跳过: 宽高比不匹配`);
continue;
}
if (rectangularity < PARAMS.MIN_RECTANGULARITY) {
console.log(` → 跳过: 矩形度不足`);
continue;
}
const isBig = isBigRatio;
if (isBig) {
if (area < PARAMS.MIN_BIG_LOCATOR_AREA) {
console.log(` → 跳过: 大定位块面积太小`);
continue;
}
if (area > PARAMS.MAX_BIG_LOCATOR_AREA) {
console.log(` → 跳过: 大定位块面积太大`);
continue;
}
} else {
if (area < PARAMS.MIN_SMALL_LOCATOR_AREA) {
console.log(` → 跳过: 小定位块面积太小`);
continue;
}
if (area > PARAMS.MAX_SMALL_LOCATOR_AREA) {
console.log(` → 跳过: 小定位块面积太大`);
continue;
}
}
console.log(` ✓ 通过筛选`);
passed++;
candidates.push({ contour, area, rect, isBig });
}
console.log(`\n ${passed} 个通过筛选的候选者`);
// Also try edge detection fallback
if (contours.length < 2) {
console.log(`\n --- 备用: Canny边缘检测 ---`);
const gray = new Float64Array(w * h);
for (let i = 0; i < w * h; i++) {
const i4 = i * 4;
gray[i] = 0.299 * rgba[i4] + 0.587 * rgba[i4 + 1] + 0.114 * rgba[i4 + 2];
}
const blur = Img.gaussianBlur5(gray, w, h);
const edges = Img.canny(blur, w, h, 50, 150);
saveDebugImage('03_edges', edges, w, h, true);
const closed = Img.close(edges, w, h, 9);
saveDebugImage('04_edges_closed', closed, w, h, true);
const econtours = Img.findContours(closed, w, h);
console.log(` 边缘检测到 ${econtours.length} 个轮廓`);
for (let i = 0; i < Math.min(econtours.length, 10); i++) {
const contour = econtours[i];
const area = Img.contourArea(contour);
const rect = Img.minAreaRect(contour);
console.log(` #${i} area=${area.toFixed(0)} center=(${rect.center.x.toFixed(0)},${rect.center.y.toFixed(0)}) size=(${rect.size.width.toFixed(0)}×${rect.size.height.toFixed(0)})`);
}
}
}
async function main() {
const imagePath = process.argv[2];
if (!imagePath) {
console.log('用法: node diagnose.js <image_path>');
console.log('示例: node diagnose.js /home/ubuntu/nanocode/web-opencv/cpp/images/14red_1.png');
process.exit(1);
}
await diagnose(imagePath);
}
main();