Last active
October 18, 2021 01:34
-
-
Save cowboy/275360358a2c25fbeafa770ad0efcd25 to your computer and use it in GitHub Desktop.
Hand tracking detection in OBS Studio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<script> | |
// =========================================================================== | |
// Hand detection in OBS Studio | |
// by "Cowboy" Ben Alman - 2021 | |
// https://gist.github.com/cowboy/275360358a2c25fbeafa770ad0efcd25 | |
// =========================================================================== | |
// | |
// The images in the "images" div should be written on a 1s (or so) timer by | |
// this plugin: https://github.com/synap5e/obs-screenshot-plugin | |
// | |
// Save this HTML file in the same folder as the images and add it to OBS as a | |
// browser source. Give it permissions if you want it to control OBS. Add an | |
// <img> in the "images" div for each image you want to process. The image id | |
// will be passed to the "notify" function when a hand has been detected in an | |
// image for longer than CONTINUOUS_DETECT_THRESHOLD. | |
// | |
// Customize the "notify" function using the APIs listed here: | |
// https://github.com/obsproject/obs-browser#control-obs | |
// | |
// Note that the sources need to be visible for the filter to render the images, | |
// but you can put those sources plus a browser source with this HTML file in it | |
// in a scene, then put that scene in all your other scenes (behind the other | |
// sources, so it stays hidden). | |
const CONTINUOUS_DETECT_THRESHOLD = 5000 | |
const IMAGE_RELOAD_AND_DETECT_INTERVAL = 500 | |
// See "Sample predictions result" at: | |
// https://victordibia.com/handtrack.js/#/docs | |
const IS_VALID_PREDICTION = (p) => p.label === 'open' && p.score > 0.7 | |
// Actually do something with the obs API. All getter methods | |
// are promisifed (see code below) | |
// https://github.com/obsproject/obs-browser#control-obs | |
const HAND_DETECT_EXCEEDS_THRESHOLD = async (id) => { | |
console.log('image id', id) | |
const { name } = await obs.getCurrentScene() | |
console.log('current scene', name) | |
} | |
</script> | |
<meta charset="UTF-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<script src="https://cdn.jsdelivr.net/npm/handtrackjs@latest/dist/handtrack.min.js"></script> | |
<title>Hand detection in OBS Studio</title> | |
<style> | |
html, | |
body { | |
margin: 0; | |
padding: 0; | |
height: 100%; | |
overflow: hidden; | |
background: #000; | |
} | |
#images { | |
display: flex; | |
flex-wrap: wrap; | |
} | |
#images > * { | |
display: block; | |
margin: 0; | |
padding: 0; | |
width: 50%; /* works well for <= 4 images, adjust if needed */ | |
} | |
</style> | |
</head> | |
<body> | |
<div id="images"> | |
<img id="keyboards" src="./keyboards-cam.png" /> | |
<img id="modular" src="./modular-cam.png" /> | |
</div> | |
<script> | |
// Promisified obsstudio interface: | |
// https://github.com/obsproject/obs-browser#control-obs | |
const obs = { | |
...obsstudio, | |
...Object.entries(obsstudio) | |
.filter(([name]) => /^get/.test(name)) | |
.reduce( | |
(acc, [name, fn]) => ({ | |
...acc, | |
[name]: () => new Promise((resolve) => fn(resolve)), | |
}), | |
{} | |
), | |
} | |
let model | |
handTrack.load().then((_model) => { | |
model = _model | |
}) | |
const images = [...document.images] | |
const reloadAndTestImages = async () => { | |
await Promise.all( | |
images.map((image) => { | |
// Force image to reload | |
image.src = image.src.replace(/\?.*$|$/, `?${Date.now()}`) | |
// console.log(image.src) | |
return new Promise((resolve) => { | |
image.onload = resolve | |
}) | |
}) | |
) | |
if (!model) { | |
return | |
} | |
for (let image of images) { | |
const predictions = await model.detect(image) | |
const valid = predictions.filter(IS_VALID_PREDICTION) | |
if (valid.length > 0) { | |
console.log(image.id, ...valid.map((p) => [p.label, p.score])) | |
detect(image.id) | |
} | |
} | |
} | |
setInterval(reloadAndTestImages, IMAGE_RELOAD_AND_DETECT_INTERVAL) | |
let lastNotifiedId = null | |
let lastDetectedId = null | |
let lastDetectTime = 0 | |
let continuousDetectStart = Infinity | |
const detect = (id) => { | |
const now = Date.now() | |
// A new id was been detected or too much time has elapsed since | |
// the last detect. Detection is no longer continuous. | |
if ( | |
id !== lastDetectedId || | |
now - lastDetectTime > IMAGE_RELOAD_AND_DETECT_INTERVAL * 3 | |
) { | |
continuousDetectStart = now | |
} | |
// Id hasn't been notified, and detection has been continuous | |
// for longer than the threshold | |
else if ( | |
id !== lastNotifiedId && | |
now - continuousDetectStart > CONTINUOUS_DETECT_THRESHOLD | |
) { | |
lastNotifiedId = id | |
HAND_DETECT_EXCEEDS_THRESHOLD(id) | |
} | |
lastDetectedId = id | |
lastDetectTime = now | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment