225 lines
10 KiB
JavaScript
225 lines
10 KiB
JavaScript
// trace_diag.js — trace actual PaperRecognizer internals by replicating _process exactly
|
|
const { createCanvas, loadImage } = require('canvas');
|
|
const { Img, PST_NAMES } = require('./paper-recognizer.js');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Exact 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
|
|
};
|
|
|
|
function matchConfig(distRatio, areaRatio, bigType) {
|
|
const Min_AR_S = 2.6, Max_AR_S = 4.0, Min_AR_L = 4.0, Max_AR_L = 6.0;
|
|
if (areaRatio < Min_AR_S || areaRatio > Max_AR_L) return -1; // UNKNOWN
|
|
const isSmall = areaRatio <= Max_AR_S;
|
|
if (isSmall) {
|
|
if (distRatio >= 18.5 && distRatio <= 26.5) return 3; // 14_S
|
|
if (distRatio >= 14.1 && distRatio < 18.5) return 5; // 11_S
|
|
if (distRatio >= 11.6 && distRatio < 14.1) return 7; // 8_S
|
|
} else {
|
|
if (distRatio >= 18.5 && distRatio <= 26.98) return 4; // 14_L
|
|
if (distRatio >= 14.5 && distRatio < 18.5) return 6; // 11_L
|
|
if (distRatio >= 12.0 && distRatio < 14.5) return 8; // 8_L
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function trace(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 to BGR (exactly like _process)
|
|
const bgr = new Uint8Array(w * h * 3);
|
|
for (let i = 0; i < w * h; i++) {
|
|
bgr[i * 3] = raw[i * 4 + 2];
|
|
bgr[i * 3 + 1] = raw[i * 4 + 1];
|
|
bgr[i * 3 + 2] = raw[i * 4];
|
|
}
|
|
|
|
console.log('\n=== ' + fname + ' (' + w + 'x' + h + ') ===');
|
|
|
|
// Step 1: HSV masks (exactly like _process)
|
|
const hsv = Img.bgrImageToHsv(bgr, w, h);
|
|
const blueMask = Img.hsvInRange(hsv, w, h, 180, 260, 30, 255, 30, 220);
|
|
const greenMask = Img.hsvInRange(hsv, w, h, 80, 170, 30, 255, 30, 220);
|
|
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]);
|
|
|
|
// Count mask pixels
|
|
const countMask = (m) => { let n=0; for(let i=0;i<w*h;i++) if(m[i]) n++; return n; };
|
|
console.log(' Mask pixels: blue=' + countMask(blueMask) + ' green=' + countMask(greenMask) + ' red=' + countMask(redMask));
|
|
|
|
// Combined mask
|
|
const colorMask = new Uint8Array(w * h);
|
|
for (let i = 0; i < w * h; i++) {
|
|
colorMask[i] = Math.max(blueMask[i], greenMask[i], redMask[i]);
|
|
}
|
|
console.log(' Combined mask pixels: ' + countMask(colorMask));
|
|
|
|
// Morph close
|
|
const closed = Img.morphClose(colorMask, w, h, 5);
|
|
console.log(' After morphClose pixels: ' + countMask(closed));
|
|
|
|
// Find contours
|
|
const contours = Img.findContours(closed, w, h);
|
|
console.log(' Raw contours: ' + contours.length);
|
|
|
|
// Filter candidates (exactly like _process)
|
|
const candidates = [];
|
|
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);
|
|
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 [b_, g_, r_] = Img.getBgr(bgr, w, h, cx, cy);
|
|
const [hh, ss, vv] = Img.bgrToHsv(b_, g_, r_);
|
|
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;
|
|
candidates.push({
|
|
center: {x: center.x, y: center.y}, area,
|
|
width: rotatedRect.size.w, height: rotatedRect.size.h,
|
|
color: locColor, isBig, isSmall, aspectRatio,
|
|
avgColor: [r_, g_, b_], contour: ct
|
|
});
|
|
}
|
|
|
|
console.log(' Candidates: ' + candidates.length);
|
|
for (const ct of candidates) {
|
|
const cat = ct.isBig ? 'BIG' : 'SMALL';
|
|
console.log(' ' + cat + ' color=' + ct.color + ' area=' + ct.area + ' ratio=' + ct.aspectRatio.toFixed(2) + ' x=' + ct.center.x.toFixed(0) + ',y=' + ct.center.y.toFixed(0) + ' size=' + ct.width.toFixed(0) + 'x' + ct.height.toFixed(0));
|
|
}
|
|
|
|
const bigCands = candidates.filter(c => c.isBig && c.area >= GP.MIN_BIG_LOCATOR_AREA && c.area <= GP.MAX_BIG_LOCATOR_AREA);
|
|
const smallCands = candidates.filter(c => c.isSmall && c.area >= GP.MIN_SMALL_LOCATOR_AREA && c.area <= GP.MAX_SMALL_LOCATOR_AREA);
|
|
console.log(' After area filter: bigs=' + bigCands.length + ' smalls=' + smallCands.length);
|
|
|
|
if (bigCands.length === 0 || smallCands.length === 0) {
|
|
console.log(' Fallback check: bigs=' + bigCands.length + ' smalls=' + smallCands.length);
|
|
if (bigCands.length >= 2 && smallCands.length === 0) {
|
|
const sortedBigs = [...bigCands].sort((a, b) => a.area - b.area);
|
|
for (const big of sortedBigs) {
|
|
for (const small of sortedBigs) {
|
|
if (big === small) continue;
|
|
if (big.area <= small.area) continue;
|
|
const ar = big.area / Math.max(small.area, 1);
|
|
if (ar < 2.0 || ar > 6.0) continue;
|
|
smallCands.push(small);
|
|
console.log(' Fallback: added small area=' + small.area + ' paired with big area=' + big.area + ' areaRatio=' + ar.toFixed(2));
|
|
}
|
|
}
|
|
}
|
|
if (bigCands.length === 0 || smallCands.length === 0) {
|
|
console.log(' → -3: No locators');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Pairing
|
|
let bestPair = null, bestScore = Infinity;
|
|
for (const big of bigCands) {
|
|
for (const small of smallCands) {
|
|
if (big === small) continue;
|
|
const areaRatio = big.area / Math.max(small.area, 1);
|
|
const yDiff = Math.abs(big.center.y - small.center.y);
|
|
const colorMatch = big.color === small.color && big.color !== 0;
|
|
const score = Math.abs(areaRatio - 4.0) + yDiff * 0.1 + (colorMatch ? 0 : 5);
|
|
console.log(' Try pair: big[' + big.center.x.toFixed(0) + ',' + big.center.y.toFixed(0) + '] small[' + small.center.x.toFixed(0) + ',' + small.center.y.toFixed(0) + '] areaRatio=' + areaRatio.toFixed(2) + ' colorMatch=' + colorMatch + ' score=' + score.toFixed(2));
|
|
if (score < bestScore) { bestScore = score; bestPair = { big, small, areaRatio, colorMatch }; }
|
|
}
|
|
}
|
|
|
|
if (!bestPair) {
|
|
console.log(' → -4: No matching pair');
|
|
return;
|
|
}
|
|
|
|
const { big: bigLoc_, small: smallLoc_, areaRatio: areaRatio2 } = bestPair;
|
|
const smallOnLeft = smallLoc_.center.x < bigLoc_.center.x;
|
|
const bigLoc = bigLoc_, smallLoc = smallLoc_;
|
|
const loc1 = smallOnLeft ? smallLoc : bigLoc;
|
|
const loc2 = smallOnLeft ? bigLoc : smallLoc;
|
|
|
|
const dist = Math.sqrt((bigLoc.center.x - smallLoc.center.x) ** 2 + (bigLoc.center.y - smallLoc.center.y) ** 2);
|
|
const bigEdgeDist = Math.max(bigLoc.width, bigLoc.height) / 2;
|
|
const smallEdgeDist = Math.max(smallLoc.width, smallLoc.height) / 2;
|
|
const bigEdgeCenter = {
|
|
x: bigLoc.center.x - (bigLoc.center.x - smallLoc.center.x) / dist * bigEdgeDist,
|
|
y: bigLoc.center.y - (bigLoc.center.y - smallLoc.center.y) / dist * bigEdgeDist
|
|
};
|
|
const smallEdgeCenter = {
|
|
x: smallLoc.center.x + (bigLoc.center.x - smallLoc.center.x) / dist * smallEdgeDist,
|
|
y: smallLoc.center.y + (bigLoc.center.y - smallLoc.center.y) / dist * smallEdgeDist
|
|
};
|
|
const effDx = bigEdgeCenter.x - smallEdgeCenter.x;
|
|
const effDy = bigEdgeCenter.y - smallEdgeCenter.y;
|
|
const effectiveDist = Math.sqrt(effDx * effDx + effDy * effDy);
|
|
const bigType = areaRatio2 < (GP.AREA_RATIO_SHORT + GP.AREA_RATIO_LONG) / 2 ? 'SHORT' : 'LONG';
|
|
const distRatio = effectiveDist / Math.max(bigLoc.height, smallLoc.height);
|
|
const paperType = matchConfig(distRatio, areaRatio2, bigType);
|
|
|
|
console.log(' Pair: big area=' + bigLoc_.area + ' small area=' + smallLoc_.area);
|
|
console.log(' dist=' + dist.toFixed(0) + ' effectiveDist=' + effectiveDist.toFixed(0) + ' distRatio=' + distRatio.toFixed(2) + ' areaRatio=' + areaRatio2.toFixed(2) + ' bigType=' + bigType);
|
|
console.log(' matchConfig → ' + paperType + ' (' + (paperType > 0 ? PST_NAMES[paperType] : 'UNKNOWN') + ')');
|
|
});
|
|
}
|
|
|
|
(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 { await trace(path.join(dir,f), f); } catch(e) { console.log('Error: '+f+': '+e.message); }
|
|
}
|
|
})();
|