Omarrran commited on
Commit
1deea7f
·
verified ·
1 Parent(s): df137d6

Upload 5 files

Browse files
Files changed (5) hide show
  1. index.html +43 -0
  2. main.js +128 -0
  3. package.json +17 -0
  4. style.css +87 -0
  5. vite.config.js +6 -0
index.html ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Transformers.js | Real-time background removal</title>
8
+ </head>
9
+
10
+ <body>
11
+ <h1>
12
+ Real-time background removal w/
13
+ <a href="https://github.com/huggingface/transformers.js" target="_blank">🤗 Transformers.js</a>
14
+ </h1>
15
+ <h4>
16
+ Runs locally in your browser, powered by
17
+ <a href="https://huggingface.co/Xenova/modnet" target="_blank">MODNet</a>
18
+ </h4>
19
+ <div id="container">
20
+ <video id="video" autoplay muted playsinline></video>
21
+ <canvas id="canvas" width="360" height="240"></canvas>
22
+ <canvas id="output-canvas" width="360" height="240"></canvas>
23
+ </div>
24
+ <div id="controls">
25
+ <div title="Read frames from your webcam and process them at a lower size (lower = faster)">
26
+ <label>Stream scale</label>
27
+ (<label id="scale-value">0.5</label>)
28
+ <br>
29
+ <input id="scale" type="range" min="0.1" max="1" step="0.1" value="0.5" disabled>
30
+ </div>
31
+ <div title="The length of the shortest edge of the image (lower = faster)">
32
+ <label>Image size</label>
33
+ (<label id="size-value">256</label>)
34
+ <br>
35
+ <input id="size" type="range" min="64" max="512" step="32" value="256" disabled>
36
+ </div>
37
+ </div>
38
+ <label id="status"></label>
39
+
40
+ <script type="module" src="/main.js"></script>
41
+ </body>
42
+
43
+ </html>
main.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './style.css';
2
+
3
+ import { env, AutoModel, AutoProcessor, RawImage } from '@xenova/transformers';
4
+
5
+ env.backends.onnx.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/';
6
+ env.backends.onnx.wasm.numThreads = 1;
7
+
8
+ // Reference the elements that we will need
9
+ const status = document.getElementById('status');
10
+ const container = document.getElementById('container');
11
+ const canvas = document.getElementById('canvas');
12
+ const outputCanvas = document.getElementById('output-canvas');
13
+ const video = document.getElementById('video');
14
+ const sizeSlider = document.getElementById('size');
15
+ const sizeLabel = document.getElementById('size-value');
16
+ const scaleSlider = document.getElementById('scale');
17
+ const scaleLabel = document.getElementById('scale-value');
18
+
19
+ function setStreamSize(width, height) {
20
+ video.width = outputCanvas.width = canvas.width = Math.round(width);
21
+ video.height = outputCanvas.height = canvas.height = Math.round(height);
22
+ }
23
+
24
+ status.textContent = 'Loading model...';
25
+
26
+ // Load model and processor
27
+ const model_id = 'Xenova/modnet';
28
+ let model;
29
+ try {
30
+ model = await AutoModel.from_pretrained(model_id, {
31
+ device: 'webgpu',
32
+ dtype: 'fp32', // TODO: add fp16 support
33
+ });
34
+ } catch (err) {
35
+ status.textContent = err.message;
36
+ alert(err.message)
37
+ throw err;
38
+ }
39
+
40
+ const processor = await AutoProcessor.from_pretrained(model_id);
41
+
42
+ // Set up controls
43
+ let size = 256;
44
+ processor.feature_extractor.size = { shortest_edge: size };
45
+ sizeSlider.addEventListener('input', () => {
46
+ size = Number(sizeSlider.value);
47
+ processor.feature_extractor.size = { shortest_edge: size };
48
+ sizeLabel.textContent = size;
49
+ });
50
+ sizeSlider.disabled = false;
51
+
52
+ let scale = 0.5;
53
+ scaleSlider.addEventListener('input', () => {
54
+ scale = Number(scaleSlider.value);
55
+ setStreamSize(video.videoWidth * scale, video.videoHeight * scale);
56
+ scaleLabel.textContent = scale;
57
+ });
58
+ scaleSlider.disabled = false;
59
+
60
+ status.textContent = 'Ready';
61
+
62
+ let isProcessing = false;
63
+ let previousTime;
64
+ const context = canvas.getContext('2d', { willReadFrequently: true });
65
+ const outputContext = outputCanvas.getContext('2d', { willReadFrequently: true });
66
+ function updateCanvas() {
67
+ const { width, height } = canvas;
68
+
69
+ if (!isProcessing) {
70
+ isProcessing = true;
71
+ (async function () {
72
+ // Read the current frame from the video
73
+ context.drawImage(video, 0, 0, width, height);
74
+ const currentFrame = context.getImageData(0, 0, width, height);
75
+ const image = new RawImage(currentFrame.data, width, height, 4);
76
+
77
+ // Pre-process image
78
+ const inputs = await processor(image);
79
+
80
+ // Predict alpha matte
81
+ const { output } = await model({ input: inputs.pixel_values });
82
+
83
+ const mask = await RawImage.fromTensor(output[0].mul(255).to('uint8')).resize(width, height);
84
+
85
+ // Update alpha channel
86
+ const outPixelData = currentFrame;
87
+ for (let i = 0; i < mask.data.length; ++i) {
88
+ outPixelData.data[4 * i + 3] = mask.data[i];
89
+ }
90
+ outputContext.putImageData(outPixelData, 0, 0);
91
+
92
+ if (previousTime !== undefined) {
93
+ const fps = 1000 / (performance.now() - previousTime);
94
+ status.textContent = `FPS: ${fps.toFixed(2)}`;
95
+ }
96
+ previousTime = performance.now();
97
+
98
+ isProcessing = false;
99
+ })();
100
+ }
101
+
102
+ window.requestAnimationFrame(updateCanvas);
103
+ }
104
+
105
+ // Start the video stream
106
+ navigator.mediaDevices.getUserMedia(
107
+ { video: true }, // Ask for video
108
+ ).then((stream) => {
109
+ // Set up the video and canvas elements.
110
+ video.srcObject = stream;
111
+ video.play();
112
+
113
+ const videoTrack = stream.getVideoTracks()[0];
114
+ const { width, height } = videoTrack.getSettings();
115
+
116
+ setStreamSize(width * scale, height * scale);
117
+
118
+ // Set container width and height depending on the image aspect ratio
119
+ const ar = width / height;
120
+ const [cw, ch] = (ar > 720 / 405) ? [720, 720 / ar] : [405 * ar, 405];
121
+ container.style.width = `${cw}px`;
122
+ container.style.height = `${ch}px`;
123
+
124
+ // Start the animation loop
125
+ setTimeout(updateCanvas, 50);
126
+ }).catch((error) => {
127
+ alert(error);
128
+ });
package.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "webgpu-video-background-removal",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "devDependencies": {
12
+ "vite": "^5.0.12"
13
+ },
14
+ "dependencies": {
15
+ "@xenova/transformers": "^3.0.0"
16
+ }
17
+ }
style.css ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ box-sizing: border-box;
3
+ padding: 0;
4
+ margin: 0;
5
+ font-family: sans-serif;
6
+ }
7
+
8
+ html,
9
+ body {
10
+ height: 100%;
11
+ }
12
+
13
+ body {
14
+ padding: 16px 32px;
15
+ }
16
+
17
+ body,
18
+ #container {
19
+ display: flex;
20
+ flex-direction: column;
21
+ justify-content: center;
22
+ align-items: center;
23
+ }
24
+
25
+ #controls {
26
+ display: flex;
27
+ padding: 1rem;
28
+ gap: 1rem;
29
+ }
30
+
31
+ #controls>div {
32
+ text-align: center;
33
+ }
34
+
35
+ h1,
36
+ h4 {
37
+ text-align: center;
38
+ }
39
+
40
+ h4 {
41
+ margin-top: 0.5rem;
42
+ }
43
+
44
+ #container {
45
+ position: relative;
46
+ width: 720px;
47
+ height: 405px;
48
+ max-width: 100%;
49
+ max-height: 100%;
50
+ border: 2px dashed #D1D5DB;
51
+ border-radius: 0.75rem;
52
+ overflow: hidden;
53
+ margin-top: 1rem;
54
+ background-size: 100% 100%;
55
+ background-position: center;
56
+ background-repeat: no-repeat;
57
+ }
58
+
59
+ #overlay,
60
+ canvas {
61
+ position: absolute;
62
+ width: 100%;
63
+ height: 100%;
64
+ }
65
+
66
+ #status {
67
+ min-height: 16px;
68
+ margin: 8px 0;
69
+ }
70
+
71
+ .bounding-box {
72
+ position: absolute;
73
+ box-sizing: border-box;
74
+ border: solid 2px;
75
+ }
76
+
77
+ .bounding-box-label {
78
+ color: white;
79
+ position: absolute;
80
+ font-size: 12px;
81
+ margin: -16px 0 0 -2px;
82
+ padding: 1px;
83
+ }
84
+
85
+ #video, #canvas {
86
+ display: none;
87
+ }
vite.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite';
2
+ export default defineConfig({
3
+ build: {
4
+ target: 'esnext'
5
+ }
6
+ });