Continuos detection added with 'beep'

This commit is contained in:
2025-08-28 21:50:03 -06:00
parent 5969947b68
commit fb2dbdf140
7 changed files with 270 additions and 228 deletions

View File

@@ -37,6 +37,131 @@ export default function HomePage() {
const [currentDetection, setCurrentDetection] = useState<DetectionResult | null>(null);
const [isPowerSaver, setPowerSaver] = useState(false);
const [userConfig, setUserConfig] = useState<Partial<DetectionConfig> | null>(null);
const [shoeDetectionCount, setShoeDetectionCount] = 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;
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) => {
console.log('🔍 Detection callback received:', detection);
setCurrentDetection(detection);
// Count actual shoe detections (not just inference attempts)
if (detection) {
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
if (detection && detection.confidence > 0.7) {
console.log('🎯 HIGH CONFIDENCE SHOE DETECTED! Opening popup...', detection);
setPopupOpen(true);
// Play detection sound with debouncing (max once per 2 seconds)
setLastSoundTime(currentTime => {
const now = Date.now();
if (now - currentTime > 2000) {
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');
console.log('🔊 Playing detection sound');
audio.play();
} catch (e) {
console.warn('Sound playback failed:', e);
}
return now;
} else {
console.log('🔇 Sound skipped - too soon after last sound');
return currentTime;
}
});
}
}, [canvasCtx, drawDetections]);
// Initialize ML detection system
const {
@@ -53,35 +178,7 @@ export default function HomePage() {
modelVariant: 'standard', // Start with standard model
enableContinuous: true,
enableTrigger: true,
onDetection: (detection) => {
console.log('🔍 Detection callback received:', detection);
setCurrentDetection(detection);
// Ensure canvas context is available before drawing
if (canvasRef.current && !canvasCtx) {
const ctx = canvasRef.current.getContext('2d');
if (ctx) setCanvasCtx(ctx);
}
if (canvasCtx && videoRef.current && canvasRef.current) {
console.log('🎨 Drawing detection on canvas:', { canvasCtx: !!canvasCtx, video: !!videoRef.current, canvas: !!canvasRef.current });
drawDetections(detection, canvasCtx, videoRef.current, canvasRef.current);
} else {
console.log('❌ Canvas drawing skipped - missing refs:', { canvasCtx: !!canvasCtx, video: !!videoRef.current, canvas: !!canvasRef.current });
// Try to get canvas context if missing
if (canvasRef.current && !canvasCtx) {
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
setCanvasCtx(ctx);
drawDetections(detection, ctx, videoRef.current, canvasRef.current);
}
}
}
// Auto-trigger popup when shoe is detected
if (detection && detection.confidence > 0.7) {
console.log('Shoe detected! Opening popup...', detection);
setPopupOpen(true);
}
},
onDetection: handleDetection,
onError: (error) => {
console.error('ML Detection Error:', error);
}
@@ -102,28 +199,51 @@ export default function HomePage() {
}
}, [stream, cameraStatus]); // Runs when stream or camera status changes
// Effect to get canvas context
// Effect to get canvas context - ensure it's available
useEffect(() => {
if (canvasRef.current) {
if (canvasRef.current && !canvasCtx) {
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
console.log('📝 Canvas context initialized');
setCanvasCtx(ctx);
}
}
}, [canvasRef]);
}, [canvasRef, canvasCtx]);
// Initialize ML detection when camera is ready
// Track initialization state to prevent multiple attempts
const [mlInitialized, setMLInitialized] = useState(false);
// Initialize ML detection when camera is ready (only once)
useEffect(() => {
if (videoRef.current && cameraStatus === 'active' && !isMLLoading && detectionEnabled) {
console.log('Initializing ML detection...');
console.log('🔍 ML initialization useEffect triggered:', {
hasVideo: !!videoRef.current,
cameraStatus,
isMLLoading,
detectionEnabled,
mlInitialized
});
if (videoRef.current && cameraStatus === 'active' && !isMLLoading && detectionEnabled && !mlInitialized) {
console.log('✅ All conditions met, initializing ML detection...');
setMLInitialized(true);
initializeML(videoRef.current).then(() => {
console.log('ML detection initialized, starting continuous detection');
console.log('ML detection initialized, starting continuous detection');
startContinuous();
}).catch((error) => {
console.error('Failed to initialize ML detection:', error);
console.error('Failed to initialize ML detection:', error);
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, initializeML, startContinuous]);
}, [cameraStatus, detectionEnabled, isMLLoading, mlInitialized, initializeML, startContinuous]);
const startStream = useCallback(async (deviceId: string) => {
// Stop previous stream if it exists
@@ -244,75 +364,6 @@ export default function HomePage() {
setPopupOpen(true);
};
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;
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
);
}, []); // Depend on config for inputSize
const renderContent = () => {
switch (cameraStatus) {
@@ -415,15 +466,22 @@ export default function HomePage() {
{/* ML Detection Status Indicator */}
{detectionEnabled && (
<div className="absolute top-4 right-4 flex items-center gap-2 bg-black/60 backdrop-blur-sm rounded-lg px-3 py-2 text-white text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span>ML Activo</span>
<div className={`w-2 h-2 rounded-full ${currentDetection ? 'bg-red-500 animate-ping' : 'bg-green-500 animate-pulse'}`}></div>
<span>{currentDetection ? '👟 SHOE DETECTED!' : 'ML Activo'}</span>
{currentDetection && (
<span className="text-green-400 font-bold">
<span className="text-red-400 font-bold">
{(currentDetection.confidence * 100).toFixed(0)}%
</span>
)}
</div>
)}
{/* Detection Counters */}
<div className="absolute top-16 right-4 bg-black/60 backdrop-blur-sm rounded-lg px-3 py-2 text-white text-xs space-y-1">
<div className="text-green-400">👟 Shoes Found: {shoeDetectionCount}</div>
<div className="text-gray-400">🔄 Inference Runs: {metrics?.detectionCount || 0}</div>
<div className="text-blue-400"> Avg Speed: {metrics?.inferenceTime ? `${metrics.inferenceTime.toFixed(0)}ms` : 'N/A'}</div>
</div>
{/* Settings Panel */}
<div className={`absolute left-0 top-0 bottom-0 w-80 bg-black/80 backdrop-blur-xl border-r border-white/20 transform transition-transform duration-500 ease-out z-40 ${

View File

@@ -42,6 +42,7 @@ export class DetectionEngine {
private lastDetectionCallbackTime?: number;
constructor() {
console.log('🏗️ DetectionEngine constructor called');
this.workerManager = new DetectionWorkerManager();
@@ -49,7 +50,7 @@ export class DetectionEngine {
const capabilities = detectDeviceCapabilities();
this.config = getRecommendedConfig(capabilities);
console.log('Detection engine initialized', { capabilities, config: this.config });
console.log('Detection engine initialized', { capabilities, config: this.config });
}
/**
@@ -58,17 +59,21 @@ export class DetectionEngine {
async initialize(modelVariant?: 'quantized' | 'standard' | 'full', onProgress?: (progress: number) => void): Promise<void> {
const variant = modelVariant || this.config.modelVariant;
console.log(`🔧 Initializing detection engine with ${variant} model...`);
try {
// Load the model into the worker
console.log('📥 Loading model into worker...');
await this.workerManager.loadModel(variant, onProgress);
// Configure the worker with current settings
console.log('⚙️ Configuring worker...');
await this.workerManager.configure(this.config);
console.log(`Detection engine initialized with ${variant} model`);
console.log(`Detection engine initialized with ${variant} model`);
} catch (error) {
console.error('Failed to initialize detection engine:', error);
console.error('Failed to initialize detection engine:', error);
throw error;
}
}
@@ -77,6 +82,12 @@ export class DetectionEngine {
* Start continuous detection
*/
startContinuousDetection(videoElement: HTMLVideoElement): void {
console.log('🚀 startContinuousDetection called:', {
isRunning: this.isRunning,
enableContinuous: this.config.enableContinuous,
videoElement: !!videoElement
});
if (this.isRunning) {
console.warn('Detection already running');
return;
@@ -86,10 +97,11 @@ export class DetectionEngine {
this.detectionMode = this.config.enableContinuous ? 'continuous' : 'trigger';
if (this.config.enableContinuous) {
console.log('🔄 Starting continuous detection loop...');
this.runContinuousLoop(videoElement);
}
console.log(`Started detection in ${this.detectionMode} mode`);
console.log(`Started detection in ${this.detectionMode} mode`);
}
/**
@@ -111,64 +123,26 @@ export class DetectionEngine {
try {
console.log('🎯 Starting trigger detection (high quality)');
// Load TensorFlow.js if not already loaded
if (!window.tf) {
const tf = await import('@tensorflow/tfjs');
await import('@tensorflow/tfjs-backend-webgl');
await tf.setBackend('webgl');
await tf.ready();
window.tf = tf;
console.log('✅ TensorFlow.js loaded in main thread');
}
// Capture image data for trigger detection (high quality)
const imageData = this.captureVideoFrame(videoElement, true);
// Load model if not already loaded
if (!this.model) {
console.log('📥 Loading model in main thread...');
this.model = await window.tf.loadGraphModel('/models/model.json');
console.log('✅ Model loaded in main thread');
}
// Capture and preprocess image with higher quality
const tensor = window.tf.tidy(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
canvas.width = 300;
canvas.height = 300;
ctx.drawImage(videoElement, 0, 0, 300, 300);
const img = window.tf.browser.fromPixels(canvas);
return img.expandDims(0);
});
console.log('📸 Trigger detection - Input tensor shape:', tensor.shape);
// Run model inference
const result = await this.model.executeAsync(tensor);
tensor.dispose();
console.log('🔬 Trigger detection - Model output:', result);
// Return high-confidence detection for manual triggers
const triggerDetection: DetectionResult = {
bbox: [0.25, 0.25, 0.5, 0.5], // Different position than continuous
confidence: 0.92, // Higher confidence for trigger
class: 'shoe',
timestamp: Date.now()
};
// Use worker manager for detection
const detections = await this.workerManager.detect(imageData);
const detection = detections.length > 0 ? detections[0] : null;
// Update metrics
this.metrics.inferenceTime = performance.now() - startTime;
this.metrics.detectionCount++;
this.metrics.timestamp = Date.now();
console.log('✅ Trigger detection completed:', triggerDetection);
console.log('✅ Trigger detection completed:', detection);
// Temporarily update the current detection to show trigger result
if (this.onDetectionCallback) {
this.onDetectionCallback(triggerDetection);
// Trigger callbacks for immediate display
if (this.onDetectionCallback && detection) {
this.onDetectionCallback(detection);
}
return triggerDetection;
return detection;
} catch (error) {
console.error('❌ Trigger detection failed:', error);
@@ -194,61 +168,30 @@ export class DetectionEngine {
console.log('🔄 Running continuous detection frame...');
try {
// Load TensorFlow.js if not already loaded
if (!window.tf) {
const tf = await import('@tensorflow/tfjs');
await import('@tensorflow/tfjs-backend-webgl');
await tf.setBackend('webgl');
await tf.ready();
window.tf = tf;
}
// Load model if not already loaded
if (!this.model) {
this.model = await window.tf.loadGraphModel('/models/model.json');
}
const startTime = performance.now();
// Capture and preprocess image (lower quality for continuous)
const tensor = window.tf.tidy(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
canvas.width = 300;
canvas.height = 300;
ctx.drawImage(videoElement, 0, 0, 300, 300);
const img = window.tf.browser.fromPixels(canvas);
return img.expandDims(0);
});
// Capture image data for continuous detection (lower quality)
const imageData = this.captureVideoFrame(videoElement, false);
// Run model inference
const result = await this.model.executeAsync(tensor);
tensor.dispose();
// Return low-confidence detection for continuous mode (below popup threshold)
const mockDetection: DetectionResult = {
bbox: [0.1, 0.1, 0.3, 0.3],
confidence: 0.5, // Medium confidence - shows bounding box but won't trigger popup
class: 'shoe',
timestamp: Date.now()
};
// Use worker manager for detection
const detections = await this.workerManager.detect(imageData);
const detection = detections.length > 0 ? detections[0] : null;
const inferenceTime = performance.now() - startTime;
console.log('⚡ Continuous detection completed:', { time: inferenceTime, confidence: mockDetection.confidence });
console.log('⚡ Continuous detection completed:', { time: inferenceTime, detection });
// Apply temporal filtering
const validDetection = this.applyTemporalFiltering(mockDetection);
const validDetection = this.applyTemporalFiltering(detection);
// Update metrics
this.updateMetrics(inferenceTime);
// Trigger callbacks (only if we have a valid detection)
// Use a debounced approach to avoid too frequent updates
if (this.onDetectionCallback && validDetection) {
// Only update if it's been at least 500ms since last detection callback
if (this.onDetectionCallback) {
// Only update if it's been at least 100ms since last detection callback for continuous
const now = Date.now();
if (!this.lastDetectionCallbackTime || now - this.lastDetectionCallbackTime > 500) {
if (!this.lastDetectionCallbackTime || now - this.lastDetectionCallbackTime > 100) {
this.onDetectionCallback(validDetection);
this.lastDetectionCallbackTime = now;
}

View File

@@ -33,18 +33,22 @@ export function detectDeviceCapabilities(): DeviceCapabilities {
/**
* Gets a recommended configuration based on the detected device tier.
* @param capabilities The detected device capabilities.
* @returns A partial DetectionConfig with recommended settings.
* @returns A full DetectionConfig with recommended settings.
*/
export function getRecommendedConfig(capabilities: DeviceCapabilities): Partial<typeof DEFAULT_CONFIG> {
export function getRecommendedConfig(capabilities: DeviceCapabilities): typeof DEFAULT_CONFIG {
const baseConfig = { ...DEFAULT_CONFIG };
switch (capabilities.tier) {
case 'high':
return {
...baseConfig,
modelVariant: 'standard',
frameSkip: 3,
confidenceThreshold: 0.6,
};
case 'mid':
return {
...baseConfig,
modelVariant: 'standard',
frameSkip: 5,
confidenceThreshold: 0.5,
@@ -52,6 +56,7 @@ export function getRecommendedConfig(capabilities: DeviceCapabilities): Partial<
case 'low':
default:
return {
...baseConfig,
modelVariant: 'quantized',
frameSkip: 8,
confidenceThreshold: 0.4,

View File

@@ -48,22 +48,28 @@ export class InferencePipeline {
process(boxes: number[], scores: number[], classes: number[], confidenceThreshold: number): DetectionResult | null {
const detections: DetectionResult[] = [];
for (let i = 0; i < scores.length; i++) {
// Process up to 5 detections like the working implementation
for (let i = 0; i < Math.min(5, scores.length); i++) {
const score = scores[i];
if (score < confidenceThreshold) continue;
// Convert to percentage and check threshold like working implementation
const scorePercent = score * 100;
if (scorePercent < (confidenceThreshold * 100)) continue;
const classIndex = classes[i];
const className = CLASS_LABELS[classIndex];
if (className !== 'shoe') continue;
// Extract bounding box [y_min, x_min, y_max, x_max]
const [yMin, xMin, yMax, xMax] = boxes.slice(i * 4, (i + 1) * 4);
// Extract bounding box [y_min, x_min, y_max, x_max] like working implementation
const yMin = boxes[i * 4];
const xMin = boxes[i * 4 + 1];
const yMax = boxes[i * 4 + 2];
const xMax = boxes[i * 4 + 3];
// Convert to [x, y, width, height] format
const bbox: [number, number, number, number] = [xMin, yMin, xMax - xMin, yMax - yMin];
const detection: DetectionResult = {
bbox,
confidence: score,
class: className,
class: 'shoe', // Assume all detections are shoes
timestamp: Date.now()
};
if (this.isValid(detection)) {

View File

@@ -35,7 +35,7 @@ export const DEFAULT_CONFIG: DetectionConfig = {
frameSkip: 5,
confidenceThreshold: 0.3, // Match the working implementation (30%)
modelVariant: 'standard',
maxDetections: 1,
maxDetections: 5, // Match the working implementation (process up to 5 detections)
inputSize: [300, 300], // Match the working implementation
enableContinuous: true,
enableTrigger: true,

View File

@@ -56,8 +56,11 @@ export function useDetection(options: UseDetectionOptions = {}): UseDetectionRet
// Initialize detection engine
const initialize = useCallback(async (videoElement: HTMLVideoElement): Promise<void> => {
console.log('🚀 useDetection.initialize called:', { videoElement: !!videoElement });
// Prevent multiple initializations
if (initializationPromiseRef.current) {
console.log('⚠️ Initialization already in progress, returning existing promise');
return initializationPromiseRef.current;
}
@@ -66,6 +69,7 @@ export function useDetection(options: UseDetectionOptions = {}): UseDetectionRet
const initPromise = (async () => {
try {
console.log('🏗️ Creating detection engine...');
// Create detection engine
const engine = new DetectionEngine();
detectionEngineRef.current = engine;
@@ -110,6 +114,12 @@ export function useDetection(options: UseDetectionOptions = {}): UseDetectionRet
// Start continuous detection
const startContinuous = useCallback(() => {
console.log('🔄 useDetection.startContinuous called:', {
hasEngine: !!detectionEngineRef.current,
hasVideo: !!videoElementRef.current,
enableContinuous
});
if (!detectionEngineRef.current || !videoElementRef.current) {
console.warn('Detection engine or video element not available');
return;
@@ -121,12 +131,14 @@ export function useDetection(options: UseDetectionOptions = {}): UseDetectionRet
}
try {
console.log('🚀 Starting continuous detection...');
detectionEngineRef.current.startContinuousDetection(videoElementRef.current);
setIsDetecting(true);
setError(null);
console.log('✅ Continuous detection started successfully');
} catch (err) {
const error = err instanceof Error ? err : new Error('Failed to start continuous detection');
console.error('Start continuous detection failed:', error);
console.error('Start continuous detection failed:', error);
setError(error.message);
onError?.(error);
}

View File

@@ -44,18 +44,24 @@ async function loadModelWorker(variant: 'quantized' | 'standard' | 'full', model
model = await tfGlobal.loadGraphModel(modelUrls[variant]);
console.log('Worker: Real model loaded successfully', model);
// Warm up the model (assuming config is available by now)
// Warm up the model like the working implementation
if (model && config) {
console.log('Worker: Warming up model with input size:', config.inputSize);
const dummyInput = tfGlobal.zeros([1, ...config.inputSize, 3]);
const result = model.execute(dummyInput);
const dummyFloat = tfGlobal.zeros([1, ...config.inputSize, 3]);
const dummyInput = tfGlobal.cast(dummyFloat, 'int32');
dummyFloat.dispose();
const result = await model.executeAsync(
{ image_tensor: dummyInput },
['detection_boxes', 'num_detections', 'detection_classes', 'detection_scores']
);
console.log('Worker: Warmup result:', result);
dummyInput.dispose();
if (Array.isArray(result)) {
result.forEach(t => t.dispose());
} else {
} else if (result) {
result.dispose();
}
dummyInput.dispose();
console.log('Worker: Model warmed up successfully.');
}
self.postMessage({ type: 'LOADED_MODEL', id });
@@ -95,14 +101,22 @@ async function detect(imageData: ImageData, id: string) {
// Create tensor from RGB data
const img = tfGlobal.tensor3d(rgbData, [height, width, 3]);
// Resize to model input size (300x300) and add batch dimension
// Resize to model input size (300x300) - this returns float32
const resized = tfGlobal.image.resizeBilinear(img, config!.inputSize);
return resized.expandDims(0); // Keep values in [0,255] range like the working implementation
// Cast to int32 as required by the model
const int32Tensor = tfGlobal.cast(resized, 'int32');
return int32Tensor.expandDims(0); // Now properly int32
});
try {
console.log('Worker: About to execute model with tensor shape:', tensor.shape);
const result = await model.executeAsync(tensor);
console.log('Worker: About to execute model with tensor shape:', tensor.shape, 'dtype:', tensor.dtype);
// Use the same input format as the working implementation
const result = await model.executeAsync(
{ image_tensor: tensor },
['detection_boxes', 'num_detections', 'detection_classes', 'detection_scores']
);
tensor.dispose();
console.log('Worker: Model execution result:', result);
@@ -118,13 +132,14 @@ async function detect(imageData: ImageData, id: string) {
}
}
if (!result || !Array.isArray(result) || result.length < 3) {
if (!result || !Array.isArray(result) || result.length < 4) {
console.error('Worker: Invalid model output:', result);
self.postMessage({ type: 'DETECTION_RESULT', result: null, id });
return;
}
const [boxes, scores, classes] = result;
// Match the working implementation: [boxes, num_detections, classes, scores]
const [boxes, numDetections, classes, scores] = result;
console.log('Worker: Extracting data from tensors...');
const boxesData = await boxes.data();
@@ -140,7 +155,10 @@ async function detect(imageData: ImageData, id: string) {
classesLength: classesData.length,
firstFewBoxes: Array.from(boxesData.slice(0, 8)),
firstFewScores: Array.from(scoresData.slice(0, 4)),
firstFewClasses: Array.from(classesData.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());