tensorflow.js 使用 opencv.js 将人脸特征点网格绘制与姿态估计线绘制结合起来,以获得更高的帧数

news2024/11/25 12:33:38

系列文章目录

  1. 如何在前端项目中使用opencv.js | opencv.js入门
  2. 如何使用tensorflow.js实现面部特征点检测
  3. tensorflow.js 如何从 public 路径加载人脸特征点检测模型
  4. tensorflow.js 如何使用opencv.js通过面部特征点估算脸部姿态并绘制示意图

文章目录

  • 系列文章目录
  • 前言
  • 一、实现步骤
    • 1. 修改drawMesh.js文件为drawCanvas.js文件
    • 2. 获取帧数信息并显示出来
    • 3. 运行代码查看效果
  • 总结


前言

本文将基于前文的工程进度,将人脸特征点网格(使用原生的canvas方法绘制)和姿态估计线(使用opencv.js+canvas绘制)统一起来,使用opencv.js绘制两者以达到更高的帧数。由于一部分工作基于前文,如果有一些跳跃或者不连贯的地方的疑问请参考前文,或者在评论区提出问题。


一、实现步骤

1. 修改drawMesh.js文件为drawCanvas.js文件

详细代码见drawCanvas.js
drawCanvas函数包含了drawTriangle和drawPoseLine函数,前者是绘制人脸特征点网格的关键函数后者是绘制姿态估计示意线的关键函数,在该文件中将原本的人脸特征点网格的实现修改为opencv.js实现,最终的帧数为60左右,与本机相机的默认帧数相同。

import { TRIANGULATION } from "./triangulation";

export const drawCanvas = (prediction, canvas) => {
  if (!prediction) return;
  const keyPoints = prediction.keypoints;
  if (!keyPoints) return;
  const canvasMat = new window.cv.Mat.zeros(
    canvas.height,
    canvas.width,
    window.cv.CV_8UC4
  );
  for (let i = 0; i < TRIANGULATION.length / 3; i++) {
    const points = [
      TRIANGULATION[i * 3],
      TRIANGULATION[i * 3 + 1],
      TRIANGULATION[i * 3 + 2],
    ].map(
      (index) =>
        new window.cv.Point(
          Math.round(keyPoints[index].x),
          Math.round(keyPoints[index].y)
        )
    );
    drawTriangle(canvasMat, points);
  }

  const circleColor = new window.cv.Scalar(0, 0, 255, 255);
  for (let i = 0; i < keyPoints.length; i++) {
    let center = new window.cv.Point(
      Math.round(keyPoints[i].x),
      Math.round(keyPoints[i].y)
    );
    window.cv.circle(canvasMat, center, 2, circleColor);
  }

  drawPoseLine(canvasMat, keyPoints);
  window.cv.imshow(canvas.id, canvasMat);
  canvasMat.delete();
};

const drawTriangle = (canvasMat, points) => {
  window.cv.line(
    canvasMat,
    points[0],
    points[1],
    new window.cv.Scalar(0, 0, 0, 255),
    1
  );
  window.cv.line(
    canvasMat,
    points[1],
    points[2],
    new window.cv.Scalar(0, 0, 0, 255),
    1
  );
  window.cv.line(
    canvasMat,
    points[2],
    points[0],
    new window.cv.Scalar(0, 0, 0, 255),
    1
  );
};

function drawPoseLine(canvasMat, keyPoints) {
  // 右下眼 145 右上眼 159 左下眼 374 左上眼 386 下嘴唇14 上嘴唇13 鼻梁5 鼻头4
  // 面部上顶点 10 下顶点 152 左顶点 454 右顶点 234
  // 左嘴角 308 右嘴角 78
  // 左眼角 263 右眼角 33

  // 左眼开合距离
  const lEyeValue = Math.pow(
    Math.pow(keyPoints[374].x - keyPoints[386].x, 2) +
      Math.pow(keyPoints[374].y - keyPoints[386].y, 2),
    0.5
  );
  // 右眼开合距离
  const rEyeValue = Math.pow(
    Math.pow(keyPoints[145].x - keyPoints[159].x, 2) +
      Math.pow(keyPoints[145].y - keyPoints[159].y, 2),
    0.5
  );
  // 嘴巴开合距离
  const mouthValue = Math.pow(
    Math.pow(keyPoints[14].x - keyPoints[13].x, 2) +
      Math.pow(keyPoints[14].y - keyPoints[13].y, 2),
    0.5
  );
  // 左眼位置
  const lEyeX = (keyPoints[374].x + keyPoints[386].x) / 2;
  const lEyeY = (keyPoints[374].y + keyPoints[386].y) / 2;

  // 右眼位置
  const rEyeX = (keyPoints[145].x - keyPoints[159].x) / 2;
  const rEyeY = (keyPoints[145].y - keyPoints[159].y) / 2;

  // 脸中心
  const faceCenterX = ((lEyeX + rEyeX) / 2 + keyPoints[4].x) / 2;
  const faceCenterY = ((lEyeY + rEyeY) / 2 + keyPoints[4].y) / 2;
  //
  var modelPoints = window.cv.matFromArray(6, 3, window.cv.CV_32F, [
    0.0,
    0.0,
    0.0, // Nose tip
    0.0,
    -330.0,
    -65.0, // Chin
    -225.0,
    170.0,
    -135.0, // Left eye left corner
    225.0,
    170.0,
    -135.0, // Right eye right corne
    -150.0,
    -150.0,
    -125.0, // Left Mouth corner
    150.0,
    -150.0,
    -125.0, // Right mouth corner
  ]);

  var imagePoints = window.cv.matFromArray(6, 2, window.cv.CV_32F, [
    keyPoints[4].x,
    keyPoints[4].y, // Nose tip
    keyPoints[152].x,
    keyPoints[152].y, // Chin
    keyPoints[263].x,
    keyPoints[263].y, // Left eye left corner
    keyPoints[33].x,
    keyPoints[33].y, // Right eye right corne
    keyPoints[308].x,
    keyPoints[308].y, // Left Mouth corner
    keyPoints[78].x,
    keyPoints[78].y, // Right mouth corner
  ]);

  var focal_length = canvasMat.cols;
  var center = [canvasMat.cols / 2, canvasMat.rows / 2];
  var cameraMatrix = window.cv.matFromArray(3, 3, window.cv.CV_64F, [
    focal_length,
    0,
    center[0],
    0,
    focal_length,
    center[1],
    0,
    0,
    1,
  ]);

  // console.log("Camera Matrix", cameraMatrix.data64F);

  var distCoeffs = window.cv.matFromArray(4, 1, window.cv.CV_64F, [0, 0, 0, 0]); // Assuming no lens distortion

  var rvec = new window.cv.Mat(3, 1, window.cv.CV_64F);
  var tvec = new window.cv.Mat(3, 1, window.cv.CV_64F);

  let ret_val = window.cv.solvePnP(
    modelPoints,
    imagePoints,
    cameraMatrix,
    distCoeffs,
    rvec,
    tvec,
    false,
    window.cv.SOLVEPNP_ITERATIVE // flags
  );

  if (!ret_val) return false;

  var rtn = getEulerAngle(rvec);

  var pitch = rtn[0]; // 俯仰角
  var yaw = rtn[1]; // 水平角
  var roll = rtn[2]; // 翻滚角
  // console.log("pitch:", pitch, "yaw:", yaw, "roll:", roll);

  var noseEndPoint2D = new window.cv.Mat(1, 2, window.cv.CV_64F);
  var jacobian = new window.cv.Mat(imagePoints.rows * 2, 13, window.cv.CV_64F);
  window.cv.projectPoints(
    window.cv.matFromArray(1, 3, window.cv.CV_64F, [0.0, 0.0, 700.0]),
    rvec,
    tvec,
    cameraMatrix,
    distCoeffs,
    noseEndPoint2D,
    jacobian
  );

  // 绘制线段,连接鼻尖和其它点
  var p1 = new window.cv.Point(
    Math.round(imagePoints.data32F[0]),
    Math.round(imagePoints.data32F[1])
  );
  var p2 = new window.cv.Point(
    Math.round(noseEndPoint2D.data64F[0]),
    Math.round(noseEndPoint2D.data64F[1])
  );

  window.cv.line(canvasMat, p1, p2, new window.cv.Scalar(255, 0, 0, 255), 2);
  modelPoints.delete();
  imagePoints.delete();
  cameraMatrix.delete();
  distCoeffs.delete();
  rvec.delete();
  tvec.delete();
  noseEndPoint2D.delete();
  jacobian.delete();
  return true;
}

