|
@@ -1,12 +1,385 @@
|
|
|
-import { Component } from '@angular/core';
|
|
|
-
|
|
|
+import { Component, ElementRef, OnInit, ViewChild, } from '@angular/core';
|
|
|
+import * as faceapi from '@vladmandic/face-api';
|
|
|
+import Parse from "parse"
|
|
|
+Parse.initialize("dev");
|
|
|
+Parse.serverURL="http://web2023.fmode.cn:9999/parse";
|
|
|
@Component({
|
|
|
selector: 'app-tab1',
|
|
|
templateUrl: 'tab1.page.html',
|
|
|
- styleUrls: ['tab1.page.scss']
|
|
|
+ styleUrls: ['tab1.page.scss']
|
|
|
})
|
|
|
-export class Tab1Page {
|
|
|
+export class Tab1Page implements OnInit {
|
|
|
+
|
|
|
+ @ViewChild('video', { static: true }) videoElement: ElementRef|any;
|
|
|
+ @ViewChild('canvas', { static: true }) canvas: ElementRef|any;
|
|
|
+
|
|
|
+// videoWidth = 0;
|
|
|
+// videoHeight = 0;
|
|
|
+
|
|
|
+
|
|
|
+ // startCamera() {
|
|
|
+ // if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
|
+ // navigator.mediaDevices.getUserMedia({ video: true })
|
|
|
+ // .then(stream => {
|
|
|
+ // this.videoElement.nativeElement.srcObject = stream;
|
|
|
+ // this.videoElement.nativeElement.play();
|
|
|
+ // })
|
|
|
+ // .catch(err => console.error('Error accessing the camera: ', err));
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // capture() {
|
|
|
+ // this.canvas.nativeElement.width = this.videoWidth;
|
|
|
+ // this.canvas.nativeElement.height = this.videoHeight;
|
|
|
+ // this.canvas.nativeElement.getContext('2d').drawImage(this.videoElement.nativeElement, 0, 0, this.videoWidth, this.videoHeight);
|
|
|
+ // const imgSrc = this.canvas.nativeElement.toDataURL('image/png');
|
|
|
+ // document.getElementById('face')?.setAttribute('src', imgSrc);
|
|
|
+
|
|
|
+ // setTimeout(() => {
|
|
|
+ // this.runFaceAnanlysis()//等待
|
|
|
+ // }, 2000);
|
|
|
+ // }
|
|
|
+
|
|
|
+
|
|
|
+ constructor() { }
|
|
|
+
|
|
|
+
|
|
|
+ ngOnInit() {
|
|
|
+ this.runFaceAnanlysis();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ onFileChange(event: any) {
|
|
|
+ const file = event.target.files[0];
|
|
|
+ if (file) {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e: any) => {
|
|
|
+ const img = new Image();
|
|
|
+ img.src = e.target.result;
|
|
|
+ img.onload = () => {
|
|
|
+ const canvasEl = this.canvas.nativeElement;
|
|
|
+ const context = canvasEl.getContext('2d');
|
|
|
+
|
|
|
+ canvasEl.width = img.width;
|
|
|
+ canvasEl.height = img.height;
|
|
|
+ context.drawImage(img, 0, 0);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ this.runFaceAnanlysis();
|
|
|
+ }, 2000);
|
|
|
+ };
|
|
|
+ document.getElementById('face')?.setAttribute('src', e.target.result);
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ runFaceAnanlysis(){
|
|
|
+ console.log(faceapi);
|
|
|
+const calculateSanTing = (positions:any) => {
|
|
|
+ // 获取特定索引的特征点位置
|
|
|
+ const leftEye = positions[36]; // 左眼角
|
|
|
+ const rightEye = positions[42]; // 右眼角
|
|
|
+ const noseBase = positions[33]; // 鼻尖
|
|
|
+ const mouth = positions[48]; // 嘴巴中心
|
|
|
+
|
|
|
+ // 计算三庭的比例
|
|
|
+ const foreheadHeight = noseBase.x - leftEye.x; // 水平距离
|
|
|
+ const noseLength = rightEye.x - leftEye.x; // 水平距离
|
|
|
+ const chinHeight = mouth.x - noseBase.x; // 水平距离
|
|
|
+
|
|
|
+ const foreheadHeightRounded = parseFloat(foreheadHeight.toFixed(2));
|
|
|
+ const noseLengthRounded = parseFloat(noseLength.toFixed(2));
|
|
|
+ const chinHeightRounded = parseFloat(chinHeight.toFixed(2));
|
|
|
+
|
|
|
+ return {
|
|
|
+ foreheadHeight: foreheadHeightRounded,
|
|
|
+ noseLength: noseLengthRounded,
|
|
|
+ chinHeight: -chinHeightRounded,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+const calculateWuYan = (positions:any) => {
|
|
|
+ // 获取特定索引的特征点位置
|
|
|
+ const leftEyebrow = positions[17]; // 左眉毛上角
|
|
|
+ const rightEyebrow = positions[22]; // 右眉毛上角
|
|
|
+
|
|
|
+ // 计算五眼的比例
|
|
|
+ const eyeSpan = Math.abs(leftEyebrow.x - rightEyebrow.x);
|
|
|
+ const fiveEyeSpan = eyeSpan * 5;
|
|
|
+
|
|
|
+ const fiveEyeSpanRounded = parseFloat(fiveEyeSpan.toFixed(2));
|
|
|
+
|
|
|
+ return fiveEyeSpanRounded;
|
|
|
+};
|
|
|
+
|
|
|
+const calculateFaceShape = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const jawOutline = positions.slice(0, 17); // 下巴轮廓
|
|
|
+ const cheekLeft = positions[29]; // 左颧骨
|
|
|
+ const cheekRight = positions[30]; // 右颧骨
|
|
|
+ const foreheadLeft = positions[21]; // 左额头
|
|
|
+ const foreheadRight = positions[22]; // 右额头
|
|
|
+
|
|
|
+ // 计算宽度
|
|
|
+ const cheekWidth = Math.sqrt((cheekRight.x - cheekLeft.x) ** 2 + (cheekRight.y - cheekLeft.y) ** 2);
|
|
|
+ const foreheadWidth = Math.sqrt((foreheadRight.x - foreheadLeft.x) ** 2 + (foreheadRight.y - foreheadLeft.y) ** 2);
|
|
|
+ const jawWidth = Math.sqrt((jawOutline[8].x - jawOutline[6].x) ** 2 + (jawOutline[8].y - jawOutline[6].y) ** 2);
|
|
|
+
|
|
|
+ // 计算长度
|
|
|
+ const faceLength = Math.sqrt((jawOutline[8].x - jawOutline[0].x) ** 2 + (jawOutline[8].y - jawOutline[0].y) ** 2);
|
|
|
+
|
|
|
+ // 计算比例
|
|
|
+ const cheekForeheadRatio = cheekWidth / foreheadWidth;
|
|
|
+ const jawForeheadRatio = jawWidth / foreheadWidth;
|
|
|
+ const faceAspectRatio = faceLength / foreheadWidth;
|
|
|
+
|
|
|
+ // 判断脸型
|
|
|
+ if (faceAspectRatio > 1.5) {
|
|
|
+ // 长脸
|
|
|
+ if (cheekForeheadRatio > 0.8 && cheekForeheadRatio < 1.2 && jawForeheadRatio < 0.8) {
|
|
|
+ return '瓜子脸';
|
|
|
+ } else {
|
|
|
+ return '长形脸';
|
|
|
+ }
|
|
|
+ } else if (faceAspectRatio < 1) {
|
|
|
+ // 宽脸
|
|
|
+ return '宽脸';
|
|
|
+ } else {
|
|
|
+ // 标准脸
|
|
|
+ if (cheekForeheadRatio > 0.8 && cheekForeheadRatio < 1.2 && jawForeheadRatio > 0.8) {
|
|
|
+ return '心形脸';
|
|
|
+ } else if (cheekForeheadRatio < 0.8 && jawForeheadRatio < 0.8) {
|
|
|
+ return '菱形脸';
|
|
|
+ } else {
|
|
|
+ return '标准脸';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+// 封装计算眉形的函数
|
|
|
+const calculateBrowShape = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const leftEyebrowLeft = positions[17]; // 左眉毛上角
|
|
|
+ const leftEyebrowRight = positions[22]; // 左眉毛下角
|
|
|
+ const rightEyebrowLeft = positions[25]; // 右眉毛上角
|
|
|
+ const rightEyebrowRight = positions[31]; // 右眉毛下角
|
|
|
+
|
|
|
+ // 计算眉毛宽度
|
|
|
+ const leftBrowWidth = Math.abs(leftEyebrowLeft.x - leftEyebrowRight.x);
|
|
|
+ const rightBrowWidth = Math.abs(rightEyebrowLeft.x - rightEyebrowRight.x);
|
|
|
+ const browWidth = Math.max(leftBrowWidth, rightBrowWidth);
|
|
|
+
|
|
|
+ // 计算眉毛长度
|
|
|
+ const leftBrowLength = Math.sqrt((leftEyebrowLeft.x - leftEyebrowRight.x) ** 2 + (leftEyebrowLeft.y - leftEyebrowRight.y) ** 2);
|
|
|
+ const rightBrowLength = Math.sqrt((rightEyebrowLeft.x - rightEyebrowRight.x) ** 2 + (rightEyebrowLeft.y - rightEyebrowRight.y) ** 2);
|
|
|
+ const browLength = Math.max(leftBrowLength, rightBrowLength);
|
|
|
+
|
|
|
+ // 判断眉形
|
|
|
+ if (browWidth > 0.5 * browLength) {
|
|
|
+ return '浓眉';
|
|
|
+ } else if (browWidth < 0.3 * browLength) {
|
|
|
+ return '细眉';
|
|
|
+ } else {
|
|
|
+ return '标准眉';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他眉形判断条件可以添加在这里
|
|
|
+ // ...
|
|
|
+ };
|
|
|
+
|
|
|
+ // 封装计算唇形的函数
|
|
|
+const calculateLipShape = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const upperLipTop = positions[61]; // 上嘴唇顶部
|
|
|
+ const upperLipBottom = positions[62]; // 上嘴唇底部
|
|
|
+ const lowerLipTop = positions[63]; // 下嘴唇顶部
|
|
|
+ const lowerLipBottom = positions[64]; // 下嘴唇底部
|
|
|
+
|
|
|
+ // 判断唇形
|
|
|
+ if (upperLipBottom.y - upperLipTop.y > 10 && lowerLipBottom.y - lowerLipTop.y > 10) {
|
|
|
+ return '厚唇';
|
|
|
+ } else if (upperLipBottom.y - upperLipTop.y < 5 && lowerLipBottom.y - lowerLipTop.y < 5) {
|
|
|
+ return '薄唇';
|
|
|
+ } else if (upperLipBottom.y - upperLipTop.y > 10 && lowerLipBottom.y - lowerLipTop.y < 5) {
|
|
|
+ return '微笑唇';
|
|
|
+ } else if (upperLipBottom.y - upperLipTop.y < 5 && lowerLipBottom.y - lowerLipTop.y > 10) {
|
|
|
+ return '嘟嘟唇';
|
|
|
+ } else {
|
|
|
+ return '标准唇型';
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // 封装计算眼睛形状的函数
|
|
|
+const calculateEyeShape = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const leftEyeInner = positions[33]; // 左眼内侧
|
|
|
+ const leftEyeOuter = positions[39]; // 左眼外侧
|
|
|
+ const rightEyeInner = positions[45]; // 右眼内侧
|
|
|
+ const rightEyeOuter = positions[51]; // 右眼外侧
|
|
|
+
|
|
|
+ // 计算眼睛长度
|
|
|
+ const leftEyeLength = Math.sqrt((leftEyeInner.x - leftEyeOuter.x) ** 2 + (leftEyeInner.y - leftEyeOuter.y) ** 2);
|
|
|
+ const rightEyeLength = Math.sqrt((rightEyeInner.x - rightEyeOuter.x) ** 2 + (rightEyeInner.y - rightEyeOuter.y) ** 2);
|
|
|
+ const eyeLength = Math.max(leftEyeLength, rightEyeLength);
|
|
|
+
|
|
|
+ // 判断眼睛形状
|
|
|
+ if (eyeLength > 10 && leftEyeLength > rightEyeLength) {
|
|
|
+ return '杏仁眼';
|
|
|
+ } else if (eyeLength > 10 && leftEyeLength < rightEyeLength) {
|
|
|
+ return '圆形眼';
|
|
|
+ } else if (leftEyeInner.y > leftEyeOuter.y) {
|
|
|
+ return '单眼皮';
|
|
|
+ } else {
|
|
|
+ return '双眼皮';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 封装计算鼻形的函数
|
|
|
+ const calculateNoseShape = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const noseBase = positions[33]; // 鼻尖
|
|
|
+ const noseTip = positions[34]; // 鼻尖
|
|
|
+ const noseSide = positions[35]; // 鼻翼
|
|
|
+
|
|
|
+ // 判断鼻形
|
|
|
+ if (noseBase.y > noseTip.y && noseSide.x > noseTip.x) {
|
|
|
+ return '鹰钩鼻';
|
|
|
+ } else if (noseBase.y > noseTip.y && noseSide.x < noseTip.x) {
|
|
|
+ return '蒜头鼻';
|
|
|
+ } else if (noseBase.y < noseTip.y) {
|
|
|
+ return '直鼻';
|
|
|
+ } else {
|
|
|
+ return '塌鼻';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 封装计算颧骨的函数
|
|
|
+ const calculateCheekBones = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const cheekLeft = positions[29]; // 左颧骨
|
|
|
+ const cheekRight = positions[30]; // 右颧骨
|
|
|
+
|
|
|
+ // 判断颧骨
|
|
|
+ if (cheekLeft.y > cheekRight.y) {
|
|
|
+ return '颧骨高';
|
|
|
+ } else {
|
|
|
+ return '颧骨平';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 封装计算下巴的函数
|
|
|
+ const calculateChin = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const chin = positions[60]; // 下巴中心
|
|
|
+
|
|
|
+ // 判断下巴
|
|
|
+ if (chin.y < chin.x) {
|
|
|
+ return '长下巴';
|
|
|
+ } else if (chin.y > chin.x) {
|
|
|
+ return '短下巴';
|
|
|
+ } else {
|
|
|
+ return '尖下巴';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 封装计算额头的函数
|
|
|
+ const calculateForehead = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const foreheadLeft = positions[21]; // 左额头
|
|
|
+ const foreheadRight = positions[22]; // 右额头
|
|
|
+
|
|
|
+ // 判断额头
|
|
|
+ if (foreheadLeft.x > foreheadRight.x) {
|
|
|
+ return '宽额头';
|
|
|
+ } else {
|
|
|
+ return '窄额头';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ const calculateCheeksShape = (positions:any) => {
|
|
|
+ // 选择关键特征点
|
|
|
+ const cheekLeft = positions[29]; // 左颧骨
|
|
|
+ const cheekRight = positions[30]; // 右颧骨
|
|
|
+
|
|
|
+ // 判断脸颊形状
|
|
|
+ if (cheekLeft.y > cheekRight.y) {
|
|
|
+ return '丰满脸颊';
|
|
|
+ } else {
|
|
|
+ return '瘦削脸颊';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+const run = async () => {
|
|
|
+ // 加载模型
|
|
|
+ await Promise.all([
|
|
|
+ faceapi.nets.ssdMobilenetv1.loadFromUri('/assets/models'),
|
|
|
+ faceapi.nets.faceLandmark68Net.loadFromUri('/assets/models'),
|
|
|
+ faceapi.nets.faceRecognitionNet.loadFromUri('/assets/models'),
|
|
|
+ faceapi.nets.ageGenderNet.loadFromUri('/assets/models'),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const face1:any = document.getElementById('face');
|
|
|
+
|
|
|
+ let faceAIData = await faceapi
|
|
|
+ .detectAllFaces(face1)
|
|
|
+ .withFaceLandmarks()
|
|
|
+ .withFaceDescriptors()
|
|
|
+ .withAgeAndGender();
|
|
|
+
|
|
|
+ console.log(faceAIData);
|
|
|
+
|
|
|
+ // 获取第一个检测到的人脸的特征点
|
|
|
+ const landmarks:any = faceAIData[0].landmarks;
|
|
|
+ const positions:any = landmarks._positions;
|
|
|
+
|
|
|
+ // 计算三庭和五眼的比例
|
|
|
+ const sanTing = calculateSanTing(positions);//计算三庭
|
|
|
+ const wuYan = calculateWuYan(positions);//计算五眼
|
|
|
+ // 计算脸型
|
|
|
+ const faceShape = calculateFaceShape(positions);
|
|
|
+ // 计算眉形
|
|
|
+ const browShape = calculateBrowShape(positions);
|
|
|
+ // 计算唇形
|
|
|
+ const lipShape = calculateLipShape(positions);
|
|
|
+ // 计算眼睛形状
|
|
|
+ const eyeShape = calculateEyeShape(positions);
|
|
|
+ // 计算鼻形
|
|
|
+ const noseShape = calculateNoseShape(positions);
|
|
|
+ // 计算颧骨
|
|
|
+ const cheekBones = calculateCheekBones(positions);
|
|
|
+ // 计算下巴
|
|
|
+ const chinShape = calculateChin(positions);
|
|
|
+ // 计算额头
|
|
|
+ const foreheadShape = calculateForehead(positions);
|
|
|
+ // 计算脸颊
|
|
|
+ const cheeksShape = calculateCheeksShape(positions);
|
|
|
+
|
|
|
+ console.log(`三庭比例: ${sanTing.foreheadHeight},${sanTing.noseLength}, ${sanTing.chinHeight}`);
|
|
|
+ console.log(`五眼比例: ${wuYan}`);
|
|
|
+ console.log(`脸型: ${faceShape}`);
|
|
|
+ console.log(`眉形: ${browShape}`);
|
|
|
+ console.log(`唇形: ${lipShape}`);
|
|
|
+ console.log(`眼睛形状: ${eyeShape}`);
|
|
|
+ console.log(`鼻形: ${noseShape}`);
|
|
|
+ console.log(`颧骨: ${cheekBones}`);
|
|
|
+ console.log(`下巴: ${chinShape}`);
|
|
|
+ console.log(`额头: ${foreheadShape}`);
|
|
|
+ console.log(`脸颊: ${cheeksShape}`);
|
|
|
+
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
|
|
|
- constructor() {}
|
|
|
+//run();
|
|
|
+ }
|
|
|
|
|
|
}
|