Spaces:
Running
Running
import DeviceDetector from "https://cdn.skypack.dev/[email protected]"; | |
// Uso: testSupport({client?: string, os?: string}[]) | |
// Client e os são expressões regulares. | |
// Veja: https://cdn.jsdelivr.net/npm/[email protected]/README.md para | |
// valores legais para client e os | |
testSupport([ | |
{client: 'Chrome'}, | |
]); | |
function testSupport(supportedDevices:{client?: string; os?: string;}[]) { | |
const deviceDetector = new DeviceDetector(); | |
const detectedDevice = deviceDetector.parse(navigator.userAgent); | |
let isSupported = false; | |
for (const device of supportedDevices) { | |
if (device.client !== undefined) { | |
const re = new RegExp(`^${device.client}$`); | |
if (!re.test(detectedDevice.client.name)) { | |
continue; | |
} | |
} | |
if (device.os !== undefined) { | |
const re = new RegExp(`^${device.os}$`); | |
if (!re.test(detectedDevice.os.name)) { | |
continue; | |
} | |
} | |
isSupported = true; | |
break; | |
} | |
if (!isSupported) { | |
alert(`Esta demonstração, rodando em ${detectedDevice.client.name}/${detectedDevice.os.name}, ` + | |
`não é bem suportada no momento, continue por sua conta e risco.`); | |
} | |
} | |
const controls = window; | |
const drawingUtils = window; | |
const mpFaceMesh = window; | |
const config = {locateFile: (file) => { | |
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh@` + | |
`${mpFaceMesh.VERSION}/${file}`; | |
}}; | |
// Nossos quadros de entrada virão daqui. | |
const videoElement = | |
document.getElementsByClassName('input_video')[0] as HTMLVideoElement; | |
const canvasElement = | |
document.getElementsByClassName('output_canvas')[0] as HTMLCanvasElement; | |
const controlsElement = | |
document.getElementsByClassName('control-panel')[0] as HTMLDivElement; | |
const canvasCtx = canvasElement.getContext('2d')!; | |
/** | |
* Opções da solução. | |
*/ | |
const solutionOptions = { | |
selfieMode: true, | |
enableFaceGeometry: false, | |
maxNumFaces: 1, | |
refineLandmarks: false, | |
minDetectionConfidence: 0.5, | |
minTrackingConfidence: 0.5 | |
}; | |
// Adicionaremos isso ao nosso painel de controle mais tarde, mas salvaremos aqui para que possamos | |
// chamar tick() cada vez que o gráfico for executado. | |
const fpsControl = new controls.FPS(); | |
// Otimização: Desative o spinner animado após sua animação de ocultação ser concluída. | |
const spinner = document.querySelector('.loading')! as HTMLDivElement; | |
spinner.ontransitionend = () => { | |
spinner.style.display = 'none'; | |
}; | |
function onResults(results: mpFaceMesh.Results): void { | |
// Ocultar o spinner. | |
document.body.classList.add('loaded'); | |
// Atualizar a taxa de quadros. | |
fpsControl.tick(); | |
// Desenhar as sobreposições. | |
canvasCtx.save(); | |
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height); | |
canvasCtx.drawImage( | |
results.image, 0, 0, canvasElement.width, canvasElement.height); | |
if (results.multiFaceLandmarks) { | |
for (const landmarks of results.multiFaceLandmarks) { | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_TESSELATION, | |
{color: '#C0C0C070', lineWidth: 1}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_RIGHT_EYE, | |
{color: '#FF3030'}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_RIGHT_EYEBROW, | |
{color: '#FF3030'}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_LEFT_EYE, | |
{color: '#30FF30'}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_LEFT_EYEBROW, | |
{color: '#30FF30'}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_FACE_OVAL, | |
{color: '#E0E0E0'}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_LIPS, {color: '#E0E0E0'}); | |
if (solutionOptions.refineLandmarks) { | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_RIGHT_IRIS, | |
{color: '#FF3030'}); | |
drawingUtils.drawConnectors( | |
canvasCtx, landmarks, mpFaceMesh.FACEMESH_LEFT_IRIS, | |
{color: '#30FF30'}); | |
} | |
} | |
} | |
canvasCtx.restore(); | |
} | |
const faceMesh = new mpFaceMesh.FaceMesh(config); | |
faceMesh.setOptions(solutionOptions); | |
faceMesh.onResults(onResults); | |
// Apresentar um painel de controle através do qual o usuário pode manipular as opções da solução. | |
new controls | |
.ControlPanel(controlsElement, solutionOptions) | |
.add([ | |
new controls.StaticText({title: 'MediaPipe Face Mesh'}), | |
fpsControl, | |
new controls.Toggle({title: 'Modo Selfie', field: 'selfieMode'}), | |
new controls.SourcePicker({ | |
onFrame: | |
async (input: controls.InputImage, size: controls.Rectangle) => { | |
const aspect = size.height / size.width; | |
let width: number, height: number; | |
if (window.innerWidth > window.innerHeight) { | |
height = window.innerHeight; | |
width = height / aspect; | |
} else { | |
width = window.innerWidth; | |
height = width * aspect; | |
} | |
canvasElement.width = width; | |
canvasElement.height = height; | |
await faceMesh.send({image: input}); | |
}, | |
}), | |
new controls.Slider({ | |
title: 'Número Máximo de Rostos', | |
field: 'maxNumFaces', | |
range: [1, 4], | |
step: 1 | |
}), | |
new controls.Toggle( | |
{title: 'Refinar Pontos de Referência', field: 'refineLandmarks'}), | |
new controls.Slider({ | |
title: 'Confiança Mínima de Detecção', | |
field: 'minDetectionConfidence', | |
range: [0, 1], | |
step: 0.01 | |
}), | |
new controls.Slider({ | |
title: 'Confiança Mínima de Rastreamento', | |
field: 'minTrackingConfidence', | |
range: [0, 1], | |
step: 0.01 | |
}), | |
]) | |
.on(x => { | |
const options = x as mpFaceMesh.Options; | |
videoElement.classList.toggle('selfie', options.selfieMode); | |
faceMesh.setOptions(options); | |
}); |