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); | |
| }); |