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

系列文章目录

  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 tip0.0,-330.0,-65.0, // Chin-225.0,170.0,-135.0, // Left eye left corner225.0,170.0,-135.0, // Right eye right corne-150.0,-150.0,-125.0, // Left Mouth corner150.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 tipkeyPoints[152].x,keyPoints[152].y, // ChinkeyPoints[263].x,keyPoints[263].y, // Left eye left cornerkeyPoints[33].x,keyPoints[33].y, // Right eye right cornekeyPoints[308].x,keyPoints[308].y, // Left Mouth cornerkeyPoints[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 distortionvar 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 angleslet theta = window.cv.norm(rotationVector, window.cv.NORM_L2);// transformed to quaternionlet 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.mzph.cn/news/804093.shtml

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

相关文章

Lecture 2~4 About Filter

文章目录 空间域上的滤波器- 线性滤波器盒状滤波器Box Filter锐化Sharpening相关运算 vs. 卷积运算 Correlation vs. Convolution - 非线性滤波器高斯滤波器Gaussian filter - 实际问题- 纹理texture 频域上的滤波器 滤波的应用- 模板匹配- 图像金字塔 空间域上的滤波器 图像…

Django的中间件

Django的中间件 【一】重点&#xff1a; django中间件是django的门户 请求来的时候需要经过中间件才能到达真正的django后端响应走的时候也需要经过中间件才能发送出去中间件按照顺序依次执行 ​ Django 中间件&#xff08;Middleware&#xff09;是 Django 框架提供的一种…

设计模式——代理模式12

代理模式给某对象提供一个代理对象&#xff0c;由代理对象来控制对原对象的引用。该模式经常出现在系统框架或相关组件中&#xff0c;如Spring框架如何解决循环依赖&#xff0c;在Mybatis 定义 Dao 层相关接口 不写实现 如何通过注解或者xml映射到对应到sql语句。下面介绍 静态…

再见 MybatisPlus,阿里推出新 ORM 框架更牛X

最近看到一个 ORM 框架 Fluent Mybatis 挺有意思的&#xff0c;整个设计理念非常符合工程师思维。 我对官方文档的部分内容进行了简单整理&#xff0c;通过这篇文章带你看看这个新晋 ORM 框架。 官方文档&#xff1a;https://gitee.com/fluent-mybatis/fluent-mybatis/wikis 提…

Nginx反向代理与Tomcat实现ssm项目前后端分离部署

Nginx nginx是一款http和支持反向代理的web服务器&#xff0c;以其优越的性能被广泛使用。以下是百度百科的介绍。 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.…

智慧园区水电能源监控管理系统

随着智慧城市的快速发展&#xff0c;智慧园区作为城市智能化的重要组成部分&#xff0c;其能源监控管理系统显得尤为关键。智慧园区水电能源监控管理系统&#xff0c;是利用先进的信息技术和自动控制技术&#xff0c;对园区内的水电能源使用进行实时监控、管理和优化的综合性智…

美国34401A安捷伦数字万用表

181/2461/8938产品概述&#xff1a; 附加功能: 6 1/2位数分辨率10种测量功能:DC/交流电压、DC/交流电流、2线和4线电阻、二极管、连续性、频率、周期基本精度:0.0035% DC&#xff0c;0.06%交流1000 V最大电压输入&#xff0c;3 A最大电流输入每秒1000次读数512读取记忆 安捷…

Linux C++ 027-STL之deque容器

Linux C 027-STL之deque容器 本节关键字&#xff1a;Linux、C、deque 相关库函数&#xff1a;pubsh_back、begin、front、sort deque基本概念 功能&#xff1a;双端数组&#xff0c;可以对头端进行插入删除操作。 deque 与 vector 的区别&#xff1a; &#xff08;1&#x…

vue将html生成pdf并分页

jspdf html2canvas 此方案有很多的css兼容问题&#xff0c;比如虚线边框、svg、页数多了内容显示不全、部分浏览器兼容问题&#xff0c;光是解决这些问题就耗费了我不少岁月和精力 后面了解到新的技术方案&#xff1a; jspdf html-to-image npm install --save html-to-i…

关于pandas 无法读取 csv 文件数据的解决方式

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 …

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量 在电气化铁路运输中&#xff0c;受电弓滑板的健康状况对于保障列车安全行驶至关重要。受电弓滑板作为连接电网与列车的直接介质&#xff0c;其磨损情况直接影响到电能的有效传输及列车的稳定运行。精确、快速测量受电弓滑板磨损情…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

CLI的使用与IOS基本命令

1、实验目的 通过本实验可以掌握&#xff1a; CLI的各种工作模式个CLI各种编辑命令“?” 和【Tab】键使用方法IOS基本命令网络设备访问限制查看设备的相关信息 2、实验拓扑 CLI的使用与IOS基本命令使用拓扑如下图所示。 3、实验步骤 &#xff08;1&#xff09;CLI模式的切…

Visual Studio Code 终端为管理员权限

第一部 1、 Visual Studio Code 快捷方式启动选项加上管理员启动 第二步 管理员方式运行 powershell Windows 10的任务栏自带了搜索。或者开始菜单选搜索只需在搜索框中输入powershell。 在出来的搜索结果中右击Windows PowerShell&#xff0c;然后选择以管理员方式运行。 执…

使用Docker Registry-v2搭建镜像仓库详细教程

我们使用docker来部署私有化镜像仓库… 1、下载 registry:v2 镜像 docker pull registry:22、在私有仓库所在的主机目录新建一个文件夹&#xff0c;用于持久化保存仓库中的镜像 mkdir -p /opt/registry3、启动registry镜像 使用docker镜像启动私有仓库容器服务&#xff0c;…

ArcGIS Desktop使用入门(四)工具箱——属性域

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

nacos服务治理

nacos 服务演变之路 单体架构 集群级垂直化 SOA 微服务 微服务优缺点 SOA与微服务区别 springcloud技术栈 服务发现概念 服务发现两种方式–客户端服务发现 服务发现两种方式–服务端发现 服务发现技术对比 nacos架构图 nacos实战 服务发现 源码解析 nacos实现了springcloud…

网络安全---RSA公钥加密与签名

实验项目&#xff1a;RSA公钥加密与签名实验 1.实验目的 本实验的学习目标是让学生获得 RSA 算法的动手经验。 通过课堂学习&#xff0c;学生应该已经了解 RSA 算法的理论部分&#xff0c; 知道在数学上如何生成公钥、私钥以及如何执行加密、解密和签名生成、验证。 通过使用…

Docker容器嵌入式开发:Docker Ubuntu18.04配置mysql数据库

在 Ubuntu 18.04 操作系统中安装 MySQL 数据库的过程。下面是安装过程的详细描述: 首先,使用以下命令安装 MySQL 服务器: sudo apt install mysql-server系统会提示是否继续安装,按下 Y 键确认。 安装过程中,系统会下载并安装 MySQL 相关的软件包,包括 libaio1、mysql…

STM32+ESP8266水墨屏天气时钟:文字取模和图片取模教程

项目背景 本次的水墨屏幕项目需要显示一些图片和文字&#xff0c;所以需要对图片和文字进行取模。 取模步骤 1.打开取模软件 2.选择图形模式 3.设置字模选项 注意&#xff1a;本次项目采用的是水墨屏&#xff0c;并且是局部刷新的代码&#xff0c;所以设置字模选项可能有点…