facecog-liveness-showcase 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +15 -0
- package/.dockerignore +48 -0
- package/.editorconfig +16 -0
- package/.eslintrc.json +47 -0
- package/.vercelignore +7 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +3 -0
- package/DOCKER.md +221 -0
- package/Dockerfile +33 -0
- package/README.md +268 -0
- package/angular.json +156 -0
- package/capacitor.config.ts +9 -0
- package/docker-compose.dev.yml +20 -0
- package/docker-compose.yml +18 -0
- package/ionic.config.json +7 -0
- package/jest.config.js +38 -0
- package/nginx.conf +50 -0
- package/package.json +131 -0
- package/patches/ng-packagr+20.3.2.patch +60 -0
- package/projects/facecog-liveness-verification/README.md +295 -0
- package/projects/facecog-liveness-verification/ng-package.json +7 -0
- package/projects/facecog-liveness-verification/package.json +48 -0
- package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
- package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
- package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
- package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
- package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
- package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
- package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
- package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
- package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
- package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
- package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
- package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
- package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
- package/projects/facecog-liveness-verification-test/src/index.html +31 -0
- package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
- package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
- package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
- package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
- package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
- package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
- package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
- package/setup-jest.ts +118 -0
- package/tsconfig.json +41 -0
- package/tsconfig.spec.json +15 -0
- package/vercel.json +24 -0
package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import * as poseDetection from '@tensorflow-models/pose-detection';
|
|
3
|
+
import * as tf from '@tensorflow/tfjs-core';
|
|
4
|
+
import '@tensorflow/tfjs-backend-webgl';
|
|
5
|
+
|
|
6
|
+
export interface PoseKeypoints {
|
|
7
|
+
keypoints: poseDetection.Keypoint[];
|
|
8
|
+
score: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Injectable({
|
|
12
|
+
providedIn: 'root'
|
|
13
|
+
})
|
|
14
|
+
export class OpenposeService {
|
|
15
|
+
private detector: poseDetection.PoseDetector | null = null;
|
|
16
|
+
private isInitialized = false;
|
|
17
|
+
|
|
18
|
+
constructor() {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Initialize MoveNet pose detector
|
|
22
|
+
*/
|
|
23
|
+
async loadModel(): Promise<void> {
|
|
24
|
+
if (this.isInitialized) {
|
|
25
|
+
console.log('[OpenPose] Model already loaded');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
console.log('[OpenPose] Loading MoveNet model...');
|
|
31
|
+
|
|
32
|
+
// Try WebGL first, fallback to WASM for Safari compatibility
|
|
33
|
+
try {
|
|
34
|
+
await tf.setBackend('webgl');
|
|
35
|
+
await tf.ready();
|
|
36
|
+
console.log('[OpenPose] Using WebGL backend');
|
|
37
|
+
} catch (webglError) {
|
|
38
|
+
console.warn('[OpenPose] WebGL backend failed, trying WASM for Safari compatibility:', webglError);
|
|
39
|
+
await tf.setBackend('wasm');
|
|
40
|
+
await tf.ready();
|
|
41
|
+
console.log('[OpenPose] Using WASM backend (Safari fallback)');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create MoveNet detector with Lightning model (faster, good for real-time)
|
|
45
|
+
const detectorConfig = {
|
|
46
|
+
modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.detector = await poseDetection.createDetector(
|
|
50
|
+
poseDetection.SupportedModels.MoveNet,
|
|
51
|
+
detectorConfig
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
this.isInitialized = true;
|
|
55
|
+
console.log('[OpenPose] MoveNet model loaded successfully');
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('[OpenPose] Failed to load model:', error);
|
|
58
|
+
throw new Error('Failed to load pose detection model');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detect pose in an image element
|
|
64
|
+
*/
|
|
65
|
+
async detectPose(imageElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement): Promise<PoseKeypoints | null> {
|
|
66
|
+
if (!this.isInitialized || !this.detector) {
|
|
67
|
+
console.warn('[OpenPose] Detector not initialized, attempting to load...');
|
|
68
|
+
await this.loadModel();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const poses = await this.detector!.estimatePoses(imageElement);
|
|
73
|
+
|
|
74
|
+
if (poses.length === 0) {
|
|
75
|
+
// Don't log every frame to reduce console spam
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Return the first detected pose
|
|
80
|
+
const pose = poses[0];
|
|
81
|
+
|
|
82
|
+
// Log keypoint details for debugging
|
|
83
|
+
const rightWrist = pose.keypoints[10]; // Index 10 is right_wrist in MoveNet
|
|
84
|
+
const rightShoulder = pose.keypoints[6]; // Index 6 is right_shoulder
|
|
85
|
+
|
|
86
|
+
console.log('[OpenPose] Pose detected - Score:', pose.score?.toFixed(3),
|
|
87
|
+
'Right wrist:', rightWrist ? `(${rightWrist.x?.toFixed(3)}, ${rightWrist.y?.toFixed(3)}, score: ${rightWrist.score?.toFixed(3)})` : 'not detected',
|
|
88
|
+
'Right shoulder:', rightShoulder ? `(${rightShoulder.x?.toFixed(3)}, ${rightShoulder.y?.toFixed(3)}, score: ${rightShoulder.score?.toFixed(3)})` : 'not detected');
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
keypoints: pose.keypoints,
|
|
92
|
+
score: pose.score || 0
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('[OpenPose] Pose detection error:', error);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detect pose from base64 image string
|
|
102
|
+
*/
|
|
103
|
+
async detectPoseFromBase64(base64Image: string): Promise<PoseKeypoints | null> {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const img = new Image();
|
|
106
|
+
|
|
107
|
+
img.onload = async () => {
|
|
108
|
+
try {
|
|
109
|
+
const pose = await this.detectPose(img);
|
|
110
|
+
resolve(pose);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
reject(error);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
img.onerror = () => {
|
|
117
|
+
reject(new Error('Failed to load image from base64'));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
img.src = base64Image;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get specific keypoint by name
|
|
126
|
+
*/
|
|
127
|
+
getKeypoint(pose: PoseKeypoints, keypointName: string): poseDetection.Keypoint | null {
|
|
128
|
+
const keypoint = pose.keypoints.find(kp => kp.name === keypointName);
|
|
129
|
+
return keypoint || null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if a keypoint is visible (has good confidence)
|
|
134
|
+
*/
|
|
135
|
+
isKeypointVisible(keypoint: poseDetection.Keypoint | null, threshold: number = 0.3): boolean {
|
|
136
|
+
if (!keypoint) return false;
|
|
137
|
+
return (keypoint.score || 0) >= threshold;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get hand keypoints (wrists)
|
|
142
|
+
*/
|
|
143
|
+
getHandKeypoints(pose: PoseKeypoints): {
|
|
144
|
+
leftWrist: poseDetection.Keypoint | null;
|
|
145
|
+
rightWrist: poseDetection.Keypoint | null;
|
|
146
|
+
leftWristVisible: boolean;
|
|
147
|
+
rightWristVisible: boolean;
|
|
148
|
+
} {
|
|
149
|
+
const leftWrist = this.getKeypoint(pose, 'left_wrist');
|
|
150
|
+
const rightWrist = this.getKeypoint(pose, 'right_wrist');
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
leftWrist,
|
|
154
|
+
rightWrist,
|
|
155
|
+
leftWristVisible: this.isKeypointVisible(leftWrist),
|
|
156
|
+
rightWristVisible: this.isKeypointVisible(rightWrist)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get face/head keypoints
|
|
162
|
+
*/
|
|
163
|
+
getHeadKeypoints(pose: PoseKeypoints): {
|
|
164
|
+
nose: poseDetection.Keypoint | null;
|
|
165
|
+
leftEye: poseDetection.Keypoint | null;
|
|
166
|
+
rightEye: poseDetection.Keypoint | null;
|
|
167
|
+
leftEar: poseDetection.Keypoint | null;
|
|
168
|
+
rightEar: poseDetection.Keypoint | null;
|
|
169
|
+
} {
|
|
170
|
+
return {
|
|
171
|
+
nose: this.getKeypoint(pose, 'nose'),
|
|
172
|
+
leftEye: this.getKeypoint(pose, 'left_eye'),
|
|
173
|
+
rightEye: this.getKeypoint(pose, 'right_eye'),
|
|
174
|
+
leftEar: this.getKeypoint(pose, 'left_ear'),
|
|
175
|
+
rightEar: this.getKeypoint(pose, 'right_ear')
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get shoulder keypoints (useful for determining hand positions relative to body)
|
|
181
|
+
*/
|
|
182
|
+
getShoulderKeypoints(pose: PoseKeypoints): {
|
|
183
|
+
leftShoulder: poseDetection.Keypoint | null;
|
|
184
|
+
rightShoulder: poseDetection.Keypoint | null;
|
|
185
|
+
} {
|
|
186
|
+
return {
|
|
187
|
+
leftShoulder: this.getKeypoint(pose, 'left_shoulder'),
|
|
188
|
+
rightShoulder: this.getKeypoint(pose, 'right_shoulder')
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get elbow keypoints (useful for determining arm bend/extension)
|
|
194
|
+
*/
|
|
195
|
+
getElbowKeypoints(pose: PoseKeypoints): {
|
|
196
|
+
leftElbow: poseDetection.Keypoint | null;
|
|
197
|
+
rightElbow: poseDetection.Keypoint | null;
|
|
198
|
+
} {
|
|
199
|
+
return {
|
|
200
|
+
leftElbow: this.getKeypoint(pose, 'left_elbow'),
|
|
201
|
+
rightElbow: this.getKeypoint(pose, 'right_elbow')
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Detect thumbs up gesture
|
|
207
|
+
* A thumbs up is detected when:
|
|
208
|
+
* 1. Wrist is raised above shoulder level
|
|
209
|
+
* 2. Wrist is roughly aligned with or above the elbow
|
|
210
|
+
* 3. Hand is visible with good confidence
|
|
211
|
+
*/
|
|
212
|
+
detectThumbsUp(pose: PoseKeypoints): {
|
|
213
|
+
leftThumbsUp: boolean;
|
|
214
|
+
rightThumbsUp: boolean;
|
|
215
|
+
anyThumbsUp: boolean;
|
|
216
|
+
confidence: number;
|
|
217
|
+
} {
|
|
218
|
+
const hands = this.getHandKeypoints(pose);
|
|
219
|
+
const shoulders = this.getShoulderKeypoints(pose);
|
|
220
|
+
const elbows = this.getElbowKeypoints(pose);
|
|
221
|
+
|
|
222
|
+
let leftThumbsUp = false;
|
|
223
|
+
let rightThumbsUp = false;
|
|
224
|
+
let maxConfidence = 0;
|
|
225
|
+
|
|
226
|
+
// Check left hand thumbs up
|
|
227
|
+
if (hands.leftWristVisible &&
|
|
228
|
+
shoulders.leftShoulder &&
|
|
229
|
+
elbows.leftElbow &&
|
|
230
|
+
hands.leftWrist) {
|
|
231
|
+
|
|
232
|
+
const wristAboveShoulder = hands.leftWrist.y < shoulders.leftShoulder.y;
|
|
233
|
+
const wristAboveOrNearElbow = hands.leftWrist.y <= elbows.leftElbow.y + 50; // Allow some tolerance
|
|
234
|
+
|
|
235
|
+
// Wrist should be raised (above shoulder)
|
|
236
|
+
if (wristAboveShoulder && wristAboveOrNearElbow) {
|
|
237
|
+
leftThumbsUp = true;
|
|
238
|
+
maxConfidence = Math.max(maxConfidence, hands.leftWrist.score || 0);
|
|
239
|
+
console.log('[OpenPose] Left thumbs up detected!', {
|
|
240
|
+
wrist: hands.leftWrist.y,
|
|
241
|
+
shoulder: shoulders.leftShoulder.y,
|
|
242
|
+
elbow: elbows.leftElbow.y
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check right hand thumbs up
|
|
248
|
+
if (hands.rightWristVisible &&
|
|
249
|
+
shoulders.rightShoulder &&
|
|
250
|
+
elbows.rightElbow &&
|
|
251
|
+
hands.rightWrist) {
|
|
252
|
+
|
|
253
|
+
const wristAboveShoulder = hands.rightWrist.y < shoulders.rightShoulder.y;
|
|
254
|
+
const wristAboveOrNearElbow = hands.rightWrist.y <= elbows.rightElbow.y + 50; // Allow some tolerance
|
|
255
|
+
|
|
256
|
+
// Wrist should be raised (above shoulder)
|
|
257
|
+
if (wristAboveShoulder && wristAboveOrNearElbow) {
|
|
258
|
+
rightThumbsUp = true;
|
|
259
|
+
maxConfidence = Math.max(maxConfidence, hands.rightWrist.score || 0);
|
|
260
|
+
console.log('[OpenPose] Right thumbs up detected!', {
|
|
261
|
+
wrist: hands.rightWrist.y,
|
|
262
|
+
shoulder: shoulders.rightShoulder.y,
|
|
263
|
+
elbow: elbows.rightElbow.y
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
leftThumbsUp,
|
|
270
|
+
rightThumbsUp,
|
|
271
|
+
anyThumbsUp: leftThumbsUp || rightThumbsUp,
|
|
272
|
+
confidence: maxConfidence
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Cleanup resources
|
|
278
|
+
*/
|
|
279
|
+
cleanup(): void {
|
|
280
|
+
if (this.detector) {
|
|
281
|
+
this.detector.dispose();
|
|
282
|
+
this.detector = null;
|
|
283
|
+
this.isInitialized = false;
|
|
284
|
+
console.log('[OpenPose] Detector disposed');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { OpenposeService, PoseKeypoints } from './openpose.service';
|
|
3
|
+
import { ReferencePose } from './reference-pose.service';
|
|
4
|
+
import * as poseDetection from '@tensorflow-models/pose-detection';
|
|
5
|
+
|
|
6
|
+
export interface PoseComparisonResult {
|
|
7
|
+
overallScore: number; // 0-1
|
|
8
|
+
handScore: number; // Hand position similarity
|
|
9
|
+
bodyScore: number; // Body pose similarity
|
|
10
|
+
matched: boolean; // True if score > threshold
|
|
11
|
+
feedback: string; // User feedback message
|
|
12
|
+
handsRequired: boolean; // True if reference pose has hand keypoints
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Injectable({
|
|
16
|
+
providedIn: 'root'
|
|
17
|
+
})
|
|
18
|
+
export class PoseComparisonService {
|
|
19
|
+
private readonly MATCH_THRESHOLD = 0.75; // 75% similarity required
|
|
20
|
+
|
|
21
|
+
constructor(private openposeService: OpenposeService) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Compare live pose with reference pose
|
|
25
|
+
*/
|
|
26
|
+
comparePoses(livePose: PoseKeypoints, referencePose: ReferencePose): PoseComparisonResult {
|
|
27
|
+
try {
|
|
28
|
+
console.log('[Pose Comparison] Comparing poses...');
|
|
29
|
+
|
|
30
|
+
// Check if reference has any hand keypoints (using lower threshold 0.15)
|
|
31
|
+
const refHands = this.getHandKeypointsWithLowerThreshold(referencePose.keypoints);
|
|
32
|
+
const handsRequired = refHands.leftWristVisible || refHands.rightWristVisible;
|
|
33
|
+
|
|
34
|
+
console.log('[Pose Comparison] Reference pose hands required:', handsRequired);
|
|
35
|
+
|
|
36
|
+
// Calculate individual scores
|
|
37
|
+
const handScore = this.calculateHandSimilarity(livePose, referencePose.keypoints);
|
|
38
|
+
const bodyScore = this.calculateBodySimilarity(livePose, referencePose.keypoints);
|
|
39
|
+
|
|
40
|
+
// Weighted overall score (hands are more important for gesture matching)
|
|
41
|
+
// If hands are required but not matched, penalize more heavily
|
|
42
|
+
let overallScore: number;
|
|
43
|
+
if (handsRequired) {
|
|
44
|
+
// Hands are 70% of score when required
|
|
45
|
+
overallScore = (handScore * 0.7) + (bodyScore * 0.3);
|
|
46
|
+
} else {
|
|
47
|
+
// Standard weighting
|
|
48
|
+
overallScore = (handScore * 0.6) + (bodyScore * 0.4);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const matched = overallScore >= this.MATCH_THRESHOLD;
|
|
52
|
+
|
|
53
|
+
// Generate feedback
|
|
54
|
+
const feedback = this.generateFeedback(overallScore, handScore, bodyScore, matched, handsRequired);
|
|
55
|
+
|
|
56
|
+
console.log(`[Pose Comparison] Hand: ${(handScore * 100).toFixed(1)}%, Body: ${(bodyScore * 100).toFixed(1)}%, Overall: ${(overallScore * 100).toFixed(1)}%, Matched: ${matched}, HandsRequired: ${handsRequired}`);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
overallScore,
|
|
60
|
+
handScore,
|
|
61
|
+
bodyScore,
|
|
62
|
+
matched,
|
|
63
|
+
feedback,
|
|
64
|
+
handsRequired
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('[Pose Comparison] Error comparing poses:', error);
|
|
68
|
+
return {
|
|
69
|
+
overallScore: 0,
|
|
70
|
+
handScore: 0,
|
|
71
|
+
bodyScore: 0,
|
|
72
|
+
matched: false,
|
|
73
|
+
feedback: 'Unable to compare poses',
|
|
74
|
+
handsRequired: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get hand keypoints with lower threshold (0.15 instead of 0.3)
|
|
81
|
+
*/
|
|
82
|
+
private getHandKeypointsWithLowerThreshold(pose: PoseKeypoints): {
|
|
83
|
+
leftWrist: any | null;
|
|
84
|
+
rightWrist: any | null;
|
|
85
|
+
leftWristVisible: boolean;
|
|
86
|
+
rightWristVisible: boolean;
|
|
87
|
+
} {
|
|
88
|
+
const leftWrist = this.openposeService.getKeypoint(pose, 'left_wrist');
|
|
89
|
+
const rightWrist = this.openposeService.getKeypoint(pose, 'right_wrist');
|
|
90
|
+
const lowThreshold = 0.15;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
leftWrist,
|
|
94
|
+
rightWrist,
|
|
95
|
+
leftWristVisible: this.openposeService.isKeypointVisible(leftWrist, lowThreshold),
|
|
96
|
+
rightWristVisible: this.openposeService.isKeypointVisible(rightWrist, lowThreshold)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Calculate hand position similarity
|
|
102
|
+
*/
|
|
103
|
+
private calculateHandSimilarity(livePose: PoseKeypoints, referencePose: PoseKeypoints): number {
|
|
104
|
+
// Use lower threshold (0.15) for detecting hands in both live and reference
|
|
105
|
+
const liveHands = this.getHandKeypointsWithLowerThreshold(livePose);
|
|
106
|
+
const refHands = this.getHandKeypointsWithLowerThreshold(referencePose);
|
|
107
|
+
const liveShoulders = this.openposeService.getShoulderKeypoints(livePose);
|
|
108
|
+
const refShoulders = this.openposeService.getShoulderKeypoints(referencePose);
|
|
109
|
+
const liveHead = this.openposeService.getHeadKeypoints(livePose);
|
|
110
|
+
const refHead = this.openposeService.getHeadKeypoints(referencePose);
|
|
111
|
+
|
|
112
|
+
let scores: number[] = [];
|
|
113
|
+
let leftHandScore: number | null = null;
|
|
114
|
+
let rightHandScore: number | null = null;
|
|
115
|
+
|
|
116
|
+
// Compare left hand position (relative to body center)
|
|
117
|
+
if (refHands.leftWristVisible) {
|
|
118
|
+
if (liveHands.leftWristVisible && liveHead.nose && refHead.nose &&
|
|
119
|
+
liveShoulders.leftShoulder && refShoulders.leftShoulder) {
|
|
120
|
+
|
|
121
|
+
const liveLeftHandPos = this.normalizeKeypointPosition(
|
|
122
|
+
liveHands.leftWrist!,
|
|
123
|
+
liveHead.nose,
|
|
124
|
+
liveShoulders.leftShoulder,
|
|
125
|
+
liveShoulders.rightShoulder
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const refLeftHandPos = this.normalizeKeypointPosition(
|
|
129
|
+
refHands.leftWrist!,
|
|
130
|
+
refHead.nose,
|
|
131
|
+
refShoulders.leftShoulder,
|
|
132
|
+
refShoulders.rightShoulder
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
leftHandScore = this.calculatePositionSimilarity(liveLeftHandPos, refLeftHandPos);
|
|
136
|
+
scores.push(leftHandScore);
|
|
137
|
+
} else {
|
|
138
|
+
// Reference has left hand but live doesn't - penalize heavily
|
|
139
|
+
leftHandScore = 0.0;
|
|
140
|
+
scores.push(leftHandScore);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Compare right hand position (relative to body center)
|
|
145
|
+
if (refHands.rightWristVisible) {
|
|
146
|
+
if (liveHands.rightWristVisible && liveHead.nose && refHead.nose &&
|
|
147
|
+
liveShoulders.rightShoulder && refShoulders.rightShoulder) {
|
|
148
|
+
|
|
149
|
+
const liveRightHandPos = this.normalizeKeypointPosition(
|
|
150
|
+
liveHands.rightWrist!,
|
|
151
|
+
liveHead.nose,
|
|
152
|
+
liveShoulders.leftShoulder,
|
|
153
|
+
liveShoulders.rightShoulder
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const refRightHandPos = this.normalizeKeypointPosition(
|
|
157
|
+
refHands.rightWrist!,
|
|
158
|
+
refHead.nose,
|
|
159
|
+
refShoulders.leftShoulder,
|
|
160
|
+
refShoulders.rightShoulder
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
rightHandScore = this.calculatePositionSimilarity(liveRightHandPos, refRightHandPos);
|
|
164
|
+
scores.push(rightHandScore);
|
|
165
|
+
} else {
|
|
166
|
+
// Reference has right hand but live doesn't - penalize heavily
|
|
167
|
+
rightHandScore = 0.0;
|
|
168
|
+
scores.push(rightHandScore);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// If no hands visible in reference (even with lower 0.15 threshold), return full score
|
|
173
|
+
// This means the reference pose genuinely doesn't require hand matching
|
|
174
|
+
if (!refHands.leftWristVisible && !refHands.rightWristVisible) {
|
|
175
|
+
console.log('[Pose Comparison] No hands detected in reference pose (threshold 0.15) - hand matching not required');
|
|
176
|
+
return 1.0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// If no scores calculated (shouldn't happen), return 0
|
|
180
|
+
if (scores.length === 0) {
|
|
181
|
+
return 0.0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// IMPORTANT: Both hands must match if both are in reference
|
|
185
|
+
// If reference has both hands, BOTH must be visible and match in live pose
|
|
186
|
+
const bothHandsInReference = refHands.leftWristVisible && refHands.rightWristVisible;
|
|
187
|
+
const bothHandsInLive = liveHands.leftWristVisible && liveHands.rightWristVisible;
|
|
188
|
+
|
|
189
|
+
if (bothHandsInReference && !bothHandsInLive) {
|
|
190
|
+
console.log('[Pose Comparison] Reference has BOTH hands, but live pose only has ONE hand - FAIL');
|
|
191
|
+
return 0.0; // Hard fail if reference has both hands but live doesn't
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Average hand scores (all required hands must match)
|
|
195
|
+
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
196
|
+
|
|
197
|
+
console.log(`[Pose Comparison] Hand scores - Left: ${leftHandScore?.toFixed(2) ?? 'N/A'}, Right: ${rightHandScore?.toFixed(2) ?? 'N/A'}, Avg: ${avgScore.toFixed(2)}`);
|
|
198
|
+
|
|
199
|
+
return avgScore;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Calculate body pose similarity (shoulders, head alignment)
|
|
204
|
+
*/
|
|
205
|
+
private calculateBodySimilarity(livePose: PoseKeypoints, referencePose: PoseKeypoints): number {
|
|
206
|
+
const liveShoulders = this.openposeService.getShoulderKeypoints(livePose);
|
|
207
|
+
const refShoulders = this.openposeService.getShoulderKeypoints(referencePose);
|
|
208
|
+
const liveHead = this.openposeService.getHeadKeypoints(livePose);
|
|
209
|
+
const refHead = this.openposeService.getHeadKeypoints(referencePose);
|
|
210
|
+
|
|
211
|
+
let scores: number[] = [];
|
|
212
|
+
|
|
213
|
+
// Compare shoulder alignment (angle)
|
|
214
|
+
if (liveShoulders.leftShoulder && liveShoulders.rightShoulder &&
|
|
215
|
+
refShoulders.leftShoulder && refShoulders.rightShoulder) {
|
|
216
|
+
|
|
217
|
+
const liveShoulderAngle = this.calculateAngle(
|
|
218
|
+
liveShoulders.leftShoulder,
|
|
219
|
+
liveShoulders.rightShoulder
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const refShoulderAngle = this.calculateAngle(
|
|
223
|
+
refShoulders.leftShoulder,
|
|
224
|
+
refShoulders.rightShoulder
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const angleDiff = Math.abs(liveShoulderAngle - refShoulderAngle);
|
|
228
|
+
const angleSim = Math.max(0, 1 - (angleDiff / 45)); // 45 degree tolerance
|
|
229
|
+
scores.push(angleSim);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Compare head position (relative to shoulders)
|
|
233
|
+
if (liveHead.nose && refHead.nose &&
|
|
234
|
+
liveShoulders.leftShoulder && liveShoulders.rightShoulder &&
|
|
235
|
+
refShoulders.leftShoulder && refShoulders.rightShoulder) {
|
|
236
|
+
|
|
237
|
+
const liveHeadPos = this.normalizeKeypointPosition(
|
|
238
|
+
liveHead.nose,
|
|
239
|
+
liveHead.nose,
|
|
240
|
+
liveShoulders.leftShoulder,
|
|
241
|
+
liveShoulders.rightShoulder
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const refHeadPos = this.normalizeKeypointPosition(
|
|
245
|
+
refHead.nose,
|
|
246
|
+
refHead.nose,
|
|
247
|
+
refShoulders.leftShoulder,
|
|
248
|
+
refShoulders.rightShoulder
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const headSim = this.calculatePositionSimilarity(liveHeadPos, refHeadPos);
|
|
252
|
+
scores.push(headSim);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// If no body keypoints visible, return 0
|
|
256
|
+
if (scores.length === 0) {
|
|
257
|
+
return 0.0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Average body scores
|
|
261
|
+
return scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Normalize keypoint position relative to body center and scale
|
|
266
|
+
*/
|
|
267
|
+
private normalizeKeypointPosition(
|
|
268
|
+
keypoint: poseDetection.Keypoint,
|
|
269
|
+
referencePoint: poseDetection.Keypoint, // Usually nose/head center
|
|
270
|
+
leftShoulder: poseDetection.Keypoint | null,
|
|
271
|
+
rightShoulder: poseDetection.Keypoint | null
|
|
272
|
+
): { x: number; y: number } {
|
|
273
|
+
// Calculate body center
|
|
274
|
+
let centerX = referencePoint.x;
|
|
275
|
+
let centerY = referencePoint.y;
|
|
276
|
+
|
|
277
|
+
// Calculate body scale (shoulder width)
|
|
278
|
+
let scale = 1.0;
|
|
279
|
+
if (leftShoulder && rightShoulder) {
|
|
280
|
+
const shoulderWidth = Math.abs(rightShoulder.x - leftShoulder.x);
|
|
281
|
+
scale = shoulderWidth || 1.0;
|
|
282
|
+
|
|
283
|
+
// Update center to midpoint between shoulders
|
|
284
|
+
centerX = (leftShoulder.x + rightShoulder.x) / 2;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Normalize position relative to center and scale
|
|
288
|
+
return {
|
|
289
|
+
x: (keypoint.x - centerX) / scale,
|
|
290
|
+
y: (keypoint.y - centerY) / scale
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Calculate similarity between two normalized positions
|
|
296
|
+
*/
|
|
297
|
+
private calculatePositionSimilarity(pos1: { x: number; y: number }, pos2: { x: number; y: number }): number {
|
|
298
|
+
const dx = pos1.x - pos2.x;
|
|
299
|
+
const dy = pos1.y - pos2.y;
|
|
300
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
301
|
+
|
|
302
|
+
// Distance threshold: 0.3 (30% of body scale)
|
|
303
|
+
const threshold = 0.3;
|
|
304
|
+
const similarity = Math.max(0, 1 - (distance / threshold));
|
|
305
|
+
|
|
306
|
+
return similarity;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Calculate angle between two points (in degrees)
|
|
311
|
+
*/
|
|
312
|
+
private calculateAngle(point1: poseDetection.Keypoint, point2: poseDetection.Keypoint): number {
|
|
313
|
+
const dx = point2.x - point1.x;
|
|
314
|
+
const dy = point2.y - point1.y;
|
|
315
|
+
const angleRad = Math.atan2(dy, dx);
|
|
316
|
+
const angleDeg = (angleRad * 180) / Math.PI;
|
|
317
|
+
return angleDeg;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generate user feedback based on scores
|
|
322
|
+
*/
|
|
323
|
+
private generateFeedback(
|
|
324
|
+
overallScore: number,
|
|
325
|
+
handScore: number,
|
|
326
|
+
bodyScore: number,
|
|
327
|
+
matched: boolean,
|
|
328
|
+
handsRequired: boolean = false
|
|
329
|
+
): string {
|
|
330
|
+
if (matched) {
|
|
331
|
+
return `Perfect match! ${Math.round(overallScore * 100)}%`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (overallScore >= 0.6) {
|
|
335
|
+
return `Almost there! ${Math.round(overallScore * 100)}% - Keep holding the pose`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// If hands are required but not visible/matched
|
|
339
|
+
if (handsRequired && handScore < 0.3) {
|
|
340
|
+
return 'Show your hand in the frame - match the arm position in the reference';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (handScore < 0.5) {
|
|
344
|
+
return 'Adjust your hand/arm position to match the reference';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (bodyScore < 0.5) {
|
|
348
|
+
return 'Adjust your body alignment to match the reference';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return `${Math.round(overallScore * 100)}% - Try to match the reference pose`;
|
|
352
|
+
}
|
|
353
|
+
}
|