fix for detection threshold
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user