function getEulerAngle(rotationVector) {
  // calculate rotation angles
  let theta = window.cv.norm(rotationVector, window.cv.NORM_L2);

  // transformed to quaternion
  let w = Math.cos(theta / 2);
  let x = (Math.sin(theta / 2) * rotationVector.data64F[0]) / theta;
  let y = (Math.sin(theta / 2) * rotationVector.data64F[1]) / theta;
  let z = (Math.sin(theta / 2) * rotationVector.data64F[2]) / theta;

  let ysqr = y * y;
  // pitch (x-axis rotation)
  let t0 = 2.0 * (w * x + y * z);
  let t1 = 1.0 - 2.0 * (x * x + ysqr);
  // console.log("t0:", t0, "t1:", t1);
  let pitch = Math.atan2(t0, t1);

  // yaw (y-axis rotation)
  let t2 = 2.0 * (w * y - z * x);
  if (t2 > 1.0) {
    t2 = 1.0;
  }
  if (t2 < -1.0) {
    t2 = -1.0;
  }
  let yaw = Math.asin(t2);

  // roll (z-axis rotation)
  let t3 = 2.0 * (w * z + x * y);
  let t4 = 1.0 - 2.0 * (ysqr + z * z);
  let roll = Math.atan2(t3, t4);

  // console.log("pitch:", pitch, "yaw:", yaw, "roll:", roll);

  // 单位转换:将弧度转换为度
  let Y = parseInt((pitch / Math.PI) * 180);
  let X = parseInt((yaw / Math.PI) * 180);
  let Z = parseInt((roll / Math.PI) * 180);

  return [Y, X, Z];
}

2. 获取帧数信息并显示出来

设计一个1秒间隔的定时器,和一个frameCount,定时器格一秒传出参数到frameRate并清零frameCount,frameCount在detector的callback函数中被增加,这样frameRate每个一秒就会获得当前的帧数,并触发组件更新,代码如下,详细代码见 index.js:
请添加图片描述

3. 运行代码查看效果

npm i -g yarn && yarn 安装依赖
npm start 运行项目,预览结果如下
请添加图片描述


总结

本文介绍了使用 opencv.js 将人脸特征点网格绘制与姿态估计线绘制结合起来,以获得更高的帧数,希望对您有所帮助,如果文章中存在任何问题、疏漏,或者您对文章有任何建议,请在评论区提出。

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

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

相关文章

Python高级

不定长参数 位置不定长参数&#xff0c;获取参数args会整合为一个元组 def info(*args):print(arg is, args)print(type(arg) is, type(args))info(1, 2, 3, 4, a, b)# 输出 # arg is (1, 2, 3, 4, a, b) # type(arg) is <class tuple> 关键字不定长参数&#xff0c;&…

JavaSE:图书管理系统

目录 一、前言 二、内容需求 三、类的设计 &#xff08;一&#xff09;图书类 1.Book 类 2.BookList 类 &#xff08;二&#xff09;操作类 1.添加图书AddOperation类 2.借阅图书BorrowOperation类 3.删除图书DelOperation类 4.显示图书ShowOperation类 5.退出系统Ex…

Python代码识别minist手写数字【附pdf】

一、概述 对于人类而言,要识别图片中的数字是一件很容易的事情,但是,如何让机器学会理解图片上的数字,这似乎并不容易。那么,能否找出一个函数(模型),通过输入相关的信息,最终得到期望的结果呢? 二、python代码实现中涉及的输入输出内容: 输入:mnist数据集每一个…

HarmonyOS 开发-手写绘制及保存图片

介绍 本示例使用drawing库的Pen和Path结合NodeContainer组件实现手写绘制功能&#xff0c;并通过image库的packToFile和packing接口将手写板的绘制内容保存为图片。 效果图预览 使用说明 在虚线区域手写绘制&#xff0c;点击撤销按钮撤销前一笔绘制&#xff0c;点击重置按钮…

动态路由-基于vue-admin-template

