|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>OBS AI Background Remover</title> |
|
|
<link rel="stylesheet" href="style.css"> |
|
|
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/selfie_segmentation.js" crossorigin="anonymous"></script> |
|
|
</head> |
|
|
<body> |
|
|
<div class="card"> |
|
|
<h1>OBS AI Background Remover</h1> |
|
|
<div> |
|
|
<h2>Live Preview</h2> |
|
|
<video id="videoPreview" autoplay playsinline></video> |
|
|
<canvas id="canvasOutput" width="1920" height="1080" style="display: none;"></canvas> |
|
|
<p id="status">Loading AI Model...</p> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2>Background Replacement</h2> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgTransparent" value="transparent" checked> |
|
|
<label for="bgTransparent">Transparent</label> |
|
|
</div> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgBlack" value="black"> |
|
|
<label for="bgBlack">Solid Black</label> |
|
|
</div> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgGray" value="gray"> |
|
|
<label for="bgGray">Solid Gray</label> |
|
|
</div> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgGreen" value="green"> |
|
|
<label for="bgGreen">Solid Green</label> |
|
|
</div> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgCustomImage" value="customImage"> |
|
|
<label for="bgCustomImage">Custom Image</label> |
|
|
<input type pigeonhole="file" id="customImageInput" accept="image/*"> |
|
|
</div> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgCustomVideo" value="customVideo"> |
|
|
<label for="bgCustomVideo">Custom Video</label> |
|
|
<input type="file" id="customVideoInput" accept="video/*"> |
|
|
</div> |
|
|
<div> |
|
|
<input type="radio" name="bgType" id="bgBlur" value="blur"> |
|
|
<label for="bgBlur">Blurred</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2>Controls</h2> |
|
|
<div> |
|
|
<label for="videoSource">Video Source</label> |
|
|
<select id="videoSource"> |
|
|
<option value="">Select a video source</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<input type="checkbox" id="enableBgRemoval"> |
|
|
<label for="enableBgRemoval">Enable Background Removal</label> |
|
|
</div> |
|
|
<div> |
|
|
<input type="checkbox" id="showPreview" checked> |
|
|
<label for="showPreview">Show Preview</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2>Performance</h2> |
|
|
<p>Processing Time: <span id="processingTime">0</span> ms</p> |
|
|
<p>FPS: <span id="fps">0</span></p> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2>OBS Integration</h2> |
|
|
<p>Add this as a Browser Source in OBS with these settings:</p> |
|
|
<p>Width: 1920</p> |
|
|
<p>Height: 1080</p> |
|
|
<p>Custom CSS: None</p> |
|
|
<button id="copyUrl">Copy OBS Browser Source URL</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const videoElement = document.getElementById('videoPreview'); |
|
|
const canvasElement = document.getElementById('canvasOutput'); |
|
|
const canvasCtx = canvasElement.getContext('2d'); |
|
|
const statusElement = document.getElementById('status'); |
|
|
const videoSourceSelect = document.getElementById('videoSource'); |
|
|
const enableBgRemoval = document.getElementById('enableBgRemoval'); |
|
|
const showPreview = document.getElementById('showPreview'); |
|
|
const processingTimeElement = document.getElementById('processingTime'); |
|
|
const fpsElement = document.getElementById('fps'); |
|
|
const copyUrlButton = document.getElementById('copyUrl'); |
|
|
let selfieSegmentation; |
|
|
let lastFrameTime = 0; |
|
|
let frameCount = 0; |
|
|
let lastFpsUpdate = 0; |
|
|
|
|
|
|
|
|
async function initModel() { |
|
|
selfieSegmentation = new SelfieSegmentation({ |
|
|
locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}` |
|
|
}); |
|
|
selfieSegmentation.setOptions({ |
|
|
modelSelection: 1, |
|
|
}); |
|
|
selfieSegmentation.onResults(onResults); |
|
|
await selfieSegmentation.initialize(); |
|
|
statusElement.textContent = 'AI Model Loaded'; |
|
|
} |
|
|
|
|
|
|
|
|
async function populateVideoSources() { |
|
|
const devices = await navigator.mediaDevices.enumerateDevices(); |
|
|
videoSourceSelect.innerHTML = '<option value="">Select a video source</option>'; |
|
|
devices.forEach(device => { |
|
|
if (device.kind === 'videoinput') { |
|
|
const option = document.createElement('option'); |
|
|
option.value = device.deviceId; |
|
|
option.text = device.label || `Camera ${videoSourceSelect.options.length + 1}`; |
|
|
videoSourceSelect.appendChild(option); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
async function startVideo(deviceId) { |
|
|
const stream = await navigator.mediaDevices.getUserMedia({ |
|
|
video: { deviceId: deviceId ? { exact: deviceId } : undefined } |
|
|
}); |
|
|
videoElement.srcObject = stream; |
|
|
videoElement.play(); |
|
|
} |
|
|
|
|
|
|
|
|
function onResults(results) { |
|
|
if (!enableBgRemoval.checked) { |
|
|
canvasCtx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); |
|
|
updatePerformance(); |
|
|
return; |
|
|
} |
|
|
|
|
|
canvasCtx.save(); |
|
|
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height); |
|
|
|
|
|
|
|
|
const bgType = document.querySelector('input[name="bgType"]:checked').value; |
|
|
if (bgType === 'transparent') { |
|
|
canvasCtx.globalCompositeOperation = 'destination-over'; |
|
|
canvasCtx.fillStyle = 'rgba(0, 0, 0, 0)'; |
|
|
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); |
|
|
} else if (bgType === 'black') { |
|
|
canvasCtx.fillStyle = 'black'; |
|
|
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); |
|
|
} else if (bgType === 'gray') { |
|
|
canvasCtx.fillStyle = 'gray'; |
|
|
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); |
|
|
} else if (bgType === 'green') { |
|
|
canvasCtx.fillStyle = 'green'; |
|
|
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); |
|
|
} else if (bgType === 'customImage') { |
|
|
const img = document.getElementById('customImageInput').files[0]; |
|
|
if (img) { |
|
|
const image = new Image(); |
|
|
image.src = URL.createObjectURL(img); |
|
|
canvasCtx.drawImage(image, 0, 0, canvasElement.width, canvasElement.height); |
|
|
} |
|
|
} else if (bgType === 'customVideo') { |
|
|
const vid = document.getElementById('customVideoInput').files[0]; |
|
|
if (vid) { |
|
|
const video = document.createElement('video'); |
|
|
video.src = URL.createObjectURL(vid); |
|
|
video.loop = true; |
|
|
video.play(); |
|
|
canvasCtx.drawImage(video, 0, 0, canvasElement.width, canvasElement.height); |
|
|
} |
|
|
} else if (bgType === 'blur') { |
|
|
canvasCtx.filter = 'blur(10px)'; |
|
|
canvasCtx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); |
|
|
canvasCtx.filter = 'none'; |
|
|
} |
|
|
|
|
|
|
|
|
canvasCtx.globalCompositeOperation = 'destination-atop'; |
|
|
canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElement.width, canvasElement.height); |
|
|
canvasCtx.globalCompositeOperation = 'source-over'; |
|
|
canvasCtx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); |
|
|
canvasCtx.restore(); |
|
|
|
|
|
updatePerformance(); |
|
|
} |
|
|
|
|
|
|
|
|
function updatePerformance() { |
|
|
const now = performance.now(); |
|
|
frameCount++; |
|
|
if (now - lastFpsUpdate > 1000) { |
|
|
const fps = frameCount / ((now - lastFpsUpdate) / 1000); |
|
|
fpsElement.textContent = fps.toFixed(1); |
|
|
frameCount = 0; |
|
|
lastFpsUpdate = now; |
|
|
} |
|
|
processingTimeElement.textContent = (now - lastFrameTime).toFixed(1); |
|
|
lastFrameTime = now; |
|
|
} |
|
|
|
|
|
|
|
|
copyUrlButton.addEventListener('click', () => { |
|
|
const url = window.location.href; |
|
|
navigator.clipboard.writeText(url).then(() => { |
|
|
alert('URL copied to clipboard!'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
videoSourceSelect.addEventListener('change', () => { |
|
|
startVideo(videoSourceSelect.value); |
|
|
}); |
|
|
|
|
|
enableBgRemoval.addEventListener('change', () => { |
|
|
canvasElement.style.display = enableBgRemoval.checked ? 'block' : 'none'; |
|
|
videoElement.style.display = !enableBgRemoval.checked ? 'block' : 'none'; |
|
|
}); |
|
|
|
|
|
showPreview.addEventListener('change', () => { |
|
|
document.querySelector('.card').style.display = showPreview.checked ? 'block' : 'none'; |
|
|
}); |
|
|
|
|
|
|
|
|
async function processFrame() { |
|
|
if (enableBgRemoval.checked) { |
|
|
await selfieSegmentation.send({ image: videoElement }); |
|
|
} |
|
|
requestAnimationFrame(processFrame); |
|
|
} |
|
|
|
|
|
|
|
|
async function init() { |
|
|
await initModel(); |
|
|
await populateVideoSources(); |
|
|
await startVideo(); |
|
|
processFrame(); |
|
|
} |
|
|
|
|
|
init(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |