Three.js + Tensorflow.js 构建实时人脸点云

news2024/12/28 3:34:48

本文重点介绍使用 Three.js 和 Tensorflow.js 实现实时人脸网格点云所需的步骤。 它假设你之前了解异步 javascript 和 Three.js 基础知识,因此不会涵盖基础知识。

该项目的源代码可以在此 Git 存储库中找到。 在阅读本文时查看该代码将会很有帮助,因为将跳过一些基本的实现步骤。

本文还将以面向对象的方式实现该项目,其中包含大量抽象,因此对 Typescript 中的类有基本的了解是一个优势。

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景

1、获取 Three.js 设置

由于本教程的目标是渲染人脸点云,因此我们需要从设置三个 js 场景开始。

设置场景所需的数据和方法封装在 sceneSetUp.ts 中名为 ThreeSetUp 的工厂类中。 该类负责创建所有必要的场景对象,如渲染器、相机和场景。 它还启动画布元素的调整大小处理程序。 该类具有以下公共方法:

  • getSetUp:此函数返回一个对象,其中包含相机、场景、渲染器和画布的大小信息。
getSetUp(){
    return {
      camera: this.camera,
      scene: this.scene,
      renderer : this.renderer,
      sizes: this.sizes,
    }
  }
  • apply OrbitControls:此方法将负责在我们的设置中添加轨道控件,并返回我们需要调用以更新轨道控件的函数。
applyOrbitControls(){
    const controls = new OrbitControls(
      this.camera, this.renderer.domElement!
    )
    controls.enableDamping = true
    return ()=> controls.update();
  }

我们的主要实现类 FacePointCloud 将启动 ThreeSetUP 类并调用这两个方法来获取设置元素并应用轨道控制。

2、从网络摄像头生成视频数据

为了使我们能够获得面部网格跟踪信息,我们需要一个像素输入来提供给面部网格跟踪器。 在这种情况下,我们将使用设备网络摄像头来生成此类输入。 我们还将使用 HTML 视频元素(不将其添加到 Dom)从网络摄像头读取媒体流,并以我们的代码可以交互的方式加载它。

在此步骤之后,我们将设置一个 HTML canvas 元素(也无需添加到 Dom)并向其渲染视频输出。 这使我们还可以选择从画布生成三个 Js 纹理并将其用作材质(我们不会在本教程中实现这一点)。 我们将使用 canvas 元素作为 FaceMeshTracker 的输入。

为了处理从网络摄像头读取媒体流并将其加载到视频 HTML 元素,我们将创建一个名为 WebcamVideo 的类。 此类将处理创建 HTML 视频元素并调用导航器 api 来加载获取用户权限并从设备的网络摄像头加载信息。

在启动此类时,将调用私有 init 方法,该方法具有以下代码:

private init(){
  navigator.mediaDevices.getUserMedia(this.videoConstraints)
  .then((mediaStream)=>{
    this.videoTarget.srcObject = mediaStream
    this.videoTarget.onloadedmetadata = () => this.onLoadMetadata()
    }
  ).catch(function (err) {
    alert(err.name + ': ' + err.message)
    }
  )
}

此方法调用导航器对象的 mediaDevices 属性上的 getUserMedia 方法。 此方法将视频约束(也称为视频设置)作为参数并返回一个承诺。 此承诺解析为包含来自网络摄像头的视频数据的 mediaStream 对象。 在 Promise 的解析回调中,我们将视频元素的源设置为返回的 mediaStream。

在promise解析回调中,我们还在视频元素上添加了一个loadedmetadata事件监听器。 该监听器的回调会触发对象的 onLoadMetaData 方法并设置以下副作用:

  • 自动播放视频
  • 确保视频内嵌播放
  • 调用我们传递给对象的可选回调,以便在事件触发时调用
private onLoadMetadata(){
  this.videoTarget.setAttribute('autoplay', 'true')
  this.videoTarget.setAttribute('playsinline', 'true')
  this.videoTarget.play()
  this.onReceivingData()
}

此时,我们有一个 WebcamVideo 对象,它负责创建包含实时网络摄像头数据的视频元素。 下一步是将视频输出绘制在画布对象上。

为此,我们将创建一个使用 WebcamVideo 类的特定 WebcamCanvas 类。 此类将创建 WebcamVideo 类的实例,并使用它通过画布上下文方法的drawImage()将视频的输出绘制到画布上。 这将在 updateFromWebcam 方法上实现。

updateFromWebCam(){
  this.canvasCtx.drawImage(
    this.webcamVideo.videoTarget,
    0,
    0,
    this.canvas.width,
    this.canvas.height
  )
}

我们必须在渲染循环中不断调用此函数,以不断使用视频的当前帧更新画布。

此时,我们已将像素输入准备为显示网络摄像头的画布元素。

3、使用 Tensorflow.js 创建 Face Mesh 检测器

创建人脸网格检测器并生成检测数据是本教程的主要部分。 这将实现 Tensorflow.js 人脸特征点检测模型。

npm add @tensorflow/tfjs-core, @tensorflow/tfjs-converter
npm add @tensorflow/tfjs-backend-webgl
npm add @tensorflow-models/face-detection
npm add @tensorflow-models/face-landmarks-detection

安装所有相关包后,我们将创建一个处理以下内容的类:

  • 加载模型
  • 获取探测器对象
  • 将检测器添加到类中
  • 实现一个公共检测函数以供其他对象使用。

我们创建了一个名为faceLandmark.ts 的文件来实现该类。 文件顶部的导入是:

import '@mediapipe/face_mesh'
import '@tensorflow/tfjs-core'
import '@tensorflow/tfjs-backend-webgl'
import * as faceLandmarksDetection from '@tensorflow-models/face-landmarks-detection'

运行和创建检测器对象将需要这些模块。

我们创建 FaceMeshDetectorClass,如下所示:

export default class FaceMeshDetector {
  detectorConfig: Config;
  model: faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh;
  detector: faceLandmarksDetection.FaceLandmarksDetector | null;

  constructor(){
    this.model = faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh;
    this.detectorConfig = {
      runtime: 'mediapipe',
      refineLandmarks: true,
      solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh',
     }
    this.detector = null;
  }

  private getDetector(){
    const detector = faceLandmarksDetection.createDetector(
      this.model, 
      this.detectorConfig as faceLandmarksDetection.MediaPipeFaceMeshMediaPipeModelConfig
    );
    return detector;
  }

  async loadDetector(){
    this.detector = await this.getDetector()
  }

  async detectFace(source: faceLandmarksDetection.FaceLandmarksDetectorInput){
    const data = await this.detector!.estimateFaces(source)
    const keypoints = (data as FaceLandmark[])[0]?.keypoints
    if(keypoints) return keypoints;
    return [];
  }
}

这个类中的主要方法是 getDetector,它调用我们从 Tensorflow.js 导入的 FaceLandMarksDetection 上的 createDetector 方法。 然后 createDetector 采用我们在构造函数中引入的模型:

this.model = faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh;

以及指定检测器参数的检测配置对象:

this.detectorConfig = {
  runtime: 'mediapipe',
  refineLandmarks: true,
  solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh',
}

检测函数将返回一个承诺,该承诺将解析为检测器对象。 然后在 loadDetector 公共异步方法中使用私有 getDetector 函数,该方法将类上的 this. detector 属性设置为检测器。

FaceMeshDetector 类还实现了一个公共的 detectorFace 方法:

async detectFace(source){
  const data = await this.detector!.estimateFaces(source)
  const keypoints = (data as FaceLandmark[])[0]?.keypoints
  if(keypoints) return keypoints;
  return [];
}

该方法采用一个源参数,即像素输入。 我们将在这里使用上面的画布元素作为跟踪源。 该函数将被如下调用:

faceMeshDetector.detectFace(this.webcamCanvas.canvas)

此方法调用检测器上的estimateFaces方法,如果此方法在网络摄像头输出中检测到人脸,它将返回一个包含检测数据的对象的数组。 该对象有一个称为关键点的属性,它包含模型在面部检测到的 478 个点中每个点的对象数组。 每个对象都有 x、y 和 z 属性,其中包括画布中点的坐标。 例子:

[
  {
    box: {
      xMin: 304.6476503248806,
      xMax: 502.5079975897382,
      yMin: 102.16298762367356,
      yMax: 349.035215984403,
      width: 197.86034726485758,
      height: 246.87222836072945
    },
    keypoints: [
      {x: 406.53152857172876, y: 256.8054528661723, z: 10.2, name:
      "lips"},
      {x: 406.544237446397, y: 230.06933367750395, z: 8},
      ...
    ],
  }
]

值得注意的是,这些点作为画布空间中的坐标返回,这意味着参考点、x: 0 和 y: 0 点位于画布的左上角。 稍后当我们必须将坐标转换为 Three.js 场景空间(其参考点位于场景中心)时,这将是相关的。

此时,我们有了像素输入源,以及面部网格检测器,它将为我们提供检测到的点。 现在,我们可以转到 Three.js 部分!

4、创建空点云

为了在 Three.js 中生成面部网格,我们必须从检测器加载面部网格点,然后将它们用作 Three js Points 对象的位置属性。 为了使三个 js 面部网格反映视频中的运动(实时反应),每当我们创建的检测器发生面部检测变化时,我们都必须更新此位置属性。

为了实现这一点,我们将创建另一个名为 PointCloud 的工厂类,它将创建一个空的 Points 对象,以及一个可用于更新此点对象的属性(例如位置属性)的公共方法。 这个类看起来像这样:

export default class PointCloud {
  bufferGeometry: THREE.BufferGeometry;
  material: THREE.PointsMaterial;
  cloud: THREE.Points<THREE.BufferGeometry, THREE.PointsMaterial>;
  
  constructor() {
    this.bufferGeometry = new THREE.BufferGeometry();
    this.material = new THREE.PointsMaterial({
      color: 0x888888,
      size: 0.0151,
      sizeAttenuation: true,
    });
    this.cloud = new THREE.Points(this.bufferGeometry,     this.material);
  }
updateProperty(attribute: THREE.BufferAttribute, name: string){
  this.bufferGeometry.setAttribute(
    name,
    attribute
  );
  this.bufferGeometry.attributes[name].needsUpdate = true;
  }
}

此类启动空的 BufferGrometry,它是点的材质以及消耗两者的点对象。 将此点对象添加到场景中不会改变任何内容,因为几何体没有任何位置属性,换句话说,没有顶点。

PointCloud 类还公开 updateProperty 方法,该方法接受缓冲区属性和属性名称。 然后,它将调用 bufferGeometry setAttribute 方法并将 needUpdate 属性设置为 true。 这将允许 Three.js 在下一个 requestAnimationFrame 迭代中反映 bufferAttribute 的更改。

我们将使用此 updateProperty 方法根据从 Tensorflow.js 检测器接收到的点来更改点云的形状。

现在,我们还准备好点云来接收新的位置数据。 所以,是时候把所有的东西都绑在一起了!

5、将跟踪信息馈送到点云

为了将所有内容结合在一起,我们将创建一个实现类来调用使所有内容正常工作所需的类、方法和步骤。 这个类称为 FacePointCloud。 在构造函数中,它将实例化以下类:

  • ThreeSetUp 类获取场景设置对象
  • CanvasWebcam 用于获取显示网络摄像头内容的画布对象
  • 用于加载跟踪模型并获取检测器的faceLandMark类
  • PointCloud 类用于设置空点云并稍后使用检测数据更新它
constructor() {
  this.threeSetUp = new ThreeSetUp()
  this.setUpElements = this.threeSetUp.getSetUp()
  this.webcamCanvas = new WebcamCanvas();
  this.faceMeshDetector = new faceLandMark()
  this.pointCloud = new PointCloud()
}

这个类还有一个名为bindFaceDataToPointCloud的方法,它执行我们逻辑的主要部分,即获取检测器提供的数据,将其转换为Three.js可以理解的形式,从中创建一个Three.js缓冲区属性并使用 它来更新点云。

async bindFaceDataToPointCloud(){
  const keypoints = await
  this.faceMeshDetector.detectFace(this.webcamCanvas.canvas)
  const flatData = flattenFacialLandMarkArray(keypoints)
  const facePositions = createBufferAttribute(flatData)
  this.pointCloud.updateProperty(facePositions, 'position')
}

因此,我们将画布像素源传递给 detectorFace 方法,然后在实用程序函数 flattenFacialLandMarkArray 中对返回的数据执行操作。 这非常重要,因为有两个问题:

正如我们上面提到的,人脸检测模型中的点将以以下形式返回:

keypoints: [
  {x: 0.542, y: 0.967, z: 0.037},
  ...
]

而 buffer 属性期望数据/数字具有以下形状:

number[] or [0.542, 0.967, 0.037, .....]

数据源(画布)之间的坐标系差异,画布的坐标系如下所示:

在这里插入图片描述

Three.js 场景坐标系如下所示:
在这里插入图片描述

因此,考虑到这两个选项,我们实现了 flattenFacialLandMarkArray 函数来解决这些问题。 该函数的代码如下所示:

