├── .babelrc ├── .github └── example.png ├── .gitignore ├── README.md ├── config-overrides.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── models │ ├── age_gender_model-shard1 │ ├── age_gender_model-weights_manifest.json │ ├── face_expression_model-shard1 │ ├── face_expression_model-weights_manifest.json │ ├── face_landmark_68_model-shard1 │ ├── face_landmark_68_model-weights_manifest.json │ ├── face_landmark_68_tiny_model-shard1 │ ├── face_landmark_68_tiny_model-weights_manifest.json │ ├── face_recognition_model-shard1 │ ├── face_recognition_model-shard2 │ ├── face_recognition_model-weights_manifest.json │ ├── mtcnn_model-shard1 │ ├── mtcnn_model-weights_manifest.json │ ├── ssd_mobilenetv1_model-shard1 │ ├── ssd_mobilenetv1_model-shard2 │ ├── ssd_mobilenetv1_model-weights_manifest.json │ ├── tiny_face_detector_model-shard1 │ └── tiny_face_detector_model-weights_manifest.json └── robots.txt ├── src ├── App.tsx ├── api │ ├── face.ts │ ├── index.ts │ └── rank.ts ├── components │ ├── animationNumber.tsx │ ├── hand.tsx │ ├── miniRank.tsx │ ├── rankItem.tsx │ └── ranks.tsx ├── contexts │ └── audioPlayer.tsx ├── globalStyle.tsx ├── helpers │ ├── createImageFromFaceMatch.ts │ ├── createNameFromFaceMatch.ts │ └── requestAnimationFrame.ts ├── index.tsx ├── modules │ ├── chamchamcham.ts │ └── player.ts ├── pages │ ├── gameEnd.tsx │ ├── gameStart.tsx │ ├── inGame │ │ ├── index.tsx │ │ ├── styledComponents.tsx │ │ └── useInGame.tsx │ ├── ranking.tsx │ └── scanning.tsx ├── react-app-env.d.ts ├── resources │ ├── BMHANNAPro.woff │ ├── center-hand.png │ ├── home-icon.png │ ├── left-hand-moving.png │ ├── left-hand.png │ ├── right-hand-moving.png │ ├── right-hand.png │ ├── sounds │ │ ├── big-cham.mp3 │ │ ├── cham.mp3 │ │ ├── charge.wav │ │ ├── end-game.mp3 │ │ ├── error.wav │ │ ├── in-game.mp3 │ │ ├── intro.mp3 │ │ ├── lose-laugh.wav │ │ ├── mouth-pop.wav │ │ ├── pipe.wav │ │ ├── scanning-bgm.mp3 │ │ ├── start-bgm.mp3 │ │ ├── throw.wav │ │ ├── whooo.wav │ │ ├── wow.wav │ │ ├── wow2.mp3 │ │ ├── wow3.mp3 │ │ ├── wow4.mp3 │ │ ├── wow5.mp3 │ │ ├── wow6.mp3 │ │ ├── wow7.mp3 │ │ └── wow8.mp3 │ ├── trophy-bronze.svg │ ├── trophy-gold.svg │ ├── trophy-silver.svg │ └── trophy.svg ├── serviceWorker.ts ├── styledComponents.tsx ├── types.ts ├── useButtonAudio.ts └── useGame.tsx └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-proposal-optional-chaining"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/.github/example.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .vscode/ 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 참참참! 2 | 3 | ![참참참 시연](./.github/example.png) 4 | 5 | 시연영상 : https://youtu.be/6rrOmDgrmoQ 6 | 7 | 사이트주소 : https://chamchamcham.minukang.now.sh 8 | 9 | 10 | 11 | 숭실대학교 미디어경영학과 제1회 디지털 컨텐츠 경진대회 출품 작품 12 | 13 | https://fun.ssu.ac.kr/ko/program/all/view/1304/description 14 | 15 | ## 팀원 16 | 17 | 팀명: (前)디앤디랩 18 | 19 | * 강민우 (https://github.com/MinuKang) - Front-End Develop 20 | * 노준혁 (https://github.com/njhyuk) - UI/UX Design 21 | 22 | ## 기술스택 23 | 24 | * React (only use hook) 25 | * Typescript 26 | * Styled-Components 27 | * Faceapi-js (Tensorflow) 28 | * Localforage (IndexedDB) 29 | 30 | ## 기능구현 31 | 32 | * `navigator.mediaDevices.getUserMedia` 를 사용한 카메라 접근 33 | * faceapi-js 를 사용하여 카메라의 데이터에서 얼굴 인식 34 | * React hook의 state와 ref를 적절히 혼용하여 훅으로 실시간 얼굴 인식 상태 디스패치 35 | * 사운드 컨텍스트를 구현하여 언제든지 Speak(말하기), Play(재생), Stop(정지)을 할 수 있도록 구현 36 | * 메인페이지 37 | * 얼굴이 인식되지 않을 경우 시작버튼 비활성화 38 | * 가장 인식이 잘되는 얼굴로 게임 시작 39 | * 얼굴인식 페이지 40 | * IndexedDB에 학습된 얼굴 데이터가 없을 경우 진행되는 페이지 41 | * 얼굴과 코의 위치를 기준하여 왼쪽과 오른쪽 얼굴을 학습 42 | * 이를 통해 고개를 돌려도 자연스럽게 같은 인물임을 인식할 수 있게됨 43 | * 학습된 얼굴 데이터는 IndexedDB에 저장하여 게임을 시작할 때 마다 사용 44 | * 게임진행 페이지 45 | * 3초 이상 고개를 돌리지 않을 경우 안내 메세지 출력 46 | * 플레이어가 고개를 돌린 방향을 인식하여 컴퓨터는 랜덤으로 방향을 결정 47 | * 첫판에는 95% 확률로 플레이어 승리, 이후 5% 씩 깎이며 10번째 판부터는 50%의 확률을 유지 48 | * 이기면 점수 업데이트와 함께 실기간으로 랭킹을 업데이트하여 어느 정도 이기면 순위권에 들 수 있는지 빠르게 확인가능 49 | * 플레이어가 이길 때 8가지의 랜덤한 응원 소리 출력 50 | * 게임종료 페이지 51 | * 랭킹과 함께 자신의 랭킹을 자연스럽게 보여주는 애니메이션 구현 52 | * 메달권에 들 경우 응원의 소리 출력, 아닐 경우 비웃는 소리 출력 53 | * 랭킹 페이지 54 | * IndexedDB에 저장된 플레이어들의 순위를 출력 55 | * 게임시작 당시의 화면의 얼굴 부분만 썸네일로 사용하여 프로필 사진으로 지정 56 | * 프로필 사진을 faceapi-js로 분석하여 나이, 표정, 성별을 판단하고 적절한 이름을 부여 57 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { useBabelRc, override } = require('customize-cra'); 2 | 3 | module.exports = override(useBabelRc()); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chamchamcham", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/animejs": "^3.1.0", 7 | "@types/localforage": "0.0.34", 8 | "@types/node": "12.11.6", 9 | "@types/react": "16.9.9", 10 | "@types/react-dom": "16.9.2", 11 | "@types/shortid": "0.0.29", 12 | "@types/styled-components": "^4.4.0", 13 | "animejs": "^3.1.0", 14 | "face-api.js": "^0.21.0", 15 | "localforage": "^1.7.3", 16 | "react": "^16.11.0", 17 | "react-dom": "^16.11.0", 18 | "react-scripts": "3.2.0", 19 | "react-use": "^13.10.0", 20 | "shortid": "^2.2.15", 21 | "styled-components": "^4.4.1", 22 | "typescript": "^3.7.2" 23 | }, 24 | "scripts": { 25 | "start": "HTTPS=true react-app-rewired start", 26 | "build": "react-app-rewired build", 27 | "test": "react-app-rewired test", 28 | "eject": "react-app-rewired eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": "react-app" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@babel/plugin-proposal-optional-chaining": "^7.7.4", 47 | "@types/jest": "24.0.19", 48 | "customize-cra": "^0.9.1", 49 | "prettier": "^1.19.1", 50 | "react-app-rewired": "^2.1.5" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | 참참참 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/models/age_gender_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/age_gender_model-shard1 -------------------------------------------------------------------------------- /public/models/age_gender_model-weights_manifest.json: -------------------------------------------------------------------------------- 1 | [{"weights":[{"name":"entry_flow/conv_in/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005431825039433498,"min":-0.7441600304023892}},{"name":"entry_flow/conv_in/bias","shape":[32],"dtype":"float32"},{"name":"entry_flow/reduction_block_0/separable_conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005691980614381678,"min":-0.6090419257388395}},{"name":"entry_flow/reduction_block_0/separable_conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009089225881239947,"min":-1.1179747833925135}},{"name":"entry_flow/reduction_block_0/separable_conv0/bias","shape":[64],"dtype":"float32"},{"name":"entry_flow/reduction_block_0/separable_conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00683894624897078,"min":-0.8138346036275228}},{"name":"entry_flow/reduction_block_0/separable_conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011632566358528886,"min":-1.3028474321552352}},{"name":"entry_flow/reduction_block_0/separable_conv1/bias","shape":[64],"dtype":"float32"},{"name":"entry_flow/reduction_block_0/expansion_conv/filters","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010254812240600587,"min":-0.9229331016540528}},{"name":"entry_flow/reduction_block_0/expansion_conv/bias","shape":[64],"dtype":"float32"},{"name":"entry_flow/reduction_block_1/separable_conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0052509616403018725,"min":-0.6406173201168285}},{"name":"entry_flow/reduction_block_1/separable_conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010788509424994973,"min":-1.4564487723743214}},{"name":"entry_flow/reduction_block_1/separable_conv0/bias","shape":[128],"dtype":"float32"},{"name":"entry_flow/reduction_block_1/separable_conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00553213918910307,"min":-0.7025816770160899}},{"name":"entry_flow/reduction_block_1/separable_conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013602388606351965,"min":-1.6186842441558837}},{"name":"entry_flow/reduction_block_1/separable_conv1/bias","shape":[128],"dtype":"float32"},{"name":"entry_flow/reduction_block_1/expansion_conv/filters","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007571851038465313,"min":-1.158493208885193}},{"name":"entry_flow/reduction_block_1/expansion_conv/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_0/separable_conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005766328409606335,"min":-0.6688940955143349}},{"name":"middle_flow/main_block_0/separable_conv0/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.012136116214826995,"min":-1.5776951079275094}},{"name":"middle_flow/main_block_0/separable_conv0/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_0/separable_conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004314773222979377,"min":-0.5652352922102984}},{"name":"middle_flow/main_block_0/separable_conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01107162026798024,"min":-1.2400214700137868}},{"name":"middle_flow/main_block_0/separable_conv1/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_0/separable_conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0036451735917259667,"min":-0.4848080876995536}},{"name":"middle_flow/main_block_0/separable_conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008791744942758598,"min":-1.134135097615859}},{"name":"middle_flow/main_block_0/separable_conv2/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_1/separable_conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004915751896652521,"min":-0.6095532351849126}},{"name":"middle_flow/main_block_1/separable_conv0/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010868691463096469,"min":-1.3368490499608656}},{"name":"middle_flow/main_block_1/separable_conv0/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_1/separable_conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005010117269029804,"min":-0.6012140722835765}},{"name":"middle_flow/main_block_1/separable_conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010311148213405235,"min":-1.3816938605963016}},{"name":"middle_flow/main_block_1/separable_conv1/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_1/separable_conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004911523706772748,"min":-0.7367285560159123}},{"name":"middle_flow/main_block_1/separable_conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008976466047997568,"min":-1.2207993825276693}},{"name":"middle_flow/main_block_1/separable_conv2/bias","shape":[128],"dtype":"float32"},{"name":"exit_flow/reduction_block/separable_conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005074804436926748,"min":-0.7104726211697447}},{"name":"exit_flow/reduction_block/separable_conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011453078307357489,"min":-1.4545409450344011}},{"name":"exit_flow/reduction_block/separable_conv0/bias","shape":[256],"dtype":"float32"},{"name":"exit_flow/reduction_block/separable_conv1/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007741751390344957,"min":-1.1380374543807086}},{"name":"exit_flow/reduction_block/separable_conv1/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011347713189966538,"min":-1.497898141075583}},{"name":"exit_flow/reduction_block/separable_conv1/bias","shape":[256],"dtype":"float32"},{"name":"exit_flow/reduction_block/expansion_conv/filters","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006717281014311547,"min":-0.8329428457746318}},{"name":"exit_flow/reduction_block/expansion_conv/bias","shape":[256],"dtype":"float32"},{"name":"exit_flow/separable_conv/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0027201742518181892,"min":-0.3237007359663645}},{"name":"exit_flow/separable_conv/pointwise_filter","shape":[1,1,256,512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010076364348916447,"min":-1.330080094056971}},{"name":"exit_flow/separable_conv/bias","shape":[512],"dtype":"float32"},{"name":"fc/age/weights","shape":[512,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008674054987290326,"min":-1.2664120281443876}},{"name":"fc/age/bias","shape":[1],"dtype":"float32"},{"name":"fc/gender/weights","shape":[512,2],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029948226377075793,"min":-0.34140978069866407}},{"name":"fc/gender/bias","shape":[2],"dtype":"float32"}],"paths":["age_gender_model-shard1"]}] -------------------------------------------------------------------------------- /public/models/face_expression_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/face_expression_model-shard1 -------------------------------------------------------------------------------- /public/models/face_expression_model-weights_manifest.json: -------------------------------------------------------------------------------- 1 | [{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0057930146946626555,"min":-0.7125408074435067}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006473719839956246,"min":-0.6408982641556684}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010509579321917366,"min":-1.408283629136927}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005666389652326995,"min":-0.7252978754978554}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010316079270605948,"min":-1.1760330368490781}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0063220320963392074,"min":-0.853474333005793}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010322785377502442,"min":-1.4658355236053466}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32"},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0042531527724920535,"min":-0.5741756242864272}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010653339647779278,"min":-1.1825207009035}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005166931012097527,"min":-0.6355325144879957}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011478300188101974,"min":-1.3888743227603388}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006144821410085641,"min":-0.8479853545918185}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010541967317169788,"min":-1.3809977185492421}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005769844849904378,"min":-0.686611537138621}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010939095534530341,"min":-1.2689350820055196}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32"},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037769308277204924,"min":-0.40790852939381317}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01188667194516051,"min":-1.4382873053644218}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006497045825509464,"min":-0.8381189114907208}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011632198913424622,"min":-1.3377028750438316}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005947182225246056,"min":-0.7969224181829715}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011436844339557722,"min":-1.4524792311238306}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv3/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006665432686899222,"min":-0.8998334127313949}},{"name":"dense2/conv3/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01283421422920975,"min":-1.642779421338848}},{"name":"dense2/conv3/bias","shape":[128],"dtype":"float32"},{"name":"dense3/conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004711699953266218,"min":-0.6737730933170692}},{"name":"dense3/conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010955964817720302,"min":-1.3914075318504784}},{"name":"dense3/conv0/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv1/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00554193468654857,"min":-0.7149095745647656}},{"name":"dense3/conv1/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016790372250126858,"min":-2.484975093018775}},{"name":"dense3/conv1/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv2/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006361540626077091,"min":-0.8142772001378676}},{"name":"dense3/conv2/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01777329678628959,"min":-1.7062364914838006}},{"name":"dense3/conv2/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv3/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006900275922289082,"min":-0.8625344902861353}},{"name":"dense3/conv3/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015449936717164282,"min":-1.9003422162112067}},{"name":"dense3/conv3/bias","shape":[256],"dtype":"float32"},{"name":"fc/weights","shape":[256,7],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004834276554631252,"min":-0.7203072066400565}},{"name":"fc/bias","shape":[7],"dtype":"float32"}],"paths":["face_expression_model-shard1"]}] -------------------------------------------------------------------------------- /public/models/face_landmark_68_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/face_landmark_68_model-shard1 -------------------------------------------------------------------------------- /public/models/face_landmark_68_model-weights_manifest.json: -------------------------------------------------------------------------------- 1 | [{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004853619781194949,"min":-0.5872879935245888}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004396426443960153,"min":-0.7298067896973853}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00635151559231328,"min":-0.5589333721235686}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009354315552057004,"min":-1.2628325995276957}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029380727048013726,"min":-0.5846764682554731}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0049374802439820535,"min":-0.6171850304977566}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009941946758943446,"min":-1.3421628124573652}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030300481062309416,"min":-0.5272283704841838}},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005672684837790097,"min":-0.7431217137505026}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010712201455060173,"min":-1.5639814124387852}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030966934035806097,"min":-0.3839899820439956}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0039155554537679636,"min":-0.48161332081345953}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01023082966898002,"min":-1.094698774580862}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0027264176630506327,"min":-0.3871513081531898}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004583378632863362,"min":-0.5454220573107401}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00915846403907327,"min":-1.117332612766939}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003091680419211294,"min":-0.5966943209077797}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005407439727409214,"min":-0.708374604290607}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00946493943532308,"min":-1.2399070660273235}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004409168514550901,"min":-0.9788354102303}},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004478132958505668,"min":-0.6493292789833219}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011063695888893277,"min":-1.2501976354449402}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003909627596537272,"min":-0.6646366914113363}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003213915404151468,"min":-0.3374611174359041}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010917326048308728,"min":-1.4520043644250609}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002800439152063108,"min":-0.38085972468058266}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0050568851770139206,"min":-0.6927932692509071}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01074961213504567,"min":-1.3222022926106174}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030654204242369708,"min":-0.5487102559384177}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00591809165244009,"min":-0.917304206128214}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01092823346455892,"min":-1.366029183069865}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002681120470458386,"min":-0.36463238398234055}},{"name":"dense2/conv3/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0048311497650894465,"min":-0.5797379718107336}},{"name":"dense2/conv3/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011227761062921263,"min":-1.4483811771168429}},{"name":"dense2/conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0034643323982463162,"min":-0.3360402426298927}},{"name":"dense3/conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394978887894574,"min":-0.49227193874471326}},{"name":"dense3/conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010051267287310432,"min":-1.2765109454884247}},{"name":"dense3/conv0/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003142924752889895,"min":-0.4588670139219247}},{"name":"dense3/conv1/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00448304671867221,"min":-0.5872791201460595}},{"name":"dense3/conv1/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016063522357566685,"min":-2.3613377865623026}},{"name":"dense3/conv1/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00287135781026354,"min":-0.47664539650374765}},{"name":"dense3/conv2/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006002906724518421,"min":-0.7923836876364315}},{"name":"dense3/conv2/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.017087187019048954,"min":-1.6061955797906016}},{"name":"dense3/conv2/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003124481205846749,"min":-0.46242321846531886}},{"name":"dense3/conv3/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006576311588287353,"min":-1.0193282961845398}},{"name":"dense3/conv3/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015590153955945782,"min":-1.99553970636106}},{"name":"dense3/conv3/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004453541601405424,"min":-0.6546706154065973}},{"name":"fc/weights","shape":[256,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010417488509533453,"min":-1.500118345372817}},{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0025084222648658005,"min":0.07683877646923065}}],"paths":["face_landmark_68_model-shard1"]}] -------------------------------------------------------------------------------- /public/models/face_landmark_68_tiny_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/face_landmark_68_tiny_model-shard1 -------------------------------------------------------------------------------- /public/models/face_landmark_68_tiny_model-weights_manifest.json: -------------------------------------------------------------------------------- 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"]}] -------------------------------------------------------------------------------- /public/models/face_recognition_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/face_recognition_model-shard1 -------------------------------------------------------------------------------- /public/models/face_recognition_model-shard2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/face_recognition_model-shard2 -------------------------------------------------------------------------------- /public/models/face_recognition_model-weights_manifest.json: -------------------------------------------------------------------------------- 1 | [{"weights":[{"name":"conv32_down/conv/filters","shape":[7,7,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0005260649557207145,"min":-0.07101876902229645}},{"name":"conv32_down/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":8.471445956577858e-7,"min":-0.00014740315964445472}},{"name":"conv32_down/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.06814416062598135,"min":5.788674831390381}},{"name":"conv32_down/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008471635042452345,"min":-0.931879854669758}},{"name":"conv32_1/conv1/conv/filters","shape":[3,3,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0007328585666768691,"min":-0.0974701893680236}},{"name":"conv32_1/conv1/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":1.5952091238361e-8,"min":-0.000001978059313556764}},{"name":"conv32_1/conv1/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.02146628510718252,"min":3.1103382110595703}},{"name":"conv32_1/conv1/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0194976619645661,"min":-2.3787147596770644}},{"name":"conv32_1/conv2/conv/filters","shape":[3,3,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0004114975824075587,"min":-0.05267169054816751}},{"name":"conv32_1/conv2/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":4.600177166424806e-9,"min":-5.70421968636676e-7}},{"name":"conv32_1/conv2/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.03400764932819441,"min":2.1677730083465576}},{"name":"conv32_1/conv2/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010974494616190593,"min":-1.240117891629537}},{"name":"conv32_2/conv1/conv/filters","shape":[3,3,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0005358753251094444,"min":-0.0760942961655411}},{"name":"conv32_2/conv1/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":5.9886454383719385e-9,"min":-7.366033889197485e-7}},{"name":"conv32_2/conv1/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014633869657329485,"min":2.769575357437134}},{"name":"conv32_2/conv1/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.022131107367721257,"min":-2.5229462399202234}},{"name":"conv32_2/conv2/conv/filters","shape":[3,3,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00030145110452876373,"min":-0.03949009469326805}},{"name":"conv32_2/conv2/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":6.8779549306497095e-9,"min":-9.010120959151119e-7}},{"name":"conv32_2/conv2/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.03929369870354148,"min":4.8010945320129395}},{"name":"conv32_2/conv2/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010553357180427103,"min":-1.2452961472903983}},{"name":"conv32_3/conv1/conv/filters","shape":[3,3,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0003133527642371608,"min":-0.040735859350830905}},{"name":"conv32_3/conv1/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":4.1064200719547974e-9,"min":-3.0387508532465503e-7}},{"name":"conv32_3/conv1/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009252088210161994,"min":2.333256721496582}},{"name":"conv32_3/conv1/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007104101251153385,"min":-0.34810096130651585}},{"name":"conv32_3/conv2/conv/filters","shape":[3,3,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00029995629892629733,"min":-0.031195455088334923}},{"name":"conv32_3/conv2/conv/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":5.62726418316814e-9,"min":-6.921534945296811e-7}},{"name":"conv32_3/conv2/scale/weights","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0467432975769043,"min":5.362040996551514}},{"name":"conv32_3/conv2/scale/biases","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010314425300149357,"min":-1.268674311918371}},{"name":"conv64_down/conv1/conv/filters","shape":[3,3,32,64],"dtype":"float32"},{"name":"conv64_down/conv1/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":8.373908033218849e-10,"min":-1.172347124650639e-7}},{"name":"conv64_down/conv1/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0066875364266189875,"min":2.5088400840759277}},{"name":"conv64_down/conv1/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01691421620986041,"min":-2.0973628100226906}},{"name":"conv64_down/conv2/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_down/conv2/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":2.3252014483766877e-9,"min":-2.673981665633191e-7}},{"name":"conv64_down/conv2/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.032557439804077146,"min":2.6351239681243896}},{"name":"conv64_down/conv2/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015429047509735706,"min":-1.5429047509735707}},{"name":"conv64_1/conv1/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_1/conv1/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":1.1319172039756998e-9,"min":-1.4941307092479238e-7}},{"name":"conv64_1/conv1/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007802607031429515,"min":3.401733160018921}},{"name":"conv64_1/conv1/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01425027146058924,"min":-0.6982633015688727}},{"name":"conv64_1/conv2/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_1/conv2/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":2.5635019893325435e-9,"min":-2.717312108692496e-7}},{"name":"conv64_1/conv2/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.04062801716374416,"min":3.542381525039673}},{"name":"conv64_1/conv2/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007973166306813557,"min":-0.7415044665336609}},{"name":"conv64_2/conv1/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_2/conv1/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":1.2535732661062331e-9,"min":-1.8302169685151004e-7}},{"name":"conv64_2/conv1/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005631206549850164,"min":2.9051668643951416}},{"name":"conv64_2/conv1/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01859012585060269,"min":-2.3795361088771445}},{"name":"conv64_2/conv2/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_2/conv2/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":2.486726369919351e-9,"min":-3.5311514452854786e-7}},{"name":"conv64_2/conv2/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.03740917467603497,"min":5.571568965911865}},{"name":"conv64_2/conv2/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006418555858088475,"min":-0.5263215803632549}},{"name":"conv64_3/conv1/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_3/conv1/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":7.432564576875473e-10,"min":-8.47312361763804e-8}},{"name":"conv64_3/conv1/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006400122362024644,"min":2.268010377883911}},{"name":"conv64_3/conv1/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010945847922680425,"min":-1.3353934465670119}},{"name":"conv64_3/conv2/conv/filters","shape":[3,3,64,64],"dtype":"float32"},{"name":"conv64_3/conv2/conv/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":2.278228722014533e-9,"min":-3.212302498040492e-7}},{"name":"conv64_3/conv2/scale/weights","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.029840927498013366,"min":7.038398265838623}},{"name":"conv64_3/conv2/scale/biases","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010651412197187834,"min":-1.161003929493474}},{"name":"conv128_down/conv1/conv/filters","shape":[3,3,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00020040544662989823,"min":-0.022245004575918704}},{"name":"conv128_down/conv1/conv/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":4.3550543563576545e-10,"min":-4.311503812794078e-8}},{"name":"conv128_down/conv1/scale/weights","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007448580685783835,"min":2.830846071243286}},{"name":"conv128_down/conv1/scale/biases","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01211262824488621,"min":-1.6957679542840696}},{"name":"conv128_down/conv2/conv/filters","shape":[3,3,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00022380277514457702,"min":-0.02484210804104805}},{"name":"conv128_down/conv2/conv/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":9.031058637304466e-10,"min":-1.1650065642122761e-7}},{"name":"conv128_down/conv2/scale/weights","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.027663578706629135,"min":3.1111555099487305}},{"name":"conv128_down/conv2/scale/biases","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008878476946961646,"min":-1.029903325847551}},{"name":"conv128_1/conv1/conv/filters","shape":[3,3,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00022380667574265425,"min":-0.032899581334170175}},{"name":"conv128_1/conv1/conv/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":4.4147297756478345e-10,"min":-5.253528433020923e-8}},{"name":"conv128_1/conv1/scale/weights","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013599334978589825,"min":3.634530782699585}},{"name":"conv128_1/conv1/scale/biases","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014059314073300829,"min":-1.4059314073300828}},{"name":"conv128_1/conv2/conv/filters","shape":[3,3,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00021715293474057143,"min":-0.02909849325523657}},{"name":"conv128_1/conv2/conv/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":9.887046963276768e-10,"min":-1.1370104007768284e-7}},{"name":"conv128_1/conv2/scale/weights","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.029993299409454943,"min":3.630716562271118}},{"name":"conv128_1/conv2/scale/biases","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00782704236460667,"min":-0.7200878975438136}},{"name":"conv128_2/conv1/conv/filters","shape":[3,3,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00017718105923895743,"min":-0.022324813464108636}},{"name":"conv128_2/conv1/conv/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":3.567012027797675e-10,"min":-5.243507680862582e-8}},{"name":"conv128_2/conv1/scale/weights","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007940645778880399,"min":4.927767753601074}},{"name":"conv128_2/conv1/scale/biases","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015933452867994122,"min":-1.5614783810634238}},{"name":"conv128_2/conv2/conv/filters","shape":[3,3,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0001451439717236687,"min":-0.01712698866339291}},{"name":"conv128_2/conv2/conv/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":1.0383988570966347e-9,"min":-1.2356946399449953e-7}},{"name":"conv128_2/conv2/scale/weights","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.02892604528688917,"min":4.750600814819336}},{"name":"conv128_2/conv2/scale/biases","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00797275748907351,"min":-0.7414664464838364}},{"name":"conv256_down/conv1/conv/filters","shape":[3,3,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0002698827827093648,"min":-0.03994265184098599}},{"name":"conv256_down/conv1/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":5.036909834755123e-10,"min":-6.396875490139006e-8}},{"name":"conv256_down/conv1/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014870181738161573,"min":4.269900798797607}},{"name":"conv256_down/conv1/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.022031106200872685,"min":-3.1063859743230484}},{"name":"conv256_down/conv2/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00046430734150549946,"min":-0.03946612402796745}},{"name":"conv256_down/conv2/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":6.693064577513153e-10,"min":-7.630093618364995e-8}},{"name":"conv256_down/conv2/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.03475512242784687,"min":3.608360528945923}},{"name":"conv256_down/conv2/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01290142021927179,"min":-1.1482263995151893}},{"name":"conv256_1/conv1/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00037147209924810076,"min":-0.04234781931428348}},{"name":"conv256_1/conv1/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":3.2105515457510146e-10,"min":-3.467395669411096e-8}},{"name":"conv256_1/conv1/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.043242172166412955,"min":5.28542947769165}},{"name":"conv256_1/conv1/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01643658619300992,"min":-1.3149268954407936}},{"name":"conv256_1/conv2/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0003289232651392619,"min":-0.041773254672686264}},{"name":"conv256_1/conv2/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":9.13591691187321e-10,"min":-1.2333487831028833e-7}},{"name":"conv256_1/conv2/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0573908618852204,"min":4.360693454742432}},{"name":"conv256_1/conv2/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0164216583850337,"min":-1.3958409627278647}},{"name":"conv256_2/conv1/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00010476927912118389,"min":-0.015610622589056398}},{"name":"conv256_2/conv1/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":2.418552539068639e-10,"min":-2.539480166022071e-8}},{"name":"conv256_2/conv1/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.06024209564807368,"min":6.598613739013672}},{"name":"conv256_2/conv1/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01578534350675695,"min":-1.1049740454729864}},{"name":"conv256_2/conv2/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00005543030908002573,"min":-0.007427661416723448}},{"name":"conv256_2/conv2/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":1.0822061852320308e-9,"min":-1.515088659324843e-7}},{"name":"conv256_2/conv2/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.04302893993901272,"min":2.2855491638183594}},{"name":"conv256_2/conv2/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006792667566561232,"min":-0.8083274404207865}},{"name":"conv256_down_out/conv1/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.000568966465253456,"min":-0.05632768006009214}},{"name":"conv256_down_out/conv1/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":4.5347887884881677e-10,"min":-6.530095855422961e-8}},{"name":"conv256_down_out/conv1/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.017565592597512638,"min":4.594101905822754}},{"name":"conv256_down_out/conv1/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.04850864223405427,"min":-6.306123490427055}},{"name":"conv256_down_out/conv2/conv/filters","shape":[3,3,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0003739110687199761,"min":-0.06954745878191555}},{"name":"conv256_down_out/conv2/conv/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":1.2668428328152895e-9,"min":-2.2549802424112154e-7}},{"name":"conv256_down_out/conv2/scale/weights","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.04351314469879749,"min":4.31956672668457}},{"name":"conv256_down_out/conv2/scale/biases","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021499746921015722,"min":-1.2039858275768804}},{"name":"fc","shape":[256,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.000357687911566566,"min":-0.04578405268052045}}],"paths":["face_recognition_model-shard1","face_recognition_model-shard2"]}] -------------------------------------------------------------------------------- /public/models/mtcnn_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/mtcnn_model-shard1 -------------------------------------------------------------------------------- /public/models/mtcnn_model-weights_manifest.json: -------------------------------------------------------------------------------- 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]}]}] -------------------------------------------------------------------------------- /public/models/ssd_mobilenetv1_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/ssd_mobilenetv1_model-shard1 -------------------------------------------------------------------------------- /public/models/ssd_mobilenetv1_model-shard2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/ssd_mobilenetv1_model-shard2 -------------------------------------------------------------------------------- /public/models/ssd_mobilenetv1_model-weights_manifest.json: -------------------------------------------------------------------------------- 1 | [{"paths":["ssd_mobilenetv1_model-shard1","ssd_mobilenetv1_model-shard2"],"weights":[{"dtype":"float32","shape":[1,1,512,9],"quantization":{"scale":0.0026856216729856004,"min":-0.34107395246917127,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/ClassPredictor/weights"},{"dtype":"float32","shape":[9],"quantization":{"scale":0.00198518248165355,"min":-0.32159956202787515,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/ClassPredictor/biases"},{"dtype":"float32","shape":[1,1,1024,18],"quantization":{"scale":0.003060340296988394,"min":-0.489654447518143,"dtype":"uint8"},"name":"Prediction/BoxPredictor_1/ClassPredictor/weights"},{"dtype":"float32","shape":[18],"quantization":{"scale":0.0008040678851744708,"min":-0.12221831854651957,"dtype":"uint8"},"name":"Prediction/BoxPredictor_1/ClassPredictor/biases"},{"dtype":"float32","shape":[1,1,512,18],"quantization":{"scale":0.0012513800578958848,"min":-0.16017664741067325,"dtype":"uint8"},"name":"Prediction/BoxPredictor_2/ClassPredictor/weights"},{"dtype":"float32","shape":[18],"quantization":{"scale":0.000338070518245884,"min":-0.05510549447407909,"dtype":"uint8"},"name":"Prediction/BoxPredictor_2/ClassPredictor/biases"},{"dtype":"float32","shape":[1,1,256,18],"quantization":{"scale":0.0011819932975021064,"min":-0.1453851755927591,"dtype":"uint8"},"name":"Prediction/BoxPredictor_3/ClassPredictor/weights"},{"dtype":"float32","shape":[18],"quantization":{"scale":0.00015985782386041154,"min":-0.026536398760828316,"dtype":"uint8"},"name":"Prediction/BoxPredictor_3/ClassPredictor/biases"},{"dtype":"float32","shape":[1,1,256,18],"quantization":{"scale":0.0007035591438704846,"min":-0.08513065640832863,"dtype":"uint8"},"name":"Prediction/BoxPredictor_4/ClassPredictor/weights"},{"dtype":"float32","shape":[18],"quantization":{"scale":0.00008793946574716008,"min":-0.013190919862074012,"dtype":"uint8"},"name":"Prediction/BoxPredictor_4/ClassPredictor/biases"},{"dtype":"float32","shape":[1,1,128,18],"quantization":{"scale":0.00081320781918133,"min":-0.11059626340866088,"dtype":"uint8"},"name":"Prediction/BoxPredictor_5/ClassPredictor/weights"},{"dtype":"float32","shape":[18],"quantization":{"scale":0.0000980533805547976,"min":-0.014609953702664841,"dtype":"uint8"},"name":"Prediction/BoxPredictor_5/ClassPredictor/biases"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":3,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/stack_1/2"},{"dtype":"int32","shape":[3],"quantization":{"scale":0.00392156862745098,"min":0,"dtype":"uint8"},"name":"Postprocessor/Slice/begin"},{"dtype":"int32","shape":[3],"quantization":{"scale":1,"min":-1,"dtype":"uint8"},"name":"Postprocessor/Slice/size"},{"dtype":"float32","shape":[1,1,512,12],"quantization":{"scale":0.003730384859384275,"min":-0.4327246436885759,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/BoxEncodingPredictor/weights"},{"dtype":"float32","shape":[12],"quantization":{"scale":0.0018744708568442102,"min":-0.3917644090804399,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/BoxEncodingPredictor/biases"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":3072,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/stack_1/1"},{"dtype":"float32","shape":[1,1,1024,24],"quantization":{"scale":0.00157488017689948,"min":-0.20000978246623397,"dtype":"uint8"},"name":"Prediction/BoxPredictor_1/BoxEncodingPredictor/weights"},{"dtype":"float32","shape":[24],"quantization":{"scale":0.0002823906713256649,"min":-0.043488163384152394,"dtype":"uint8"},"name":"Prediction/BoxPredictor_1/BoxEncodingPredictor/biases"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":1536,"dtype":"uint8"},"name":"Prediction/BoxPredictor_1/stack_1/1"},{"dtype":"float32","shape":[1,1,512,24],"quantization":{"scale":0.0007974451663447361,"min":-0.11004743295557358,"dtype":"uint8"},"name":"Prediction/BoxPredictor_2/BoxEncodingPredictor/weights"},{"dtype":"float32","shape":[24],"quantization":{"scale":0.0001350417988849621,"min":-0.02039131163162928,"dtype":"uint8"},"name":"Prediction/BoxPredictor_2/BoxEncodingPredictor/biases"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":384,"dtype":"uint8"},"name":"Prediction/BoxPredictor_2/stack_1/1"},{"dtype":"float32","shape":[1,1,256,24],"quantization":{"scale":0.0007113990246080885,"min":-0.0860792819775787,"dtype":"uint8"},"name":"Prediction/BoxPredictor_3/BoxEncodingPredictor/weights"},{"dtype":"float32","shape":[24],"quantization":{"scale":0.000050115815418608046,"min":-0.007617603943628423,"dtype":"uint8"},"name":"Prediction/BoxPredictor_3/BoxEncodingPredictor/biases"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":96,"dtype":"uint8"},"name":"Prediction/BoxPredictor_3/stack_1/1"},{"dtype":"float32","shape":[1,1,256,24],"quantization":{"scale":0.000590049314732645,"min":-0.06903576982371946,"dtype":"uint8"},"name":"Prediction/BoxPredictor_4/BoxEncodingPredictor/weights"},{"dtype":"float32","shape":[24],"quantization":{"scale":0.00003513663861097074,"min":-0.006359731588585704,"dtype":"uint8"},"name":"Prediction/BoxPredictor_4/BoxEncodingPredictor/biases"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":24,"dtype":"uint8"},"name":"Prediction/BoxPredictor_4/stack_1/1"},{"dtype":"float32","shape":[1,1,128,24],"quantization":{"scale":0.0005990567744946948,"min":-0.07907549423329971,"dtype":"uint8"},"name":"Prediction/BoxPredictor_5/BoxEncodingPredictor/weights"},{"dtype":"float32","shape":[24],"quantization":{"scale":0.00003392884288640583,"min":-0.006039334033780238,"dtype":"uint8"},"name":"Prediction/BoxPredictor_5/BoxEncodingPredictor/biases"},{"dtype":"float32","shape":[],"quantization":{"scale":1,"min":0.007843137718737125,"dtype":"uint8"},"name":"Preprocessor/mul/x"},{"dtype":"int32","shape":[2],"quantization":{"scale":1,"min":512,"dtype":"uint8"},"name":"Preprocessor/ResizeImage/size"},{"dtype":"float32","shape":[],"quantization":{"scale":1,"min":1,"dtype":"uint8"},"name":"Preprocessor/sub/y"},{"dtype":"float32","shape":[3,3,3,32],"quantization":{"scale":0.03948551065781537,"min":-5.014659853542552,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_0_pointwise/weights"},{"dtype":"float32","shape":[32],"quantization":{"scale":0.0498106133704092,"min":-7.371970778820562,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_0_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,32,1],"quantization":{"scale":0.036833542468501075,"min":-4.714693435968138,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_depthwise/depthwise_weights"},{"dtype":"float32","shape":[32],"quantization":{"scale":0.012173276705046495,"min":-0.012173276705046495,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[32],"quantization":{"scale":0.032182769214405736,"min":-2.4780732295092416,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[32],"quantization":{"scale":0.028287527607936486,"min":-3.366215785344442,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[32],"quantization":{"scale":0.04716738532571232,"min":3.9071404665769224e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,32,64],"quantization":{"scale":0.04010109433940812,"min":-4.290817094316669,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_pointwise/weights"},{"dtype":"float32","shape":[64],"quantization":{"scale":0.2212210038129021,"min":-34.51047659481273,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_1_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,64,1],"quantization":{"scale":0.010024750933927648,"min":-1.343316625146305,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_depthwise/depthwise_weights"},{"dtype":"float32","shape":[64],"quantization":{"scale":0.006120916675118839,"min":0.5227176547050476,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[64],"quantization":{"scale":0.02317035385206634,"min":-0.7646216771181892,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[64],"quantization":{"scale":0.04980821422502106,"min":-5.8275610643274645,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[64],"quantization":{"scale":0.051751047022202436,"min":3.916113799002297e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,64,128],"quantization":{"scale":0.021979344124887504,"min":-2.1319963801140878,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_pointwise/weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.09958663267247816,"min":-11.054116226645077,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_2_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,128,1],"quantization":{"scale":0.01943492702409333,"min":-2.6237151482525993,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_depthwise/depthwise_weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.017852897737540452,"min":0.40204083919525146,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.029888209174661076,"min":-1.972621805527631,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.029319268581913967,"min":-5.130872001834945,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.014018708584355373,"min":3.9083178263362604e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,128,128],"quantization":{"scale":0.020776657964669022,"min":-2.5347522716896207,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_pointwise/weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.14383157094319662,"min":-9.636715253194174,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_3_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,128,1],"quantization":{"scale":0.004463558571011412,"min":-0.5981168485155293,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_depthwise/depthwise_weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.006487431245691636,"min":0.47910428047180176,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.026542164297664865,"min":-1.2209395576925839,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.05119945675719018,"min":-8.60150873520795,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.03081628388049556,"min":3.911508751095344e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,128,256],"quantization":{"scale":0.010758659886378868,"min":-1.0328313490923713,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_pointwise/weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.08058219610476026,"min":-9.34753474815219,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_4_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,256,1],"quantization":{"scale":0.01145936741548426,"min":-1.3292866201961742,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_depthwise/depthwise_weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.0083988838336047,"min":0.36280909180641174,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.02858148649627087,"min":-3.6584302715226715,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.03988401375564874,"min":-7.099354448505476,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.009090481683904049,"min":0.020878996700048447,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,256,256],"quantization":{"scale":0.008951201625898773,"min":-1.1189002032373465,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_pointwise/weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.051758006974762565,"min":-5.745138774198645,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_5_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,256,1],"quantization":{"scale":0.004110433190476661,"min":-0.6042336790000691,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_depthwise/depthwise_weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.013170199768216002,"min":0.3386639356613159,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.03599378548416437,"min":-3.70735990486893,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.026967673208199296,"min":-3.748506575939702,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.012615410486857097,"min":3.9111388979838637e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,256,512],"quantization":{"scale":0.00822840648538926,"min":-1.1848905338960536,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.06608965817619772,"min":-7.468131373910342,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_6_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,512,1],"quantization":{"scale":0.008801074355256323,"min":-0.9593171047229393,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_depthwise/depthwise_weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.030577416513480393,"min":0.3285980224609375,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.04778536441279393,"min":-8.935863145192464,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.04331884945140165,"min":-9.660103427662568,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.04126455444367785,"min":0.000604183878749609,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,512,512],"quantization":{"scale":0.009305818408143287,"min":-1.1446156642016243,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.04640720217835669,"min":-4.733534622192383,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_7_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,512,1],"quantization":{"scale":0.008138792655047248,"min":-0.9766551186056698,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_depthwise/depthwise_weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.027351748358969596,"min":0.34030041098594666,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.04415061053107767,"min":-7.019947074441349,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.02476683784933651,"min":-2.9224868662217083,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.02547598832684076,"min":0.00026032101595774293,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,512,512],"quantization":{"scale":0.01083052625843123,"min":-1.2563410459780227,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.06360894371481503,"min":-7.951117964351878,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_8_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,512,1],"quantization":{"scale":0.006704086883395326,"min":-0.8648272079579971,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_depthwise/depthwise_weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.015343831567203297,"min":0.2711026668548584,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.03378283930759804,"min":-4.797163181678922,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.021910778213949763,"min":-3.987761634938857,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.009284070410007296,"min":0.000021581046894425526,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,512,512],"quantization":{"scale":0.012783036979974485,"min":-1.9046725100161983,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.07273082733154297,"min":-9.52773838043213,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_9_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,512,1],"quantization":{"scale":0.006126228033327589,"min":-0.7351473639993107,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_depthwise/depthwise_weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.029703759212119908,"min":0.28687000274658203,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.04394429898729511,"min":-6.3279790541704966,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.016566915605582443,"min":-2.7501079905266854,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.012152872833551145,"min":3.913338286370366e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,512,512],"quantization":{"scale":0.01354524388032801,"min":-1.7473364605623134,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.08566816367355047,"min":-9.937506986131854,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_10_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,512,1],"quantization":{"scale":0.006012305558896532,"min":-0.7876120282154457,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_depthwise/depthwise_weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.01469323155926723,"min":0.29223933815956116,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.030889174517463234,"min":-3.2433633243336395,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.014836942448335536,"min":-2.047498057870304,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.007234466105343445,"min":0.00013165915152058005,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,512,512],"quantization":{"scale":0.016261722527298274,"min":-1.4798167499841428,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.091437328563017,"min":-14.172785927267636,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_11_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,512,1],"quantization":{"scale":0.004750356487199372,"min":-0.650798838746314,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_depthwise/depthwise_weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.008174965545242907,"min":0.3120670020580292,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.030133422215779623,"min":-2.41067377726237,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.006088157261119169,"min":-0.7853722866843729,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.003668997334498985,"min":3.9124486300013356e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,512,1024],"quantization":{"scale":0.010959514449624454,"min":-1.4028178495519301,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_pointwise/weights"},{"dtype":"float32","shape":[1024],"quantization":{"scale":0.10896045834410424,"min":-14.818622334798176,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_12_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,1024,1],"quantization":{"scale":0.004633033509347953,"min":-0.5652300881404502,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_depthwise/depthwise_weights"},{"dtype":"float32","shape":[1024],"quantization":{"scale":0.022285057224479377,"min":0.23505790531635284,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_depthwise/BatchNorm/gamma"},{"dtype":"float32","shape":[1024],"quantization":{"scale":0.0324854850769043,"min":-3.9957146644592285,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_depthwise/BatchNorm/beta"},{"dtype":"float32","shape":[1024],"quantization":{"scale":0.014760061806323482,"min":-2.125448900110581,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_depthwise/BatchNorm/moving_mean"},{"dtype":"float32","shape":[1024],"quantization":{"scale":0.0036057423142825855,"min":3.9067056828997994e-36,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_depthwise/BatchNorm/moving_variance"},{"dtype":"float32","shape":[1,1,1024,1024],"quantization":{"scale":0.017311988157384536,"min":-2.094750567043529,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_pointwise/weights"},{"dtype":"float32","shape":[1024],"quantization":{"scale":0.16447528764313343,"min":-25.658144872328815,"dtype":"uint8"},"name":"MobilenetV1/Conv2d_13_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[1,1,1024,256],"quantization":{"scale":0.0026493051472832175,"min":-0.36825341547236723,"dtype":"uint8"},"name":"Prediction/Conv2d_0_pointwise/weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.012474596734140433,"min":-2.3078003958159803,"dtype":"uint8"},"name":"Prediction/Conv2d_0_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,256,512],"quantization":{"scale":0.014533351449405445,"min":-1.8166689311756807,"dtype":"uint8"},"name":"Prediction/Conv2d_1_pointwise/weights"},{"dtype":"float32","shape":[512],"quantization":{"scale":0.024268776762719248,"min":-2.4754152297973633,"dtype":"uint8"},"name":"Prediction/Conv2d_1_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[1,1,512,128],"quantization":{"scale":0.002208403746287028,"min":-0.28709248701731366,"dtype":"uint8"},"name":"Prediction/Conv2d_2_pointwise/weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.012451349052728392,"min":-1.5937726787492341,"dtype":"uint8"},"name":"Prediction/Conv2d_2_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,128,256],"quantization":{"scale":0.026334229637594783,"min":-2.8967652601354263,"dtype":"uint8"},"name":"Prediction/Conv2d_3_pointwise/weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.02509917792151956,"min":-1.4055539636050953,"dtype":"uint8"},"name":"Prediction/Conv2d_3_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[1,1,256,128],"quantization":{"scale":0.004565340046789132,"min":-0.3971845840706545,"dtype":"uint8"},"name":"Prediction/Conv2d_4_pointwise/weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.017302456556581983,"min":-2.5953684834872974,"dtype":"uint8"},"name":"Prediction/Conv2d_4_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,128,256],"quantization":{"scale":0.025347338470758176,"min":-3.8527954475552426,"dtype":"uint8"},"name":"Prediction/Conv2d_5_pointwise/weights"},{"dtype":"float32","shape":[256],"quantization":{"scale":0.033134659598855414,"min":-2.9158500446992766,"dtype":"uint8"},"name":"Prediction/Conv2d_5_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[1,1,256,64],"quantization":{"scale":0.002493104397081861,"min":-0.2817207968702503,"dtype":"uint8"},"name":"Prediction/Conv2d_6_pointwise/weights"},{"dtype":"float32","shape":[64],"quantization":{"scale":0.011383360974928912,"min":-1.2749364291920382,"dtype":"uint8"},"name":"Prediction/Conv2d_6_pointwise/convolution_bn_offset"},{"dtype":"float32","shape":[3,3,64,128],"quantization":{"scale":0.020821522731407017,"min":-2.7484410005457263,"dtype":"uint8"},"name":"Prediction/Conv2d_7_pointwise/weights"},{"dtype":"float32","shape":[128],"quantization":{"scale":0.052144218893612135,"min":-3.5979511036592373,"dtype":"uint8"},"name":"Prediction/Conv2d_7_pointwise/convolution_bn_offset"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":6,"dtype":"uint8"},"name":"Prediction/BoxPredictor_5/stack_1/1"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":1,"dtype":"uint8"},"name":"concat_1/axis"},{"dtype":"int32","shape":[1],"quantization":{"scale":1,"min":0,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/strided_slice/stack"},{"dtype":"int32","shape":[1],"quantization":{"scale":1,"min":1,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/strided_slice/stack_1"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":5118,"dtype":"uint8"},"name":"Postprocessor/stack/1"},{"dtype":"int32","shape":[],"quantization":{"scale":1,"min":4,"dtype":"uint8"},"name":"Prediction/BoxPredictor_0/stack/3"},{"dtype":"float32","shape":[1, 5118, 4],"name":"Output/extra_dim"}]}] -------------------------------------------------------------------------------- /public/models/tiny_face_detector_model-shard1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/public/models/tiny_face_detector_model-shard1 -------------------------------------------------------------------------------- /public/models/tiny_face_detector_model-weights_manifest.json: -------------------------------------------------------------------------------- 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"]}] -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GlobalStyle from './globalStyle'; 3 | 4 | // Contexts 5 | import { Provider as AudioPlayerProvider } from './contexts/audioPlayer'; 6 | 7 | // Hooks 8 | import useGame from './useGame'; 9 | 10 | // Pages 11 | import RankingPage from './pages/ranking'; 12 | import GameEndPage from './pages/gameEnd'; 13 | import GameStartPage from './pages/gameStart'; 14 | import InGamePage from './pages/inGame'; 15 | import ScanningPage from './pages/scanning'; 16 | 17 | // Components 18 | import { Container, ToastMessage } from './styledComponents'; 19 | 20 | function App() { 21 | const { 22 | videoRef, 23 | handlePlayGame, 24 | player, 25 | page, 26 | toastMessage, 27 | setToastMessage, 28 | handleGoHome, 29 | handleGoRanking, 30 | handleScanningFace, 31 | gamePlayId, 32 | gameDrawHandler, 33 | handleGameEnd, 34 | } = useGame(); 35 | return ( 36 | 37 | 38 | 39 | {toastMessage && {toastMessage}} 40 | {page === 'main' ? ( 41 | 47 | ) : page === 'ranking' ? ( 48 | 49 | ) : page === 'scanning' ? ( 50 | 55 | ) : page === 'in-game' ? ( 56 | 62 | ) : page === 'end' ? ( 63 | 64 | ) : null} 65 | 66 | 68 | ); 69 | } 70 | 71 | export default App; 72 | -------------------------------------------------------------------------------- /src/api/face.ts: -------------------------------------------------------------------------------- 1 | import * as faceapi from 'face-api.js'; 2 | import { C3FaceMatch } from '../modules/chamchamcham'; 3 | import store from './index'; 4 | 5 | interface IFaceMatcher { 6 | id: string; 7 | data: any; 8 | } 9 | 10 | export async function getBestMatchCollection( 11 | faceMatch: C3FaceMatch 12 | ): Promise { 13 | const matchers = ( 14 | (await store.getItem('faceMatches')) || [] 15 | ).map((matches) => { 16 | const faceMatcher = faceapi.FaceMatcher.fromJSON(matches.data); 17 | return { 18 | faceMatcher, 19 | distance: faceMatcher.findBestMatch(faceMatch.descriptor).distance, 20 | }; 21 | }); 22 | const [bestMatcher] = matchers 23 | .filter(({ distance, faceMatcher }) => { 24 | return distance < faceMatcher.distanceThreshold; 25 | }) 26 | .sort((a, b) => { 27 | return a.distance - b.distance; 28 | }); 29 | return bestMatcher ? bestMatcher.faceMatcher : null; 30 | } 31 | 32 | export async function setBestMatchCollection( 33 | id: string, 34 | faceMatcher: faceapi.FaceMatcher 35 | ) { 36 | const faceMatchers = 37 | (await store.getItem('faceMatches')) || []; 38 | if (!faceMatchers.some((match) => match.id === id)) { 39 | await store.setItem('faceMatches', [ 40 | ...faceMatchers, 41 | { 42 | id, 43 | data: faceMatcher.toJSON(), 44 | }, 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import localforage from 'localforage'; 2 | 3 | export default localforage.createInstance({ 4 | name: 'chamchamcham', 5 | }); 6 | -------------------------------------------------------------------------------- /src/api/rank.ts: -------------------------------------------------------------------------------- 1 | import store from './index'; 2 | import shortid from 'shortid'; 3 | 4 | export interface IRank { 5 | id: string; 6 | name: string; 7 | createdAt: Date; 8 | image: Blob; 9 | point: number; 10 | } 11 | 12 | export interface IFormatRank extends IRank { 13 | joint: number; 14 | rank: number; 15 | } 16 | 17 | export async function getRankings(): Promise { 18 | return (await store.getItem('rank')) || []; 19 | } 20 | 21 | export function formatRankingsSelector(ranks: IRank[]): IFormatRank[] { 22 | const sortRank = ranks.slice().sort((a, b) => { 23 | return b.point - a.point; 24 | }); 25 | const rankMapSet = Array.from(new Set(sortRank.map((rank) => rank.point))); 26 | return sortRank.map((rank, _index, allRanks) => { 27 | return { 28 | ...rank, 29 | joint: rankMapSet.indexOf(rank.point), 30 | rank: allRanks.findIndex(({ point }) => point === rank.point), 31 | }; 32 | }); 33 | } 34 | 35 | export async function getFormatRankings(): Promise { 36 | return formatRankingsSelector(await getRankings()); 37 | } 38 | 39 | export async function getFormatRankById( 40 | id: string 41 | ): Promise { 42 | return (await getFormatRankings()).find((rank) => rank.id === id); 43 | } 44 | 45 | export async function addRanking(rank: Omit): Promise { 46 | const ranks = await getRankings(); 47 | const id = shortid.generate(); 48 | await store.setItem('rank', [ 49 | ...ranks, 50 | { 51 | ...rank, 52 | id, 53 | }, 54 | ]); 55 | return id; 56 | } 57 | -------------------------------------------------------------------------------- /src/components/animationNumber.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface IProps { 4 | value: number; 5 | max: number; 6 | } 7 | 8 | export default function AnimationNumber({ value, max }: IProps) { 9 | const [showValue, setShowValue] = React.useState(value); 10 | React.useEffect(() => { 11 | let timer = window.requestAnimationFrame(update); 12 | const maxValue = value > max ? max : value; 13 | function update() { 14 | setShowValue((prevValue) => { 15 | if (prevValue + 1 < maxValue) { 16 | timer = window.requestAnimationFrame(update); 17 | } 18 | return prevValue + 1; 19 | }); 20 | } 21 | return () => { 22 | window.cancelAnimationFrame(timer); 23 | }; 24 | }, [value, max]); 25 | return <>{showValue > max ? max : showValue}; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/hand.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { css, keyframes } from 'styled-components'; 3 | import { usePrevious } from 'react-use'; 4 | 5 | // Resources 6 | import centerImage from '../resources/center-hand.png'; 7 | import leftMovingImage from '../resources/left-hand-moving.png'; 8 | import leftImage from '../resources/left-hand.png'; 9 | import rightMovingImage from '../resources/right-hand-moving.png'; 10 | import rightImage from '../resources/right-hand.png'; 11 | import AudioPlayerContext from '../contexts/audioPlayer'; 12 | import { 13 | requestAnimationFrameTimeout, 14 | IAnimationFrameRef, 15 | cancelAnimationFrameTimeout, 16 | } from '../helpers/requestAnimationFrame'; 17 | 18 | type Direction = 'center' | 'right' | 'left'; 19 | 20 | const ANIMATION_TIME = 200; 21 | 22 | function moveHandAnimation( 23 | prevDirection: Direction, 24 | direction: Direction, 25 | dispatch: React.Dispatch 26 | ) { 27 | let timerHandle: IAnimationFrameRef; 28 | let callback = () => cancelAnimationFrameTimeout(timerHandle); 29 | if (direction !== prevDirection) { 30 | const movingImage: HandDirection = 31 | prevDirection === 'center' 32 | ? direction === 'right' 33 | ? 'right-moving' 34 | : 'left-moving' 35 | : prevDirection === 'right' 36 | ? 'right-moving' 37 | : 'left-moving'; 38 | const targetImage: HandDirection = 39 | prevDirection === 'center' 40 | ? direction === 'right' 41 | ? 'right' 42 | : 'left' 43 | : 'center'; 44 | dispatch(movingImage); 45 | timerHandle = requestAnimationFrameTimeout(() => { 46 | dispatch(targetImage); 47 | if (prevDirection !== 'center' && direction !== 'center') { 48 | timerHandle = requestAnimationFrameTimeout(() => { 49 | callback = moveHandAnimation('center', direction, dispatch); 50 | }, ANIMATION_TIME); 51 | } 52 | }, ANIMATION_TIME); 53 | } 54 | return () => callback(); 55 | } 56 | 57 | interface IProps { 58 | direction: Direction; 59 | } 60 | 61 | const handStartKeyframe = keyframes` 62 | from { 63 | transform: translate3d(-50%, 100%, 0); 64 | } 65 | to { 66 | transform: translate3d(-50%, 0%, 0); 67 | } 68 | `; 69 | 70 | const handShakingKeyframe = keyframes` 71 | from { 72 | transform: translate3d(-50%, 0, 0) scale(1); 73 | } 74 | to { 75 | transform: translate3d(-50%, 0, 0) scale(1.1); 76 | } 77 | `; 78 | 79 | const HandWrapper = styled.div` 80 | position: absolute; 81 | z-index: 10; 82 | left: 50%; 83 | transform: translate3d(-50%, 100%, 0); 84 | height: 60%; 85 | bottom: -20px; 86 | pointer-events: none; 87 | animation: ${handStartKeyframe} 500ms both; 88 | width: 100%; 89 | `; 90 | 91 | type HandDirection = 92 | | 'left' 93 | | 'left-moving' 94 | | 'center' 95 | | 'right-moving' 96 | | 'right'; 97 | 98 | const HandImage = styled.img<{ type: HandDirection; visible: boolean }>` 99 | display: block; 100 | height: 100%; 101 | margin: 0 auto; 102 | visibility: ${(props) => (props.visible ? 'visible' : 'hidden')}; 103 | position: absolute; 104 | bottom: 0; 105 | left: 50%; 106 | transform: translate3d(-50%, 0, 0); 107 | ${(props) => 108 | props.type === 'center' && 109 | css` 110 | animation: ${handShakingKeyframe} 500ms infinite alternate-reverse; 111 | `} 112 | `; 113 | 114 | const handImageMap: Record = { 115 | left: leftImage, 116 | 'left-moving': leftMovingImage, 117 | center: centerImage, 118 | 'right-moving': rightMovingImage, 119 | right: rightImage, 120 | }; 121 | 122 | function Hand({ direction }: IProps) { 123 | const audioPlayer = React.useContext(AudioPlayerContext); 124 | const [currentDirection, setCurrentDirection] = React.useState( 125 | 'center' 126 | ); 127 | const prevDirection = usePrevious(direction); 128 | React.useEffect(() => { 129 | if (prevDirection && prevDirection !== direction) { 130 | if (direction !== 'center') { 131 | audioPlayer.play('throw-sound'); 132 | } 133 | return moveHandAnimation(prevDirection, direction, setCurrentDirection); 134 | } 135 | }, [direction]); 136 | return ( 137 | 138 | {(Object.entries(handImageMap) as [HandDirection, string][]).map( 139 | ([key, value]) => ( 140 | 147 | ) 148 | )} 149 | 150 | ); 151 | } 152 | 153 | export default Hand; 154 | -------------------------------------------------------------------------------- /src/components/miniRank.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IFormatRank } from '../api/rank'; 3 | import styled, { css } from 'styled-components'; 4 | import trophyGold from '../resources/trophy-gold.svg'; 5 | import trophySilver from '../resources/trophy-silver.svg'; 6 | import trophyBronze from '../resources/trophy-bronze.svg'; 7 | 8 | const Image = styled.img` 9 | width: 50px; 10 | height: 50px; 11 | object-fit: cover; 12 | transform: scaleX(-1); 13 | border: 2.5px solid #000; 14 | margin-right: 10px; 15 | `; 16 | 17 | const Content = styled.div` 18 | background-color: #726672; 19 | padding: 5px 7.5px; 20 | flex: 1; 21 | display: flex; 22 | align-items: center; 23 | `; 24 | 25 | const Rank = styled.div` 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | width: 57.5px; 30 | background-color: #5f525f; 31 | border-right: 2.5px solid #000; 32 | color: #a19ba1; 33 | font-size: 35px; 34 | img { 35 | width: 40px; 36 | height: 40px; 37 | } 38 | `; 39 | 40 | const PointValue = styled.div` 41 | font-size: 35px; 42 | color: #fff; 43 | -webkit-text-stroke: 1px #000; 44 | text-shadow: 3px 3px 0 #000; 45 | letter-spacing: -0.025em; 46 | `; 47 | 48 | const Wrapper = styled.div<{ mine: boolean }>` 49 | position: relative; 50 | display: flex; 51 | width: 235px; 52 | height: 65px; 53 | border: 2.5px solid #000; 54 | & ~ & { 55 | margin-top: 7.5px; 56 | } 57 | ${(props) => 58 | props.mine && 59 | css` 60 | ${Rank} { 61 | background-color: #ff9821; 62 | } 63 | ${Content} { 64 | background-color: #ffa800; 65 | } 66 | ${Rank} { 67 | color: #fff; 68 | } 69 | `} 70 | `; 71 | 72 | interface IProps { 73 | rank: IFormatRank; 74 | mine?: boolean; 75 | } 76 | 77 | const RankItem = React.forwardRef( 78 | ({ rank, mine }, ref) => { 79 | const imageSrc = React.useMemo(() => URL.createObjectURL(rank.image), [ 80 | rank, 81 | ]); 82 | React.useEffect(() => { 83 | return () => { 84 | URL.revokeObjectURL(imageSrc); 85 | }; 86 | }, [rank]); 87 | const joint = rank.joint + 1; 88 | return ( 89 | 90 | 91 | {joint === 1 ? ( 92 | Gold Medal 93 | ) : joint === 2 ? ( 94 | Silver Medal 95 | ) : joint === 3 ? ( 96 | Bronze Medal 97 | ) : ( 98 | joint 99 | )} 100 | 101 | 102 | {rank.name} 103 | {rank.point * 100} 104 | 105 | 106 | ); 107 | } 108 | ); 109 | 110 | export default React.memo(RankItem); 111 | -------------------------------------------------------------------------------- /src/components/rankItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IFormatRank } from '../api/rank'; 3 | import styled, { css } from 'styled-components'; 4 | import trophyGold from '../resources/trophy-gold.svg'; 5 | import trophySilver from '../resources/trophy-silver.svg'; 6 | import trophyBronze from '../resources/trophy-bronze.svg'; 7 | 8 | const Image = styled.img` 9 | width: 50px; 10 | height: 50px; 11 | object-fit: cover; 12 | transform: scaleX(-1); 13 | border: 2.5px solid #000; 14 | margin-left: 5px; 15 | `; 16 | 17 | const Content = styled.div` 18 | background-color: #726672; 19 | padding: 5px; 20 | flex: 1; 21 | display: flex; 22 | align-items: center; 23 | `; 24 | 25 | const Rank = styled.div` 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | width: 50px; 30 | height: 50px; 31 | background-color: #5f525f; 32 | color: #a19ba1; 33 | font-size: 35px; 34 | `; 35 | 36 | const Name = styled.div` 37 | margin-left: 12px; 38 | font-size: 35px; 39 | color: #fff; 40 | -webkit-text-stroke: 1px #000; 41 | text-shadow: 3px 3px 0 #000; 42 | letter-spacing: -0.025em; 43 | `; 44 | 45 | const Point = styled.div<{ rank: number }>` 46 | width: 158px; 47 | background-color: #5f525f; 48 | padding: 0 15px; 49 | display: flex; 50 | position: relative; 51 | align-items: center; 52 | justify-content: flex-end; 53 | border-left: 2.5px solid #000; 54 | font-size: 35px; 55 | color: #fff; 56 | -webkit-text-stroke: 1px #000; 57 | text-shadow: 3px 3px 0 #000; 58 | letter-spacing: -0.025em; 59 | &::before { 60 | display: block; 61 | left: 30px; 62 | top: 50%; 63 | position: absolute; 64 | transform: translate3d(-50%, -50%, 0); 65 | ${(props) => { 66 | if (props.rank > 3) { 67 | return css` 68 | content: '-'; 69 | `; 70 | } else { 71 | return css` 72 | content: ''; 73 | width: 40px; 74 | height: 40px; 75 | background-image: url("${ 76 | props.rank === 1 77 | ? trophyGold 78 | : props.rank === 2 79 | ? trophySilver 80 | : trophyBronze 81 | }"); 82 | background-size: 100% 100%; 83 | background-repeat: no-repeat; 84 | `; 85 | } 86 | }} 87 | } 88 | `; 89 | 90 | const PointValue = styled.div``; 91 | 92 | const Wrapper = styled.div<{ mine: boolean }>` 93 | position: relative; 94 | display: flex; 95 | border: 2.5px solid #000; 96 | & ~ & { 97 | margin-top: 10px; 98 | } 99 | ${(props) => 100 | props.mine && 101 | css` 102 | ${Rank}, ${Point} { 103 | background-color: #ff9821; 104 | } 105 | ${Content} { 106 | background-color: #ffa800; 107 | } 108 | ${Rank} { 109 | color: #fff; 110 | } 111 | `} 112 | `; 113 | 114 | interface IProps { 115 | rank: IFormatRank; 116 | mine?: boolean; 117 | } 118 | 119 | const RankItem = React.forwardRef( 120 | ({ rank, mine }, ref) => { 121 | const imageSrc = React.useMemo(() => URL.createObjectURL(rank.image), [ 122 | rank, 123 | ]); 124 | React.useEffect(() => { 125 | return () => { 126 | URL.revokeObjectURL(imageSrc); 127 | }; 128 | }, [rank]); 129 | return ( 130 | 131 | 132 | {rank.rank + 1} 133 | {rank.name} 134 | {rank.name} 135 | 136 | 137 | {rank.point * 100} 138 | 139 | 140 | ); 141 | } 142 | ); 143 | 144 | export default React.memo(RankItem); 145 | -------------------------------------------------------------------------------- /src/components/ranks.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { getFormatRankings, IFormatRank } from '../api/rank'; 3 | import RankItem from './rankItem'; 4 | import styled from 'styled-components'; 5 | import anime from 'animejs'; 6 | 7 | const Wrapper = styled.div` 8 | width: 630px; 9 | height: 100%; 10 | margin: 0 auto; 11 | background-color: rgba(0, 0, 0, 0.5); 12 | padding: 10px; 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | `; 17 | 18 | const Message = styled.p` 19 | color: #fff; 20 | font-size: 30px; 21 | letter-spacing: -0.025em; 22 | `; 23 | 24 | const Scroller = styled.div` 25 | max-height: 440px; 26 | height: 100%; 27 | width: 100%; 28 | overflow-y: scroll; 29 | position: relative; 30 | `; 31 | 32 | interface IProps { 33 | mineId?: string; 34 | } 35 | 36 | export default function RankingPage(props: IProps) { 37 | const [isLoading, setLoading] = React.useState(false); 38 | const scrollerRef = React.useRef(null); 39 | const mineItemRef = React.useRef(null); 40 | const [ranks, setRanks] = React.useState(() => []); 41 | React.useEffect(() => { 42 | setLoading(true); 43 | (async () => { 44 | setRanks(await getFormatRankings()); 45 | setLoading(false); 46 | })(); 47 | }, []); 48 | React.useLayoutEffect(() => { 49 | setTimeout(() => { 50 | if (scrollerRef.current && mineItemRef.current) { 51 | anime({ 52 | targets: scrollerRef.current, 53 | scrollTop: 54 | mineItemRef.current.offsetTop - 55 | scrollerRef.current.offsetHeight + 56 | mineItemRef.current.offsetHeight, 57 | duration: 1000, 58 | }); 59 | } 60 | }, 1500); 61 | }, [ranks]); 62 | return ( 63 | 64 | {isLoading ? ( 65 | 로딩 중입니다... 66 | ) : ranks.length ? ( 67 | 68 | {ranks.map((rank) => ( 69 | 75 | ))} 76 | 77 | ) : ( 78 | 랭킹이 존재하지 않습니다. 79 | )} 80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/contexts/audioPlayer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | type VoiceId = 'hello'; 4 | type SoundId = 5 | | 'lose-laugh' 6 | | 'mouth-pop' 7 | | 'pipe-sound' 8 | | 'throw-sound' 9 | | 'wow' 10 | | 'wow2' 11 | | 'wow3' 12 | | 'wow4' 13 | | 'wow5' 14 | | 'wow6' 15 | | 'wow7' 16 | | 'wow8' 17 | | 'cham' 18 | | 'big-cham' 19 | | 'error' 20 | | 'intro' 21 | | 'in-game' 22 | | 'end-game' 23 | | 'scanning-bgm' 24 | | 'start-bgm' 25 | | 'charge' 26 | | 'whooo'; 27 | 28 | // Sounds 29 | const soundMapper: Record = { 30 | 'lose-laugh': require('../resources/sounds/lose-laugh.wav'), 31 | 'mouth-pop': require('../resources/sounds/mouth-pop.wav'), 32 | 'pipe-sound': require('../resources/sounds/pipe.wav'), 33 | 'throw-sound': require('../resources/sounds/throw.wav'), 34 | 'start-bgm': require('../resources/sounds/start-bgm.mp3'), 35 | 'in-game': require('../resources/sounds/in-game.mp3'), 36 | 'end-game': require('../resources/sounds/end-game.mp3'), 37 | 'scanning-bgm': require('../resources/sounds/scanning-bgm.mp3'), 38 | intro: require('../resources/sounds/intro.mp3'), 39 | cham: require('../resources/sounds/cham.mp3'), 40 | 'big-cham': require('../resources/sounds/big-cham.mp3'), 41 | wow: require('../resources/sounds/wow.wav'), 42 | wow2: require('../resources/sounds/wow2.mp3'), 43 | wow3: require('../resources/sounds/wow3.mp3'), 44 | wow4: require('../resources/sounds/wow4.mp3'), 45 | wow5: require('../resources/sounds/wow5.mp3'), 46 | wow6: require('../resources/sounds/wow6.mp3'), 47 | wow7: require('../resources/sounds/wow7.mp3'), 48 | wow8: require('../resources/sounds/wow8.mp3'), 49 | error: require('../resources/sounds/error.wav'), 50 | whooo: require('../resources/sounds/whooo.wav'), 51 | charge: require('../resources/sounds/charge.wav'), 52 | }; 53 | 54 | interface IAudioPlayOption { 55 | loop?: boolean; 56 | } 57 | 58 | interface IAudioPlayerContext { 59 | speak(id: VoiceId | string): void; 60 | play(id: SoundId, options?: IAudioPlayOption): void; 61 | stop(id?: SoundId): void; 62 | } 63 | 64 | const AudioPlayerContext = React.createContext({} as IAudioPlayerContext); 65 | 66 | export default AudioPlayerContext; 67 | 68 | export const Provider = ({ children }: React.PropsWithChildren<{}>) => { 69 | const [speakId, setSpeakId] = React.useState(null); 70 | const soundWrapperRef = React.useRef(null); 71 | React.useEffect(() => { 72 | if (speakId) { 73 | const utter = new SpeechSynthesisUtterance(speakId); 74 | speechSynthesis.speak(utter); 75 | return () => speechSynthesis.cancel(); 76 | } 77 | }, [speakId]); 78 | const getAudioElement = (id: SoundId): HTMLAudioElement | null => { 79 | return soundWrapperRef.current?.querySelector(`[data-id="${id}"]`) || null; 80 | }; 81 | const handlePlayMusic = (id: SoundId, options: IAudioPlayOption) => { 82 | const audio = getAudioElement(id); 83 | if (audio) { 84 | audio.loop = Boolean(options?.loop); 85 | if (!audio.paused) { 86 | audio.pause(); 87 | audio.currentTime = 0; 88 | } 89 | audio.play(); 90 | } 91 | }; 92 | const handleStopMusic = (id?: SoundId) => { 93 | if (id) { 94 | const audio = getAudioElement(id); 95 | if (audio) { 96 | audio.pause(); 97 | audio.currentTime = 0; 98 | } 99 | } else { 100 | (Object.keys(soundMapper) as SoundId[]).forEach((key) => { 101 | handleStopMusic(key); 102 | }); 103 | } 104 | }; 105 | return ( 106 | 112 | {children} 113 |
114 | {Object.entries(soundMapper).map(([key, src]) => ( 115 |
118 |
119 | ); 120 | }; 121 | -------------------------------------------------------------------------------- /src/globalStyle.tsx: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | const webFontUrl = require('./resources/BMHANNAPro.woff'); 3 | 4 | export default createGlobalStyle` 5 | *, ::before, ::after { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | @font-face { 12 | font-family: 'BMHANNAPro'; 13 | src: url('${webFontUrl}') format('woff'); 14 | font-weight: normal; 15 | font-style: normal; 16 | } 17 | 18 | img, 19 | video, 20 | canvas { 21 | max-width: 100%; 22 | } 23 | 24 | body { 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | html { 30 | font-size: 0.69vmax; 31 | } 32 | 33 | html, 34 | body { 35 | width: 100%; 36 | height: 100%; 37 | } 38 | 39 | @keyframes body-background-pattern { 40 | from { 41 | background-position-y: 0; 42 | } 43 | 44 | to { 45 | background-position-y: 57px; 46 | } 47 | } 48 | 49 | body { 50 | font-family: 'BMHANNAPro', sans-serif; 51 | position: relative; 52 | background-color: #ffa800; 53 | background-size: 57px 57px; 54 | background-image: repeating-linear-gradient(-45deg, 55 | transparent, 56 | transparent 20px, 57 | rgba(255, 255, 255, 0.3) 20px, 58 | rgba(255, 255, 255, 0.3) 40px); 59 | animation: 1s body-background-pattern infinite linear; 60 | border: 1rem solid #000; 61 | overflow: hidden; 62 | } 63 | 64 | button { 65 | background-color: transparent; 66 | border: 0; 67 | cursor: pointer; 68 | } 69 | 70 | #root { 71 | width: 100%; 72 | height: 100%; 73 | position: relative; 74 | z-index: 2; 75 | } 76 | 77 | button, 78 | input, 79 | select, 80 | textarea { 81 | font: inherit; 82 | } 83 | 84 | video, 85 | canvas { 86 | transform: translate3d(0, -50%, 0) scaleX(-1); 87 | position: absolute; 88 | width: 100%; 89 | height: auto; 90 | top: 50%; 91 | left: 0; 92 | pointer-events: none; 93 | clip-path: inset(2.5rem); 94 | } 95 | 96 | video { 97 | z-index: -1; 98 | filter: brightness(1.1); 99 | } 100 | 101 | canvas { 102 | z-index: 15; 103 | opacity: 0.5; 104 | } 105 | `; 106 | -------------------------------------------------------------------------------- /src/helpers/createImageFromFaceMatch.ts: -------------------------------------------------------------------------------- 1 | import { C3FaceMatch } from '../modules/chamchamcham'; 2 | 3 | export default function createImageFromFaceMatch( 4 | match: C3FaceMatch, 5 | input: HTMLVideoElement 6 | ): Promise { 7 | return new Promise((resolve) => { 8 | const canvas = document.createElement('canvas'); 9 | canvas.width = Math.floor(match.detection.box.width); 10 | canvas.height = Math.floor(match.detection.box.height); 11 | const context = canvas.getContext('2d') as CanvasRenderingContext2D; 12 | context.drawImage( 13 | input, 14 | Math.floor(match.detection.box.left), 15 | Math.floor(match.detection.box.top), 16 | canvas.width, 17 | canvas.height, 18 | 0, 19 | 0, 20 | canvas.width, 21 | canvas.height 22 | ); 23 | canvas.toBlob((blob) => { 24 | if (blob) { 25 | resolve(blob); 26 | } 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/helpers/createNameFromFaceMatch.ts: -------------------------------------------------------------------------------- 1 | import { C3FaceMatch } from '../modules/chamchamcham'; 2 | 3 | const expressionMap: Record = { 4 | neutral: '평범한', 5 | happy: '행복한', 6 | sad: '슬픈', 7 | angry: '화난', 8 | fearful: '근심가득한', 9 | disgusted: '귀찮은', 10 | surprised: '깜짝놀란', 11 | }; 12 | 13 | export default function createNameFromFaceMatch(match: C3FaceMatch) { 14 | const gender = match.gender === 'male' ? '남' : '녀'; 15 | const expression = 16 | expressionMap[match.expressions.asSortedArray()[0].expression]; 17 | const age = Math.ceil(match.age); 18 | return `${expression} ${age}세 미경${gender}`; 19 | } 20 | -------------------------------------------------------------------------------- /src/helpers/requestAnimationFrame.ts: -------------------------------------------------------------------------------- 1 | export interface IAnimationFrameRef { 2 | value: number; 3 | } 4 | 5 | export function requestAnimationFrameTimeout( 6 | fn: VoidFunction, 7 | delay: number 8 | ): IAnimationFrameRef { 9 | const start = new Date().getTime(); 10 | const handle: IAnimationFrameRef = { 11 | value: 0, 12 | }; 13 | 14 | function loop() { 15 | const current = new Date().getTime(); 16 | const delta = current - start; 17 | if (delta >= delay) { 18 | fn.call(null); 19 | } else { 20 | handle.value = window.requestAnimationFrame(loop); 21 | } 22 | } 23 | 24 | handle.value = window.requestAnimationFrame(loop); 25 | return handle; 26 | } 27 | 28 | export function cancelAnimationFrameTimeout(ref?: IAnimationFrameRef) { 29 | return ref && window.cancelAnimationFrame(ref.value); 30 | } 31 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import ChamChamCham from './modules/chamchamcham'; 5 | 6 | ChamChamCham.loadModel().then(() => { 7 | ReactDOM.render(, document.getElementById('root')); 8 | }); 9 | -------------------------------------------------------------------------------- /src/modules/chamchamcham.ts: -------------------------------------------------------------------------------- 1 | import * as faceapi from 'face-api.js'; 2 | 3 | const MODEL_URL = '/models'; 4 | 5 | export type C3FaceMatch = faceapi.WithAge< 6 | faceapi.WithGender< 7 | faceapi.WithFaceExpressions< 8 | faceapi.WithFaceDescriptor< 9 | faceapi.WithFaceLandmarks> 10 | > 11 | > 12 | > 13 | >; 14 | 15 | export default class ChamChamCham { 16 | public readonly canvas: HTMLCanvasElement; 17 | public readonly context: CanvasRenderingContext2D; 18 | 19 | public constructor( 20 | public readonly input: HTMLVideoElement, 21 | appendedElement: HTMLElement = document.body 22 | ) { 23 | this.canvas = faceapi.createCanvasFromMedia(input); 24 | this.canvas.width = input.videoWidth; 25 | this.canvas.height = input.videoHeight; 26 | this.context = this.canvas.getContext('2d') as CanvasRenderingContext2D; 27 | appendedElement.appendChild(this.canvas); 28 | faceapi.matchDimensions(this.canvas, this.displaySize); 29 | } 30 | 31 | public get displaySize() { 32 | return { 33 | width: this.input.videoWidth, 34 | height: this.input.videoHeight, 35 | }; 36 | } 37 | 38 | public async getDetectSingleFace() { 39 | const detection = await faceapi 40 | .detectSingleFace(this.input, new faceapi.TinyFaceDetectorOptions()) 41 | .withFaceLandmarks() 42 | .withFaceDescriptor() 43 | .withAgeAndGender() 44 | .withFaceExpressions(); 45 | return detection && faceapi.resizeResults(detection, this.displaySize); 46 | } 47 | 48 | public async getDetectAllFace() { 49 | const detections = await faceapi 50 | .detectAllFaces(this.input, new faceapi.TinyFaceDetectorOptions()) 51 | .withFaceLandmarks() 52 | .withFaceDescriptors() 53 | .withAgeAndGender() 54 | .withFaceExpressions(); 55 | return faceapi.resizeResults(detections, this.displaySize); 56 | } 57 | 58 | private getTwoPointDegree(point1: faceapi.Point, point2: faceapi.Point) { 59 | const dx = point2.x - point1.x; 60 | const dy = point2.y - point1.y; 61 | const rad = Math.atan2(dx, dy); 62 | return (rad * 180) / Math.PI; 63 | } 64 | 65 | public getMatchFacePositionType( 66 | detection: C3FaceMatch, 67 | allowable: number = 33.333 68 | ) { 69 | const { nosePosition, faceDegree } = this.getMatchFacePosition(detection); 70 | if (faceDegree > 25) { 71 | return null; 72 | } 73 | return nosePosition < allowable 74 | ? ('left' as const) 75 | : nosePosition > 100 - allowable 76 | ? ('right' as const) 77 | : ('center' as const); 78 | } 79 | 80 | public getMatchFacePosition(detection: C3FaceMatch) { 81 | const landmark = detection.landmarks; 82 | 83 | const nosePoints = landmark.getNose(); 84 | const topNosePoint = nosePoints[0]; 85 | 86 | // Face points 87 | const facePoints = landmark.getJawOutline(); 88 | 89 | // Check face is not a center alignment 90 | const middleFacePoint = facePoints[Math.floor(facePoints.length / 2)]; 91 | const leftFacePoint = facePoints[0]; 92 | const rightFacePoint = facePoints[facePoints.length - 1]; 93 | 94 | const percentOfNosePosition = Math.abs( 95 | ((rightFacePoint.x - topNosePoint.x) / 96 | (leftFacePoint.x - rightFacePoint.x)) * 97 | 100 98 | ); 99 | 100 | return { 101 | faceDegree: Math.abs( 102 | this.getTwoPointDegree(topNosePoint, middleFacePoint) 103 | ), 104 | nosePosition: percentOfNosePosition, 105 | }; 106 | } 107 | 108 | public clear() { 109 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); 110 | } 111 | 112 | public drawLandmark( 113 | detection: Parameters[1] 114 | ) { 115 | this.clear(); 116 | faceapi.draw.drawFaceLandmarks(this.canvas, detection); 117 | } 118 | 119 | public static async loadModel() { 120 | return Promise.all([ 121 | faceapi.loadTinyFaceDetectorModel(MODEL_URL), 122 | faceapi.loadMtcnnModel(MODEL_URL), 123 | faceapi.loadSsdMobilenetv1Model(MODEL_URL), 124 | faceapi.loadFaceLandmarkModel(MODEL_URL), 125 | faceapi.loadFaceRecognitionModel(MODEL_URL), 126 | faceapi.loadFaceExpressionModel(MODEL_URL), 127 | faceapi.loadAgeGenderModel(MODEL_URL), 128 | ]); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/modules/player.ts: -------------------------------------------------------------------------------- 1 | import * as faceapi from 'face-api.js'; 2 | import shortid from 'shortid'; 3 | import ChamChamCham, { C3FaceMatch } from './chamchamcham'; 4 | import createImageFromFaceMatch from '../helpers/createImageFromFaceMatch'; 5 | import createNameFromFaceMatch from '../helpers/createNameFromFaceMatch'; 6 | 7 | export const maxDescriptorDistance = 0.35; 8 | export default class Player { 9 | public readonly name: string; 10 | private faceMatcher: faceapi.FaceMatcher; 11 | private readonly createdAt: Date; 12 | private profileImage!: Blob; 13 | 14 | public constructor( 15 | private readonly c3: ChamChamCham, 16 | private readonly match: C3FaceMatch, 17 | faceMatcher?: faceapi.FaceMatcher 18 | ) { 19 | this.name = createNameFromFaceMatch(match); 20 | const label = new faceapi.LabeledFaceDescriptors(shortid.generate(), [ 21 | match.descriptor, 22 | ]); 23 | this.faceMatcher = 24 | faceMatcher || new faceapi.FaceMatcher(label, maxDescriptorDistance); 25 | this.createdAt = new Date(); 26 | } 27 | 28 | public async loadProfileImage() { 29 | return createImageFromFaceMatch(this.match, this.c3.input).then((blob) => { 30 | this.profileImage = blob; 31 | }); 32 | } 33 | 34 | public toData() { 35 | return { 36 | name: this.name, 37 | createdAt: this.createdAt, 38 | image: this.profileImage, 39 | }; 40 | } 41 | 42 | public getFaceMatcher() { 43 | return this.faceMatcher; 44 | } 45 | 46 | public get labelName() { 47 | return this.faceMatcher.labeledDescriptors[0].label; 48 | } 49 | 50 | public async saveBestMatch(faceMatch: { 51 | detection: C3FaceMatch; 52 | match: faceapi.FaceMatch; 53 | }) { 54 | if (faceMatch && faceMatch.match.distance > 0.15) { 55 | const label = new faceapi.LabeledFaceDescriptors(this.labelName, [ 56 | faceMatch.detection.descriptor, 57 | ]); 58 | this.faceMatcher = new faceapi.FaceMatcher( 59 | [...this.faceMatcher.labeledDescriptors, label], 60 | this.faceMatcher.distanceThreshold 61 | ); 62 | } 63 | } 64 | 65 | public async getBestMatch() { 66 | const detections = await this.c3.getDetectAllFace(); 67 | const [faceMatch] = detections 68 | .map((detection) => ({ 69 | detection, 70 | match: this.faceMatcher.findBestMatch(detection.descriptor), 71 | })) 72 | .filter(({ match }) => match.label === this.labelName) 73 | .sort((dp1, dp2) => dp1.match.distance - dp2.match.distance); 74 | return faceMatch || null; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/gameEnd.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Title, HomeButton } from '../styledComponents'; 3 | import RankingPage from '../components/ranks'; 4 | import styled from 'styled-components'; 5 | import AudioPlayerContext from '../contexts/audioPlayer'; 6 | import useButtonAudio from '../useButtonAudio'; 7 | import { getFormatRankById } from '../api/rank'; 8 | 9 | interface IProps { 10 | onHomeClick(): void; 11 | gamePlayId: string; 12 | } 13 | 14 | const Container = styled.div` 15 | width: 100%; 16 | height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | padding: 75px; 21 | justify-content: space-between; 22 | ${Title} { 23 | color: #ff0000; 24 | } 25 | `; 26 | 27 | const Content = styled.div` 28 | flex: 1; 29 | overflow: hidden; 30 | margin: 50px 0; 31 | `; 32 | 33 | export default function Ranking({ gamePlayId, onHomeClick }: IProps) { 34 | const audioPlayer = React.useContext(AudioPlayerContext); 35 | React.useEffect(() => { 36 | (async () => { 37 | const rank = await getFormatRankById(gamePlayId); 38 | audioPlayer.play(rank && rank.joint < 3 ? 'whooo' : 'lose-laugh'); 39 | })(); 40 | audioPlayer.stop(); 41 | audioPlayer.play('end-game', { loop: true }); 42 | return () => { 43 | audioPlayer.stop(); 44 | }; 45 | }, []); 46 | const { handleClick, handleHover } = useButtonAudio(); 47 | const handleHomeClick = React.useCallback(() => { 48 | handleClick(); 49 | onHomeClick(); 50 | }, [onHomeClick, handleClick]); 51 | return ( 52 | 53 | GAME OVER 54 | 55 | 56 | 57 | 58 | 처음으로 59 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/pages/gameStart.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { 4 | Button, 5 | TrophyButton, 6 | TitleWrapper, 7 | AnimeTitle, 8 | } from '../styledComponents'; 9 | import { IGameDrawHandler } from '../useGame'; 10 | import { C3FaceMatch } from '../modules/chamchamcham'; 11 | import { FacePosition } from '../types'; 12 | import useButtonAudio from '../useButtonAudio'; 13 | import AudioPlayerContext from '../contexts/audioPlayer'; 14 | import { usePrevious } from 'react-use'; 15 | 16 | interface IProps { 17 | onStartClick(faceMatch: C3FaceMatch): void; 18 | onRankingClick(): void; 19 | gameDrawHandlerRef: React.MutableRefObject; 20 | setToastMessage: React.Dispatch; 21 | } 22 | 23 | const Container = styled.div` 24 | width: 100%; 25 | height: 100%; 26 | display: flex; 27 | flex-direction: column; 28 | align-items: center; 29 | padding: 120px 0 80px; 30 | justify-content: space-between; 31 | `; 32 | 33 | export default function GameStartPage(props: IProps) { 34 | const { 35 | gameDrawHandlerRef, 36 | onRankingClick, 37 | onStartClick, 38 | setToastMessage, 39 | } = props; 40 | const audioPlayer = React.useContext(AudioPlayerContext); 41 | const [position, setPosition] = React.useState(); 42 | const [startFace, setStartFace] = React.useState(null); 43 | const [readyDetection, setReadyDetection] = React.useState(false); 44 | const prevStartFace = usePrevious(startFace); 45 | React.useEffect(() => { 46 | if (position && startFace && position === 'center') { 47 | setToastMessage(null); 48 | } else { 49 | if (startFace && position !== 'center') { 50 | setToastMessage('중앙을 바라봐주세요'); 51 | } else { 52 | setToastMessage('얼굴을 인식할 수 없습니다.'); 53 | } 54 | } 55 | }, [position, startFace]); 56 | React.useEffect(() => { 57 | if (!prevStartFace && startFace) { 58 | handleIntroPlay(); 59 | } 60 | }, [startFace]); 61 | React.useEffect(() => { 62 | if (readyDetection) { 63 | setToastMessage(null); 64 | } else { 65 | setToastMessage('카메라 인식 준비 중입니다.'); 66 | } 67 | }, [readyDetection]); 68 | const handleIntroPlay = React.useCallback(() => { 69 | audioPlayer.play('intro'); 70 | }, []); 71 | React.useEffect(() => { 72 | audioPlayer.play('start-bgm', { loop: true }); 73 | gameDrawHandlerRef.current = async ({ c3 }) => { 74 | const detection = await c3.getDetectSingleFace(); 75 | if (detection) { 76 | setReadyDetection(true); 77 | const facePosition = c3.getMatchFacePositionType(detection); 78 | setPosition(facePosition); 79 | setStartFace(detection); 80 | c3.drawLandmark(detection); 81 | } else { 82 | c3.clear(); 83 | setStartFace(null); 84 | } 85 | }; 86 | return () => { 87 | gameDrawHandlerRef.current = undefined; 88 | setToastMessage(null); 89 | }; 90 | }, []); 91 | const { handleClick, handleHover } = useButtonAudio(); 92 | const handleStartClick = React.useCallback(() => { 93 | if (startFace) { 94 | handleClick(); 95 | onStartClick(startFace); 96 | } 97 | }, [startFace, onStartClick, handleClick]); 98 | const handleRankingClick = React.useCallback(() => { 99 | handleClick(); 100 | onRankingClick(); 101 | }, [handleClick, onRankingClick]); 102 | const disabledStart = !Boolean(startFace && position === 'center'); 103 | return ( 104 | 105 | 110 | 111 | 112 | 113 | 114 | 115 | 121 | 122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /src/pages/inGame/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Hand from '../../components/hand'; 3 | import MiniRankItem from '../../components/miniRank'; 4 | import { 5 | TitleWrapper, 6 | AnimeTitle, 7 | Container, 8 | Point, 9 | PointValue, 10 | MiniRank, 11 | PointTrophy, 12 | } from './styledComponents'; 13 | import useInGame, { IProps, CURRENT_USER_ID } from './useInGame'; 14 | 15 | import trophyGold from '../../resources/trophy-gold.svg'; 16 | import trophySilver from '../../resources/trophy-silver.svg'; 17 | import trophyBronze from '../../resources/trophy-bronze.svg'; 18 | 19 | export default function InGamePage(props: IProps) { 20 | const { 21 | showTitle, 22 | computerPosition, 23 | point, 24 | pointTrophy, 25 | ranksWithCurrent, 26 | } = useInGame(props); 27 | const pointFormat = String(point * 100); 28 | return ( 29 | 30 | 31 | {ranksWithCurrent.map((rank) => ( 32 | 37 | ))} 38 | 39 | 40 | {Array.from(new Array(3)).map((_, index, { length }) => { 41 | const value = index === length - 1 ? '참!' : '참'; 42 | return ( 43 | index ? 'visible' : 'hidden' }} 46 | title={value}> 47 | {value} 48 | 49 | ); 50 | })} 51 | 52 | 53 | 54 | {pointTrophy < 3 ? ( 55 | 56 | {pointTrophy === 0 ? ( 57 | Gold Medal 58 | ) : pointTrophy === 1 ? ( 59 | Silver Medal 60 | ) : ( 61 | Bronze Medal 62 | )} 63 | 64 | ) : null} 65 | {pointFormat} 66 | 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/pages/inGame/styledComponents.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Title } from '../../styledComponents'; 3 | export { Title, TitleWrapper, AnimeTitle } from '../../styledComponents'; 4 | 5 | export const Container = styled.div` 6 | width: 100%; 7 | height: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | padding: 120px 0 80px; 12 | justify-content: space-between; 13 | `; 14 | 15 | export const Point = styled.div` 16 | position: absolute; 17 | right: 4rem; 18 | bottom: 4rem; 19 | display: flex; 20 | z-index: 10; 21 | `; 22 | 23 | export const PointValue = styled(Title)` 24 | font-size: 10rem; 25 | `; 26 | 27 | export const PointTrophy = styled.div` 28 | margin-right: 1rem; 29 | height: 10rem; 30 | width: 10rem; 31 | `; 32 | 33 | export const MiniRank = styled.div` 34 | position: absolute; 35 | left: 50px; 36 | top: 50px; 37 | `; 38 | -------------------------------------------------------------------------------- /src/pages/inGame/useInGame.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import C3Player from '../../modules/player'; 3 | import { IGameDrawHandler } from '../../useGame'; 4 | import { 5 | addRanking, 6 | IFormatRank, 7 | IRank, 8 | getRankings, 9 | formatRankingsSelector, 10 | } from '../../api/rank'; 11 | import { setBestMatchCollection } from '../../api/face'; 12 | import { usePrevious } from 'react-use'; 13 | import AudioPlayerContext from '../../contexts/audioPlayer'; 14 | import { FacePosition } from '../../types'; 15 | import { 16 | requestAnimationFrameTimeout, 17 | IAnimationFrameRef, 18 | cancelAnimationFrameTimeout, 19 | } from '../../helpers/requestAnimationFrame'; 20 | 21 | export interface IProps { 22 | gameDrawHandlerRef: React.MutableRefObject; 23 | setToastMessage: React.Dispatch; 24 | player: C3Player; 25 | handleGameEnd(gameId: string): void; 26 | } 27 | 28 | export const CURRENT_USER_ID = 'currentUser'; 29 | 30 | export default function useInGame({ 31 | setToastMessage, 32 | gameDrawHandlerRef, 33 | handleGameEnd, 34 | player, 35 | }: IProps) { 36 | const audioPlayer = React.useContext(AudioPlayerContext); 37 | const [ranks, setRanks] = React.useState(null); 38 | const [position, setPosition] = React.useState('center'); 39 | const [computerPosition, setComputerPosition] = React.useState( 40 | 'center' 41 | ); 42 | const [point, setPoint] = React.useState(0); 43 | const prevPosition = usePrevious(position); 44 | const ranksWithCurrent = React.useMemo(() => { 45 | const currentRank: IRank = { 46 | ...player.toData(), 47 | id: CURRENT_USER_ID, 48 | point, 49 | }; 50 | return formatRankingsSelector([...(ranks || []), currentRank]) 51 | .reduce((result, rank) => { 52 | if ( 53 | rank.id === CURRENT_USER_ID || 54 | !result.some((findRank) => findRank.rank === rank.rank) 55 | ) { 56 | return result.concat(rank); 57 | } 58 | return result; 59 | }, []) 60 | .filter((rank) => { 61 | return rank.joint < 4 || rank.id === CURRENT_USER_ID; 62 | }); 63 | }, [ranks, player, point]); 64 | const pointTrophy = ranksWithCurrent.find( 65 | (rank) => rank.id === CURRENT_USER_ID 66 | )!.joint; 67 | React.useEffect(() => { 68 | if (point) { 69 | setTimeout(() => { 70 | const wows = [ 71 | 'wow', 72 | 'wow2', 73 | 'wow3', 74 | 'wow4', 75 | 'wow5', 76 | 'wow6', 77 | 'wow7', 78 | 'wow8', 79 | ] as const; 80 | audioPlayer.play(wows[Math.floor(Math.random() * wows.length)]); 81 | }, 500); 82 | } 83 | }, [point]); 84 | const [showTitle, setShowTitle] = React.useState<0 | 1 | 2 | 3>(0); 85 | React.useEffect(() => { 86 | if (showTitle) { 87 | audioPlayer.play(showTitle === 3 ? 'big-cham' : 'cham'); 88 | } 89 | }, [showTitle]); 90 | React.useEffect(() => { 91 | let timerHandle: IAnimationFrameRef; 92 | if (position && position !== 'center' && prevPosition === 'center') { 93 | setToastMessage(null); 94 | setShowTitle(3); 95 | const weight = 1 - (1 + point) / 20; 96 | const playerWin = Math.random() < (weight > 0.5 ? weight : 0.5); 97 | if (playerWin) { 98 | setComputerPosition(position === 'right' ? 'left' : 'right'); 99 | setPoint((prevPoint) => prevPoint + 1); 100 | } else { 101 | setComputerPosition(position); 102 | (async () => { 103 | const gamePlayId = await addRanking({ 104 | ...player.toData(), 105 | point, 106 | }); 107 | await setBestMatchCollection( 108 | player.labelName, 109 | player.getFaceMatcher() 110 | ); 111 | setTimeout(() => { 112 | handleGameEnd(gamePlayId); 113 | }, 500); 114 | })(); 115 | } 116 | } else { 117 | if (prevPosition !== 'center' && position === 'center') { 118 | setToastMessage(null); 119 | setShowTitle(0); 120 | timerHandle = requestAnimationFrameTimeout(() => { 121 | setShowTitle(1); 122 | timerHandle = requestAnimationFrameTimeout(() => { 123 | setShowTitle(2); 124 | timerHandle = requestAnimationFrameTimeout(() => { 125 | audioPlayer.speak('고개를 아무 방향으로 짧게 돌려주세요!'); 126 | setToastMessage('고개를 아무 방향으로 짧게 돌려주세요!'); 127 | }, 3000); 128 | }, 700); 129 | }, 500); 130 | } else if (!position && prevPosition === 'center') { 131 | audioPlayer.play('error'); 132 | setToastMessage('얼굴을 너무 돌리거나 가리지 말아주세요.'); 133 | } 134 | setComputerPosition('center'); 135 | } 136 | return () => cancelAnimationFrameTimeout(timerHandle); 137 | }, [position]); 138 | React.useEffect(() => { 139 | let timer = 0; 140 | gameDrawHandlerRef.current = async ({ c3, player }) => { 141 | const bestMatch = await player.getBestMatch(); 142 | if (bestMatch) { 143 | window.clearTimeout(timer); 144 | timer = 0; 145 | c3.drawLandmark(bestMatch.detection); 146 | const facePosition = c3.getMatchFacePositionType(bestMatch.detection); 147 | setPosition(facePosition); 148 | } else if (!timer) { 149 | timer = window.setTimeout(() => { 150 | c3.clear(); 151 | setPosition(null); 152 | }, 1000); 153 | } 154 | }; 155 | audioPlayer.stop(); 156 | audioPlayer.play('in-game', { loop: true }); 157 | (async () => { 158 | setRanks(await getRankings()); 159 | })(); 160 | return () => { 161 | setToastMessage(null); 162 | gameDrawHandlerRef.current = undefined; 163 | }; 164 | }, []); 165 | return { 166 | showTitle, 167 | computerPosition, 168 | point, 169 | pointTrophy, 170 | ranksWithCurrent, 171 | }; 172 | } 173 | -------------------------------------------------------------------------------- /src/pages/ranking.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Title, HomeButton } from '../styledComponents'; 3 | import RankingPage from '../components/ranks'; 4 | import styled from 'styled-components'; 5 | import useButtonAudio from '../useButtonAudio'; 6 | 7 | interface IProps { 8 | onHomeClick(): void; 9 | } 10 | 11 | const Container = styled.div` 12 | width: 100%; 13 | height: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | padding: 75px; 18 | justify-content: space-between; 19 | `; 20 | 21 | const Content = styled.div` 22 | flex: 1; 23 | overflow: hidden; 24 | margin: 50px 0; 25 | `; 26 | 27 | export default function Ranking({ onHomeClick }: IProps) { 28 | const { handleClick, handleHover } = useButtonAudio(); 29 | const handleHomeClick = React.useCallback(() => { 30 | handleClick(); 31 | onHomeClick(); 32 | }, [onHomeClick, handleClick]); 33 | return ( 34 | 35 | RANKING 36 | 37 | 38 | 39 | 40 | 처음으로 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/scanning.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AnimationNumber from '../components/animationNumber'; 3 | import { IGameDrawHandler } from '../useGame'; 4 | import AudioPlayerContext from '../contexts/audioPlayer'; 5 | import styled from 'styled-components'; 6 | import { Title } from '../styledComponents'; 7 | 8 | interface IProps { 9 | setToastMessage: React.Dispatch; 10 | handlePlayGame(): void; 11 | gameDrawHandlerRef: React.MutableRefObject; 12 | } 13 | 14 | const Container = styled.div` 15 | width: 100%; 16 | height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | padding: 75px; 21 | justify-content: space-between; 22 | `; 23 | 24 | const Gauge = styled.div` 25 | border: 2.5px solid #000; 26 | background-color: #fff; 27 | height: 60px; 28 | width: 350px; 29 | position: relative; 30 | overflow: hidden; 31 | `; 32 | 33 | const GaugeBar = styled.div` 34 | position: absolute; 35 | height: 100%; 36 | left: 0; 37 | top: 0; 38 | transition: 300ms width; 39 | background-image: linear-gradient(to bottom, #f2a613, #ebc50f); 40 | &::after { 41 | display: block; 42 | position: absolute; 43 | content: ''; 44 | height: 100%; 45 | top: 0; 46 | left: -3px; 47 | right: -3px; 48 | box-shadow: inset 0px -2.5px 0px 2.5px #a85131, 49 | inset 0px 2.5px 0 2.5px #f9ef85; 50 | } 51 | `; 52 | 53 | const GaugeText = styled.div` 54 | position: absolute; 55 | left: 50%; 56 | top: 50%; 57 | transform: translate3d(-50%, -50%, 0); 58 | font-size: 35px; 59 | color: #fff; 60 | -webkit-text-stroke: 1px #000; 61 | text-shadow: 3px 3px 0 #000; 62 | letter-spacing: -0.025em; 63 | z-index: 1; 64 | `; 65 | 66 | export default function ScanningPage({ 67 | setToastMessage, 68 | handlePlayGame, 69 | gameDrawHandlerRef, 70 | }: IProps) { 71 | const audioPlayer = React.useContext(AudioPlayerContext); 72 | const [successDirection, setSuccessDirection] = React.useState(() => ({ 73 | right: false, 74 | left: false, 75 | })); 76 | const [nosePositionSet, setNosePositionSet] = React.useState( 77 | () => [] 78 | ); 79 | const leftNosePositionSetLength = React.useMemo( 80 | () => nosePositionSet.filter((v) => v < 50).length, 81 | [nosePositionSet] 82 | ); 83 | const rightNosePositionSetLength = React.useMemo( 84 | () => nosePositionSet.filter((v) => v > 50).length, 85 | [nosePositionSet] 86 | ); 87 | const speakAndToast = (message: string | null) => { 88 | message && audioPlayer.speak(message); 89 | setToastMessage(message); 90 | }; 91 | const timerRef = React.useRef(); 92 | React.useEffect(() => { 93 | audioPlayer.stop(); 94 | audioPlayer.play('scanning-bgm', { loop: true }); 95 | gameDrawHandlerRef.current = async ({ c3, player }) => { 96 | const bestMatch = await player.getBestMatch(); 97 | if (bestMatch) { 98 | player.saveBestMatch(bestMatch); 99 | c3.drawLandmark(bestMatch.detection); 100 | const { nosePosition } = c3.getMatchFacePosition(bestMatch.detection); 101 | setNosePositionSet((prevSet) => 102 | Array.from(new Set([...prevSet, Math.round(nosePosition)])) 103 | ); 104 | } else { 105 | c3.clear(); 106 | } 107 | }; 108 | return () => { 109 | setToastMessage(null); 110 | clearTimeout(timerRef.current); 111 | gameDrawHandlerRef.current = undefined; 112 | }; 113 | }, []); 114 | React.useEffect(() => { 115 | if (!timerRef.current) { 116 | if (!successDirection.left) { 117 | if (leftNosePositionSetLength > 20) { 118 | speakAndToast('잘하셨습니다.'); 119 | timerRef.current = window.setTimeout(() => { 120 | timerRef.current = 0; 121 | setSuccessDirection((prev) => ({ 122 | ...prev, 123 | left: true, 124 | })); 125 | }, 2000); 126 | } else { 127 | speakAndToast('천천히 왼쪽으로 고개를 돌려서 게이지를 완성해주세요.'); 128 | } 129 | } else if (!successDirection.right) { 130 | if (rightNosePositionSetLength > 20) { 131 | speakAndToast( 132 | '잘하셨습니다. 이제 곧 게임이 시작됩니다. 가운데를 바라봐주세요' 133 | ); 134 | setSuccessDirection((prev) => ({ 135 | ...prev, 136 | right: true, 137 | })); 138 | } else { 139 | speakAndToast( 140 | '천천히 오른쪽으로 고개를 돌려서 게이지를 완성해주세요.' 141 | ); 142 | } 143 | } else { 144 | timerRef.current = window.setTimeout(() => { 145 | handlePlayGame(); 146 | }, 5000); 147 | } 148 | } 149 | }, [nosePositionSet, successDirection]); 150 | const renderNosePositionPercent = !successDirection.left 151 | ? Math.floor((leftNosePositionSetLength / 21) * 100) 152 | : Math.floor((rightNosePositionSetLength / 21) * 100); 153 | const renderNosePositionPercentValue = `${ 154 | renderNosePositionPercent > 100 ? 100 : renderNosePositionPercent 155 | }%`; 156 | React.useEffect(() => { 157 | audioPlayer.play('charge'); 158 | }, [renderNosePositionPercentValue]); 159 | return ( 160 | 161 | 얼굴 인식 중... 162 | {/*
{leftNosePositionSetLength}
163 |
{rightNosePositionSetLength}
*/} 164 | 165 | 166 | 171 | % 172 | 173 | 174 | 175 |
176 | ); 177 | } 178 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | type ThenArg = T extends Promise ? U : T; -------------------------------------------------------------------------------- /src/resources/BMHANNAPro.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/BMHANNAPro.woff -------------------------------------------------------------------------------- /src/resources/center-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/center-hand.png -------------------------------------------------------------------------------- /src/resources/home-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/home-icon.png -------------------------------------------------------------------------------- /src/resources/left-hand-moving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/left-hand-moving.png -------------------------------------------------------------------------------- /src/resources/left-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/left-hand.png -------------------------------------------------------------------------------- /src/resources/right-hand-moving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/right-hand-moving.png -------------------------------------------------------------------------------- /src/resources/right-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/right-hand.png -------------------------------------------------------------------------------- /src/resources/sounds/big-cham.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/big-cham.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/cham.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/cham.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/charge.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/charge.wav -------------------------------------------------------------------------------- /src/resources/sounds/end-game.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/end-game.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/error.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/error.wav -------------------------------------------------------------------------------- /src/resources/sounds/in-game.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/in-game.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/intro.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/intro.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/lose-laugh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/lose-laugh.wav -------------------------------------------------------------------------------- /src/resources/sounds/mouth-pop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/mouth-pop.wav -------------------------------------------------------------------------------- /src/resources/sounds/pipe.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/pipe.wav -------------------------------------------------------------------------------- /src/resources/sounds/scanning-bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/scanning-bgm.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/start-bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/start-bgm.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/throw.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/throw.wav -------------------------------------------------------------------------------- /src/resources/sounds/whooo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/whooo.wav -------------------------------------------------------------------------------- /src/resources/sounds/wow.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow.wav -------------------------------------------------------------------------------- /src/resources/sounds/wow2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow2.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/wow3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow3.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/wow4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow4.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/wow5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow5.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/wow6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow6.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/wow7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow7.mp3 -------------------------------------------------------------------------------- /src/resources/sounds/wow8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minuukang/chamchamcham/82a5a418ce0da4b47c643c154034699a447c402c/src/resources/sounds/wow8.mp3 -------------------------------------------------------------------------------- /src/resources/trophy-bronze.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/resources/trophy-gold.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/resources/trophy-silver.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/resources/trophy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get('content-type'); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && contentType.indexOf('javascript') === -1) 118 | ) { 119 | // No service worker found. Probably a different app. Reload the page. 120 | navigator.serviceWorker.ready.then(registration => { 121 | registration.unregister().then(() => { 122 | window.location.reload(); 123 | }); 124 | }); 125 | } else { 126 | // Service worker found. Proceed as normal. 127 | registerValidSW(swUrl, config); 128 | } 129 | }) 130 | .catch(() => { 131 | console.log( 132 | 'No internet connection found. App is running in offline mode.' 133 | ); 134 | }); 135 | } 136 | 137 | export function unregister() { 138 | if ('serviceWorker' in navigator) { 139 | navigator.serviceWorker.ready.then(registration => { 140 | registration.unregister(); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/styledComponents.tsx: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | import trophyImage from './resources/trophy.svg'; 3 | import homeIcon from './resources/home-icon.png'; 4 | 5 | export const Container = styled.div` 6 | position: relative; 7 | z-index: 4; 8 | width: 100%; 9 | height: 100%; 10 | `; 11 | 12 | export const Title = styled.h1` 13 | font-size: 13.5rem; 14 | color: #fff; 15 | position: relative; 16 | z-index: 3; 17 | display: inline-block; 18 | text-align: center; 19 | white-space: nowrap; 20 | text-shadow: ${Array.from(new Array(4)) 21 | .map((_, i) => `${i + 1}px 0 0 #ffe1cc`) 22 | .join(', ')}; 23 | &::after { 24 | display: block; 25 | content: attr(title); 26 | color: #000; 27 | position: absolute; 28 | left: 2px; 29 | top: 0; 30 | z-index: -2; 31 | -webkit-text-stroke: 1.4rem #000; 32 | text-shadow: ${Array.from(new Array(8)) 33 | .map((_, i) => `${i * 2 - 7}px ${i + 8}px 0 #000`) 34 | .join(', ')}; 35 | } 36 | `; 37 | 38 | export const ButtonGroup = styled.div` 39 | position: absolute; 40 | bottom: 8rem; 41 | left: 50%; 42 | transform: translateX(-50%); 43 | z-index: 3; 44 | text-align: center; 45 | white-space: nowrap; 46 | color: #fff; 47 | font-size: 4rem; 48 | line-height: 1.5; 49 | text-shadow: 1px 1px 0 #000; 50 | `; 51 | 52 | export const TrophyButton = styled.button` 53 | position: absolute; 54 | right: 4rem; 55 | top: 4rem; 56 | background-image: url("${trophyImage}"); 57 | background-size: 100%; 58 | background-repeat: no-repeat; 59 | width: 9rem; 60 | height: 9rem; 61 | transition: all 300ms; 62 | z-index: 3; 63 | &:hover { 64 | transform: scale(1.1); 65 | } 66 | &:active { 67 | transform: scale(1); 68 | } 69 | &::after { 70 | display: block; 71 | white-space: nowrap; 72 | content: attr(title); 73 | background-color: rgba(0, 0, 0, 0.75); 74 | border-radius: .5rem; 75 | padding: 1rem; 76 | color: #fff; 77 | font-size: 3rem; 78 | position: absolute; 79 | top: 100%; 80 | left: 50%; 81 | opacity: 0; 82 | pointer-events: none; 83 | transition: 300ms all; 84 | transform: translate3d(-50%, -1rem, 0); 85 | } 86 | &:hover::after { 87 | opacity: 1; 88 | transform: translate3d(-50%, 1rem, 0); 89 | } 90 | `; 91 | 92 | export const Button = styled.button` 93 | display: flex; 94 | align-items: center; 95 | justify-content: center; 96 | position: relative; 97 | padding: 0 50px; 98 | height: 109px; 99 | border: 5px solid #000; 100 | background-image: linear-gradient(to bottom, #f2a613, #ebc50f); 101 | font-size: 50px; 102 | color: #fff; 103 | -webkit-text-stroke: 2px #000; 104 | text-shadow: 3px 3px 0 #000; 105 | transition: all 300ms; 106 | overflow: hidden; 107 | &:not(:disabled):hover { 108 | transform: scale(1.1); 109 | } 110 | &:active { 111 | transform: scale(1); 112 | } 113 | &::after { 114 | display: block; 115 | position: absolute; 116 | content: ''; 117 | height: 100%; 118 | top: 0; 119 | left: -3px; 120 | right: -3px; 121 | box-shadow: inset 0px -2.5px 0px 2.5px #a85131, 122 | inset 0px 2.5px 0 2.5px #f9ef85; 123 | } 124 | &:disabled { 125 | opacity: 0.5; 126 | cursor: not-allowed; 127 | } 128 | `; 129 | 130 | export const HomeButton = styled(Button)` 131 | &::before { 132 | width: 50px; 133 | height: 43px; 134 | background-image: url("${homeIcon}"); 135 | background-size: 100% 100%; 136 | display: inline-block; 137 | content: ""; 138 | vertical-align: middle; 139 | margin-right: 15px; 140 | } 141 | `; 142 | 143 | export const ToastMessage = styled.div` 144 | position: absolute; 145 | top: 50%; 146 | left: 50%; 147 | transform: translate3d(-50%, -50%, 0); 148 | font-size: 5rem; 149 | text-align: center; 150 | background-color: rgba(0, 0, 0, 0.7); 151 | padding: 1rem; 152 | border-radius: 0.5rem; 153 | color: #fff; 154 | z-index: 999; 155 | pointer-events: none; 156 | white-space: nowrap; 157 | `; 158 | 159 | const animeLetterKeyframe = keyframes` 160 | from { 161 | transform: scale(1, 1); 162 | } 163 | to { 164 | transform: scale(1, 1.1); 165 | } 166 | `; 167 | 168 | export const AnimeTitle = styled(Title)` 169 | display: inline-block; 170 | transform-origin: 50% 100%; 171 | animation: ${animeLetterKeyframe} 500ms infinite alternate-reverse; 172 | ${Array.from(new Array(4)) 173 | .map((_, index) => { 174 | return `&:nth-of-type(${index + 1}) { 175 | animation-delay: ${index * 200}ms; 176 | }`; 177 | }) 178 | .join('\n')} 179 | `; 180 | 181 | export const TitleWrapper = styled.hgroup``; 182 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type FacePosition = 'center' | 'left' | 'right'; 2 | -------------------------------------------------------------------------------- /src/useButtonAudio.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AudioPlayerContext from './contexts/audioPlayer'; 3 | 4 | export default function useButtonAudio() { 5 | const audioPlayer = React.useContext(AudioPlayerContext); 6 | return { 7 | handleHover: React.useCallback(() => { 8 | audioPlayer.play('mouth-pop'); 9 | }, [audioPlayer]), 10 | handleClick: React.useCallback(() => { 11 | audioPlayer.play('pipe-sound'); 12 | }, [audioPlayer]), 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/useGame.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import C3Player from './modules/player'; 3 | import ChamChamCham, { C3FaceMatch } from './modules/chamchamcham'; 4 | import { getBestMatchCollection } from './api/face'; 5 | 6 | export type GamePage = 'main' | 'ranking' | 'scanning' | 'in-game' | 'end'; 7 | 8 | export interface IGameDrawHandler { 9 | (param: { c3: ChamChamCham; player: C3Player }): Promise; 10 | } 11 | 12 | function useGameState() { 13 | const videoRef = React.useRef(null); 14 | const [page, setPage] = React.useState('main'); 15 | const [player, setPlayer] = React.useState(null); 16 | const [toastMessage, setToastMessage] = React.useState(null); 17 | const c3Instance = React.useRef(); 18 | const playerInstance = React.useRef(null); 19 | const [gamePlayId, setGamePlayId] = React.useState(null); 20 | const gameDrawHandler = React.useRef(); 21 | const setPlayerHandler = (player: C3Player | null) => { 22 | setPlayer((playerInstance.current = player)); 23 | }; 24 | return { 25 | videoRef, 26 | gameDrawHandler, 27 | toastMessage, 28 | setToastMessage, 29 | player, 30 | setPlayer: setPlayerHandler, 31 | c3Instance, 32 | playerInstance, 33 | page, 34 | setPage, 35 | gamePlayId, 36 | setGamePlayId, 37 | }; 38 | } 39 | 40 | function useGameHandler() { 41 | const gameState = useGameState(); 42 | const { setPlayer, c3Instance, setPage, setGamePlayId } = gameState; 43 | const handlePlayGame = React.useCallback(() => { 44 | setPage('in-game'); 45 | }, []); 46 | const handleScanningFace = React.useCallback( 47 | async (faceMatch: C3FaceMatch) => { 48 | const bestMatcher = await getBestMatchCollection(faceMatch); 49 | const player = new C3Player( 50 | c3Instance.current!, 51 | faceMatch, 52 | bestMatcher || undefined 53 | ); 54 | setPlayer(player); 55 | await player.loadProfileImage(); 56 | setPage(bestMatcher ? 'in-game' : 'scanning'); 57 | }, 58 | [] 59 | ); 60 | const handleGoRanking = React.useCallback(() => { 61 | setPage('ranking'); 62 | }, []); 63 | const handleGoHome = React.useCallback(() => { 64 | setPage('main'); 65 | setGamePlayId(null); 66 | setPlayer(null); 67 | }, []); 68 | const handleGameEnd = React.useCallback((gameId: string) => { 69 | setGamePlayId(gameId); 70 | setPage('end'); 71 | }, []); 72 | return { 73 | ...gameState, 74 | handlePlayGame, 75 | handleGoHome, 76 | handleGoRanking, 77 | handleScanningFace, 78 | handleGameEnd, 79 | }; 80 | } 81 | 82 | export default function useSetupGame() { 83 | const props = useGameHandler(); 84 | const { 85 | c3Instance, 86 | playerInstance, 87 | videoRef, 88 | gameDrawHandler, 89 | page, 90 | setToastMessage, 91 | } = props; 92 | const [readyUserMedia, setReadyUserMedia] = React.useState(false); 93 | const [videoDetectionError, setVideoDetectionError] = React.useState(false); 94 | React.useEffect(() => { 95 | if (videoDetectionError) { 96 | setToastMessage('카메라를 인식 할 수 없습니다.'); 97 | } 98 | }, [videoDetectionError]); 99 | React.useEffect(() => { 100 | let cancelTimer = 0; 101 | let inCancel = false; 102 | async function draw() { 103 | const c3 = c3Instance.current!; 104 | const player = playerInstance.current!; 105 | if (gameDrawHandler.current) { 106 | try { 107 | await gameDrawHandler.current({ 108 | c3, 109 | player, 110 | }); 111 | } catch (err) { 112 | console.error(err); 113 | } 114 | } else { 115 | c3.clear(); 116 | } 117 | if (!inCancel) { 118 | cancelTimer = window.requestAnimationFrame(draw); 119 | } 120 | } 121 | if (readyUserMedia) { 122 | cancelTimer = window.requestAnimationFrame(draw); 123 | return () => { 124 | inCancel = true; 125 | window.cancelAnimationFrame(cancelTimer); 126 | }; 127 | } 128 | }, [readyUserMedia, page]); 129 | React.useEffect(() => { 130 | (async () => { 131 | if (!videoRef.current) { 132 | throw new Error('VideoRef is not defined'); 133 | } 134 | try { 135 | const stream = await navigator.mediaDevices.getUserMedia({ 136 | audio: false, 137 | video: { 138 | width: { min: 640, ideal: 1920, max: 1920 }, 139 | height: { min: 400, ideal: 1080 }, 140 | aspectRatio: 1.777777778, 141 | frameRate: { max: 30 }, 142 | facingMode: 'user', 143 | }, 144 | }); 145 | videoRef.current.srcObject = stream; 146 | videoRef.current.oncanplay = () => { 147 | c3Instance.current = new ChamChamCham( 148 | videoRef.current!, 149 | document.getElementById('root')! 150 | ); 151 | setReadyUserMedia(true); 152 | }; 153 | } catch (e) { 154 | setVideoDetectionError(true); 155 | } 156 | })(); 157 | }, []); 158 | return props; 159 | } 160 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------