Hovra över markerad kod för att se förklaringär. Understrukna delär har extra information.
Sobel-operatorn detekterar kanter genom att beräkna gradienter i x- och y-led.
function detectEdges(imageData) { const width = imageData.width; const height = imageData.height; const data = imageData.data; // Konvertera till grayscale const gray = new Uint8Array(width * height); for (let i = 0; i < data.length; i += 4) { // Viktad konvertering: 0.3R + 0.59G + 0.11B gray[i/4] = 0.3*data[i] + 0.59*data[i+1] + 0.11*data[i+2]; } // Kant-resultåt array const edges = new Uint8Array(width * height); // Sobel-operator (3x3 kernel) for (let y = 1; y < height-1; y++) { for (let x = 1; x < width-1; x++) { const idx = y*width + x; // Gx: Horisontell gradient const gx = -gray[(y-1)*width+(x-1)] + gray[(y-1)*width+(x+1)] + -2*gray[y*width+(x-1)] + 2*gray[y*width+(x+1)] + -gray[(y+1)*width+(x-1)] + gray[(y+1)*width+(x+1)]; // Gy: Vertikal gradient const gy = -gray[(y-1)*width+(x-1)] - 2*gray[(y-1)*width+x] - gray[(y-1)*width+(x+1)] + gray[(y+1)*width+(x-1)] + 2*gray[(y+1)*width+x] + gray[(y+1)*width+(x+1)]; // Gradient magnitude edges[idx] = Math.sqrt(gx*gx + gy*gy); } } return edges; }
Gx (horisontell): [-1, 0, +1; -2, 0, +2; -1, 0, +1]
Gy (vertikal): [-1, -2, -1; 0, 0, 0; +1, +2, +1]
Simulerar hur en magnet påverkade bilden på gamla katodstralerorsteknik (CRT).
function applyGlitch(ctx, intensity, edges) { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; const width = canvas.width; const height = canvas.height; // Distorsionscentrum (slumpvist) const cx = width * (0.3 + Math.random() * 0.4); const cy = height * (0.3 + Math.random() * 0.4); const warpFreq = 0.02 + intensity * 0.01; const warpAmp = intensity * 3; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const idx = (y * width + x) * 4; const edgeVal = edges[y * width + x] / 255; // Avstand från centrum const dx = x - cx; const dy = y - cy; const dist = Math.sqrt(dx*dx + dy*dy); // Magnetic warp const offsetX = Math.sin(dist * warpFreq) * warpAmp * edgeVal; const offsetY = Math.cos(dist * warpFreq) * warpAmp * edgeVal; // RGB Separation (chromatic aberration) const rgbOffset = Math.floor(intensity * 2 * edgeVal); // Beräkna kalla-koordinater const srcX = Math.floor(x + offsetX); const srcY = Math.floor(y + offsetY); // Hamta pixelvärden med offset för varje kanal if (srcX >= 0 && srcX < width && srcY >= 0 && srcY < height) { const srcIdx = (srcY * width + srcX) * 4; // Rod kanal med extra offset data[idx] = data[srcIdx + rgbOffset * 4] || data[srcIdx]; // Gron kanal (oförändrad) data[idx+1] = data[srcIdx+1]; // Bla kanal med negativ offset data[idx+2] = data[srcIdx - rgbOffset * 4 + 2] || data[srcIdx+2]; } // Scanlines (var annan rad) if (y % 2 === 0) { data[idx] *= 0.95; data[idx+1] *= 0.95; data[idx+2] *= 0.95; } } } ctx.putImageData(imageData, 0, 0); }
Webbläsaren pollar ESP32 varje 30ms för att hamta aktuellt movement score.
const ESP32_IP = '192.168.0.124'; // Andras till din ESP32:s IP async function updateCSI() { try { const response = await fetch( `http://${ESP32_IP}/sensor/movement_score`, { mode: 'cors' } ); const json = await response.json(); // JSON format: { "id": "sensor-movement_score", "value": 4.135, "state": "4.14" } const score = parseFloat(json.value); // Kolla om score överstiger threshold const threshold = calibration.baseline + calibration.threshold; if (score > threshold) { // Beräkna intensitet baseråt på hur mycket över threshold const intensity = (score - threshold) / settings.sensitivity; applyGlitch(ctx, Math.min(intensity, 1), currentEdges); } // Uppdatera UI statusEl.textContent = `Score: ${score.toFixed(2)} | Threshold: ${threshold.toFixed(2)}`; statusEl.className = 'online'; } catch (error) { statusEl.textContent = 'Offline'; statusEl.className = 'offline'; } // Polla igen efter 30ms setTimeout(updateCSI, 30); }
Kalibrering samplär CSI-värden under 3 sekunder för att bestämma baseline vid stillhet.
const calibration = { baseline: 1.2, // CSI-värde vid stillhet threshold: 0.5, // Marginal för triggning baseThreshold: 0.5, isCalibrating: false, samples: [], sampleCount: 60 // Antal samples (60 * 30ms = ~2s) }; function startCalibration() { calibration.isCalibrating = true; calibration.samples = []; statusEl.textContent = 'Kalibrering... Sta stilla!'; // Vanta tills tillräckligt med samples samlats const checkCalibration = setInterval(() => { if (calibration.samples.length >= calibration.sampleCount) { clearInterval(checkCalibration); finishCalibration(); } }, 100); } function finishCalibration() { // Beräkna medelvärde av samples const sum = calibration.samples.reduce((a, b) => a + b, 0); calibration.baseline = sum / calibration.samples.length; // Satt threshold till lite över baseline calibration.threshold = calibration.baseThreshold; calibration.isCalibrating = false; statusEl.textContent = `Kalibrerad! Baseline: ${calibration.baseline.toFixed(2)}`; } // Under polling, samla samples om kalibrering pagår if (calibration.isCalibrating) { calibration.samples.push(score); }