基于 vue-admin-template的动态路由 1. 拆分静态路由与动态路由 静态路由----所有人都可以访问—首页/登录/404 动态路由–有权限的人才可以访问—组织/角色/员工/权限 2. 根据用户权限添加动态路由 获取对应的权限标识(vuex中actions中把用户资料通过return 进行返回&…

代码随想录算法训练营DAY17|C++二叉树Part.4|110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

文章目录 110.平衡二叉树思路伪代码CPP代码 257.二叉树的所有路径思路伪代码实现CPP代码 404.左叶子之和思路伪代码CPP代码 110.平衡二叉树 力扣题目链接 文章讲解&#xff1a;110.平衡二叉树 视频讲解&#xff1a;后序遍历求高度&#xff0c;高度判断是否平衡 | LeetCode&…

CSS导读 (复合选择器)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 二、CSS的复合选择器 2.1 什么是复合选择器 2.2 后代选择器(重要) 2.3 子选择器(重要) Questions 小提…

七年老测试整理的RF框架大全,一看就会,一学就懂

1.RF框架 全称robot framework,一个基于python开发的&#xff0c;自动化测试框架&#xff0c;这个框架可以做&#xff1a;web自动化&#xff0c;接口自动化&#xff0c;APP自动化。 github官网 1&#xff09;.安装python 检查python环境 python -V或 pip -V 2&#xff09;.…

【linux基础】bash脚本的学习:定义变量及引用变量、统计目标目录下所有文件行数、列数

假设目的&#xff1a;统计并输出指定文件夹下所有文件行数 单个文件可以用 wc -l &#xff1b;多个文件&#xff0c;可以用通配符 / 借助bash脚本 1.定义变量名&#xff0c;使用引号 a"bestqc.com.map" b"Anno.variant_function" c"enrichment/GOe…

UE4_导入内容_Alembic文件导入器

Alembic文件导入器 Alembic文件格式(.abc)是一个开放的计算机图形交换框架&#xff0c;它将复杂的动画化场景浓缩成一组非过程式的、与应用程序无关的烘焙几何结果。虚幻引擎4(UE4)允许你通过 Alembic导入器 导入你的Alembic文件&#xff0c;这让你可以在外部自由地创建复杂的…

android支付宝接入流程

接入前准备 接入APP支付能力前&#xff0c;开发者需要完成以下前置步骤。 本文档展示了如何从零开始&#xff0c;使用支付宝开放平台服务端 SDK 快速接入App支付产品&#xff0c;完成与支付宝对接的部分。 第一步&#xff1a;创建应用并获取APPID 要在您的应用中接入支付宝…

Hot100【十一】:编辑距离

// 定义dp[i][j]: 表示word1前i个字符转换到word2前j个字符最小操作数 // 初始化dp[m1][n1] class Solution {public int minDistance(String word1, String word2) {int m word1.length();int n word2.length();// 1. dp数组int[][] dp new int[m 1][n 1];// 2. dp数组初…

代码算法训练营day14 | 理论基础、递归遍历

day14&#xff1a; 理论基础二叉树的分类&#xff1a;二叉树的种类&#xff1a;满二叉树完全二叉树二叉搜索树平衡二叉搜索树 二叉树的存储方式&#xff1a;链式存储顺序存储 二叉树的遍历方式&#xff1a;深度优先和广度优先遍历实现方式 二叉树的定义&#xff1a; 递归遍历递…

【攻防世界】web2(逆向解密)

进入题目环境&#xff0c;查看页面信息&#xff1a; <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;…

磁盘管理与文件管理

文章目录 一、磁盘结构二、MBR与磁盘分区分区的优势与缺点分区的方式文件系统分区工具挂载与解挂载 一、磁盘结构 1.硬盘结构 硬盘分类&#xff1a; 1.机械硬盘&#xff1a;靠磁头转动找数据 慢 便宜 2.固态硬盘&#xff1a;靠芯片去找数据 快 贵 硬盘的数据结构&#xff1a;…

重温OKHTTP源码

本文基于OkHttp4.12.0源码分析 官方地址 概括 本篇主要是对okhttp开源库的一个详细解析&#xff0c;包含详细的请求流程分析、各大拦截器的解读等。 使用方法 同步请求&#xff1a;创建一个OKHttpClient对象&#xff0c;一个Request对象&#xff0c;然后利用它们创建一个Ca…

动态代理

动态代理 动态代理和静态代理角色一致。 代理类是动态生成的&#xff0c;不是我们直接写好的。 动态代理分为俩大类&#xff1a;基于接口的动态代理、基于类的动态代理 基于接口&#xff1a;JDK动态代理&#xff08;以下示例就是这个&#xff09; 基于类&#xff1a;cglib jav…

微机原理——绪论

本篇文章是我在观看网课时记录的笔记。如有错误欢迎批评指正。 微机原理————绪论 我们在使用计算机时&#xff0c;最重要最核心的就是计算机的CPU(中央处理器)&#xff0c;决定了计算机的计算速度&#xff0c;但是CPU无法直接读取外界的温度、湿度、压力之类的物理量&…

MSTP/RSTP的保护功能

目录 原理概述 实验目的 实验内容 实验拓扑 1.配置RSTP/MSTP 2.配置BPDU保护 3.配置根保护 4.配置环路保护 5.配置TC-BPDU保护 原理概述 在RSTP或MSTP交换网络中&#xff0c;为了防止恶意攻击或临时环路的产生&#xff0c;可配置保护功能来增强网络的健壮性和安全性。…

VSCode配置AI自动补全插件Tabnine

面向软件开发人员的 AI 助手 使用 AI 代码完成更快地编写代码 什么是Tabnine Tabnine 是一款 AI 代码助手&#xff0c;可让您成为更好的开发人员。Tabnine 将通过所有最流行的编码语言和 IDE 的实时代码完成、聊天和代码生成来提高您的开发速度。 无论您将其称为 IntelliSens…