optimazed camera controll again

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-29 23:09:07 +09:00
parent 708fa4e9ab
commit 73130dd8ce
4 changed files with 52 additions and 54 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -7,7 +7,7 @@
<title>Svelte app</title> <title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'> <link rel='icon' type='image/png' href='/assets/nubzuki.png'>
<link rel='stylesheet' href='/global.css'> <link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'> <link rel='stylesheet' href='/build/bundle.css'>

View File

@@ -1,91 +1,89 @@
import { FaceDetector, FilesetResolver } from '@mediapipe/tasks-vision'; import { FaceLandmarker, FilesetResolver } from '@mediapipe/tasks-vision';
export const cameraInput = { export const cameraInput = {
left: false, left: false,
right: false, right: false,
active: false, active: false,
zone: 'center', zone: 'center'
x: 0.5
}; };
let video; let video;
let faceDetector; let faceLandmarker;
let lastTime = 0;
// 🔥 NARROW CENTER ZONE: const DETECTION_INTERVAL = 60;
// 0.48 and 0.52 means the center is only 4% of the screen width.
// Adjust these if it's still too hard to trigger movement.
const LEFT_THRESHOLD = 0.48;
const RIGHT_THRESHOLD = 0.52;
export async function initCameraControl() { export async function initCameraControl() {
if (cameraInput.active) return video; if (cameraInput.active)
return video;
video = document.createElement('video'); video = document.createElement('video');
video.autoplay = true; video.autoplay = true;
video.playsInline = true; video.playsInline = true;
video.muted = true; video.muted = true;
// Set low resolution and frame rate for better performance
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
video: { video: {
width: 160, width: { ideal: 160 },
height: 120, height: { ideal: 120 },
frameRate: { ideal: 20 } // Slightly higher for faster reaction frameRate: { ideal: 30 }
} }
}); });
video.srcObject = stream; video.srcObject = stream;
//load models for face landmark detection
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'
); );
faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
faceDetector = await FaceDetector.createFromOptions(vision, {
baseOptions: { baseOptions: {
modelAssetPath: `https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.task`, modelAssetPath:
delegate: 'GPU' 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task',
}, },
runningMode: 'VIDEO' runningMode: 'VIDEO',
}); numFaces: 1
video.addEventListener('loadeddata', () => {
cameraInput.active = true;
startDetectionLoop();
}); });
cameraInput.active = true;
return video; return video;
} }
function startDetectionLoop() { export function updateCameraControl() {
const detect = (now) => { const now = performance.now();
const result = faceDetector.detectForVideo(video, now);
if (result.detections.length > 0) { // Limit detection frequency to improve performance
const box = result.detections[0].boundingBox; if (now - lastTime < DETECTION_INTERVAL)
// Calculate raw center point (0 to 1) return;
const rawX = (box.originX + box.width / 2) / 160; lastTime = now;
cameraInput.x = rawX;
// INSTANT LOGIC (No smoothing) if (!faceLandmarker || !video || video.readyState < 2)
if (rawX < LEFT_THRESHOLD) { return;
updateZones(false, true, 'right'); // Mirrored: Face on left of cam = move right
} else if (rawX > RIGHT_THRESHOLD) {
updateZones(true, false, 'left'); // Mirrored: Face on right of cam = move left
} else {
updateZones(false, false, 'center');
}
} else {
updateZones(false, false, 'center');
}
video.requestVideoFrameCallback(detect); const result = faceLandmarker.detectForVideo(video, now);
};
video.requestVideoFrameCallback(detect); // If no face is detected, reset to center
} if (!result.faceLandmarks.length) {
cameraInput.left = false;
cameraInput.right = false;
cameraInput.zone = 'center';
return;
}
function updateZones(l, r, z) { // look at the nose and determine zone
cameraInput.left = l; const x = result.faceLandmarks[0][1].x;
cameraInput.right = r; if (x < 0.4) {
cameraInput.zone = z; cameraInput.left = false;
cameraInput.right = true;
cameraInput.zone = 'right';
} else if (x > 0.6) {
cameraInput.left = true;
cameraInput.right = false;
cameraInput.zone = 'left';
} else {
cameraInput.left = false;
cameraInput.right = false;
cameraInput.zone = 'center';
}
} }

View File

@@ -16,4 +16,4 @@ export const PLAT_TYPE = {
MOVING: 'moving', MOVING: 'moving',
SPRING: 'spring', SPRING: 'spring',
ONE_TIME: 'one-time' ONE_TIME: 'one-time'
}; };