| @@ -28,12 +28,14 @@ | |||||
| "@ionic-native/status-bar": "^5.0.0", | "@ionic-native/status-bar": "^5.0.0", | ||||
| "@ionic/angular": "^5.0.0", | "@ionic/angular": "^5.0.0", | ||||
| "cordova-res": "^0.15.2", | "cordova-res": "^0.15.2", | ||||
| "face-api.js": "^0.22.2", | |||||
| "faker": "^5.1.0", | "faker": "^5.1.0", | ||||
| "firebase": "^8.2.6", | "firebase": "^8.2.6", | ||||
| "hammerjs": "^2.0.8", | "hammerjs": "^2.0.8", | ||||
| "moment": "^2.29.1", | "moment": "^2.29.1", | ||||
| "rxjs": "~6.5.5", | "rxjs": "~6.5.5", | ||||
| "sharp": "^0.27.0", | "sharp": "^0.27.0", | ||||
| "three": "^0.125.2", | |||||
| "tslib": "^2.0.0", | "tslib": "^2.0.0", | ||||
| "uninstall": "0.0.0", | "uninstall": "0.0.0", | ||||
| "zone.js": "~0.10.3" | "zone.js": "~0.10.3" | ||||
| @@ -98,4 +100,4 @@ | |||||
| "android" | "android" | ||||
| ] | ] | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -1,3 +1,9 @@ | |||||
| <ion-content> | <ion-content> | ||||
| <div class="container"> | |||||
| <img class="glass-image" [ngStyle]="glassProperties" src="/assets/ar-accessories/glass.png" alt=""/> | |||||
| <video id="playback-video" #videoElement autoPlay></video> | |||||
| <button *ngIf="areNeuralNetsLoaded" (click)="getCameraStream()">Get access</button> | |||||
| <button *ngIf="areNeuralNetsLoaded" (click)="stopCameraStream()">Stop stream</button> | |||||
| <button *ngIf="areNeuralNetsLoaded" (click)="toggleDetection()">{{ isDetecting ? 'Stop detection' : 'Detect and draw' }}</button>} | |||||
| </div> | |||||
| </ion-content> | </ion-content> | ||||
| @@ -1 +1,42 @@ | |||||
| @import '../colors'; | |||||
| @import '../colors'; | |||||
| .container { | |||||
| position: relative; | |||||
| } | |||||
| .glass-image { | |||||
| position: absolute; | |||||
| top: 0px; | |||||
| left: 0px; | |||||
| transform-origin: 15% 50%; | |||||
| } | |||||
| #playback-video, #result-canvas, #three-container { | |||||
| display: block; | |||||
| width: 100vw; | |||||
| height: 75vw; | |||||
| max-width: 600px; | |||||
| max-height: 450px; | |||||
| } | |||||
| #result-canvas, #three-container { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: 0; | |||||
| } | |||||
| .left-eye, .right-eye { | |||||
| position: absolute; | |||||
| width: 4px; | |||||
| height: 4px; | |||||
| border-radius: 50%; | |||||
| } | |||||
| .left-eye { | |||||
| background-color: blue; | |||||
| } | |||||
| .right-eye { | |||||
| background-color: red; | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| import { Component, OnInit } from '@angular/core'; | |||||
| import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; | |||||
| import { detectSingleFace, loadFaceExpressionModel, loadFaceLandmarkModel, loadFaceLandmarkTinyModel, loadFaceRecognitionModel, loadSsdMobilenetv1Model, loadTinyFaceDetectorModel, TinyFaceDetectorOptions } from 'face-api.js'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-ar-fan-cam', | selector: 'app-ar-fan-cam', | ||||
| @@ -6,10 +7,106 @@ import { Component, OnInit } from '@angular/core'; | |||||
| styleUrls: ['./ar-fan-cam.page.scss'], | styleUrls: ['./ar-fan-cam.page.scss'], | ||||
| }) | }) | ||||
| export class ArFanCamPage implements OnInit { | export class ArFanCamPage implements OnInit { | ||||
| @ViewChild('videoElement', null) videoElement: ElementRef<HTMLVideoElement>; | |||||
| mediaStream: MediaStream|null = null; | |||||
| glassProperties: { | |||||
| [property: string]: any, | |||||
| } = { | |||||
| transform: 'scale(0)', | |||||
| }; | |||||
| areNeuralNetsLoaded = false; | |||||
| isDetecting = false; | |||||
| leftEyeX = 0; | |||||
| leftEyeY = 0; | |||||
| rightEyeX = 0; | |||||
| rightEyeY = 0; | |||||
| width = 360; | |||||
| height = 270; | |||||
| constructor() { } | constructor() { } | ||||
| loadNeuralNetModels = async () => { | |||||
| await loadTinyFaceDetectorModel('/assets/weights'); | |||||
| await loadSsdMobilenetv1Model('/assets/weights'); | |||||
| await loadFaceLandmarkTinyModel('/assets/weights'); | |||||
| await loadFaceLandmarkModel('/assets/weights'); | |||||
| await loadFaceRecognitionModel('/assets/weights'); | |||||
| await loadFaceExpressionModel('/assets/weights'); | |||||
| this.areNeuralNetsLoaded = true; | |||||
| } | |||||
| getCameraStream = async () => { | |||||
| const stream = await window.navigator.mediaDevices.getUserMedia({ | |||||
| video: { | |||||
| facingMode: 'user', | |||||
| width: this.width, | |||||
| }, | |||||
| }); | |||||
| this.mediaStream = stream; | |||||
| this.videoElement.nativeElement.srcObject = stream; | |||||
| }; | |||||
| stopCameraStream = async () => { | |||||
| if (this.mediaStream) { | |||||
| this.mediaStream.getVideoTracks().forEach(track => track.stop()); | |||||
| this.mediaStream = null; | |||||
| } | |||||
| } | |||||
| detectAndDrawFace = async () => { | |||||
| const tinyFaceDetectorOptions = new TinyFaceDetectorOptions(); | |||||
| let detectionWithLandmarks = await detectSingleFace(this.videoElement.nativeElement, tinyFaceDetectorOptions).withFaceLandmarks(true); | |||||
| if (detectionWithLandmarks) { | |||||
| const leftEye = detectionWithLandmarks.landmarks.getLeftEye(); | |||||
| const rightEye = detectionWithLandmarks.landmarks.getRightEye(); | |||||
| this.leftEyeX = leftEye[0].x; | |||||
| this.leftEyeY = leftEye[0].y; | |||||
| this.rightEyeX = rightEye[0].x; | |||||
| this.rightEyeY = rightEye[0].y; | |||||
| const distanceBetweenEyes = Math.sqrt(Math.pow(this.rightEyeX - this.leftEyeX, 2) + Math.pow(this.rightEyeY - this.leftEyeY, 2)); | |||||
| const angleOfRotation = (Math.atan2(this.rightEyeY - this.leftEyeY, this.rightEyeX - this.leftEyeX) * 180) / Math.PI; | |||||
| const scaleMultiplier = distanceBetweenEyes / 130; | |||||
| this.glassProperties = { | |||||
| transform: `translate(calc(-14.5% + ${this.leftEyeX}px), calc(-45% + ${this.leftEyeY}px)) scale(${scaleMultiplier}) rotate(${angleOfRotation}deg)`, | |||||
| } | |||||
| } | |||||
| requestAnimationFrame(this.detectAndDrawFace); | |||||
| } | |||||
| toggleDetection = () => { | |||||
| if (!this.isDetecting) { | |||||
| this.isDetecting = true; | |||||
| this.detectAndDrawFace(); | |||||
| } else { | |||||
| this.isDetecting = false; | |||||
| } | |||||
| } | |||||
| ngOnInit() { | ngOnInit() { | ||||
| this.loadNeuralNetModels(); | |||||
| const feedWidth = window.innerWidth > 600 ? 600 : window.innerWidth; | |||||
| const feedHeight = feedWidth * 0.75; | |||||
| this.width = feedWidth; | |||||
| this.height = feedHeight; | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1 @@ | |||||
| [{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008194216092427571,"min":-0.9423348506291708}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006839508168837603,"min":-0.8412595047670252}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009194007106855804,"min":-1.2779669878529567}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0036026100317637128,"min":-0.3170296827952067}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.000740380117706224,"min":-0.06367269012273527}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037702228508743585,"min":-0.6220867703942692}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0033707996209462483,"min":-0.421349952618281}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014611541991140328,"min":-1.8556658328748217}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002832523046755323,"min":-0.30307996600281956}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006593170586754294,"min":-0.6329443763284123}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.012215249211180444,"min":-1.6001976466646382}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002384825547536214,"min":-0.3028728445370992}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005859645441466687,"min":-0.7617539073906693}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013121426806730382,"min":-1.7845140457153321}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032247188044529336,"min":-0.46435950784122243}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002659512618008782,"min":-0.32977956463308894}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015499923743453681,"min":-1.9839902391620712}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032450980999890497,"min":-0.522460794098237}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005911862382701799,"min":-0.792189559282041}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021025861478319356,"min":-2.2077154552235325}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00349616945958605,"min":-0.46149436866535865}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008104994250278847,"min":-1.013124281284856}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.029337059282789044,"min":-3.5791212325002633}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0038808938334969913,"min":-0.4230174278511721}},{"name":"fc/weights","shape":[128,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014016061670639936,"min":-1.8921683255363912}},{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029505149698724935,"min":0.088760145008564}}],"paths":["face_landmark_68_tiny_model-shard1"]}] | |||||
| @@ -0,0 +1 @@ | |||||
| [{"paths":["mtcnn_model-shard1"],"weights":[{"dtype":"float32","name":"pnet/conv1/weights","shape":[3,3,3,10]},{"dtype":"float32","name":"pnet/conv1/bias","shape":[10]},{"dtype":"float32","name":"pnet/prelu1_alpha","shape":[10]},{"dtype":"float32","name":"pnet/conv2/weights","shape":[3,3,10,16]},{"dtype":"float32","name":"pnet/conv2/bias","shape":[16]},{"dtype":"float32","name":"pnet/prelu2_alpha","shape":[16]},{"dtype":"float32","name":"pnet/conv3/weights","shape":[3,3,16,32]},{"dtype":"float32","name":"pnet/conv3/bias","shape":[32]},{"dtype":"float32","name":"pnet/prelu3_alpha","shape":[32]},{"dtype":"float32","name":"pnet/conv4_1/weights","shape":[1,1,32,2]},{"dtype":"float32","name":"pnet/conv4_1/bias","shape":[2]},{"dtype":"float32","name":"pnet/conv4_2/weights","shape":[1,1,32,4]},{"dtype":"float32","name":"pnet/conv4_2/bias","shape":[4]},{"dtype":"float32","name":"rnet/conv1/weights","shape":[3,3,3,28]},{"dtype":"float32","name":"rnet/conv1/bias","shape":[28]},{"dtype":"float32","name":"rnet/prelu1_alpha","shape":[28]},{"dtype":"float32","name":"rnet/conv2/weights","shape":[3,3,28,48]},{"dtype":"float32","name":"rnet/conv2/bias","shape":[48]},{"dtype":"float32","name":"rnet/prelu2_alpha","shape":[48]},{"dtype":"float32","name":"rnet/conv3/weights","shape":[2,2,48,64]},{"dtype":"float32","name":"rnet/conv3/bias","shape":[64]},{"dtype":"float32","name":"rnet/prelu3_alpha","shape":[64]},{"dtype":"float32","name":"rnet/fc1/weights","shape":[576,128]},{"dtype":"float32","name":"rnet/fc1/bias","shape":[128]},{"dtype":"float32","name":"rnet/prelu4_alpha","shape":[128]},{"dtype":"float32","name":"rnet/fc2_1/weights","shape":[128,2]},{"dtype":"float32","name":"rnet/fc2_1/bias","shape":[2]},{"dtype":"float32","name":"rnet/fc2_2/weights","shape":[128,4]},{"dtype":"float32","name":"rnet/fc2_2/bias","shape":[4]},{"dtype":"float32","name":"onet/conv1/weights","shape":[3,3,3,32]},{"dtype":"float32","name":"onet/conv1/bias","shape":[32]},{"dtype":"float32","name":"onet/prelu1_alpha","shape":[32]},{"dtype":"float32","name":"onet/conv2/weights","shape":[3,3,32,64]},{"dtype":"float32","name":"onet/conv2/bias","shape":[64]},{"dtype":"float32","name":"onet/prelu2_alpha","shape":[64]},{"dtype":"float32","name":"onet/conv3/weights","shape":[3,3,64,64]},{"dtype":"float32","name":"onet/conv3/bias","shape":[64]},{"dtype":"float32","name":"onet/prelu3_alpha","shape":[64]},{"dtype":"float32","name":"onet/conv4/weights","shape":[2,2,64,128]},{"dtype":"float32","name":"onet/conv4/bias","shape":[128]},{"dtype":"float32","name":"onet/prelu4_alpha","shape":[128]},{"dtype":"float32","name":"onet/fc1/weights","shape":[1152,256]},{"dtype":"float32","name":"onet/fc1/bias","shape":[256]},{"dtype":"float32","name":"onet/prelu5_alpha","shape":[256]},{"dtype":"float32","name":"onet/fc2_1/weights","shape":[256,2]},{"dtype":"float32","name":"onet/fc2_1/bias","shape":[2]},{"dtype":"float32","name":"onet/fc2_2/weights","shape":[256,4]},{"dtype":"float32","name":"onet/fc2_2/bias","shape":[4]},{"dtype":"float32","name":"onet/fc2_3/weights","shape":[256,10]},{"dtype":"float32","name":"onet/fc2_3/bias","shape":[10]}]}] | |||||
| @@ -0,0 +1 @@ | |||||
| [{"weights":[{"name":"conv0/filters","shape":[3,3,3,16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009007044399485869,"min":-1.2069439495311063}},{"name":"conv0/bias","shape":[16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005263455241334205,"min":-0.9211046672334858}},{"name":"conv1/depthwise_filter","shape":[3,3,16,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004001977630690033,"min":-0.5042491814669441}},{"name":"conv1/pointwise_filter","shape":[1,1,16,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013836609615999109,"min":-1.411334180831909}},{"name":"conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0015159862590771096,"min":-0.30926119685173037}},{"name":"conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002666276225856706,"min":-0.317286870876948}},{"name":"conv2/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015265831292844286,"min":-1.6792414422128714}},{"name":"conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0020280554598453,"min":-0.37113414915168985}},{"name":"conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006100742489683862,"min":-0.8907084034938438}},{"name":"conv3/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016276211832083907,"min":-2.0508026908425725}},{"name":"conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394414279975143,"min":-0.7637432129944072}},{"name":"conv4/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006716050119961009,"min":-0.8059260143953211}},{"name":"conv4/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021875603993733724,"min":-2.8875797271728514}},{"name":"conv4/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0041141652009066415,"min":-0.8187188749804216}},{"name":"conv5/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008423839597141042,"min":-0.9013508368940915}},{"name":"conv5/pointwise_filter","shape":[1,1,256,512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.030007277283014035,"min":-3.8709387695088107}},{"name":"conv5/bias","shape":[512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008402082966823203,"min":-1.4871686851277068}},{"name":"conv8/filters","shape":[1,1,512,25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.028336129469030042,"min":-4.675461362389957}},{"name":"conv8/bias","shape":[25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002268134028303857,"min":-0.41053225912299807}}],"paths":["tiny_face_detector_model-shard1"]}] | |||||
| @@ -11,6 +11,7 @@ | |||||
| "moduleResolution": "node", | "moduleResolution": "node", | ||||
| "importHelpers": true, | "importHelpers": true, | ||||
| "target": "es2015", | "target": "es2015", | ||||
| "skipLibCheck": true, | |||||
| "lib": [ | "lib": [ | ||||
| "es2018", | "es2018", | ||||
| "dom" | "dom" | ||||