Inferencing perfomance
This commit is contained in:
165
app/page.tsx
165
app/page.tsx
@@ -19,10 +19,8 @@ type CameraStatus = 'loading' | 'active' | 'denied' | 'no_devices';
|
|||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
const [stream, setStream] = useState<MediaStream | null>(null);
|
const [stream, setStream] = useState<MediaStream | null>(null);
|
||||||
const [cameraStatus, setCameraStatus] = useState<CameraStatus>('loading');
|
const [cameraStatus, setCameraStatus] = useState<CameraStatus>('loading');
|
||||||
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D | null>(null);
|
|
||||||
|
|
||||||
const [videoDevices, setVideoDevices] = useState<MediaDeviceInfo[]>([]);
|
const [videoDevices, setVideoDevices] = useState<MediaDeviceInfo[]>([]);
|
||||||
const [selectedDeviceId, setSelectedDeviceId] = useState<string>('');
|
const [selectedDeviceId, setSelectedDeviceId] = useState<string>('');
|
||||||
@@ -40,80 +38,11 @@ export default function HomePage() {
|
|||||||
const [shoeDetectionCount, setShoeDetectionCount] = useState(0);
|
const [shoeDetectionCount, setShoeDetectionCount] = useState(0);
|
||||||
const [lastSoundTime, setLastSoundTime] = useState(0);
|
const [lastSoundTime, setLastSoundTime] = useState(0);
|
||||||
|
|
||||||
// Draw detections function - defined early to avoid initialization order issues
|
|
||||||
const drawDetections = useCallback((
|
|
||||||
detection: DetectionResult | null,
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
video: HTMLVideoElement,
|
|
||||||
canvas: HTMLCanvasElement
|
|
||||||
) => {
|
|
||||||
console.log('🎨 drawDetections called:', { detection, videoSize: { width: video.videoWidth, height: video.videoHeight }, canvasSize: { width: canvas.width, height: canvas.height } });
|
|
||||||
|
|
||||||
// Clear canvas
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
if (!detection) {
|
|
||||||
console.log('❌ No detection to draw');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const videoWidth = video.videoWidth;
|
// Clean detection callback - no canvas drawing needed
|
||||||
const videoHeight = video.videoHeight;
|
|
||||||
|
|
||||||
// Ensure canvas matches video dimensions
|
|
||||||
if (canvas.width !== videoWidth || canvas.height !== videoHeight) {
|
|
||||||
canvas.width = videoWidth;
|
|
||||||
canvas.height = videoHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bounding box coordinates [x, y, width, height]
|
|
||||||
const [x, y, width, height] = detection.bbox;
|
|
||||||
|
|
||||||
// Convert normalized coordinates to pixel coordinates
|
|
||||||
const displayX = x * videoWidth;
|
|
||||||
const displayY = y * videoHeight;
|
|
||||||
const displayWidth = width * videoWidth;
|
|
||||||
const displayHeight = height * videoHeight;
|
|
||||||
|
|
||||||
// Draw bounding box with glow effect
|
|
||||||
ctx.shadowColor = '#ff0000';
|
|
||||||
ctx.shadowBlur = 10;
|
|
||||||
ctx.strokeStyle = '#ff0000';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.strokeRect(displayX, displayY, displayWidth, displayHeight);
|
|
||||||
|
|
||||||
// Reset shadow for text
|
|
||||||
ctx.shadowBlur = 0;
|
|
||||||
|
|
||||||
// Draw label with background
|
|
||||||
const labelText = `${detection.class} (${(detection.confidence * 100).toFixed(1)}%)`;
|
|
||||||
ctx.font = 'bold 16px Arial';
|
|
||||||
const textMetrics = ctx.measureText(labelText);
|
|
||||||
const textWidth = textMetrics.width;
|
|
||||||
const textHeight = 16;
|
|
||||||
const padding = 6;
|
|
||||||
|
|
||||||
// Label background
|
|
||||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.8)';
|
|
||||||
ctx.fillRect(
|
|
||||||
displayX,
|
|
||||||
displayY > textHeight + padding ? displayY - textHeight - padding : displayY + displayHeight + 2,
|
|
||||||
textWidth + padding * 2,
|
|
||||||
textHeight + padding
|
|
||||||
);
|
|
||||||
|
|
||||||
// Label text
|
|
||||||
ctx.fillStyle = 'white';
|
|
||||||
ctx.fillText(
|
|
||||||
labelText,
|
|
||||||
displayX + padding,
|
|
||||||
displayY > textHeight + padding ? displayY - padding : displayY + displayHeight + textHeight + padding
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Stable detection callback using useCallback to prevent re-registration
|
|
||||||
const handleDetection = useCallback((detection: DetectionResult | null) => {
|
const handleDetection = useCallback((detection: DetectionResult | null) => {
|
||||||
console.log('🔍 Detection callback received:', detection);
|
const callbackId = Math.random().toString(36).substr(2, 9);
|
||||||
|
console.log(`🔍 Detection callback received [${callbackId}]:`, detection);
|
||||||
setCurrentDetection(detection);
|
setCurrentDetection(detection);
|
||||||
|
|
||||||
// Count actual shoe detections (not just inference attempts)
|
// Count actual shoe detections (not just inference attempts)
|
||||||
@@ -121,47 +50,49 @@ export default function HomePage() {
|
|||||||
setShoeDetectionCount(prev => prev + 1);
|
setShoeDetectionCount(prev => prev + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get canvas context if not available
|
|
||||||
let ctx = canvasCtx;
|
|
||||||
if (canvasRef.current && !ctx) {
|
|
||||||
ctx = canvasRef.current.getContext('2d');
|
|
||||||
if (ctx) {
|
|
||||||
setCanvasCtx(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw detection if we have everything we need
|
|
||||||
if (ctx && videoRef.current && canvasRef.current) {
|
|
||||||
console.log('🎨 Drawing detection on canvas');
|
|
||||||
drawDetections(detection, ctx, videoRef.current, canvasRef.current);
|
|
||||||
} else {
|
|
||||||
console.log('❌ Canvas drawing skipped - missing refs:', { canvasCtx: !!ctx, video: !!videoRef.current, canvas: !!canvasRef.current });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-trigger popup when shoe is detected with high confidence
|
// Auto-trigger popup when shoe is detected with high confidence
|
||||||
if (detection && detection.confidence > 0.7) {
|
if (detection && detection.confidence > 0.7) {
|
||||||
console.log('🎯 HIGH CONFIDENCE SHOE DETECTED! Opening popup...', detection);
|
console.log(`🎯 HIGH CONFIDENCE SHOE DETECTED! [${callbackId}] Opening popup...`, detection);
|
||||||
setPopupOpen(true);
|
setPopupOpen(true);
|
||||||
|
|
||||||
// Play detection sound with debouncing (max once per 2 seconds)
|
// Play detection sound with debouncing (max once per 2 seconds)
|
||||||
setLastSoundTime(currentTime => {
|
setLastSoundTime(currentTime => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
console.log(`🔊 Sound check [${callbackId}]: now=${now}, lastTime=${currentTime}, diff=${now - currentTime}ms`);
|
||||||
|
|
||||||
if (now - currentTime > 2000) {
|
if (now - currentTime > 2000) {
|
||||||
try {
|
try {
|
||||||
const audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuX5fLceTsFLorh8dueUA0VM6fo86JcEQ1QpO3zpWcdBSaF0fDfgToAFmq79+mbNAcZaLTp56lXFQhEnN3vuWcTBjaNz+jdjD8JEmy09+udOQcTO4nY5dqnfGUkGHS40+PvfWIuEHHf6N2AgDgGHWmy9+ibNgcZaLTn46tXFgpHot3wumwdBiqR0fDfgT4IG2u59+yfRAwKUqjn86JdEgxPndn0um0dBSyV5fLccjwGHnS89O6SQwsRYbDs46xfFgpApN7yv2clBjGN3fDbdz8JEG++7+6VNgkURY7S39KmfGQlGnS40+PoeCURYrTp46tWFwlGot3wuW0eBSqR0fDfgT8IHGy79+ibNgcZaLTo46tXFgpHot3wumwdBiqR0fDfgT4IG2u59+yfRAwKUqjn86JdEgxPndn0um0dBSyV5fLccjwGHnS89O6SQwsRYbDs46xfFgpApN7yv2clBjGN3fDbdz8JEG++7+6VNgkURY7S39KmfGQlGnS40+PoeSgIH2y69+mfRAoPUKLe8aNeAE');
|
const audioId = Math.random().toString(36).substr(2, 9);
|
||||||
console.log('🔊 Playing detection sound');
|
// Use AudioContext for more reliable single-play behavior
|
||||||
audio.play();
|
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||||
|
console.log(`🔊 Playing detection sound [callback:${callbackId}] [audio:${audioId}]`);
|
||||||
|
|
||||||
|
// Simple beep using Web Audio API
|
||||||
|
const oscillator = audioContext.createOscillator();
|
||||||
|
const gainNode = audioContext.createGain();
|
||||||
|
|
||||||
|
oscillator.connect(gainNode);
|
||||||
|
gainNode.connect(audioContext.destination);
|
||||||
|
|
||||||
|
oscillator.frequency.setValueAtTime(800, audioContext.currentTime);
|
||||||
|
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||||
|
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.3);
|
||||||
|
|
||||||
|
oscillator.start(audioContext.currentTime);
|
||||||
|
oscillator.stop(audioContext.currentTime + 0.3);
|
||||||
|
|
||||||
|
console.log(`▶️ Audio beep started [${audioId}]`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Sound playback failed:', e);
|
console.warn(`Sound playback failed [${callbackId}]:`, e);
|
||||||
}
|
}
|
||||||
return now;
|
return now;
|
||||||
} else {
|
} else {
|
||||||
console.log('🔇 Sound skipped - too soon after last sound');
|
console.log(`🔇 Sound skipped [${callbackId}] - too soon after last sound (${now - currentTime}ms ago)`);
|
||||||
return currentTime;
|
return currentTime;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [canvasCtx, drawDetections]);
|
}, []);
|
||||||
|
|
||||||
// Initialize ML detection system
|
// Initialize ML detection system
|
||||||
const {
|
const {
|
||||||
@@ -199,49 +130,30 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
}, [stream, cameraStatus]); // Runs when stream or camera status changes
|
}, [stream, cameraStatus]); // Runs when stream or camera status changes
|
||||||
|
|
||||||
// Effect to get canvas context - ensure it's available
|
|
||||||
useEffect(() => {
|
|
||||||
if (canvasRef.current && !canvasCtx) {
|
|
||||||
const ctx = canvasRef.current.getContext('2d');
|
|
||||||
if (ctx) {
|
|
||||||
console.log('📝 Canvas context initialized');
|
|
||||||
setCanvasCtx(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [canvasRef, canvasCtx]);
|
|
||||||
|
|
||||||
// Track initialization state to prevent multiple attempts
|
// Track initialization state to prevent multiple attempts
|
||||||
const [mlInitialized, setMLInitialized] = useState(false);
|
const [mlInitialized, setMLInitialized] = useState(false);
|
||||||
|
|
||||||
// Initialize ML detection when camera is ready (only once)
|
// Initialize ML detection when camera is ready (only once)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('🔍 ML initialization useEffect triggered:', {
|
// Only log in development and when conditions change meaningfully
|
||||||
hasVideo: !!videoRef.current,
|
if (process.env.NODE_ENV === 'development') {
|
||||||
cameraStatus,
|
console.log('🔍 ML init check:', {
|
||||||
isMLLoading,
|
ready: videoRef.current && cameraStatus === 'active' && !isMLLoading && detectionEnabled && !mlInitialized
|
||||||
detectionEnabled,
|
});
|
||||||
mlInitialized
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (videoRef.current && cameraStatus === 'active' && !isMLLoading && detectionEnabled && !mlInitialized) {
|
if (videoRef.current && cameraStatus === 'active' && !isMLLoading && detectionEnabled && !mlInitialized) {
|
||||||
console.log('✅ All conditions met, initializing ML detection...');
|
console.log('✅ Starting ML detection...');
|
||||||
setMLInitialized(true);
|
setMLInitialized(true);
|
||||||
|
|
||||||
initializeML(videoRef.current).then(() => {
|
initializeML(videoRef.current).then(() => {
|
||||||
console.log('✅ ML detection initialized, starting continuous detection');
|
console.log('✅ ML ready, starting continuous detection');
|
||||||
startContinuous();
|
startContinuous();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('❌ Failed to initialize ML detection:', error);
|
console.error('❌ ML initialization failed:', error);
|
||||||
setMLInitialized(false); // Reset on error to allow retry
|
setMLInitialized(false); // Reset on error to allow retry
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.log('⏳ Waiting for conditions:', {
|
|
||||||
hasVideo: !!videoRef.current,
|
|
||||||
cameraActive: cameraStatus === 'active',
|
|
||||||
notLoading: !isMLLoading,
|
|
||||||
detectionEnabled,
|
|
||||||
alreadyInitialized: mlInitialized
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [cameraStatus, detectionEnabled, isMLLoading, mlInitialized, initializeML, startContinuous]);
|
}, [cameraStatus, detectionEnabled, isMLLoading, mlInitialized, initializeML, startContinuous]);
|
||||||
|
|
||||||
@@ -461,7 +373,6 @@ export default function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<video ref={videoRef} autoPlay playsInline muted onCanPlay={() => videoRef.current?.play()} className="h-full w-full object-cover" />
|
<video ref={videoRef} autoPlay playsInline muted onCanPlay={() => videoRef.current?.play()} className="h-full w-full object-cover" />
|
||||||
<canvas ref={canvasRef} className="absolute top-0 left-0 w-full h-full"></canvas>
|
|
||||||
|
|
||||||
{/* ML Detection Status Indicator */}
|
{/* ML Detection Status Indicator */}
|
||||||
{detectionEnabled && (
|
{detectionEnabled && (
|
||||||
|
|||||||
@@ -165,7 +165,10 @@ export class DetectionEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frameSkipCounter = 0;
|
this.frameSkipCounter = 0;
|
||||||
console.log('🔄 Running continuous detection frame...');
|
// Only log every 10th detection to reduce noise
|
||||||
|
if (this.metrics.detectionCount % 10 === 0) {
|
||||||
|
console.log(`🔄 Continuous detection running... (${this.metrics.detectionCount} inferences)`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|||||||
@@ -119,17 +119,9 @@ async function detect(imageData: ImageData, id: string) {
|
|||||||
);
|
);
|
||||||
tensor.dispose();
|
tensor.dispose();
|
||||||
|
|
||||||
console.log('Worker: Model execution result:', result);
|
// Reduced logging for performance
|
||||||
console.log('Worker: Result type:', typeof result);
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.log('Worker: Result is array:', Array.isArray(result));
|
console.log('Worker: Model execution completed, processing results...');
|
||||||
|
|
||||||
if (result) {
|
|
||||||
console.log('Worker: Result length:', result.length);
|
|
||||||
if (Array.isArray(result)) {
|
|
||||||
result.forEach((item, index) => {
|
|
||||||
console.log(`Worker: Result[${index}]:`, item, 'shape:', item?.shape);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result || !Array.isArray(result) || result.length < 4) {
|
if (!result || !Array.isArray(result) || result.length < 4) {
|
||||||
@@ -146,20 +138,13 @@ async function detect(imageData: ImageData, id: string) {
|
|||||||
const scoresData = await scores.data();
|
const scoresData = await scores.data();
|
||||||
const classesData = await classes.data();
|
const classesData = await classes.data();
|
||||||
|
|
||||||
console.log('Worker: Raw model outputs:', {
|
// Only log detailed outputs when debugging specific issues
|
||||||
boxesShape: boxes.shape,
|
const maxScore = Math.max(...Array.from(scoresData.slice(0, 10)));
|
||||||
scoresShape: scores.shape,
|
const scoresAbove30 = Array.from(scoresData.slice(0, 10)).filter(s => s > 0.3).length;
|
||||||
classesShape: classes.shape,
|
|
||||||
boxesLength: boxesData.length,
|
if (process.env.NODE_ENV === 'development' && (maxScore > 0.3 || scoresAbove30 > 0)) {
|
||||||
scoresLength: scoresData.length,
|
console.log('Worker: Potential detection found:', { maxScore, scoresAbove30 });
|
||||||
classesLength: classesData.length,
|
}
|
||||||
firstFewBoxes: Array.from(boxesData.slice(0, 8)),
|
|
||||||
firstFewScores: Array.from(scoresData.slice(0, 4)),
|
|
||||||
firstFewClasses: Array.from(classesData.slice(0, 4)),
|
|
||||||
maxScore: Math.max(...Array.from(scoresData.slice(0, 10))),
|
|
||||||
scoresAbove20: Array.from(scoresData.slice(0, 10)).filter(s => s > 0.2).length,
|
|
||||||
scoresAbove30: Array.from(scoresData.slice(0, 10)).filter(s => s > 0.3).length
|
|
||||||
});
|
|
||||||
|
|
||||||
result.forEach(t => t.dispose());
|
result.forEach(t => t.dispose());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user