function flattenFacialLandMarkArray(data: vector[]){
  let array: number[] = [];
  data.forEach((el)=>{
    el.x = mapRangetoRange(500 / videoAspectRatio, el.x,
      screenRange.height) - 1
    
    el.y = mapRangetoRange(500 / videoAspectRatio, el.y,
      screenRange.height, true)+1
    el.z = (el.z / 100 * -1) + 0.5;
    
    array = [
      ...array,
      ...Object.values(el),
    ]
  })
  return array.filter((el)=> typeof el === 'number');

flattenFacialLandMarkArray 函数获取我们从人脸检测器接收到的关键点输入,并将它们展开到一个数组中,以数字 [] 形式而不是对象 [] 形式。 在将数字传递到新的输出数组之前,它通过 mapRangetoRange 函数将它们从画布坐标系映射到 Three.js 坐标系。 该函数如下所示:

function mapRangetoRange(from: number, point: number, range: range, invert: boolean = false): number{
  let pointMagnitude: number = point/from;
  if(invert) pointMagnitude = 1-pointMagnitude;
  const targetMagnitude = range.to - range.from;
  const pointInRange = targetMagnitude * pointMagnitude +
    range.from;
  
  return pointInRange
}

我们现在可以创建初始化函数和动画循环。 这是在 FacePointCloud 类的 initWork 方法中实现的,如下所示:

async initWork() {
  const { camera, scene, renderer } = this.setUpElements
  camera.position.z = 3
  camera.position.y = 1
  camera.lookAt(0,0,0)
  const orbitControlsUpdate = this.threeSetUp.applyOrbitControls()
  const gridHelper = new THREE.GridHelper(10, 10)
  scene.add(gridHelper)
  scene.add(this.pointCloud.cloud)
  
  await this.faceMeshDetector.loadDetector()
  
  const animate = () => {
    requestAnimationFrame(animate)
    if (this.webcamCanvas.receivingStreem){
      this.bindFaceDataToPointCloud()
    }
    this.webcamCanvas.updateFromWebCam()
    orbitControlsUpdate()
    renderer.render(scene, camera)
  }
  
  animate()
}

我们可以看到这个 init 函数如何将所有内容联系在一起,它获取 Three.js 设置元素并设置相机,向场景和点云添加 gridHelper。

然后,它将检测器加载到faceLandMark 类上,并开始设置我们的动画函数。 在此动画函数中,我们首先检查 WebcamCanvas 元素是否正在接收来自网络摄像头的流,然后调用 bindFaceDataToPointCloud 方法,该方法在内部调用检测面部函数并将数据转换为 bufferAttribute 并更新点云位置属性。

现在,如果运行代码,应该在浏览器中得到以下结果!
在这里插入图片描述


原文链接:Three.js实时构建人脸点云 — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1113370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx配置download模块

nginx.conf配置 location /download{alias /usr/local/webapp/download/;sendfile on;autoindex on; # 开启目录文件列表autoindex_exact_size on; # 显示出文件的确切大小&#xff0c;单位是bytesautoindex_localtime on; # 显示的文件时间为文件的服务器时间charset utf…

hexo发生错误 Error: Spawn failed

错误描述 仓库中有东西&#xff0c;运行如下命令后报错 hexo d报错提示: 原因分析: 看别人的博客是用git进行push或hexo d的时候改变了一些.deploy_git文件下的内容&#xff0c;这个.deploy_git的内容对于hexo来说可能是系统文件&#xff0c;这里挖坑 解决办法 一个个的…

如何在不恢复出厂设置的情况下解锁 Android 手机密码?

如何在不恢复出厂设置的情况下解锁 Android 手机密码&#xff1f; 当您忘记 Android 手机的密码时&#xff0c;可能会有压力&#xff0c;尤其是当您不想恢复出厂设置并删除所有数据时。但是&#xff0c;有一些方法可以在不诉诸如此激烈的步骤的情况下解锁手机。我们将在这篇文…

平板有必要买触控笔吗?推荐的ipad手写笔

iPad之所以能吸引这么多人&#xff0c;主要是因为它的功能出色。用来画画、做笔记&#xff0c;也是一种不错的体验。但如果只是用来看电视和打游戏的话&#xff0c;那就真的有点大材小用了。如果你不需要昂贵的苹果电容笔&#xff0c;也不需要用来专业的绘图&#xff0c;那你可…

SSM+SpringBoot重点

SSM+SpringBoot重点 0、VO DTO DO PO介绍 VO ​ vo(view object)视图对象 ​ 用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来,一般由后端传输给前端。 DTO ​ DTO(data transfer object)数据传输对象 ​ 用于展示层和逻辑层之间的数据传输。 …

视频监控这样做,简单又高效!

随着技术的不断进步&#xff0c;视频监控系统已经变得更加高效和智能&#xff0c;可以提供更全面的监控和分析功能&#xff0c;有助于提高安全性、管理效率和决策支持。 客户案例 超市连锁店 福建某全国性超市连锁店面临高额商品损失、偷窃问题&#xff0c;以及对客户安全和员工…

(论文翻译)UFO: Unified Feature Optimization——UFO:统一特性优化

作者&#xff1a; Teng Xi 论文总结&#xff1a;总结 Code: https://github.com/PaddlePaddle/VIMER/tree/main/UFO 摘要&#xff1a; 本文提出了一种新的统一特征优化(Unified Feature Optimization, UFO)范式&#xff0c;用于在现实世界和大规模场景下训练和部署深度模型…

Python学习基础笔记七十六——Python装饰器2

装饰器&#xff0c;英文名字decorator。 我们开发Python代码的时候&#xff0c;经常碰到装饰器。 通常被装饰后的函数&#xff0c;会在原来的函数的基础上&#xff0c;增加一些功能。 通常装饰器本事也是一个函数&#xff0c;那么装饰器是怎么装饰另外一个函数的呢&#xff1f…

Unity 镜面反射

放置地板和模型 首先&#xff0c;让我们放置地板和将放置在其上的 3D 模型。这次&#xff0c;我使用 Plane 作为地板。从层次视图中选择“创建”→“3D 对象”→“平面”。我们还在地板上放置了 Unity-chan、Cube 和 Sphere。 接下来&#xff0c;创建地板的材质。在项目视图中…

安装 Dispatch 库

首先&#xff0c;我们需要安装 Dispatch 库。在命令行中运行以下命令来安装 Dispatch&#xff1a; $ sbt console然后&#xff0c;在 Scala 控制台中&#xff0c;导入所需的库&#xff1a; import dispatch._接下来&#xff0c;我们需要设置代理服务器。在 Dispatch 中&#…

个性联邦学习

Towards Personalized Federated Learning 一、背景二、解决策略 2.1 策略一.全局模型个性化2.2 策略二.学习个性化模型 三、具体方案 3.1 全局模型个性化 3.1.1 基于数据&#xff1a;减少客户端数据统计异构性3.1.2 基于模型&#xff1a;在学习一个强大的全局模型&#xff0c;…

014 - ARM64上的GIC-400(GICv2)

本章节涉及到的参考文档有三个&#xff1a; BCM2711 ARM Peripherals.pdfARM Generic Interrupt Controller Architecture Specification.pdf (简称gic_v2)CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual.pdf 1. GIC 发展历史 在早期的 ARM 系统…

睿趣科技:抖音小店新手运营攻略

随着短视频平台的兴起&#xff0c;抖音已经成为了一个炙手可热的营销工具。越来越多的商家选择在抖音上开设小店&#xff0c;以此来拓展自己的业务。那么&#xff0c;作为新手&#xff0c;如何运营好自己的抖音小店呢?本文将为您提供一些实用的建议。 首先&#xff0c;要明确自…

python实现图片与视频转换:将视频保存为图片,将批量图片保存为视频

1、将视频保存为图片 1.1 完整代码 # -*- coding: utf-8 -*- import cv2 import os cap cv2.VideoCapture(TestFiles/2.mp4) index 0 prop cv2.CAP_PROP_FRAME_COUNT total int(cap.get(prop)) # 获取视频总帧数 while(cap.isOpened()):# ret返回布尔值ret, frame cap.r…

竹云荣膺2023十大数字化转型创新企业

10月14日上午&#xff0c;“2023DTWORLD国际数字科技领袖峰会/ IDI Award 数创奖颁奖盛典”在深圳会展中心&#xff08;福田&#xff09;2号馆成功举办。本届峰会由深圳市科学技术协会等相关政府部门指导&#xff0c;由深圳市科技交流服务中心、深圳市人工智能行业协会、湾盟产…

医疗终端札记

文章目录 一、打印Windows 下打印 PDF打印 Word转换格式 一、打印 Windows 下打印 PDF 从 Windows 命令行打印 PDF AcroRd32.exe /t "C:\Path\To\Your\File.pdf" "PrinterName" # 其中&#xff0c;“C:\Path\To\Your\File.pdf”是您要打印的PDF文件的完…

嵌入式软件开发笔试面试

C语言部分&#xff1a; 1.gcc的四步编译过程 1.预处理 展开头文件&#xff0c;删除注释、空行等无用内容&#xff0c;替换宏定义。 gcc -E hello.c -o hello.i 2.编译 检查语法错误&#xff0c;如果有错则报错&#xff0c;没有错误则生成汇编文件。 gcc -S hello.i -o h…

在线录音工具分享,总有一款适合你!

“有人知道怎么在线录音吗&#xff1f;在网页上播放了一首民谣&#xff0c;觉得旋律很好听&#xff0c;但是不能下载&#xff0c;就想用录音的方式记录下来&#xff0c;可是完全不会操作&#xff0c;真的很急&#xff01;有没有好心人教教我&#xff0c;谢谢&#xff01;” 随…

衍射:经典波动行为

一、说明 在本页中&#xff0c;我将尝试引导您了解光背后的基础知识。光是粒子还是波&#xff1f; 衍射是与光传播偏差相关的现象的通用名称&#xff0c;与几何光学预测的偏差&#xff08;即光的直线传播&#xff09;有关&#xff0c;它揭示了光的性质波而不是微粒物质。 图1&a…

为T507-H开发板配置Samba服务,实现跨系统的文件共享——飞凌嵌入式

作为一款经典的国产芯&#xff0c;全志T507-H芯片被广泛应用于车载电子、电力、医疗、工业控制、物联网、智能终端等诸多领域当中&#xff0c;而在各种复杂的嵌入式Linux应用场景当中&#xff0c;“打通ARM板卡与Windows设备间的壁垒以实现跨平台的文件共享”是一项不能被忽视的…