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.rhkb.cn/news/302596.html

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

相关文章

【Liunx】什么是make和makefile?

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

蓝桥杯-油漆面积

代码及其解析:(AC80%&#xff09; 思路:是把平面划成单位边长为1&#xff08;面积也是1&#xff09;的方格。每读入一个矩形&#xff0c;就把它覆盖的方格标注为已覆盖&#xff1b;对所有矩形都这样处理&#xff0c;最后统计被覆盖的方格数量即可。编码极其简单&#xff0c;但…

让无刷电机转起来——换相(BLDC)

目录 1. 引言 2. 无刷电机换相原理 2.1 通电原理&#xff08;一相&#xff09; 2.2 换相原理&#xff08;三相&#xff09; 2.3 驱动电路 2.3.1 上下桥臂 2.3.2 六步换相 3. 结束语 1. 引言 前面博客&#xff0c;博主对于无刷电机的驱动方式与电路作了简要的介绍&#…

一键开启Scrum回顾会议的精彩时刻

其实回顾会议作为一个检视、反馈、改进环节&#xff0c;不仅在传统的瀑布管理模式中&#xff0c;还是在Scrum一类的敏捷管理流程中&#xff0c;都是非常重要的活动。一些团队认为它无法产生直接的价值&#xff0c;所以有意忽略了这个会议&#xff1b;一些团队在越来越多的回顾中…

DNS 各记录类型说明及规则

各记录类型使用目的 记录类型使用目的A 记录将域名指向一个 IP 地址。CNAME 记录将域名指向另一个域名&#xff0c;再由另一个域名提供 IP 地址。MX 记录设置邮箱&#xff0c;让邮箱能收到邮件。NS 记录将子域名交给其他 DNS 服务商解析。AAAA 记录将域名指向一个 IPv6 地址。…

Vite 项目中环境变量的配置和使用

Vite 项目中环境变量的声明 我们要在 Vite 项目中进行环境变量的声明&#xff0c;那么需要在项目的根目录下&#xff0c;新建 .env.[mode] 文件用于声明环境变量&#xff0c;如&#xff1a; .env.test 文件用于测试环境下项目全局变量的声明.env.dev 文件用于开发环境下项目全…

Linux初学(十七)防火墙

一、防火墙简介 1.1 防火墙的类别 安全产品 杀毒&#xff1a; 针对病毒&#xff0c;特征篡改系统中的文件杀毒软件针对处理病毒程序防火墙&#xff1a; 针对木马&#xff0c;特征系统窃取防火墙针对处理木马 防火墙分为两种 硬件防火墙软件防火墙 硬件防火墙 各个网络安全…

2024智能计算、大数据应用与信息科学国际会议(ICBDAIS2024)

2024智能计算、大数据应用与信息科学国际会议(ICBDAIS2024) 会议简介 智能计算、大数据应用与信息科学之间存在相互依存、相互促进的关系。智能计算和大数据应用的发展离不开信息科学的支持和推动&#xff0c;而信息科学的发展又需要智能计算和大数据应用的不断拓展和应用。智…

C++ 学习笔记

文章目录 【 字符串相关 】C 输入输出流strcpy_s() 字符串复制输出乱码 【 STL 】各个 STL 支持的常见方法 ? : 运算符switch case 运算符 switch(expression) {case constant-expression :statement(s);break; // 可选的case constant-expression :statement(s);break; //…

LT8712SX DP转两路HDMI2.0 MST 4K60hz,芯片方案

1. 特性 ⚫USB Type-C▪兼容USB上的VESA DisplayPort Alt模式 c型标准1.0b - DP Alt模式支持引脚分配C, D和E -符合USB供电规范3.1 -符合USB Type-C电缆和连接器 规范1.3 ▪内置三CC逻辑和PD控制器充电器和 正常的沟通 ▪支持UFP和DFP数据角色 ▪支持电源&#xff0c;接…

非关系型数据库--------------------Redis 群集模式

目录 一、集群原理 二、集群的作用 &#xff08;1&#xff09;数据分区 &#xff08;2&#xff09;高可用 Redis集群的作用和优势 三、Redis集群的数据分片 四、Redis集群的工作原理 五、搭建redis群集模式 5.1启用脚本配置集群 5.2修改集群配置 5.3启动redis节点 5…

Java 那些诗一般的 数据类型 (下篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能接…

通过 Spark SQL 和 DataFrame 操作表和多种内部数据源总结

文章目录 前言在Spark应用中使用Spark SQLSQL 表和视图内部表和外部表创建库和表创建视图查看元数据表缓存读取表数据 表和 DataFrame 的数据来源DataFrameReaderDataFrameWriterParquetJSONCSVAvroORCImagesBinary Files 总结 前言 本文将探讨 Spark 中 Spark SQL 接口是如何…

StockTrading AI小模型股票自动交易系统 转载

Stock-Trading StockTrading AI小模型股票自动交易系统 项目文档 Stock-Trading 语雀 项目展示 功能介绍 对接证券平台&#xff0c;实现股票自动化交易使用QuartZ定时任务调度&#xff0c;每日自动更新数据使用DL4J框架实现LSTM模型指导股票买入&#xff0c;采用T1短线交易策…

C/C++如何快速学习?少走3年弯路

于我而言&#xff0c;最开始学习就是 C&#xff0c;除了计算机专业&#xff0c;其他专业可能学习的第一门编程语言为 C 语言&#xff0c;还是谭浩强爷爷那本&#xff0c;当时想着有点 C 基础&#xff0c;无外乎就是 C 语言的升级版&#xff0c;于是开启了 C 的路程。 语言这个…

鸿蒙、如何使用@ohos.contact 接口,实现对联系人的增删查改功能

介绍 本示例使用ohos.contact 接口&#xff0c;实现了对联系人的增删查改功能。 效果预览 使用说明 1.点击 按钮&#xff0c;跳转添加联系人界面&#xff0c;输入联系人信息&#xff0c;点击 √&#xff0c;确认添加联系人&#xff0c;并返回首页&#xff1b; 2.点击联系人…

学习记录14-运算放大器2

目录 前言 一、理想放大器 二、虚断 二、虚短 虚短的两个使用条件 1.虚短概念 2.如果我们将运放的同相端和反相端颠倒会怎样呢&#xff1f; 总结 前言 主要讲述运算放大器的虚短虚断 一、理想放大器 如果没有基础或只是想简单了解&#xff0c;可以看我前一篇文章&am…

Jackson 2.x 系列【15】序列化器 JsonSerializer

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 概述2. 方法2.1 构造2.2 序列化2.3 其他 3. 实现类3.1 StdSerializer3.1.1 源…

vue3学习笔记(pinia)

defineModel&#xff1a;快速实现组件的双向绑定 pinia&#xff1a;在仓库中提供数据和使用数据 创建store文件夹&#xff0c;在里面创建counter.js&#xff0c;以提供数据&#xff0c;注意需要return 和 export&#xff0c;export的是一个函数。 import { defineStore } from…

智慧驿站式的“智慧公厕”,给城市新基建带来新变化

随着智慧城市建设的推进&#xff0c;智慧驿站作为一种多功能城市部件&#xff0c;正逐渐在城市中崭露头角。这些智慧驿站集合了智慧公厕的管理功能&#xff0c;为城市的新基建带来了全新的变革。本文以智慧驿站智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案…