202 lines
7.6 KiB
JavaScript
202 lines
7.6 KiB
JavaScript
/**
|
||
* diagnose2.js — 使用 PaperRecognizer 内部函数诊断
|
||
*/
|
||
const { createCanvas, loadImage } = require('canvas');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { Img, PaperRecognizer } = require('./paper-recognizer.js');
|
||
|
||
const OUTPUT_DIR = '/home/ubuntu/nanocode/web-opencv/miniappB/samples/';
|
||
|
||
function saveDebugImage(label, data, w, h, isGray) {
|
||
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'));
|
||
}
|
||
|
||
// Patch bgrImageToHsv for diagnose
|
||
Img.bgrImageToHsv_ = Img.bgrImageToHsv || function(bgr, w, h) {
|
||
const out = new Float64Array(w * h * 3);
|
||
for (let i = 0; i < w * h; i++) {
|
||
const j = i * 3;
|
||
const [hh, ss, vv] = Img.bgrToHsv(bgr[j], bgr[j+1], bgr[j+2]);
|
||
out[j] = hh; out[j+1] = ss; out[j+2] = vv;
|
||
}
|
||
return out;
|
||
};
|
||
|
||
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 imgData = ctx.getImageData(0, 0, w, h);
|
||
const rgba = new Uint8Array(imgData.data.buffer);
|
||
|
||
// Convert RGBA → BGR (matching paper-recognizer preprocess)
|
||
const bgr = new Float64Array(w * h * 3);
|
||
const gray = new Float64Array(w * h);
|
||
for (let i = 0; i < w * h; i++) {
|
||
const i4 = i * 4, i3 = i * 3;
|
||
const r = rgba[i4], g = rgba[i4+1], b = rgba[i4+2];
|
||
bgr[i3] = b; bgr[i3+1] = g; bgr[i3+2] = r;
|
||
gray[i] = 0.299 * r + 0.587 * g + 0.114 * b;
|
||
}
|
||
|
||
// HSV via paper-recognizer's bgrImageToHsv
|
||
const hsv = Img.bgrImageToHsv(bgr, w, h);
|
||
|
||
// ---- HSV sampling from locator-like regions ----
|
||
// Find bright-ish, saturated regions near the center strip
|
||
console.log(`\n--- HSV 全图统计 ---`);
|
||
const stats = { h: [], s: [], v: [] };
|
||
for (let i = 0; i < w * h; i++) {
|
||
const j = i * 3;
|
||
stats.h.push(hsv[j]); stats.s.push(hsv[j+1]); stats.v.push(hsv[j+2]);
|
||
}
|
||
stats.h.sort((a,b)=>a-b); stats.s.sort((a,b)=>a-b); stats.v.sort((a,b)=>a-b);
|
||
const m = stats.h.length;
|
||
const p5 = Math.floor(m*0.05), p25 = Math.floor(m*0.25), p50 = Math.floor(m*0.5), p75 = Math.floor(m*0.75), p95 = Math.floor(m*0.95);
|
||
console.log(` H: p5=${stats.h[p5].toFixed(0)} p50=${stats.h[p50].toFixed(0)} p95=${stats.h[p95].toFixed(0)}`);
|
||
console.log(` S: p5=${stats.s[p5].toFixed(0)} p50=${stats.s[p50].toFixed(0)} p95=${stats.s[p95].toFixed(0)}`);
|
||
console.log(` V: p5=${stats.v[p5].toFixed(0)} p50=${stats.v[p50].toFixed(0)} p95=${stats.v[p95].toFixed(0)}`);
|
||
|
||
// ---- Color segmentation (matching actual code) ----
|
||
console.log(`\n--- 颜色分割 ---`);
|
||
|
||
// Blue mask
|
||
const blueMask = Img.hsvInRange(hsv, w, h, 180, 260, 30, 255, 30, 220);
|
||
// Green mask
|
||
const greenMask = Img.hsvInRange(hsv, w, h, 80, 170, 30, 255, 30, 220);
|
||
// Red mask (added - paper-recognizer missing this!)
|
||
const redMask1 = Img.hsvInRange(hsv, w, h, 0, 15, 30, 255, 30, 220);
|
||
const redMask2 = Img.hsvInRange(hsv, w, h, 340, 360, 30, 255, 30, 220);
|
||
|
||
const redMask = new Uint8Array(w * h);
|
||
for (let i = 0; i < w * h; i++) redMask[i] = Math.max(redMask1[i], redMask2[i]);
|
||
|
||
const colorMask = new Uint8Array(w * h);
|
||
for (let i = 0; i < w * h; i++) {
|
||
colorMask[i] = Math.max(blueMask[i], greenMask[i], redMask[i]);
|
||
}
|
||
|
||
let bluePx = 0, greenPx = 0, redPx = 0;
|
||
for (let i = 0; i < w * h; i++) {
|
||
if (blueMask[i] > 0) bluePx++;
|
||
if (greenMask[i] > 0) greenPx++;
|
||
if (redMask[i] > 0) redPx++;
|
||
}
|
||
const total = w * h;
|
||
console.log(` 蓝: ${bluePx} (${(bluePx/total*100).toFixed(2)}%)`);
|
||
console.log(` 绿: ${greenPx} (${(greenPx/total*100).toFixed(2)}%)`);
|
||
console.log(` 红: ${redPx} (${(redPx/total*100).toFixed(2)}%)`);
|
||
console.log(` 总: ${Math.max(bluePx, greenPx, redPx)} (${(Math.max(bluePx, greenPx, redPx)/total*100).toFixed(2)}%)`);
|
||
|
||
saveDebugImage(`diag_${fname.replace(/\.\w+$/,'')}_01_color`, colorMask, w, h, true);
|
||
|
||
// Morphology close
|
||
const closed = Img.morphClose(colorMask, w, h, 5);
|
||
const closed2 = Img.morphClose(closed, w, h, 5);
|
||
saveDebugImage(`diag_${fname.replace(/\.\w+$/,'')}_02_closed`, closed2, w, h, true);
|
||
|
||
// Find contours
|
||
const contours = Img.findContours(closed2, w, h);
|
||
console.log(`\n 轮廓数: ${contours.length}`);
|
||
|
||
// Run through filtering logic (matching actual code)
|
||
let candidateNum = 0;
|
||
for (const ct of contours) {
|
||
const area = Img.contourArea(ct);
|
||
if (area < 100) continue;
|
||
|
||
const brect = Img.boundingRect(ct);
|
||
const bbArea = brect.w * brect.h;
|
||
if (area / bbArea < 0.5) continue;
|
||
|
||
const rotatedRect = Img.minAreaRect(ct);
|
||
const rectArea = rotatedRect.size.w * rotatedRect.size.h;
|
||
if (area / rectArea < 0.5) continue;
|
||
|
||
const aspectRatio = Math.max(rotatedRect.size.w, rotatedRect.size.h) /
|
||
Math.max(Math.min(rotatedRect.size.w, rotatedRect.size.h), 1);
|
||
|
||
if (aspectRatio >= 0.9 && aspectRatio <= 1.1) continue;
|
||
if (aspectRatio > 4.0) continue;
|
||
|
||
const tol = 0.35;
|
||
const isBig = Math.abs(aspectRatio - 2.0) < tol || Math.abs(aspectRatio - 3.0) < tol;
|
||
const isSmall = Math.abs(aspectRatio - 1.667) < tol;
|
||
|
||
// Color determination
|
||
const cx = Math.round(rotatedRect.center.x), cy = Math.round(rotatedRect.center.y);
|
||
const i3 = (Math.min(h-1, Math.max(0, cy)) * w + Math.min(w-1, Math.max(0, cx))) * 3;
|
||
const hh = hsv[i3], ss = hsv[i3+1], vv = hsv[i3+2];
|
||
|
||
let colorLabel = '?';
|
||
if (hh >= 180 && hh <= 260) colorLabel = 'BLUE';
|
||
else if (hh >= 80 && hh <= 170) colorLabel = 'GREEN';
|
||
else if (hh <= 15 || hh >= 340) colorLabel = 'RED';
|
||
else colorLabel = `H=${hh.toFixed(0)}`;
|
||
|
||
console.log(` #${candidateNum} area=${area.toFixed(0)} ar=${aspectRatio.toFixed(2)} rect=${(area/rectArea).toFixed(2)} ` +
|
||
`center=(${cx},${cy}) size=(${rotatedRect.size.w.toFixed(0)},${rotatedRect.size.h.toFixed(0)}) ` +
|
||
`color=${colorLabel} isBig=${isBig} isSmall=${isSmall} ` +
|
||
`hsv=(${hh.toFixed(0)},${ss.toFixed(0)},${vv.toFixed(0)})`);
|
||
|
||
candidateNum++;
|
||
}
|
||
|
||
if (candidateNum === 0) {
|
||
console.log(` 无候选轮廓! 尝试更宽松的参数...`);
|
||
// Try lower thresholds
|
||
const testMasks = [
|
||
['蓝 S20', Img.hsvInRange(hsv, w, h, 180, 260, 20, 255, 20, 255)],
|
||
['绿 S20', Img.hsvInRange(hsv, w, h, 80, 170, 20, 255, 20, 255)],
|
||
['红 S20', (() => {
|
||
const m1 = Img.hsvInRange(hsv, w, h, 0, 15, 20, 255, 20, 255);
|
||
const m2 = Img.hsvInRange(hsv, w, h, 340, 360, 20, 255, 20, 255);
|
||
const m = new Uint8Array(w*h);
|
||
for (let i=0;i<w*h;i++) m[i]=Math.max(m1[i],m2[i]);
|
||
return m;
|
||
})()]
|
||
];
|
||
for (const [label, mask] of testMasks) {
|
||
let cnt = 0;
|
||
for (let i=0;i<w*h;i++) if(mask[i]>0) cnt++;
|
||
console.log(` ${label}: ${cnt} pixels (${(cnt/total*100).toFixed(2)}%)`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
const args = process.argv.slice(2);
|
||
const defaultImages = [
|
||
'/home/ubuntu/nanocode/web-opencv/cpp/images/11green_1.png',
|
||
'/home/ubuntu/nanocode/web-opencv/cpp/images/14red_1.png',
|
||
'/home/ubuntu/nanocode/web-opencv/cpp/images/14blue_1.png',
|
||
];
|
||
const files = args.length > 0 ? args : defaultImages;
|
||
|
||
for (const f of files) {
|
||
await diagnose(f);
|
||
}
|
||
}
|
||
|
||
main();
|