// 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= 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); } } })();