import DeviceDetector from "https://cdn.skypack.dev/device-detector-js@2.2.10"; // Uso: testSupport({client?: string, os?: string}[]) // Client e os são expressões regulares. // Veja: https://cdn.jsdelivr.net/npm/device-detector-js@2.2.10/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); });