fix for detection threshold

This commit is contained in:
2026-03-14 18:04:52 +01:00
parent 8cdb518022
commit 742008cde1

View File

@@ -1,7 +1,6 @@
/* main.js - websnap */ /* main.js - websnap */
import { HandLandmarker, FilesetResolver } import { HandLandmarker, FilesetResolver }
from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest"; from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest";
/* set html elements */ /* set html elements */
const video = document.getElementById("video"); const video = document.getElementById("video");
const canvas = document.getElementById("canvas"); const canvas = document.getElementById("canvas");
@@ -9,9 +8,9 @@ const overlay = document.getElementById("overlay");
const octx = overlay.getContext("2d"); const octx = overlay.getContext("2d");
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const flash = document.getElementById("flash"); const flash = document.getElementById("flash");
let lastShot = 0; let lastShot = 0;
let peaceFrames = 0;
const PEACE_FRAMES_REQUIRED = 6; /* ~100 ms at 60 fps */
/* function to request fullscreen */ /* function to request fullscreen */
function requestFS() { function requestFS() {
const el = document.documentElement; const el = document.documentElement;
@@ -19,19 +18,16 @@ function requestFS() {
?.call(el); ?.call(el);
} }
requestFS(); requestFS();
/* add listeners for fullscreen */ /* add listeners for fullscreen */
document.addEventListener("click", requestFS, { once: true }); document.addEventListener("click", requestFS, { once: true });
document.addEventListener("touchend", requestFS, { once: true }); document.addEventListener("touchend", requestFS, { once: true });
document.addEventListener("dblclick", requestFS); document.addEventListener("dblclick", requestFS);
/* camera setup */ /* camera setup */
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
/* ideal res: 4096x2160 ; 4k */ /* ideal res: 4096x2160 ; 4k */
video: { width: { ideal: 4096 }, height: { ideal: 2160 }, facingMode: "user" } video: { width: { ideal: 4096 }, height: { ideal: 2160 }, facingMode: "user" }
}); });
video.srcObject = stream; video.srcObject = stream;
/* import mediapipe for handgestures */ /* import mediapipe for handgestures */
const vision = await FilesetResolver.forVisionTasks( const vision = await FilesetResolver.forVisionTasks(
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm" "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm"
@@ -39,18 +35,20 @@ const vision = await FilesetResolver.forVisionTasks(
const handLandmarker = await HandLandmarker.createFromOptions(vision, { const handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: { baseOptions: {
modelAssetPath: modelAssetPath:
"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task" "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",
delegate: "GPU" /* faster inference → more consistent detections */
}, },
runningMode: "VIDEO", runningMode: "VIDEO",
numHands: 1 numHands: 1,
minHandDetectionConfidence: 0.3, /* default 0.5 — loosens initial detection */
minHandPresenceConfidence: 0.3, /* default 0.5 — keeps tracking through partial occlusion */
minTrackingConfidence: 0.3 /* default 0.5 — less likely to drop the hand mid-gesture */
}); });
/* flash for when picture gets taken */ /* flash for when picture gets taken */
function triggerFlash() { function triggerFlash() {
flash.classList.add("pop"); flash.classList.add("pop");
requestAnimationFrame(() => requestAnimationFrame(() => flash.classList.remove("pop"))); requestAnimationFrame(() => requestAnimationFrame(() => flash.classList.remove("pop")));
} }
/* capture a picture */ /* capture a picture */
function takePhoto() { function takePhoto() {
triggerFlash(); triggerFlash();
@@ -62,7 +60,6 @@ function takePhoto() {
saveAs(blob, "websnap-" + Date.now() + ".png"); saveAs(blob, "websnap-" + Date.now() + ".png");
}, "image/png"); }, "image/png");
} }
/* detect shit */ /* detect shit */
function detectLoop(timestamp) { function detectLoop(timestamp) {
const dw = overlay.offsetWidth, dh = overlay.offsetHeight; const dw = overlay.offsetWidth, dh = overlay.offsetHeight;
@@ -71,35 +68,46 @@ function detectLoop(timestamp) {
} }
/* draw rect */ /* draw rect */
octx.clearRect(0, 0, dw, dh); octx.clearRect(0, 0, dw, dh);
if (video.readyState < 2) { requestAnimationFrame(detectLoop); return; } if (video.readyState < 2) { requestAnimationFrame(detectLoop); return; }
const result = handLandmarker.detectForVideo(video, timestamp); const result = handLandmarker.detectForVideo(video, timestamp);
if (result.landmarks.length) { if (result.landmarks.length) {
const lm = result.landmarks[0]; const lm = result.landmarks[0];
/* finger indexes, for peace sign */ /* finger indexes, for peace sign */
const indexUp = lm[8].y < lm[6].y;
const middleUp = lm[12].y < lm[10].y; /* hand scale: wrist(0) → middle MCP(9) distance in normalised coords.
const ringDown = lm[16].y > lm[14].y; normalising against hand size makes thresholds work at any distance */
const pinkyDown = lm[20].y > lm[18].y; const scale = Math.hypot(lm[9].x - lm[0].x, lm[9].y - lm[0].y);
const fingerGap = Math.abs(lm[8].x - lm[12].x); const minBend = scale * 0.5;
const fingerUp = (tip, pip) => (pip.y - tip.y) > minBend;
const fingerDown = (tip, pip) => (tip.y - pip.y) > -minBend * 0.3; /* relaxed */
const indexUp = fingerUp(lm[8], lm[6]);
const middleUp = fingerUp(lm[12], lm[10]);
const ringDown = fingerDown(lm[16], lm[14]);
const pinkyDown = fingerDown(lm[20], lm[18]);
/* gap normalised so it works at any distance */
const fingerGap = Math.hypot(lm[8].x - lm[12].x, lm[8].y - lm[12].y) / scale;
const peace = const peace =
indexUp && indexUp &&
middleUp && middleUp &&
ringDown && ringDown &&
pinkyDown && pinkyDown &&
fingerGap > 0.05; fingerGap > 0.35;
if (peace && Date.now() - lastShot > 2500) { /* wait a bit */ /* require gesture to hold for several frames to kill noisy false positives */
takePhoto(); if (peace) {
lastShot = Date.now(); peaceFrames++;
if (peaceFrames >= PEACE_FRAMES_REQUIRED && Date.now() - lastShot > 2500) { /* wait a bit */
takePhoto();
lastShot = Date.now();
peaceFrames = 0;
}
} else {
peaceFrames = 0;
} }
} }
requestAnimationFrame(detectLoop); requestAnimationFrame(detectLoop);
} }
requestAnimationFrame(detectLoop); requestAnimationFrame(detectLoop);