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

193 lines
7.8 KiB
JavaScript

// diagnose_fails.js — 诊断失败的试纸图片
const { createCanvas, loadImage } = require('canvas');
const { PaperRecognizer, PST, PST_NAMES, Img } = require('./paper-recognizer.js');
const fs = require('fs');
const path = require('path');
// Mirror the GP constants from paper-recognizer.js
const GP = {
DEFAULT_SAMPLE_RADIUS: 5,
MIN_CONTOUR_AREA: 100,
MAX_AREA_RATIO: 0.3,
MIN_RECTANGULARITY: 0.5,
BIG_LOCATOR_RATIO_SHORT: 2.0,
BIG_LOCATOR_RATIO_LONG: 3.0,
SMALL_LOCATOR_RATIO: 1.667,
MIN_BIG_LOCATOR_AREA: 200,
MAX_BIG_LOCATOR_AREA: 8000,
MIN_SMALL_LOCATOR_AREA: 50,
MAX_SMALL_LOCATOR_AREA: 2000,
COLOR_DISTANCE_THRESHOLD: 60,
AREA_RATIO_SHORT: 3.33,
AREA_RATIO_LONG: 5.0,
AREA_ERROR_RATIO: 0.4
};
const LOCATOR_COLOR = { UNKNOWN:0, RED:1, GREEN:2, BLUE:3 };
const COLOR_NAMES = {1:'RED', 2:'GREEN', 3:'BLUE'};
function diagnose(fpath, fname) {
return loadImage(fpath).then(img => {
const c = createCanvas(img.width, img.height);
const ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0);
const id = ctx.getImageData(0, 0, img.width, img.height);
const w = img.width, h = img.height;
const raw = new Uint8Array(id.data.buffer);
// Convert RGBA to BGR
const bgr = new Uint8Array(w * h * 3);
for (let i = 0; i < w * h; i++) {
bgr[i * 3] = raw[i * 4 + 2]; // B
bgr[i * 3 + 1] = raw[i * 4 + 1]; // G
bgr[i * 3 + 2] = raw[i * 4]; // R
}
console.log('\n=== ' + fname + ' (' + w + 'x' + h + ') ===');
const RED_H = [0, 15], RED_H2 = [340, 360];
const GREEN_H = [80, 170];
const BLUE_H = [180, 260];
const MIN_S = 30, MIN_V = 20;
const allCandidates = [];
for (const color of [1, 2, 3]) {
let hRange1, hRange2;
if (color === 1) { hRange1 = RED_H; hRange2 = RED_H2; }
else if (color === 2) { hRange1 = GREEN_H; hRange2 = null; }
else { hRange1 = BLUE_H; hRange2 = null; }
// HSV mask
const mask = new Uint8Array(w * h);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const i = (y * w + x) * 3;
const bb = bgr[i], gg = bgr[i + 1], rr = bgr[i + 2];
const r = rr / 255, g = gg / 255, b = bb / 255;
const mx = Math.max(r, g, b), mn = Math.min(r, g, b), d = mx - mn;
let H = 0;
if (d !== 0) {
if (mx === r) H = 60 * (((g - b) / d) % 6);
else if (mx === g) H = 60 * (((b - r) / d) + 2);
else H = 60 * (((r - g) / d) + 4);
}
if (H < 0) H += 360;
const S = mx === 0 ? 0 : (d / mx) * 100;
const V = mx * 100;
let ok = false;
if (H >= hRange1[0] && H <= hRange1[1]) ok = S >= MIN_S && V >= MIN_V;
if (!ok && hRange2 && H >= hRange2[0] && H <= hRange2[1]) ok = S >= MIN_S && V >= MIN_V;
if (ok) mask[y * w + x] = 255;
}
}
const closed = Img.morphClose(mask, w, h, 5);
const contours = Img.findContours(closed, w, h);
let colorContours = 0;
for (const ct of contours) {
const area = Img.contourArea(ct);
if (area < GP.MIN_CONTOUR_AREA) continue;
const brect = Img.boundingRect(ct);
const bbArea = brect.w * brect.h;
if (area / bbArea < GP.MIN_RECTANGULARITY) continue;
const rotatedRect = Img.minAreaRect(ct);
const rectArea = rotatedRect.size.w * rotatedRect.size.h;
if (area / rectArea < GP.MIN_RECTANGULARITY) continue;
const aspectRatio = Math.max(rotatedRect.size.w, rotatedRect.size.h) /
Math.max(Math.min(rotatedRect.size.w, rotatedRect.size.h), 1);
// square filter
if (aspectRatio >= 0.9 && aspectRatio <= 1.1) continue;
if (aspectRatio > 4.0) continue;
const distToBigShort = Math.abs(aspectRatio - GP.BIG_LOCATOR_RATIO_SHORT);
const distToBigLong = Math.abs(aspectRatio - GP.BIG_LOCATOR_RATIO_LONG);
const distToSmall = Math.abs(aspectRatio - GP.SMALL_LOCATOR_RATIO);
const tolBig = 0.35, tolSmall = 0.50;
const nearestBigDist = Math.min(distToBigShort, distToBigLong);
const isBig = nearestBigDist < tolBig && nearestBigDist < distToSmall;
const isSmall = distToSmall < tolSmall && distToSmall <= nearestBigDist;
if (!isBig && !isSmall) continue;
const center = rotatedRect.center;
const cx = Math.round(center.x), cy = Math.round(center.y);
const [bb_c, gg_c, rr_c] = Img.getBgr(bgr, w, h, cx, cy);
const [hh, ss, vv] = Img.bgrToHsv(bb_c, gg_c, rr_c);
let locColor = 0;
if (hh >= 180 && hh <= 260) locColor = 3;
else if (hh >= 80 && hh <= 170) locColor = 2;
else if (hh <= 15 || hh >= 340) locColor = 1;
allCandidates.push({
color, locColor, isBig, isSmall, area,
x: cx, y: cy, ratio: aspectRatio,
w: rotatedRect.size.w, h: rotatedRect.size.h,
hsv: [hh.toFixed(1), ss.toFixed(1), vv.toFixed(1)]
});
colorContours++;
}
}
// Show all candidates
console.log(' Candidates found: ' + allCandidates.length);
for (const ct of allCandidates) {
const cat = ct.isBig ? 'BIG' : 'SMALL';
const hsv = ct.hsv.join(',');
console.log(' ' + COLOR_NAMES[ct.color] + ' [' + ct.x + ',' + ct.y + ' ' + ct.w.toFixed(0) + 'x' + ct.h.toFixed(0) + '] ratio=' + ct.ratio.toFixed(2) + ' area=' + ct.area + ' ' + cat + ' HSV(' + hsv + ')');
}
// Group by color
for (const color of [1, 2, 3]) {
const cands = allCandidates.filter(c => c.color === color);
const bigCands = cands.filter(c => c.isBig && c.area >= GP.MIN_BIG_LOCATOR_AREA && c.area <= GP.MAX_BIG_LOCATOR_AREA);
const smallCands = cands.filter(c => c.isSmall && c.area >= GP.MIN_SMALL_LOCATOR_AREA && c.area <= GP.MAX_SMALL_LOCATOR_AREA);
console.log(' ' + COLOR_NAMES[color] + ': bigs=' + bigCands.length + ' smalls=' + smallCands.length);
if (bigCands.length === 0 && smallCands.length === 0 && cands.length > 0) {
const filteredBig = cands.filter(c => c.isBig);
const filteredSmall = cands.filter(c => c.isSmall);
for (const b of filteredBig) console.log(' BIG filtered: area=' + b.area + ' x=' + b.x + ',y=' + b.y + ' (area range: ' + GP.MIN_BIG_LOCATOR_AREA + '-' + GP.MAX_BIG_LOCATOR_AREA + ')');
for (const s of filteredSmall) console.log(' SMALL filtered: area=' + s.area + ' x=' + s.x + ',y=' + s.y + ' (area range: ' + GP.MIN_SMALL_LOCATOR_AREA + '-' + GP.MAX_SMALL_LOCATOR_AREA + ')');
}
// Pairing
if (bigCands.length >= 1 && smallCands.length >= 1) {
for (const big of bigCands) {
for (const small of smallCands) {
if (big === small) continue;
const areaRatio = big.area / Math.max(small.area, 1);
const dx = big.x - small.x, dy = big.y - small.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const distRatio = dist / Math.max(Math.max(big.w, big.h), Math.max(small.w, small.h));
const bigType = areaRatio < (GP.AREA_RATIO_SHORT + GP.AREA_RATIO_LONG) / 2 ? 'SHORT' : 'LONG';
console.log(' PAIR: dist=' + dist.toFixed(0) + ' distRatio=' + distRatio.toFixed(2) + ' areaRatio=' + areaRatio.toFixed(2) + ' bigType=' + bigType);
}
}
}
}
});
}
(async () => {
const dir = '/home/ubuntu/nanocode/web-opencv/cpp/images/';
const fails = [
'4b85f78723d00b4fb4ef06cc4ca6f1e8.jpg',
'4fa2f133456bb7620482a400f9d06a65.jpg',
'4fbbe9223faf5a2e1f3e6c85f6464af2.jpg',
'a8ec33379c878fc8350224bcfeaa7dcb.jpg',
'da753742af5c2b51a1fcb00437fda470.jpg',
'aec56e059994ac921d3bda5338f3a5d5.jpg',
'IMG_0541.JPG',
'IMG_0547.JPG',
'IMG_20190927_154351.png'
];
for (const f of fails) {
try {
const fpath = path.join(dir, f);
await diagnose(fpath, f);
} catch (e) {
console.log('Error: ' + f + ': ' + e.message);
}
}
})();