Spaces:
Running
Running
File size: 6,570 Bytes
36cb1b4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
// Initialize variables
let isVirtual = false;
// DOM elements
const videoContainer = document.getElementById('videoContainer');
const videoElement = document.getElementById('videoElement');
const canvasElement = document.getElementById('backgroundCanvas');
const backgroundImage = document.getElementById('yourBackgroundImage');
const ctx = canvasElement.getContext('2d');
const startButton = document.getElementById('startButton');
const stopButton = document.getElementById('stopButton');
const errorText = document.getElementById('errorText');
// MobileNetV1 or ResNet50
const BodyPixModel = 'MobileNetV1';
async function startWebCamStream() {
try {
// Start the webcam stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
videoElement.srcObject = stream;
// Wait for the video to play
await videoElement.play();
} catch (error) {
displayError(error)
}
}
// Function to start the virtual background
async function startVirtualBackground() {
try {
// Set canvas dimensions to match video dimensions
canvasElement.width = videoElement.videoWidth;
canvasElement.height = videoElement.videoHeight;
// Load the BodyPix model:
let net = null;
switch (BodyPixModel) {
case 'MobileNetV1':
/*
This is a lightweight architecture that is suitable for real-time applications and has lower computational requirements.
It provides good performance for most use cases.
*/
net = await bodyPix.load({
architecture: 'MobileNetV1',
outputStride: 16, // Output stride (16 or 32). 16 is faster, 32 is more accurate.
multiplier: 0.75, // The model's depth multiplier. Options: 0.50, 0.75, or 1.0.
quantBytes: 2, // The number of bytes to use for quantization (4 or 2).
});
break;
case 'ResNet50':
/*
This is a deeper and more accurate architecture compared to MobileNetV1.
It may provide better segmentation accuracy, but it requires more computational resources and can be slower.
*/
net = await bodyPix.load({
architecture: 'ResNet50',
outputStride: 16, // Output stride (16 or 32). 16 is faster, 32 is more accurate.
quantBytes: 4, // The number of bytes to use for quantization (4 or 2).
});
break;
default:
break;
}
// Start the virtual background loop
isVirtual = true;
videoElement.hidden = true;
canvasElement.hidden = false;
display(canvasElement, 'block');
// Show the stop button and hide the start button
startButton.style.display = 'none';
stopButton.style.display = 'block';
async function updateCanvas() {
if (isVirtual) {
// 1. Segmentation Calculation
const segmentation = await net.segmentPerson(videoElement, {
flipHorizontal: false, // Whether to flip the input video horizontally
internalResolution: 'medium', // The resolution for internal processing (options: 'low', 'medium', 'high')
segmentationThreshold: 0.7, // Segmentation confidence threshold (0.0 - 1.0)
maxDetections: 10, // Maximum number of detections to return
scoreThreshold: 0.2, // Confidence score threshold for detections (0.0 - 1.0)
nmsRadius: 20, // Non-Maximum Suppression (NMS) radius for de-duplication
minKeypointScore: 0.3, // Minimum keypoint detection score (0.0 - 1.0)
refineSteps: 10, // Number of refinement steps for segmentation
});
// 2. Creating a Background Mask
const background = { r: 0, g: 0, b: 0, a: 0 };
const mask = bodyPix.toMask(segmentation, background, { r: 0, g: 0, b: 0, a: 255 });
if (mask) {
ctx.putImageData(mask, 0, 0);
ctx.globalCompositeOperation = 'source-in';
// 3. Drawing the Background
if (backgroundImage.complete) {
ctx.drawImage(backgroundImage, 0, 0, canvasElement.width, canvasElement.height);
} else {
// If the image is not loaded yet, wait for it to load and then draw
backgroundImage.onload = () => {
ctx.drawImage(backgroundImage, 0, 0, canvasElement.width, canvasElement.height);
};
}
// Draw the mask (segmentation)
ctx.globalCompositeOperation = 'destination-over';
ctx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
ctx.globalCompositeOperation = 'source-over';
// Add a delay to control the frame rate (adjust as needed) less CPU intensive
// await new Promise((resolve) => setTimeout(resolve, 100));
// Continue updating the canvas
requestAnimationFrame(updateCanvas);
}
}
}
// Start update canvas
updateCanvas();
} catch (error) {
stopVirtualBackground();
displayError(error);
}
}
// Function to stop the virtual background
function stopVirtualBackground() {
isVirtual = false;
videoElement.hidden = false;
canvasElement.hidden = true;
display(canvasElement, 'none');
// Hide the stop button and show the start button
startButton.style.display = 'block';
stopButton.style.display = 'none';
}
// Helper function to set the display style of an element
function display(element, style) {
element.style.display = style;
}
// Helper function to display errors
function displayError(error){
console.error(error);
// Display error message in the <p> element
errorText.textContent = 'An error occurred: ' + error.message;
}
// Add click event listeners to the buttons
startButton.addEventListener('click', startVirtualBackground);
stopButton.addEventListener('click', stopVirtualBackground);
// Start video stream
startWebCamStream(); |