CoolFace
Apppublic

webml-community/RF-DETR-Nano-WebGPU

sourceHugging Faceapache-2.0updated 7mo agoView on Hugging Face
5likes
index.html207 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="en">3<head>4    <meta charset="UTF-8">5    <meta name="viewport" content="width=device-width, initial-scale=1.0">6    <title>RF-DETR WebGPU</title>7	<link rel="stylesheet" href="style.css" />8</head>9<body>10 11    <h1>RF-DETR WebGPU</h1>12    <div class="subtitle">13        Real-Time Detection Transformers<br>14        running 100% locally in your browser.15    </div>16 17    <div class="container">18        <div id="status">19            <div class="spinner"></div>20            <div id="status-content">21                <div id="status-text">Initializing...</div>22                <div id="status-sub">Please allow camera access</div>23            </div>24        </div>25        <div id="fps">FPS: 0.0</div>26        <video id="webcam" autoplay playsinline muted></video>27        <canvas id="overlay"></canvas>28    </div>29 30    <div class="controls">31        <label class="control-label">32            <span>Threshold</span>33            <input type="range" id="threshold" min="0" max="1" step="0.01" value="0.5">34            <span id="thresh-val">0.50</span>35        </label>36    </div>37 38    <footer>39        Powered by <a href="https://github.com/huggingface/transformers.js" target="_blank">Transformers.js v4</a>40    </footer>41 42    <script type="module">43        import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@next';44 45        const video = document.getElementById('webcam');46        const overlay = document.getElementById('overlay');47        const status = document.getElementById('status');48        const statusText = document.getElementById('status-text');49        const statusSub = document.getElementById('status-sub');50        const fpsElem = document.getElementById('fps');51        const slider = document.getElementById('threshold');52        const sliderVal = document.getElementById('thresh-val');53 54        let detector;55        let lastTime = performance.now();56        let threshold = 0.5;57        58        const inputCanvas = document.createElement('canvas');59        const inputCtx = inputCanvas.getContext('2d', { willReadFrequently: true });60        61        const COLORS = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899'];62 63        slider.addEventListener('input', (e) => {64            threshold = parseFloat(e.target.value);65            sliderVal.textContent = threshold.toFixed(2);66        });67        68        // Handle high DPI displays69        function resizeOverlay() {70            const width = video.clientWidth;71            const height = video.clientHeight;72            const dpr = window.devicePixelRatio || 1;73            74            overlay.width = width * dpr;75            overlay.height = height * dpr;76            77            const ctx = overlay.getContext('2d');78            ctx.scale(dpr, dpr);79            80            inputCanvas.width = video.videoWidth;81            inputCanvas.height = video.videoHeight;82        }83 84        window.addEventListener('resize', resizeOverlay);85 86        // 1. Start Camera87        try {88            const stream = await navigator.mediaDevices.getUserMedia({89                video: {90                    facingMode: 'environment',91                    width: { ideal: 640 },92                    height: { ideal: 480 }93                },94                audio: false95            });96            97            video.srcObject = stream;98            await new Promise(r => video.onloadedmetadata = r);99            video.play();100            resizeOverlay();101 102        } catch (e) {103            statusText.textContent = "Camera Error";104            statusSub.textContent = e.message;105            document.querySelector('.spinner').style.display = 'none';106            throw e;107        }108 109        // 2. Load Model110        statusText.textContent = "Loading Model...";111        statusSub.textContent = "Downloading RF-DETR Nano (fp32)";112        113        try {114            detector = await pipeline('object-detection', 'onnx-community/rfdetr_nano-ONNX', {115                device: 'webgpu',116                dtype: 'fp32',117            });118            119            // 3. Warmup120            statusText.textContent = "Compiling Shaders...";121            statusSub.textContent = "This may take a moment";122            123            inputCtx.drawImage(video, 0, 0, inputCanvas.width, inputCanvas.height);124            await detector(inputCanvas, { threshold: 0.5, percentage: true });125 126            status.style.opacity = '0';127            setTimeout(() => status.style.display = 'none', 300);128 129        } catch (e) {130            statusText.textContent = "Model Error";131            statusSub.textContent = e.message;132            document.querySelector('.spinner').style.display = 'none';133            throw e;134        }135 136        // 4. Render Loop137        async function loop() {138            const now = performance.now();139            const dt = now - lastTime;140            lastTime = now;141            142            if (dt > 0) {143                fpsElem.textContent = `FPS: ${(1000 / dt).toFixed(1)}`;144            }145 146            inputCtx.drawImage(video, 0, 0, inputCanvas.width, inputCanvas.height);147 148            const results = await detector(inputCanvas, {149                threshold: threshold,150                percentage: true151            });152            drawResults(results);153 154            requestAnimationFrame(loop);155        }156 157        function drawResults(results) {158            const ctx = overlay.getContext('2d');159            const w = video.clientWidth;160            const h = video.clientHeight;161            162            // Clear canvas163            ctx.save();164            ctx.setTransform(1, 0, 0, 1, 0, 0);165            ctx.clearRect(0, 0, overlay.width, overlay.height);166            ctx.restore();167 168            // Set styles common to all results169            ctx.font = '600 13px system-ui';170            ctx.lineWidth = 2.5;171 172            results.forEach((res, i) => {173                const { box, label, score } = res;174                const color = COLORS[i % COLORS.length];175 176                const x1 = box.xmin * w;177                const y1 = box.ymin * h;178                const width = (box.xmax - box.xmin) * w;179                const height = (box.ymax - box.ymin) * h;180 181                // Box182                ctx.strokeStyle = color;183                ctx.beginPath();184                ctx.roundRect(x1, y1, width, height, 6);185                ctx.stroke();186 187                // Label188                ctx.fillStyle = color;189                const text = `${label} ${(score*100).toFixed(0)}%`;190                const textMetrics = ctx.measureText(text);191                const textWidth = textMetrics.width;192                const textHeight = 22;193 194                ctx.beginPath();195                ctx.roundRect(x1, y1 - textHeight - 4, textWidth + 12, textHeight, 4);196                ctx.fill();197 198                ctx.fillStyle = 'white';199                ctx.fillText(text, x1 + 6, y1 - 9);200            });201        }202 203        requestAnimationFrame(loop);204 205    </script>206</body>207</html>