From f2ee880bf16ebc76cc107e1359ad72792bb6e560 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 24 Aug 2026 11:24:42 +1000 Subject: [PATCH] =?UTF-8?q?fix:=20deduplicate=20repeated=20marker=20ids=20?= =?UTF-8?q?in=20a=20frame=20=E2=80=94=20two=20quads=20decoding=20to=20one?= =?UTF-8?q?=20id=20gave=20solvePnP=20contradictory=20constraints=20(reproj?= =?UTF-8?q?=205px+)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/js/ar/detect.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/public/js/ar/detect.js b/public/js/ar/detect.js index 3ab7bd5..59a75de 100644 --- a/public/js/ar/detect.js +++ b/public/js/ar/detect.js @@ -14,9 +14,25 @@ export function createDetector() { */ export function detectMarkers(detector, imageData, knownIds) { const raw = detector.detect(imageData).filter(m => quadArea(m.corners) > 100); - const usable = (knownIds && knownIds.size) ? raw.filter(m => knownIds.has(m.id)) : []; + + /* Deduplicate by id. Two quads in one frame can decode to the same id — a + * misread of a different crest under blur or glare. That is poison for the + * board solve: both quads are handed the SAME world corners, so solvePnP gets + * two contradictory constraints for one point set and the pose collapses + * (seen on device as reproj jumping to 5px+). Keep the largest quad, which is + * the closest and best-resolved reading. */ + const best = new Map(); + for (const m of raw) { + const area = quadArea(m.corners); + const prev = best.get(m.id); + if (!prev || area > prev.area) best.set(m.id, { m, area }); + } + const uniq = [...best.values()].map(e => e.m); + + const usable = (knownIds && knownIds.size) ? uniq.filter(m => knownIds.has(m.id)) : []; usable.rawCount = raw.length; usable.rawIds = raw.map(m => m.id); + usable.dupes = raw.length - uniq.length; return usable; }