├── .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 | 
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 |
67 |
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 |
93 | ) : joint === 2 ? (
94 |
95 | ) : joint === 3 ? (
96 |
97 | ) : (
98 | joint
99 | )}
100 |
101 |
102 |
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 |
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 |
116 | ))}
117 |
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 |
58 | ) : pointTrophy === 1 ? (
59 |
60 | ) : (
61 |
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 |
4 |
--------------------------------------------------------------------------------
/src/resources/trophy-gold.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/resources/trophy-silver.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/resources/trophy.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------