Files
newbury-exhibit-v3/public/js/ar/detect.js
T

69 lines
2.8 KiB
JavaScript

/* detect.js — tuned ArUco 4x4 detection.
* Phantom-ID fix: reject any marker decoded with hamming distance > 0 (maxHamming: 0).
*/
export function createDetector() {
return new AR.Detector({ dictionaryName: 'ARUCO_4X4_1000', maxHammingDistance: 0 });
}
/* Returns the markers usable for solving: present in the scene, sane geometry.
* The returned array also carries diagnostics so the HUD can distinguish
* "nothing detected" from "detected but not in the scene" — the latter looks
* identical on screen but means the scene never loaded, or the crest belongs to
* a different layout. knownIds may be null/empty; then nothing is solvable and
* rawIds tells you what the camera actually saw.
*/
export function detectMarkers(detector, imageData, knownIds) {
return finaliseMarkers(detectRaw(detector, imageData), knownIds);
}
/* Raw decode over whatever image you hand it — a full detection frame or an ROI
* crop. Split out from detectMarkers so roi.js can run the same decode on a crop
* and have the results finalised together with everything else. The area floor
* is in the pixels of THIS image, which is what we want: a crop is higher
* resolution, so a marker that scrapes past the floor in a full frame clears it
* comfortably in a crop. */
export function detectRaw(detector, imageData) {
return detector.detect(imageData).filter(m => quadArea(m.corners) > 100);
}
/* Dedupe, filter to the scene, attach diagnostics. */
export function finaliseMarkers(raw, knownIds) {
/* 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;
}
/* Centre of a quad — used to merge markers that the same crest produced in two
* overlapping ROI crops. */
export function quadCentre(c) {
let x = 0, y = 0;
for (let i = 0; i < 4; i++) { x += c[i].x; y += c[i].y; }
return { x: x / 4, y: y / 4 };
}
export function quadArea(c) {
// shoelace
let a = 0;
for (let i = 0; i < 4; i++) {
const p = c[i], q = c[(i + 1) % 4];
a += p.x * q.y - q.x * p.y;
}
return Math.abs(a) / 2;
}