Compare commits

...

1049 Commits
main ... v2.7.1

Author SHA1 Message Date
Vladimir Mandic 2ea6847ef9 major release 2022-05-09 08:16:00 -04:00
Vladimir Mandic f4b2f202e5 2.7.1 2022-05-09 08:14:00 -04:00
Vladimir Mandic f7a4d6840c update wiki 2022-04-23 13:02:00 -04:00
Vladimir Mandic 147f766d5e update todo 2022-04-21 09:58:13 -04:00
Vladimir Mandic 8cadcb6715 support 4k input 2022-04-21 09:39:40 -04:00
Vladimir Mandic 768c8c062c update tfjs 2022-04-21 09:38:36 -04:00
Vladimir Mandic 8aa4d3d647 add attention draw methods 2022-04-18 12:26:05 -04:00
Vladimir Mandic f0fc73b98d fix coloring function 2022-04-18 11:29:45 -04:00
Vladimir Mandic b18e2ace64 enable precompile as part of warmup 2022-04-15 07:54:27 -04:00
Vladimir Mandic 803eb0f571 prepare release beta 2022-04-14 11:55:49 -04:00
Vladimir Mandic 1eb4429da4 change default face crop 2022-04-14 11:47:08 -04:00
Vladimir Mandic 190f376d18 update wiki 2022-04-11 11:55:30 -04:00
Vladimir Mandic 3ba05e4d08 face attention model is available in human-models 2022-04-11 11:51:04 -04:00
Vladimir Mandic 97a67c8b00 beta release 2.7 2022-04-11 11:46:35 -04:00
Vladimir Mandic 048d8bac01 refactor draw methods 2022-04-11 11:46:00 -04:00
Vladimir Mandic 2d7cf52e72 implement face attention model 2022-04-11 11:45:24 -04:00
Vladimir Mandic 1bc2831f2b add electronjs demo 2022-04-10 11:00:41 -04:00
Vladimir Mandic d9d6cbaf8f rebuild 2022-04-10 10:13:13 -04:00
Vladimir Mandic 08c0d7af85 rebuild 2022-04-05 12:25:41 -04:00
Vladimir Mandic 0c99cfdbe8 update tfjs 2022-04-01 12:38:05 -04:00
Vladimir Mandic 5214eaaf6a update 2022-04-01 09:13:32 -04:00
Vladimir Mandic b5dbb83b78 2.6.5 2022-04-01 09:12:13 -04:00
Vladimir Mandic ca89a31f96 bundle offscreencanvas types 2022-04-01 09:12:04 -04:00
Vladimir Mandic 42ba9b0c4d prototype precompile pass 2022-03-19 11:02:30 -04:00
Vladimir Mandic 7c0e711e34 fix changelog generation 2022-03-16 11:38:57 -04:00
Vladimir Mandic 1ee5ebd553 fix indexdb config check 2022-03-16 11:19:56 -04:00
Vladimir Mandic 8b0ceb23be update typescript and tensorflow 2022-03-07 13:24:06 -05:00
Vladimir Mandic d47964f4a3 2.6.4 2022-02-27 07:25:45 -05:00
Vladimir Mandic cf08ef892c fix types typo 2022-02-17 08:15:57 -05:00
Vladimir Mandic f7ecc00d90 refresh 2022-02-14 07:53:28 -05:00
Vladimir Mandic 089f2641e5 add config option wasmPlatformFetch 2022-02-10 15:35:32 -05:00
Vladimir Mandic ea7d0fd2e0 2.6.3 2022-02-10 15:32:53 -05:00
Vladimir Mandic cf53f2d350 rebuild 2022-02-10 12:27:21 -05:00
Vladimir Mandic f7b7fdca1f update toolkit 2022-02-07 10:12:59 -05:00
Vladimir Mandic 306ee94cfe 2.6.2 2022-02-07 09:47:17 -05:00
Vladimir Mandic 0839aaaf0f update todo 2022-01-20 08:24:23 -05:00
Vladimir Mandic 455c7f519d release rebuild 2022-01-20 08:17:06 -05:00
Vladimir Mandic 9a94631146 2.6.1 2022-01-20 07:54:56 -05:00
Vladimir Mandic 733ce5bb66 implement model caching using indexdb 2022-01-17 11:03:21 -05:00
Vladimir Mandic afd3312109 prototype global fetch handler 2022-01-16 09:49:55 -05:00
Vladimir Mandic 6fb16a1bba update samples 2022-01-15 09:18:14 -05:00
Vladimir Mandic 5ef19460c1 update samples 2022-01-15 09:11:04 -05:00
Vladimir Mandic c8c7bb488d update samples with images under cc licence only 2022-01-14 16:10:32 -05:00
Vladimir Mandic db4e49909c fix face box and hand tracking when in front of face 2022-01-14 09:46:16 -05:00
Vladimir Mandic 3c49e5c1ef 2.5.8 2022-01-14 09:42:57 -05:00
Vladimir Mandic 60ab255d26 update 2022-01-08 12:43:44 -05:00
Vladimir Mandic b20ac01743 update wiki 2022-01-05 11:49:10 -05:00
Vladimir Mandic 93b46e00b8 update wiki 2022-01-05 09:55:07 -05:00
Vladimir Mandic 2b53c78b96 update 2022-01-05 08:34:31 -05:00
Vladimir Mandic 2b4fe6c78e update demos 2022-01-01 08:13:04 -05:00
Vladimir Mandic 266501a460 update blazepose 2021-12-31 13:58:03 -05:00
Vladimir Mandic 1e128b3dbc update dependencies 2021-12-30 12:39:29 -05:00
Vladimir Mandic c7209e5654 update hand annotations 2021-12-30 12:14:09 -05:00
Vladimir Mandic 939ce728a9 update blazepose 2021-12-29 12:37:46 -05:00
Vladimir Mandic 04a5e76816 update 2021-12-28 11:39:54 -05:00
Vladimir Mandic 36b657b901 update demos 2021-12-28 09:40:32 -05:00
Vladimir Mandic 13db065375 fix samples 2021-12-28 07:03:05 -05:00
libowen.eric d8114a2871 fix(src): typo 2021-12-28 06:59:16 -05:00
Vladimir Mandic e772efa633 change on how face box is calculated 2021-12-27 10:59:56 -05:00
Vladimir Mandic 806a2e5382 2.5.7 2021-12-27 09:29:15 -05:00
Vladimir Mandic 13ff1ff480 update 2021-12-22 10:04:41 -05:00
Vladimir Mandic 605168e7fa fix posenet 2021-12-18 12:24:01 -05:00
Vladimir Mandic 75816c7132 release refresh 2021-12-15 09:30:26 -05:00
Vladimir Mandic 4ad62d1056 2.5.6 2021-12-15 09:26:40 -05:00
Vladimir Mandic 8360cdb2ce strong type for string enums 2021-12-15 09:26:32 -05:00
Vladimir Mandic b5862fb6a2 update 2021-12-14 15:45:43 -05:00
Vladimir Mandic d28671f0a7 rebuild 2021-12-13 21:38:55 -05:00
Vladimir Mandic 8d6c319e5e update tfjs 2021-12-09 14:44:26 -05:00
Vladimir Mandic e5ee7453ea fix node detection in electron environment 2021-12-07 17:02:33 -05:00
Vladimir Mandic 5486b0d631 update 2021-12-01 08:27:05 -05:00
Vladimir Mandic c03fd41ca7 2.5.5 2021-12-01 08:21:55 -05:00
Vladimir Mandic 816c50d29d update readme 2021-11-26 12:14:40 -05:00
Vladimir Mandic 35c824fb75 added human-motion 2021-11-26 12:12:46 -05:00
Vladimir Mandic b82cde957b add offscreencanvas typedefs 2021-11-26 11:55:52 -05:00
Vladimir Mandic a60721c5d8 update blazepose and extend hand annotations 2021-11-24 16:17:03 -05:00
Vladimir Mandic d797c871c7 release preview 2021-11-23 10:40:40 -05:00
Vladimir Mandic f3720857ae fix face box scaling on detection 2021-11-23 08:36:32 -05:00
Vladimir Mandic e342e8ec47 cleanup 2021-11-22 14:44:25 -05:00
Vladimir Mandic ba05d3fe42 2.5.4 2021-11-22 14:33:46 -05:00
Vladimir Mandic 9304d59413 prototype blazepose detector 2021-11-22 14:33:40 -05:00
Vladimir Mandic 581dbf8471 minor fixes 2021-11-21 16:55:17 -05:00
Vladimir Mandic 20cb91739d add body 3d interpolation 2021-11-19 18:30:57 -05:00
Vladimir Mandic c8f85ead30 edit blazepose keypoints 2021-11-19 16:11:03 -05:00
Vladimir Mandic ff8f5968fb new build process 2021-11-18 10:10:06 -05:00
Vladimir Mandic 473389f313 2.5.3 2021-11-18 10:06:07 -05:00
Vladimir Mandic 5cccc4118a update typescript 2021-11-17 16:50:21 -05:00
Vladimir Mandic 8bb509926b create typedef rollup 2021-11-17 15:45:49 -05:00
Vladimir Mandic 12fc9079dc optimize centernet 2021-11-16 20:16:49 -05:00
Vladimir Mandic 98a1817d13 cache frequent tf constants 2021-11-16 18:31:07 -05:00
Vladimir Mandic ce2e669dad add extra face rotation prior to mesh 2021-11-16 13:07:44 -05:00
Vladimir Mandic 53fcecdf11 release 2.5.2 2021-11-15 09:26:38 -05:00
Vladimir Mandic 5811ee3fb6 improve error handling 2021-11-14 11:22:52 -05:00
Vladimir Mandic 51ac90163e 2.5.2 2021-11-14 10:43:00 -05:00
Vladimir Mandic e63b817c33 fix mobilefacenet module 2021-11-13 17:26:19 -05:00
Vladimir Mandic 50a678c33a fix gear and ssrnet modules 2021-11-13 12:23:32 -05:00
Vladimir Mandic ee2deb88dc fix for face crop when mesh is disabled 2021-11-12 15:17:08 -05:00
Vladimir Mandic eaa691f252 implement optional face masking 2021-11-12 15:07:23 -05:00
Vladimir Mandic 1d7317b3ef update todo 2021-11-11 17:02:32 -05:00
Vladimir Mandic 471896bf5b add similarity score range normalization 2021-11-11 17:01:10 -05:00
Vladimir Mandic 200bccbb43 add faceid demo 2021-11-11 11:30:55 -05:00
Vladimir Mandic db7790d7d8 documentation overhaul 2021-11-10 12:21:45 -05:00
Vladimir Mandic 2f6524722e auto tensor shape and channels handling 2021-11-09 19:39:18 -05:00
Vladimir Mandic 7a1707cf18 disable use of path2d in node 2021-11-09 18:10:54 -05:00
Vladimir Mandic 4ed8c9c190 update wiki 2021-11-09 14:45:45 -05:00
Vladimir Mandic 32539a10f5 add liveness module and facerecognition demo 2021-11-09 14:37:50 -05:00
Vladimir Mandic dfd1ee8418 initial version of facerecognition demo 2021-11-09 10:39:23 -05:00
Vladimir Mandic 4477bbffc6 rebuild 2021-11-08 16:41:30 -05:00
Vladimir Mandic c5fdd9c336 add type defs when working with relative path imports 2021-11-08 16:36:20 -05:00
Vladimir Mandic eb7e1558d0 disable humangl backend if webgl 1.0 is detected 2021-11-08 11:35:35 -05:00
Vladimir Mandic e87bd17ff4 add additional hand gestures 2021-11-08 07:36:26 -05:00
Vladimir Mandic 585ca1d1b8 2.5.1 2021-11-08 06:25:07 -05:00
Vladimir Mandic a1b7c0d88d update automated tests 2021-11-07 10:10:23 -05:00
Vladimir Mandic 85cfeb4026 new human.compare api 2021-11-07 10:03:33 -05:00
Vladimir Mandic 6cae3f00b6 added links to release notes 2021-11-07 08:14:14 -05:00
Vladimir Mandic 6e5f5829b2 update readme 2021-11-06 10:26:04 -04:00
Vladimir Mandic cb461be486 new frame change detection algorithm 2021-11-06 10:21:51 -04:00
Vladimir Mandic 51f4b1fa20 add histogram equalization 2021-11-05 15:35:53 -04:00
Vladimir Mandic f6e733b5f0 add histogram equalization 2021-11-05 15:09:54 -04:00
Vladimir Mandic 1e197a74bf implement wasm missing ops 2021-11-05 13:36:53 -04:00
Vladimir Mandic c113370c21 performance and memory optimizations 2021-11-05 11:28:06 -04:00
Vladimir Mandic cacebc7dbe fix react compatibility issues 2021-11-04 06:34:13 -04:00
Vladimir Mandic 1ddbdcdd2e improve box rescaling for all modules 2021-11-03 16:32:07 -04:00
Vladimir Mandic e45509d163 improve precision using wasm backend 2021-11-02 11:42:15 -04:00
Vladimir Mandic 4ff1a7d063 refactor predict with execute 2021-11-02 11:07:11 -04:00
Vladimir Mandic 28d306e157 update tests 2021-10-31 09:58:48 -04:00
Vladimir Mandic be767fb02d update hand landmarks model 2021-10-31 09:06:33 -04:00
Vladimir Mandic f773ef3dbe patch tfjs type defs 2021-10-31 08:03:42 -04:00
Vladimir Mandic 20ee0f5f3f start 2.5 major version 2021-10-30 12:21:54 -04:00
Vladimir Mandic 560b3ff671 build and docs cleanup 2021-10-29 15:55:20 -04:00
Vladimir Mandic 2f874b30bb fix firefox bug 2021-10-28 17:25:50 -04:00
Vladimir Mandic c7484046a7 update tfjs 2021-10-28 14:40:31 -04:00
Vladimir Mandic 884684f501 2.4.3 2021-10-28 13:59:57 -04:00
Vladimir Mandic 405fd8a6c2 additional human.performance counters 2021-10-27 09:45:38 -04:00
Vladimir Mandic 856ca5a9a0 2.4.2 2021-10-27 09:44:17 -04:00
Vladimir Mandic 0ccf0820f0 add ts demo 2021-10-27 08:16:06 -04:00
Vladimir Mandic 8b29d262d8 switch from es2018 to es2020 for main build 2021-10-26 19:38:23 -04:00
Vladimir Mandic 404a275590 switch to custom tfjs for demos 2021-10-26 15:08:05 -04:00
Vladimir Mandic 464fcf5622 update todo 2021-10-25 13:45:04 -04:00
Vladimir Mandic 32562dfb83 release 2.4 2021-10-25 13:29:29 -04:00
Vladimir Mandic 173aac2eac 2.4.1 2021-10-25 13:09:41 -04:00
Vladimir Mandic 5af91547c6 refactoring plus jsdoc comments 2021-10-25 13:09:00 -04:00
Vladimir Mandic 90503d595d increase face similarity match resolution 2021-10-25 09:44:13 -04:00
Vladimir Mandic 33e3a5913b update todo 2021-10-23 09:42:41 -04:00
Vladimir Mandic dbd64ae6c2 time based caching 2021-10-23 09:38:52 -04:00
Vladimir Mandic 6ca5df0082 turn on minification 2021-10-22 20:14:13 -04:00
Vladimir Mandic e433a08076 update todo 2021-10-22 16:11:02 -04:00
Vladimir Mandic 5afebaa588 initial work on skipTime 2021-10-22 16:09:52 -04:00
Vladimir Mandic 4cec6f5735 added generic types 2021-10-22 14:46:19 -04:00
Vladimir Mandic f1de75c5be enhanced typing exports 2021-10-22 13:49:40 -04:00
Vladimir Mandic fcd82fb07d update tfjs to 3.10.0 2021-10-22 09:48:27 -04:00
Vladimir Mandic 1680032088 add optional autodetected custom wasm path 2021-10-21 12:42:08 -04:00
Vladimir Mandic 39fd4f5147 2.3.6 2021-10-21 11:31:46 -04:00
Vladimir Mandic 06f20e86c2 fix for human.draw labels and typedefs 2021-10-21 10:54:51 -04:00
Vladimir Mandic b28327d063 refactor human.env to a class type 2021-10-21 10:26:44 -04:00
Vladimir Mandic 309bac7fbe add human.custom.esm using custom tfjs build 2021-10-20 17:49:00 -04:00
Vladimir Mandic 4f46a81eda update handtrack boxes and refactor handpose 2021-10-20 09:10:57 -04:00
Vladimir Mandic 5f6aac9928 update demos 2021-10-19 11:28:59 -04:00
Vladimir Mandic 486d5374a2 2.3.5 2021-10-19 11:25:05 -04:00
Jimmy Nyström 9025e60e3a Removed direct usage of performance.now
Switched to using the utility function that works in both nodejs and browser environments
2021-10-19 09:58:14 -04:00
Vladimir Mandic e3640ec5bd update 2021-10-19 08:09:46 -04:00
Vladimir Mandic ecd295334c 2.3.4 2021-10-19 08:05:19 -04:00
Vladimir Mandic 4ed754e539 update dependencies and refresh release 2021-10-19 07:58:51 -04:00
Vladimir Mandic 2ca389560f minor blazepose optimizations 2021-10-15 09:34:40 -04:00
Vladimir Mandic e6d794205c compress samples 2021-10-15 07:25:51 -04:00
Vladimir Mandic 96a8a3c837 remove posenet from default package 2021-10-15 06:49:41 -04:00
Vladimir Mandic cd35d733d9 enhanced movenet postprocessing 2021-10-14 12:26:59 -04:00
Vladimir Mandic 86a4cedf81 update handtrack skip algorithm 2021-10-13 14:49:41 -04:00
Vladimir Mandic 80be4436bb use transferrable buffer for worker messages 2021-10-13 11:53:54 -04:00
Vladimir Mandic 7e8973a7be update todo 2021-10-13 11:02:44 -04:00
Vladimir Mandic fcc64e845c add optional anti-spoofing module 2021-10-13 10:56:56 -04:00
Vladimir Mandic 86db81d29c update todo 2021-10-13 08:36:20 -04:00
Vladimir Mandic 4b0bb6aa50 add node-match advanced example using worker thread pool 2021-10-13 08:06:11 -04:00
Vladimir Mandic 569b1afcfd package updates 2021-10-12 14:17:33 -04:00
Vladimir Mandic e7dce1311c optimize image preprocessing 2021-10-12 11:39:18 -04:00
Vladimir Mandic de97051399 update imagefx 2021-10-12 09:48:00 -04:00
Vladimir Mandic bb7f1bd521 set webgpu optimized flags 2021-10-11 09:22:39 -04:00
Vladimir Mandic 3925d6d426 major precision improvements to movenet and handtrack 2021-10-10 22:29:20 -04:00
Vladimir Mandic 6efedef077 image processing fixes 2021-10-10 17:52:43 -04:00
Vladimir Mandic 54371fbba5 redesign body and hand caching and interpolation 2021-10-08 18:39:04 -04:00
Vladimir Mandic f20d8b9801 demo default config cleanup 2021-10-08 07:48:48 -04:00
Vladimir Mandic 8293bc26b9 improve gaze and face angle visualizations in draw 2021-10-07 10:33:10 -04:00
Vladimir Mandic d991adc940 release 2.3.1 2021-10-06 11:33:58 -04:00
Vladimir Mandic 008569c274 2.3.1 2021-10-06 11:30:44 -04:00
Vladimir Mandic 4da8745033 workaround for chrome offscreencanvas bug 2021-10-06 11:30:34 -04:00
Vladimir Mandic b8c9b98011 fix backend conflict in webworker 2021-10-04 17:03:36 -04:00
Vladimir Mandic 051ab8c9f5 add blazepose v2 and add annotations to body results 2021-10-04 16:29:15 -04:00
Vladimir Mandic 0ee5d5eceb fix backend order initialization 2021-10-03 08:12:26 -04:00
Vladimir Mandic e4ef0c39f9 added docker notes 2021-10-02 11:41:51 -04:00
Vladimir Mandic a1b13e0627 update dependencies 2021-10-02 07:46:07 -04:00
Vladimir Mandic f4056e717e updated hint rules 2021-10-01 12:07:14 -04:00
Vladimir Mandic 445325dd10 updated facematch demo 2021-10-01 11:40:57 -04:00
Vladimir Mandic 09596f8d21 update wiki 2021-09-30 14:29:14 -04:00
Vladimir Mandic b02c6fa413 breaking change: new similarity and match methods 2021-09-30 14:28:16 -04:00
Vladimir Mandic ceaff322a8 update facematch demo 2021-09-29 08:02:23 -04:00
Vladimir Mandic 443cb24241 update movenet-multipose and samples 2021-09-28 17:07:34 -04:00
Vladimir Mandic 4b807b5f11 tweaked default values 2021-09-28 13:48:29 -04:00
Vladimir Mandic bd5cc2b36b update todo 2021-09-28 12:02:47 -04:00
Vladimir Mandic 7d7ad66e16 enable handtrack as default model 2021-09-28 12:02:17 -04:00
Vladimir Mandic 0c951b322a redesign face processing 2021-09-28 12:01:48 -04:00
Vladimir Mandic 725256edc0 update types and dependencies 2021-09-27 14:39:54 -04:00
Vladimir Mandic e3fd7a5b61 refactoring 2021-09-27 13:58:13 -04:00
Vladimir Mandic 72aad6e812 define app specific types 2021-09-27 09:19:43 -04:00
Vladimir Mandic a7ddea2585 implement box caching for movenet 2021-09-27 08:53:41 -04:00
Vladimir Mandic bdc21b32ff update todo 2021-09-26 10:09:30 -04:00
Vladimir Mandic 79c6240d7e update todo 2021-09-26 10:03:39 -04:00
Vladimir Mandic e7f38c7f33 update wiki 2021-09-26 06:53:06 -04:00
Vladimir Mandic cd35d39352 autodetect number of bodies and hands 2021-09-25 19:14:03 -04:00
Vladimir Mandic 7da344b8d5 upload new samples 2021-09-25 16:31:44 -04:00
Vladimir Mandic 619f6bcc87 new samples gallery and major code folder restructure 2021-09-25 11:51:15 -04:00
Vladimir Mandic c2a1d57eeb update todo 2021-09-24 09:57:03 -04:00
Vladimir Mandic cc71f647bf new release 2021-09-24 09:55:27 -04:00
Vladimir Mandic 77b3010fb1 2.2.3 2021-09-24 09:46:35 -04:00
Vladimir Mandic 91df711db0 optimize model loading 2021-09-23 14:09:41 -04:00
Vladimir Mandic 88a9701d4a support segmentation for nodejs 2021-09-22 19:27:12 -04:00
Vladimir Mandic a259b1f0c1 update todo and docs 2021-09-22 16:00:43 -04:00
Vladimir Mandic 291af25ef8 redo segmentation and handtracking 2021-09-22 15:16:14 -04:00
Vladimir Mandic 17b251748e prototype handtracking 2021-09-21 16:48:16 -04:00
Vladimir Mandic 2ee248b05d automated browser tests 2021-09-20 22:06:49 -04:00
Vladimir Mandic 1d3d25e0bc support for dynamic backend switching 2021-09-20 21:59:49 -04:00
Vladimir Mandic 1cd528f8e5 initial automated browser tests 2021-09-20 17:17:13 -04:00
Vladimir Mandic 97a3abc654 enhanced automated test coverage 2021-09-20 09:42:34 -04:00
Vladimir Mandic a6d01c2ed3 more automated tests 2021-09-19 14:20:22 -04:00
Vladimir Mandic 7d28cd06f2 added configuration validation 2021-09-19 14:07:53 -04:00
Vladimir Mandic accd2d1e65 updated build platform and typedoc theme 2021-09-18 19:09:02 -04:00
Vladimir Mandic 22827eafe2 prevent validation failed on some model combinations 2021-09-17 14:30:57 -04:00
Vladimir Mandic d5d2afee0f webgl exception handling 2021-09-17 14:07:44 -04:00
Vladimir Mandic 22364c9583 2.2.2 2021-09-17 14:07:32 -04:00
Vladimir Mandic 5587f64f82 experimental webgl status monitoring 2021-09-17 11:23:00 -04:00
Vladimir Mandic 29428ca093 major release 2021-09-16 10:49:42 -04:00
Vladimir Mandic 47b4f40a19 2.2.1 2021-09-16 10:46:24 -04:00
Vladimir Mandic 1a81b000c9 add vr model demo 2021-09-16 10:15:20 -04:00
Vladimir Mandic b56b6ef9ef update readme 2021-09-15 19:12:05 -04:00
Vladimir Mandic c3da1c0b78 all tests passing 2021-09-15 19:02:51 -04:00
Vladimir Mandic 6a7338a89f redefine draw helpers interface 2021-09-15 18:58:54 -04:00
Vladimir Mandic bfe889c03d add simple webcam and webrtc demo 2021-09-15 13:59:18 -04:00
Vladimir Mandic d29ac601d4 added visual results browser to demo 2021-09-15 11:15:38 -04:00
Vladimir Mandic f492002a3c reorganize tfjs bundle 2021-09-14 22:07:13 -04:00
Vladimir Mandic 86506b8b8c experimental custom tfjs bundle - disabled 2021-09-14 20:07:08 -04:00
Vladimir Mandic 5916396d48 add platform and backend capabilities detection 2021-09-13 23:24:04 -04:00
Vladimir Mandic d2f92820b6 update changelog and todo 2021-09-13 13:54:42 -04:00
Vladimir Mandic d3f38e9029 update dependencies 2021-09-13 13:34:41 -04:00
Vladimir Mandic b338abca11 enhanced automated tests 2021-09-13 13:30:46 -04:00
Vladimir Mandic 1dfe9e00e5 enable canvas patching for nodejs 2021-09-13 13:30:08 -04:00
Vladimir Mandic 36960735d1 full ts strict typechecks 2021-09-13 13:29:14 -04:00
Vladimir Mandic 45af8e225c fix multiple memory leaks 2021-09-13 13:28:35 -04:00
Vladimir Mandic 97ce559e60 modularize human class and add model validation 2021-09-12 18:37:06 -04:00
Vladimir Mandic 71be3eb818 update todo 2021-09-12 13:18:33 -04:00
Vladimir Mandic 0ebf93154f add dynamic kernel op detection 2021-09-12 13:17:33 -04:00
Vladimir Mandic 5382624645 added human.env diagnostic class 2021-09-12 12:42:17 -04:00
Vladimir Mandic bd6e291987 minor typos 2021-09-12 08:49:56 -04:00
Vladimir Mandic d3ecc64468 release candidate 2021-09-12 00:30:11 -04:00
Vladimir Mandic 27d0b4dfa4 parametrize face config 2021-09-12 00:05:06 -04:00
Vladimir Mandic 6970c43ec5 mark all config items as optional 2021-09-11 23:59:41 -04:00
Vladimir Mandic 7c0751b15e redefine config and result interfaces 2021-09-11 23:54:35 -04:00
Vladimir Mandic b6f3f6a494 fix usge of string enums 2021-09-11 23:08:18 -04:00
Vladimir Mandic 69021653be start using partial definitions 2021-09-11 16:11:00 -04:00
Vladimir Mandic 1d27bc03ed implement event emitters 2021-09-11 16:00:16 -04:00
Vladimir Mandic 56aad00975 fix iife loader 2021-09-11 11:42:48 -04:00
Vladimir Mandic 0692c9c71f update sourcemaps 2021-09-11 11:17:13 -04:00
Vladimir Mandic 65b114ae28 simplify dependencies 2021-09-11 10:29:31 -04:00
Vladimir Mandic 48aa359bb5 change build process 2021-09-10 21:21:29 -04:00
Vladimir Mandic e55df688d8 updated wiki 2021-09-06 08:17:48 -04:00
Vladimir Mandic 558c61de05 update lint exceptions 2021-09-05 17:05:46 -04:00
Vladimir Mandic 4aac3a02b9 update wiki 2021-09-05 16:48:57 -04:00
Vladimir Mandic ce703f48fd add benchmark info 2021-09-05 16:42:11 -04:00
Vladimir Mandic b0bd103db2 update hand detector processing algorithm 2021-09-02 08:50:16 -04:00
Vladimir Mandic ff6cadead0 update 2021-08-31 18:24:30 -04:00
Vladimir Mandic 50c66535c5 simplify canvas handling in nodejs 2021-08-31 18:22:16 -04:00
Vladimir Mandic 6965b67b08 full rebuild 2021-08-31 14:50:16 -04:00
Vladimir Mandic 30a2e6a074 2.1.5 2021-08-31 14:49:07 -04:00
Vladimir Mandic 7af32d08c3 added demo node-canvas 2021-08-31 14:48:55 -04:00
Vladimir Mandic 6066ad8e31 update node-fetch 2021-08-31 13:29:29 -04:00
Vladimir Mandic cfeac24fc2 dynamically generate default wasm path 2021-08-31 13:00:06 -04:00
Vladimir Mandic 42100caad7 updated wiki 2021-08-23 08:41:50 -04:00
Vladimir Mandic 40de479c1e implement finger poses in hand detection and gestures 2021-08-20 20:43:03 -04:00
Vladimir Mandic 070bb3a2c1 implemented movenet-multipose model 2021-08-20 09:05:07 -04:00
Vladimir Mandic aabe01f9b0 update todo 2021-08-19 17:28:07 -04:00
Vladimir Mandic 25a98c1cce 2.1.4 2021-08-19 16:17:03 -04:00
Vladimir Mandic a009d58377 add static type definitions to main class 2021-08-19 16:16:56 -04:00
Vladimir Mandic 2eb8b91549 fix interpolation overflow 2021-08-18 14:28:31 -04:00
Vladimir Mandic 9ae7f21c81 rebuild full 2021-08-17 18:49:49 -04:00
Vladimir Mandic 6854256668 update angle calculations 2021-08-17 18:46:50 -04:00
Vladimir Mandic 83fb9cac03 improve face box caching 2021-08-17 09:15:47 -04:00
Vladimir Mandic f4a1fb2d48 strict type checks 2021-08-17 08:51:17 -04:00
Vladimir Mandic 7c6d9dd3fe add webgu checks 2021-08-15 08:09:40 -04:00
Vladimir Mandic cdb7cd5267 update todo 2021-08-14 18:02:39 -04:00
Vladimir Mandic d047ddcd73 experimental webgpu support 2021-08-14 18:00:26 -04:00
Vladimir Mandic 6f5d4d2fe2 add experimental webgu demo 2021-08-14 13:39:26 -04:00
Vladimir Mandic df58810073 add backend initialization checks 2021-08-14 11:17:51 -04:00
Vladimir Mandic a77a39dbfa complete async work 2021-08-14 11:16:26 -04:00
Vladimir Mandic f77aa04dfe update node-webcam 2021-08-13 18:47:37 -04:00
Vladimir Mandic 2f9d7dedae list detect cameras 2021-08-13 10:34:09 -04:00
Vladimir Mandic fc01b2a4a6 switch to async data reads 2021-08-12 09:31:16 -04:00
Vladimir Mandic 24e389019b 2.1.3 2021-08-12 09:29:48 -04:00
Vladimir Mandic 8b6e9327a4 fix centernet & update blazeface 2021-08-11 18:59:02 -04:00
Vladimir Mandic d467fbbafd update todo 2021-08-09 10:46:03 -04:00
Vladimir Mandic 88977c0fe4 update model list 2021-08-06 08:50:50 -04:00
Vladimir Mandic 8572d589ca minor update 2021-08-06 08:29:41 -04:00
Vladimir Mandic f68e76c945 minor update 2021-08-05 10:38:04 -04:00
Vladimir Mandic eeed676823 update build process to remove warnings 2021-07-31 20:42:28 -04:00
Vladimir Mandic 8d6e8dd1f6 update todo 2021-07-31 07:43:50 -04:00
Vladimir Mandic b48a809c5c update typedoc links 2021-07-31 07:29:37 -04:00
Vladimir Mandic e741ba7f02 replace movenet with lightning-v4 2021-07-30 07:18:54 -04:00
Vladimir Mandic 767e7b93ad update eslint rules 2021-07-30 06:49:41 -04:00
Vladimir Mandic a0248a9f7c enable webgl uniform support for faster warmup 2021-07-29 16:35:16 -04:00
Vladimir Mandic 4a5b7c40bb 2.1.2 2021-07-29 16:34:03 -04:00
Vladimir Mandic d08b657ecd fix unregistered ops in tfjs 2021-07-29 16:06:03 -04:00
Vladimir Mandic 92e9b87829 update build 2021-07-29 12:50:06 -04:00
Vladimir Mandic 7c0256b502 fix typo 2021-07-29 11:26:19 -04:00
Vladimir Mandic 21f457d7a0 updated wiki 2021-07-29 11:06:34 -04:00
Vladimir Mandic b3a04b0315 rebuild new release 2021-07-29 11:03:21 -04:00
Vladimir Mandic 036757265e 2.1.1 2021-07-29 11:02:02 -04:00
Vladimir Mandic dbeaabbcbe updated gesture types 2021-07-29 11:01:50 -04:00
Vladimir Mandic 26c49a59d0 update tfjs and typescript 2021-07-29 09:53:13 -04:00
Vladimir Mandic 35def2fd62 updated minimum version of nodejs to v14 2021-07-29 09:41:17 -04:00
Vladimir Mandic fab6eb0b06 add note on manually disping tensor 2021-06-18 13:39:20 -04:00
Vladimir Mandic a9fffcb434 update todo 2021-06-18 09:19:34 -04:00
Vladimir Mandic 3b75e5e82c modularize model loading 2021-06-18 09:16:21 -04:00
Vladimir Mandic 53b4939a62 update typedoc 2021-06-18 07:25:33 -04:00
Vladimir Mandic 473d1450bc 2.0.3 2021-06-18 07:20:33 -04:00
Vladimir Mandic 5ce07ef6c8 update 2021-06-16 15:47:01 -04:00
Vladimir Mandic 24181693a1 update 2021-06-16 15:46:05 -04:00
Vladimir Mandic f9f85ef4fe fix demo paths 2021-06-16 15:40:35 -04:00
Vladimir Mandic 72d02e10f1 added multithreaded demo 2021-06-14 10:23:06 -04:00
Vladimir Mandic df33ca0a31 2.0.2 2021-06-14 10:20:49 -04:00
Vladimir Mandic 2dba5ceb5b reorganize demos 2021-06-14 08:16:10 -04:00
Vladimir Mandic c07d8c2317 fix centernet box width & height 2021-06-11 16:12:24 -04:00
Vladimir Mandic eb6d344560 update todo 2021-06-09 07:27:19 -04:00
Vladimir Mandic 0bb866547a update 2021-06-09 07:19:03 -04:00
Vladimir Mandic e2f585c92c update demo menu documentation 2021-06-09 07:17:54 -04:00
Vladimir Mandic 017724ff61 update 2021-06-08 07:37:15 -04:00
Vladimir Mandic 8c9c87ffc7 add body segmentation sample 2021-06-08 07:29:08 -04:00
Vladimir Mandic 011011d29c add release notes 2021-06-08 07:09:37 -04:00
Vladimir Mandic 05ee137610 release 2.0 2021-06-08 07:06:16 -04:00
Vladimir Mandic bf1e64f4fc 2.0.1 2021-06-08 07:02:11 -04:00
Vladimir Mandic 337cb9b381 add video drag&drop capability 2021-06-07 08:38:16 -04:00
Vladimir Mandic 76db076d32 update readme 2021-06-06 20:49:48 -04:00
Vladimir Mandic e80a23c92b update packages 2021-06-06 20:47:59 -04:00
Vladimir Mandic 8b0f7c1ccb modularize build platform 2021-06-06 20:34:29 -04:00
Vladimir Mandic a4befc8106 custom build tfjs from sources 2021-06-06 19:00:34 -04:00
Vladimir Mandic 266679d7fb update wasm to tfjs 3.7.0 2021-06-06 12:58:06 -04:00
Vladimir Mandic c4a32361de update defaults 2021-06-05 20:06:36 -04:00
Vladimir Mandic 7f5e5aa00a modularize build platform 2021-06-05 17:51:46 -04:00
Vladimir Mandic bf42a1c64e enable body segmentation and background replacement in demo 2021-06-05 16:13:41 -04:00
Vladimir Mandic 9f63f39e4c minor git corruption 2021-06-05 15:23:17 -04:00
Vladimir Mandic 819b4a3f88 update 2021-06-05 15:10:28 -04:00
Vladimir Mandic 791087bb78 update 2021-06-05 13:02:01 -04:00
Vladimir Mandic fd5973b0c2 unified build 2021-06-05 12:59:11 -04:00
Vladimir Mandic a092f681f9 enable body segmentation and background replacement 2021-06-05 11:54:49 -04:00
Vladimir Mandic f0f7e00969 work on body segmentation 2021-06-04 20:22:05 -04:00
Vladimir Mandic 3c43aa57db added experimental body segmentation module 2021-06-04 13:52:40 -04:00
Vladimir Mandic c5f0ebe03f add meet and selfie models 2021-06-04 13:51:01 -04:00
Vladimir Mandic 78431fca00 update for tfjs 3.7.0 2021-06-04 09:20:59 -04:00
Vladimir Mandic 09af0460c2 update 2021-06-04 07:03:34 -04:00
Vladimir Mandic 3be72e428d update gaze strength calculations 2021-06-03 09:53:11 -04:00
Vladimir Mandic b713dd5271 update build with automatic linter 2021-06-03 09:41:53 -04:00
Vladimir Mandic f552172f64 add live hints to demo 2021-06-02 17:29:50 -04:00
Vladimir Mandic 66318c3355 switch worker from module to iife importscripts 2021-06-02 16:46:07 -04:00
Vladimir Mandic d8f80b2d81 release candidate 2021-06-02 13:39:02 -04:00
Vladimir Mandic d77d8e4942 update wiki 2021-06-02 13:35:59 -04:00
Vladimir Mandic 9d7a3148ec update tests and demos 2021-06-02 13:35:33 -04:00
Vladimir Mandic f2c937d2cd added samples to git 2021-06-02 12:44:12 -04:00
Vladimir Mandic 71cde6b67d implemented drag & drop for image processing 2021-06-02 12:43:43 -04:00
Vladimir Mandic b79e380114 release candidate 2021-06-01 08:59:09 -04:00
Vladimir Mandic e19c65526c breaking changes to results.face output properties 2021-06-01 07:37:17 -04:00
Vladimir Mandic a7f779378d breaking changes to results.object output properties 2021-06-01 07:07:01 -04:00
Vladimir Mandic 8e727302e4 breaking changes to results.hand output properties 2021-06-01 07:01:59 -04:00
Vladimir Mandic efc7e530df breaking changes to results.body output properties 2021-06-01 06:55:40 -04:00
Vladimir Mandic d71bf8bb2f update wiki 2021-05-31 10:40:24 -04:00
Vladimir Mandic 5788a80a7a implemented human.next global interpolation method 2021-05-31 10:40:07 -04:00
Vladimir Mandic 8d228ecff3 update wiki 2021-05-30 23:22:21 -04:00
Vladimir Mandic b22d92b0bc finished draw buffering and smoothing and enabled by default 2021-05-30 23:21:48 -04:00
Vladimir Mandic 3dc60c73ae update wiki 2021-05-30 18:46:23 -04:00
Vladimir Mandic 5761eb282a update typedoc definitions 2021-05-30 18:45:39 -04:00
Vladimir Mandic c9446e93cb update pwa scope 2021-05-30 18:00:51 -04:00
Vladimir Mandic 764c2fae0c implemented service worker 2021-05-30 17:56:40 -04:00
Vladimir Mandic 3e761f4801 update todo 2021-05-30 12:05:27 -04:00
Vladimir Mandic 4194d0e752 quantized centernet 2021-05-30 12:03:52 -04:00
Vladimir Mandic 22d70df905 release candidate 2021-05-30 12:03:34 -04:00
Vladimir Mandic a2793091f2 added usage restrictions 2021-05-30 09:51:23 -04:00
Vladimir Mandic 8b9ccd9670 update security policy 2021-05-30 09:41:24 -04:00
Vladimir Mandic 363e1a3370 quantize handdetect model 2021-05-29 18:29:57 -04:00
Vladimir Mandic 5916b7ff18 update todo list 2021-05-29 09:24:09 -04:00
Vladimir Mandic c980736c30 added experimental movenet-lightning and removed blazepose from default dist 2021-05-29 09:20:01 -04:00
Vladimir Mandic a8082b0815 update 2021-05-28 15:54:29 -04:00
Vladimir Mandic 1678d3192f added experimental face.rotation.gaze 2021-05-28 15:53:51 -04:00
Vladimir Mandic 54ddb2e2be fix and optimize for mobile platform 2021-05-28 10:43:48 -04:00
Vladimir Mandic bb5fe30692 lock typescript to 4.2 due to typedoc incompatibility with 4.3 2021-05-27 16:07:02 -04:00
Vladimir Mandic ade5d65d06 1.9.4 2021-05-27 16:05:20 -04:00
Vladimir Mandic 4596666c0d fix demo facecompare 2021-05-26 08:52:31 -04:00
Vladimir Mandic 7ba3f9b24e webhint and lighthouse optimizations 2021-05-26 08:47:31 -04:00
Vladimir Mandic 1af638c5c5 update 2021-05-26 07:59:52 -04:00
Vladimir Mandic da7721d580 add camera startup diag messages 2021-05-26 07:57:51 -04:00
Vladimir Mandic 4ab5bbb6e6 update all box calculations 2021-05-25 08:58:20 -04:00
Vladimir Mandic 70af13ce8c implemented unified result.persons that combines face, body and hands for each person 2021-05-24 11:10:13 -04:00
Vladimir Mandic 5e11171344 update iris distance docs 2021-05-24 07:18:03 -04:00
Vladimir Mandic edf2d896cf update iris distance calculations 2021-05-24 07:16:38 -04:00
Vladimir Mandic 4d404c7592 added experimental results interpolation for smooth draw operations 2021-05-23 13:55:33 -04:00
Vladimir Mandic fefc3123b9 1.9.3 2021-05-23 13:54:44 -04:00
Vladimir Mandic bdceeea1d8 use green weighted for input diff calculation 2021-05-23 13:54:22 -04:00
Vladimir Mandic 6e894c3baf implement experimental drawOptions.bufferedOutput and bufferedFactor 2021-05-23 13:52:49 -04:00
Vladimir Mandic 5aeb449a67 use explicit tensor interface 2021-05-22 21:54:18 -04:00
Vladimir Mandic 0fb63cea21 add tfjs types and remove all instances of any 2021-05-22 21:47:59 -04:00
Vladimir Mandic 0b907e56d9 enhance strong typing 2021-05-22 14:53:51 -04:00
Vladimir Mandic b11ae9bfae rebuild all for release 2021-05-22 13:17:07 -04:00
Vladimir Mandic 597de41751 1.9.2 2021-05-22 13:15:11 -04:00
Vladimir Mandic 4b221c17ea add id and boxraw on missing objects 2021-05-22 12:41:29 -04:00
Vladimir Mandic 714d95f6ed restructure results strong typing 2021-05-22 12:33:19 -04:00
Vladimir Mandic 618ef6f7fa update dependencies 2021-05-21 06:54:02 -04:00
Vladimir Mandic 670918ea37 1.9.1 2021-05-21 06:51:31 -04:00
Vladimir Mandic 6ced134157 caching improvements 2021-05-20 19:14:07 -04:00
Vladimir Mandic 0c741d2055 add experimental mb3-centernet object detection 2021-05-19 08:27:28 -04:00
Vladimir Mandic 5b0dd5783d individual model skipFrames values still max high threshold for caching 2021-05-18 11:38:22 -04:00
Vladimir Mandic 5741bce450 config.videoOptimized has been removed and config.cacheSensitivity has been added instead 2021-05-18 11:36:57 -04:00
Vladimir Mandic 956e4743c7 caching determination is now dynamic based on detection of input change and not based on input types 2021-05-18 11:36:24 -04:00
Vladimir Mandic 8741695dbd human 1.9.0 beta with breaking changes regarding caching 2021-05-18 11:26:16 -04:00
Vladimir Mandic 53c070e3c5 update dependencies 2021-05-18 08:22:33 -04:00
Vladimir Mandic 2cf87b8151 1.8.5 2021-05-18 08:14:56 -04:00
Vladimir Mandic 8ad1d98970 update demos 2021-05-17 09:35:41 -04:00
Vladimir Mandic 0131b10767 update 2021-05-17 08:56:57 -04:00
Vladimir Mandic 91d37adde0 add node-video sample 2021-05-16 23:55:08 -04:00
Vladimir Mandic eba2f66151 update 2021-05-11 15:08:27 -04:00
Vladimir Mandic 2a30491cb9 add node-webcam demo 2021-05-11 10:11:55 -04:00
Vladimir Mandic 4f9d5c7b09 fix node build and update model signatures 2021-05-11 07:53:06 -04:00
Vladimir Mandic a66516fa34 1.8.4 2021-05-11 07:09:44 -04:00
Vladimir Mandic 48351b1539 update & fix posenet 2021-05-05 10:07:44 -04:00
Vladimir Mandic 5546580eda 1.8.3 2021-05-05 09:55:39 -04:00
Vladimir Mandic 0120290047 switch posenet weights 2021-05-04 20:46:33 -04:00
Vladimir Mandic 31a794a8db update tfjs version 2021-05-04 11:19:46 -04:00
Vladimir Mandic 12b9cca3ae 1.8.2 2021-05-04 11:17:50 -04:00
Vladimir Mandic 16540a2f5a release 1.8 with major changes and tfjs 3.6.0 2021-04-30 11:55:04 -04:00
Vladimir Mandic be315a7a73 1.8.1 2021-04-30 11:53:56 -04:00
Vladimir Mandic ab3d8e647e update 2021-04-28 08:58:21 -04:00
Vladimir Mandic a9b9c2f254 blazeface optimizations 2021-04-28 08:55:26 -04:00
Vladimir Mandic 2546e7b7e9 add hand labels in draw 2021-04-26 07:37:29 -04:00
Vladimir Mandic b41d4783ef cleanup demo workflow 2021-04-26 07:19:30 -04:00
Vladimir Mandic 0a42d1a2d6 update browser demo defaults 2021-04-25 16:58:18 -04:00
Vladimir Mandic 88b646b283 convert blazeface to module 2021-04-25 16:56:10 -04:00
Vladimir Mandic 8678b4d57f version 1.8 release candidate 2021-04-25 14:32:55 -04:00
Vladimir Mandic 7145c04248 build NodeJS deliverables in non-minified form 2021-04-25 14:32:19 -04:00
Vladimir Mandic 8ecaea5d3b stop building sourcemaps for NodeJS deliverables 2021-04-25 14:32:07 -04:00
Vladimir Mandic 08baeb8768 remove deallocate, profile, scoped 2021-04-25 14:31:51 -04:00
Vladimir Mandic 211c3f9a48 replaced maxFaces, maxDetections, maxHands, maxResults with maxDetected 2021-04-25 14:31:39 -04:00
Vladimir Mandic 53d5ecbb75 replaced nmsRadius with built-in default 2021-04-25 14:31:26 -04:00
Vladimir Mandic db39ac03c0 unified minConfidence and scoreThresdold as minConfidence 2021-04-25 14:31:13 -04:00
Vladimir Mandic 266f547a88 add exception handlers to all demos 2021-04-25 14:30:40 -04:00
Vladimir Mandic 5e089f1f12 remove blazeface-front and add unhandledrejection handler 2021-04-25 14:15:38 -04:00
Vladimir Mandic 3c11ef4189 major update for 1.8 release candidate 2021-04-25 13:16:04 -04:00
Vladimir Mandic 439a7af2e4 enable webworker detection 2021-04-25 07:51:01 -04:00
Vladimir Mandic 25d8396082 1.7.1 2021-04-25 07:50:12 -04:00
Vladimir Mandic 5bf05e379f remove obsolete binary models 2021-04-24 18:46:22 -04:00
Vladimir Mandic 437f2f914e enable cross origin isolation 2021-04-24 18:43:59 -04:00
Vladimir Mandic 4dba6b29cb rewrite posenet decoder 2021-04-24 16:04:49 -04:00
Vladimir Mandic 970660c3bf update demo node decoder 2021-04-24 12:12:10 -04:00
Vladimir Mandic e060f1947a update posenet model 2021-04-24 11:49:26 -04:00
Vladimir Mandic 69ba00f535 remove efficientpose 2021-04-24 09:31:46 -04:00
Vladimir Mandic b40d5deca7 major version rebuild 2021-04-22 19:47:04 -04:00
Vladimir Mandic 1411c7153d 1.6.1 2021-04-22 19:46:29 -04:00
Vladimir Mandic 403493fff6 update for tfjs 3.5.0 2021-04-22 19:46:18 -04:00
Vladimir Mandic 1300dc03e1 update todo 2021-04-22 18:39:29 -04:00
Vladimir Mandic 5cb05205a7 add npmrc 2021-04-20 08:02:21 -04:00
Vladimir Mandic a363a08633 update 2021-04-19 16:19:03 -04:00
Vladimir Mandic c4a353787a added filter.flip feature 2021-04-19 16:02:47 -04:00
Vladimir Mandic 22cf040972 update todo 2021-04-19 11:36:44 -04:00
Vladimir Mandic 589861ddef update 2021-04-19 11:24:17 -04:00
Vladimir Mandic 9e1041d9d7 update 2021-04-19 11:20:24 -04:00
Vladimir Mandic af54748ccf update node demo 2021-04-19 11:17:55 -04:00
Vladimir Mandic 0fd7995542 added demo load image from http 2021-04-19 11:15:29 -04:00
Vladimir Mandic d270a45492 update gestures 2021-04-19 09:30:04 -04:00
Vladimir Mandic e0666cebe2 mobile demo optimization and iris gestures 2021-04-18 19:33:40 -04:00
Vladimir Mandic e2aff03f71 full rebuild 2021-04-16 18:03:15 -04:00
Vladimir Mandic 093e50b305 new look 2021-04-16 18:00:24 -04:00
Vladimir Mandic 3eab916003 added benchmarks 2021-04-16 09:31:58 -04:00
Vladimir Mandic 6745c5c3d9 added node-multiprocess demo 2021-04-16 08:34:16 -04:00
Vladimir Mandic da92d53667 update 2021-04-15 17:13:27 -04:00
Vladimir Mandic 97fe47df86 fix image orientation 2021-04-15 15:26:31 -04:00
Vladimir Mandic c1a087ae92 flat app style 2021-04-15 15:01:27 -04:00
Vladimir Mandic 82730dda1d add full nodejs test coverage 2021-04-15 09:43:55 -04:00
Vladimir Mandic f625e1e7ef 1.5.2 2021-04-14 12:53:12 -04:00
Vladimir Mandic e42465d004 update tfjs 3.4.0 2021-04-14 12:53:00 -04:00
Vladimir Mandic e231e68d2d experimental node-wasm support 2021-04-13 21:45:45 -04:00
Vladimir Mandic 3618e831cc 1.5.1 2021-04-13 11:32:26 -04:00
Vladimir Mandic 43f564a978 fix for safari imagebitmap 2021-04-13 11:32:22 -04:00
Vladimir Mandic 0af73ab567 refactored human.config and human.draw 2021-04-13 11:05:52 -04:00
Vladimir Mandic 8ab49c7440 1.4.3 2021-04-12 17:49:14 -04:00
Vladimir Mandic de2b82e45d implement webrtc 2021-04-12 17:48:59 -04:00
Vladimir Mandic 9aa295e714 1.4.2 2021-04-12 08:29:58 -04:00
Vladimir Mandic 6e0fe556e8 added support for multiple instances of human 2021-04-12 08:29:52 -04:00
Vladimir Mandic f4f73e52fd update 2021-04-10 23:37:44 -04:00
Vladimir Mandic 485e122472 update cdn links 2021-04-10 23:16:06 -04:00
Vladimir Mandic dbab940f55 update 2021-04-09 21:58:45 -04:00
Vladimir Mandic 03d6be4e4c fix typedoc 2021-04-09 21:53:48 -04:00
Vladimir Mandic e8dffde291 update readme 2021-04-09 21:31:53 -04:00
Vladimir Mandic 0e20bfe665 exception handling 2021-04-09 10:02:40 -04:00
Vladimir Mandic e1f285f314 1.4.1 2021-04-09 08:08:05 -04:00
Vladimir Mandic b572858b97 add modelBasePath option 2021-04-09 08:07:58 -04:00
Vladimir Mandic 3cc1a8201f update badges 2021-04-08 19:16:17 -04:00
Vladimir Mandic 7ea4eec64e update cdn links 2021-04-08 18:37:58 -04:00
Vladimir Mandic 0788b049b3 update 2021-04-08 12:10:15 -04:00
Vladimir Mandic d886f86021 1.3.5 2021-04-06 11:38:07 -04:00
Vladimir Mandic 7022c265a7 update tslib 2021-04-06 11:38:01 -04:00
Vladimir Mandic 320638ba41 update wiki 2021-04-06 07:45:44 -04:00
Vladimir Mandic 9b5ced8f62 add dynamic viewport and fix web worker 2021-04-05 11:48:24 -04:00
Vladimir Mandic 3cf07dc501 add cdn links 2021-04-05 09:35:56 -04:00
Vladimir Mandic 2d263bd2d4 1.3.4 2021-04-04 09:26:35 -04:00
Vladimir Mandic 6958539708 update 2021-04-04 09:26:32 -04:00
Vladimir Mandic e9bd519bc3 implement webhint 2021-04-04 09:25:18 -04:00
Vladimir Mandic 6723c2a322 update 2021-04-03 11:36:53 -04:00
Vladimir Mandic 6d07e937a8 update keywords 2021-04-03 11:10:07 -04:00
Vladimir Mandic d615ef1e6e 1.3.3 2021-04-03 10:49:30 -04:00
Vladimir Mandic b3e8747126 fix linting and tests 2021-04-03 10:49:14 -04:00
Vladimir Mandic cb3014edc1 1.3.2 2021-04-02 08:39:50 -04:00
Vladimir Mandic 34f01f2053 input type validation 2021-04-02 08:37:35 -04:00
Vladimir Mandic e78ecaaf18 update 2021-04-02 08:15:39 -04:00
Vladimir Mandic 1aa7ea2db6 update 2021-04-02 08:09:06 -04:00
Vladimir Mandic abf0ba65c9 update 2021-04-02 08:06:28 -04:00
Vladimir Mandic 0e27370b8c update demos 2021-04-02 08:05:19 -04:00
Vladimir Mandic 5931a6f541 normalize all scores 2021-04-01 09:24:56 -04:00
Vladimir Mandic 942fa18b52 1.3.1 2021-03-30 09:04:51 -04:00
Vladimir Mandic 8ffec55b4a update 2021-03-30 09:04:49 -04:00
Vladimir Mandic 49a9717b90 added face3d demo 2021-03-30 09:03:18 -04:00
Vladimir Mandic b4c4364953 initial work on face3d three.js demo 2021-03-29 15:59:16 -04:00
Vladimir Mandic fd3b67851b enable buffering 2021-03-29 15:05:14 -04:00
Vladimir Mandic db6aa75ddb new icons 2021-03-29 15:01:16 -04:00
Vladimir Mandic 1d715dfead new serve module and demo structure 2021-03-29 14:40:34 -04:00
Vladimir Mandic 6d978c0b9b move gl flags to correct location 2021-03-28 13:22:22 -04:00
Vladimir Mandic 957872e5a1 minor rotation calculation fix 2021-03-28 08:49:56 -04:00
Vladimir Mandic 9cdae38de1 remove debug output 2021-03-28 08:44:53 -04:00
Vladimir Mandic 491ebe018f new face rotation calculations 2021-03-28 08:40:39 -04:00
ButzYung e8d082f6ad cleanup 2021-03-28 07:32:31 -04:00
ButzYung 395b72680b rotationMatrixToEulerAngle, and fixes 2021-03-28 07:32:31 -04:00
ButzYung 749fc56646 face rotation matrix 2021-03-28 07:32:31 -04:00
Vladimir Mandic 5653b4577d update 2021-03-27 15:45:37 -04:00
Vladimir Mandic 8b9374c09b experimental: add efficientpose 2021-03-27 15:43:48 -04:00
Vladimir Mandic 9bb0769637 implement nanodet 2021-03-27 10:25:31 -04:00
Vladimir Mandic dc7abbb459 start working on efficientpose 2021-03-26 18:50:19 -04:00
Vladimir Mandic f1ae581c7b update contributing guide 2021-03-26 15:37:43 -04:00
Vladimir Mandic a63e33042e update contributing guidelines 2021-03-26 12:37:08 -04:00
Vladimir Mandic 93d318aff1 update 2021-03-25 08:50:41 -04:00
Vladimir Mandic 32e634306a 1.2.5 2021-03-25 08:44:04 -04:00
Vladimir Mandic 58781b8626 fix broken exports 2021-03-25 08:43:51 -04:00
Vladimir Mandic 7ca8f5c448 update faces database 2021-03-24 11:43:28 -04:00
Vladimir Mandic cece9f71fc updated face description 2021-03-24 11:08:49 -04:00
Vladimir Mandic f296ed1427 added face matching example to docs 2021-03-23 15:35:54 -04:00
Vladimir Mandic f50ff300c8 update 2021-03-23 15:25:43 -04:00
Vladimir Mandic 13ea73e650 improve fact matching 2021-03-23 15:24:58 -04:00
Vladimir Mandic 125381a67f 1.2.4 2021-03-23 14:46:50 -04:00
Vladimir Mandic 272985bb25 update nanodet and face rotation check 2021-03-23 14:46:44 -04:00
Vladimir Mandic 0d27b59f63 1.2.3 2021-03-21 17:47:05 -04:00
Vladimir Mandic ca511a5385 update demos 2021-03-21 17:47:00 -04:00
Vladimir Mandic bf89c7ee98 1.2.2 2021-03-21 16:16:17 -04:00
Vladimir Mandic 05ccdc3eb0 precise face rotation 2021-03-21 16:16:13 -04:00
Vladimir Mandic f78957af02 update 2021-03-21 14:24:10 -04:00
Vladimir Mandic 8301f49db7 1.2.1 2021-03-21 14:21:47 -04:00
Vladimir Mandic 3193811a63 update wiki 2021-03-21 14:19:04 -04:00
Vladimir Mandic 64adb7ebd8 new module: face description 2021-03-21 14:18:51 -04:00
Vladimir Mandic 79a434b384 1.1.11 2021-03-21 07:49:58 -04:00
Vladimir Mandic 0e15a5e846 refactor face classes 2021-03-21 07:49:55 -04:00
Vladimir Mandic 2829608a0e 1.1.10 2021-03-18 16:55:04 -04:00
Vladimir Mandic 4e7aef79e4 cleanup 2021-03-18 16:55:00 -04:00
Vladimir Mandic c31bc21d80 update github templates 2021-03-18 11:58:46 -04:00
Vladimir Mandic 61db8f0754 update 2021-03-18 07:40:08 -04:00
Vladimir Mandic 18e1712864 update keywords 2021-03-18 07:14:24 -04:00
Vladimir Mandic 13a2472f75 update 2021-03-17 20:26:43 -04:00
Vladimir Mandic c1c6b66315 redefine tensor 2021-03-17 20:23:12 -04:00
Vladimir Mandic de0fffbfdc enforce types 2021-03-17 20:16:40 -04:00
Vladimir Mandic d7571891e7 regen type declarations 2021-03-17 18:57:00 -04:00
Vladimir Mandic 6f8ee4db53 switch to single jumbo dts 2021-03-17 18:48:02 -04:00
Vladimir Mandic 72329a42a9 update node demo 2021-03-17 18:36:12 -04:00
Vladimir Mandic 2464b8c0ef update 2021-03-17 18:33:12 -04:00
Vladimir Mandic b0ed511405 type definitions 2021-03-17 18:23:19 -04:00
Vladimir Mandic f796d39e60 update package docs 2021-03-17 14:45:15 -04:00
Vladimir Mandic 57343739b6 1.1.9 2021-03-17 14:35:23 -04:00
Vladimir Mandic 8523da07b5 fix box clamping and raw output 2021-03-17 14:35:11 -04:00
Vladimir Mandic 4819930e43 update readme 2021-03-17 12:03:36 -04:00
Vladimir Mandic aa79cd3b20 hierarchical readme notes 2021-03-17 12:01:54 -04:00
Vladimir Mandic 6c3daeeff1 update readme 2021-03-17 11:48:34 -04:00
Vladimir Mandic 1a92741bac update 2021-03-17 11:41:57 -04:00
Vladimir Mandic f95d097ef0 1.1.8 2021-03-17 11:40:35 -04:00
Vladimir Mandic 7738c2ad3d update 2021-03-17 11:40:31 -04:00
Vladimir Mandic c356b02625 add experimental nanodet object detection 2021-03-17 11:32:37 -04:00
Vladimir Mandic a0cf463f03 full models signature 2021-03-17 09:01:59 -04:00
Vladimir Mandic b92474954a 1.1.7 2021-03-16 07:16:28 -04:00
Vladimir Mandic 8313cc9092 fix for seedrandom 2021-03-16 07:16:25 -04:00
Vladimir Mandic 733c124afe update todo 2021-03-15 12:31:23 -04:00
Vladimir Mandic 0d8e0f9316 custom typedoc 2021-03-15 12:29:51 -04:00
Vladimir Mandic 295148fc0f 1.1.6 2021-03-15 12:14:52 -04:00
Vladimir Mandic 5e082a5294 implement human.match and embedding demo 2021-03-15 12:14:48 -04:00
Vladimir Mandic ea02afb813 1.1.5 2021-03-15 08:56:45 -04:00
Vladimir Mandic 2d844828c0 full rebuild 2021-03-15 08:56:36 -04:00
Vladimir Mandic 7f31cee9d6 update 2021-03-15 08:52:28 -04:00
Vladimir Mandic 87c3171712 1.1.4 2021-03-14 13:49:05 -04:00
Vladimir Mandic 6294882ea8 fix broken build 2021-03-14 13:49:01 -04:00
Vladimir Mandic 6c6f2bebac 1.1.3 2021-03-14 13:39:52 -04:00
Vladimir Mandic 1da76b4adc update 2021-03-14 13:39:47 -04:00
Vladimir Mandic 71368d3889 added api specs 2021-03-13 22:38:35 -05:00
Vladimir Mandic 43d283125b add typedocs and types 2021-03-13 22:31:09 -05:00
Vladimir Mandic fc8e5bf258 strong typings 2021-03-13 13:47:45 -05:00
Vladimir Mandic da656f62a0 update 2021-03-13 12:30:03 -05:00
Vladimir Mandic c2fd74d097 update docs 2021-03-13 12:13:45 -05:00
Vladimir Mandic 588fa49d4c update 2021-03-13 11:37:16 -05:00
Vladimir Mandic 896cb0aac0 update embedding and strong typings 2021-03-13 11:26:53 -05:00
Vladimir Mandic 092873adaa 1.1.2 2021-03-12 18:24:38 -05:00
Vladimir Mandic 75f3e3c269 distance based on minkowski space and limited euclidean space 2021-03-12 18:24:34 -05:00
Vladimir Mandic 8de79a92b2 guard against invalid input images 2021-03-12 16:43:36 -05:00
Vladimir Mandic f8703e3dc0 1.1.1 2021-03-12 12:54:34 -05:00
Vladimir Mandic ca400cd45d update wiki 2021-03-12 12:54:30 -05:00
Vladimir Mandic 12b0058a1b switched face embedding to mobileface 2021-03-12 12:54:08 -05:00
Vladimir Mandic 162ace9fc3 updated docs 2021-03-11 22:11:49 -05:00
Vladimir Mandic dc4fae1169 1.0.4 2021-03-11 22:04:54 -05:00
Vladimir Mandic e4574d9fdf add face return tensor 2021-03-11 22:04:44 -05:00
Vladimir Mandic c3ecdf5486 add test for face descriptors 2021-03-11 18:26:04 -05:00
Vladimir Mandic 622de45e50 wip on embedding 2021-03-11 13:31:36 -05:00
Vladimir Mandic 1c921294ba simplify face box coordinate calculations 2021-03-11 11:44:22 -05:00
Vladimir Mandic fac2b6b544 annotated models and removed gender-ssrnet 2021-03-11 10:30:20 -05:00
Vladimir Mandic d5b620dbe8 autodetect inputSizes 2021-03-11 10:26:14 -05:00
Vladimir Mandic 13285582d6 update todo 2021-03-10 10:54:39 -05:00
Vladimir Mandic 8a7b0e5f90 update 2021-03-10 10:28:20 -05:00
Vladimir Mandic 3708cf8541 update todo 2021-03-10 10:12:39 -05:00
Vladimir Mandic d511aa0a8b 1.0.3 2021-03-10 10:02:55 -05:00
Vladimir Mandic 3b984fdd35 strong typing for public classes and hide private classes 2021-03-10 10:02:52 -05:00
Vladimir Mandic 74ad5a2837 enhanced age, gender, emotion detection 2021-03-10 09:44:45 -05:00
Vladimir Mandic 02696a65b3 full rebuild 2021-03-09 18:36:26 -05:00
Vladimir Mandic a5ef2081de 1.0.2 2021-03-09 18:34:43 -05:00
Vladimir Mandic 7d512d8327 update tfjs and esbuild 2021-03-09 18:34:04 -05:00
Vladimir Mandic 1abbd2c909 remove blazeface-front, blazepose-upper, faceboxes 2021-03-09 18:33:50 -05:00
Vladimir Mandic f8b13a8bba remove blazeface-front and faceboxes 2021-03-09 18:32:35 -05:00
Vladimir Mandic 47ff4f8d40 update 2021-03-09 13:37:49 -05:00
Vladimir Mandic 8f0403073c update 2021-03-09 13:19:52 -05:00
Vladimir Mandic cc466f5554 update 2021-03-09 13:18:08 -05:00
Vladimir Mandic 16ecba7f75 1.0.1 2021-03-09 13:15:59 -05:00
Vladimir Mandic 9fd20393be fix for face detector when mesh is disabled 2021-03-09 13:15:40 -05:00
Vladimir Mandic 8e9f78c6d7 update badges 2021-03-08 15:06:56 -05:00
Vladimir Mandic 05b37e0266 optimize for npm 2021-03-08 14:12:12 -05:00
Vladimir Mandic 16bd0d227f 0.40.9 2021-03-08 10:06:40 -05:00
Vladimir Mandic f4bc35c3b3 fix performance issue when running with low confidence 2021-03-08 10:06:34 -05:00
Vladimir Mandic b02a2962f3 0.40.8 2021-03-08 07:32:30 -05:00
Vladimir Mandic 36451ea918 update docs and demo 2021-03-08 07:32:24 -05:00
Vladimir Mandic 14a430edd4 0.40.7 2021-03-06 17:23:36 -05:00
Vladimir Mandic 0813fc0e00 update 2021-03-06 17:23:24 -05:00
Vladimir Mandic cd949deec9 implemented 3d face angle calculations 2021-03-06 17:22:47 -05:00
Vladimir Mandic 0d7cb8d8ea 0.40.6 2021-03-06 10:38:22 -05:00
Vladimir Mandic 18a4789639 add curve draw output 2021-03-06 10:38:04 -05:00
Vladimir Mandic 0316725a01 update 2021-03-05 14:42:32 -05:00
Vladimir Mandic 157ceb5bec update readme 2021-03-05 14:40:44 -05:00
Vladimir Mandic a7ad592693 update 2021-03-05 14:31:06 -05:00
Vladimir Mandic 86f68b4e7d 0.40.5 2021-03-05 14:30:46 -05:00
Vladimir Mandic de2ff38ff8 fix human.draw 2021-03-05 14:30:09 -05:00
Vladimir Mandic 48f072308e 0.40.4 2021-03-05 11:44:00 -05:00
Vladimir Mandic c2e74d2ba1 update human.draw helper methods 2021-03-05 11:43:50 -05:00
Vladimir Mandic 388c968e9c fix demo 2021-03-05 07:45:30 -05:00
Vladimir Mandic ee20490791 0.40.3 2021-03-05 07:45:20 -05:00
Vladimir Mandic 05084df8ad 0.40.2 2021-03-05 07:39:47 -05:00
Vladimir Mandic 95519c45b7 added blazepose-upper 2021-03-05 07:39:37 -05:00
Vladimir Mandic e55a8de6b0 0.40.1 2021-03-04 10:33:18 -05:00
Vladimir Mandic 75161c9127 implement blazepose and update demos 2021-03-04 10:33:08 -05:00
Vladimir Mandic 8ccc8f7f47 update 2021-03-03 12:10:44 -05:00
Vladimir Mandic 83a74f4149 add todo list 2021-03-03 12:04:59 -05:00
Vladimir Mandic c6837fc608 update 2021-03-03 10:00:14 -05:00
Vladimir Mandic 074a078396 0.30.6 2021-03-03 09:59:31 -05:00
Vladimir Mandic b77b98e8d4 fine tuning age and face models 2021-03-03 09:59:04 -05:00
Vladimir Mandic 4eb6fa709c 0.30.5 2021-03-02 11:27:47 -05:00
Vladimir Mandic 7969623849 add debug logging flag 2021-03-02 11:27:42 -05:00
Vladimir Mandic 28d00d997f 0.30.4 2021-03-01 17:20:06 -05:00
Vladimir Mandic 278cc818af added skipInitial flag 2021-03-01 17:20:02 -05:00
Vladimir Mandic 0cf856d73b update 2021-02-28 07:39:48 -05:00
Vladimir Mandic cc489e860e 0.30.3 2021-02-28 07:39:29 -05:00
Vladimir Mandic f44f0bb0e5 update 2021-02-28 07:38:13 -05:00
meeki007 6dc288bff6 typo 2021-02-28 07:37:37 -05:00
Vladimir Mandic b448f71260 update 2021-02-26 10:13:31 -05:00
Vladimir Mandic fc20b3f48f 0.30.2 2021-02-26 10:13:10 -05:00
Vladimir Mandic 7654f52c28 update 2021-02-26 10:05:56 -05:00
Vladimir Mandic b7e0674abe rebuild 2021-02-26 09:04:15 -05:00
meeki007 2afb1887d4 fix typo 2021-02-26 08:59:12 -05:00
Vladimir Mandic 8ac0176d90 update 2021-02-25 07:51:26 -05:00
Vladimir Mandic fea37e8b15 0.30.1 2021-02-25 07:51:00 -05:00
Vladimir Mandic d1ef671f89 update to tfjs 3.2.0 2021-02-25 07:50:13 -05:00
Vladimir Mandic e48d7526c7 0.20.11 2021-02-24 09:57:39 -05:00
Vladimir Mandic 1fcbd5d950 update default gender model 2021-02-24 09:57:33 -05:00
Vladimir Mandic 97feba5e28 update 2021-02-22 09:14:10 -05:00
Vladimir Mandic c41fbd706a 0.20.10 2021-02-22 09:13:16 -05:00
Vladimir Mandic 87e0559706 updated model defaults 2021-02-22 09:13:11 -05:00
Vladimir Mandic 7bfe845d03 update 2021-02-21 21:49:59 -05:00
Vladimir Mandic cc836bd21c 0.20.9 2021-02-21 21:49:36 -05:00
Vladimir Mandic 8a8d34dfd3 update 2021-02-21 14:50:03 -05:00
Vladimir Mandic 191298f408 0.20.8 2021-02-21 14:46:56 -05:00
Vladimir Mandic 4eacad24ae update 2021-02-21 14:07:04 -05:00
Vladimir Mandic d1322124fe 0.20.7 2021-02-21 14:06:35 -05:00
Vladimir Mandic 8aa92fad2c build fix 2021-02-21 14:06:13 -05:00
Vladimir Mandic 3b29de4de8 0.20.6 2021-02-21 13:34:30 -05:00
Vladimir Mandic a6f8c8951e embedding fix 2021-02-21 13:34:26 -05:00
Vladimir Mandic 9a7b143269 update wiki 2021-02-21 08:19:55 -05:00
Vladimir Mandic bf030ddc9b 0.20.5 2021-02-21 07:21:03 -05:00
Vladimir Mandic 1a5d5cb37e fix imagefx and add dev builds 2021-02-21 07:20:58 -05:00
Vladimir Mandic 56feb455a1 update 2021-02-19 08:36:47 -05:00
Vladimir Mandic cbe5573ca5 0.20.4 2021-02-19 08:35:48 -05:00
Vladimir Mandic e24882f472 update imagefx 2021-02-19 08:35:41 -05:00
Vladimir Mandic be8cb4431d update 2021-02-17 10:23:21 -05:00
Vladimir Mandic b07eec1829 0.20.3 2021-02-17 10:22:49 -05:00
Vladimir Mandic 3ca1773e12 update tfjs to 3.1.0 2021-02-17 10:22:38 -05:00
Vladimir Mandic eae3fb89b2 rebuild 2021-02-13 09:22:40 -05:00
Vladimir Mandic deba687c79 0.20.2 2021-02-13 09:22:10 -05:00
Vladimir Mandic a8ccee95bf update lint rules 2021-02-13 09:21:48 -05:00
Vladimir Mandic 17c9aa6583 updated lint rules 2021-02-13 09:16:41 -05:00
Vladimir Mandic 36058ac875 update dev server 2021-02-13 08:42:10 -05:00
Vladimir Mandic e8281bc48d Merge branch 'main' of https://github.com/vladmandic/human into main 2021-02-08 13:30:52 -05:00
Vladimir Mandic 79e4ab4a86 update readme 2021-02-08 13:30:49 -05:00
Vladimir Mandic 4a660c6204 Create codeql-analysis.yml 2021-02-08 13:29:58 -05:00
Vladimir Mandic 21bba7a486 Create SECURITY.md 2021-02-08 13:28:32 -05:00
Vladimir Mandic 238f527244 update template 2021-02-08 13:25:28 -05:00
Vladimir Mandic bffc34edc2 add templates 2021-02-08 13:24:23 -05:00
Vladimir Mandic ee00d0fbd8 update default github docs 2021-02-08 13:20:37 -05:00
Vladimir Mandic 905c1ac31a update 2021-02-08 13:10:10 -05:00
Vladimir Mandic c546458172 0.20.1 2021-02-08 13:09:36 -05:00
Vladimir Mandic 47a17adcdf menu fixes 2021-02-08 13:07:49 -05:00
Vladimir Mandic 402ef8ae32 updated typings 2021-02-08 12:47:38 -05:00
Vladimir Mandic 3824deba29 convert to typescript 2021-02-08 11:39:09 -05:00
Vladimir Mandic d373ee088c update 2021-02-06 17:42:47 -05:00
Vladimir Mandic 5aea22fc4b 0.11.5 2021-02-06 17:42:21 -05:00
Vladimir Mandic 60b817ba4c added faceboxes alternative model 2021-02-06 17:41:53 -05:00
Vladimir Mandic 70f9ee05ad 0.11.4 2021-02-06 10:19:45 -05:00
Vladimir Mandic dc33353bd3 update 2021-02-06 10:19:45 -05:00
Vladimir Mandic fc118c7e06 0.11.3 2021-02-02 20:35:15 -05:00
Vladimir Mandic 3efb713855 update 2021-02-02 20:35:14 -05:00
Vladimir Mandic 7dfcef0023 update wiki 2021-01-30 13:24:06 -05:00
Vladimir Mandic 1a7da68c4e 0.11.2 2021-01-30 13:23:26 -05:00
Vladimir Mandic fbd1eb94c2 added warmup for nodejs 2021-01-30 13:23:07 -05:00
Vladimir Mandic 49137f72d4 update for tfjs 3.0.0 2021-01-29 10:26:58 -05:00
Vladimir Mandic 0faa021bb1 0.11.1 2021-01-29 10:25:26 -05:00
Vladimir Mandic 5d6a123f0d 0.10.2 2021-01-22 08:15:34 -05:00
Vladimir Mandic 7e1a1392ae update 2021-01-22 08:15:33 -05:00
Vladimir Mandic c17311e3d1 update to tfjs 2.8.5 2021-01-20 08:05:30 -05:00
Vladimir Mandic 205855d583 0.10.1 2021-01-20 08:04:59 -05:00
Vladimir Mandic fdead00263 update 2021-01-18 08:22:51 -05:00
Vladimir Mandic 510f9a39f6 0.9.26 2021-01-18 08:22:35 -05:00
Vladimir Mandic 01e3eec30e fix face detection when mesh is disabled 2021-01-18 08:22:25 -05:00
Vladimir Mandic 0231e12d2b version bump 2021-01-13 11:14:23 -05:00
Vladimir Mandic dcab4a6c9c 0.9.25 2021-01-13 11:13:43 -05:00
Vladimir Mandic 46cf1694bf update 2021-01-13 09:40:04 -05:00
Vladimir Mandic 2a041305e9 added humangl custom backend 2021-01-13 09:35:31 -05:00
Vladimir Mandic 27159a59dc rebuild 2021-01-12 10:18:58 -05:00
Vladimir Mandic 61c4d7ae6d code cleanup and enable minification 2021-01-12 09:55:08 -05:00
Vladimir Mandic 44ae1b517a fix safari incopatibility 2021-01-12 08:24:00 -05:00
Vladimir Mandic caa414835f 0.9.24 2021-01-12 08:23:19 -05:00
Vladimir Mandic 872fb5fdb2 work on blazepose 2021-01-11 14:35:57 -05:00
Vladimir Mandic 520b6b4af1 update changelog 2021-01-11 09:02:55 -05:00
Vladimir Mandic 499ed87323 full rebuild 2021-01-11 09:02:43 -05:00
Vladimir Mandic 35d1de7d41 0.9.23 2021-01-11 09:02:15 -05:00
Vladimir Mandic 98bd96426a added iris gesture 2021-01-11 09:02:02 -05:00
Vladimir Mandic c0fd223bcb update 2021-01-06 06:52:01 -05:00
Vladimir Mandic 049facf72b fix emotion labels 2021-01-06 06:51:20 -05:00
Vladimir Mandic f586b8d3b1 full rebuild 2021-01-05 16:50:25 -05:00
Vladimir Mandic a79fe751e8 0.9.22 2021-01-05 16:49:55 -05:00
Vladimir Mandic 113d8c1ca7 update wiki 2021-01-05 16:48:19 -05:00
Vladimir Mandic 6ba9cda7bb remove iris coords if iris is disabled 2021-01-05 16:41:54 -05:00
Vladimir Mandic f7a51349bf update iris objects 2021-01-05 16:33:43 -05:00
Vladimir Mandic e4a85fcb85 web worker fix 2021-01-03 10:41:56 -05:00
Vladimir Mandic 4fb51ad583 0.9.21 2021-01-03 10:41:27 -05:00
Vladimir Mandic bf1dd37e35 0.9.20 2021-01-03 10:23:49 -05:00
Vladimir Mandic 59e465c158 update to tfjs 2.8.2 2021-01-03 10:23:45 -05:00
Vladimir Mandic 79ac90f684 stricter linting, fix face annotations 2020-12-27 08:12:22 -05:00
Vladimir Mandic 2f3b952c28 update nodejs platform support 2020-12-23 13:55:22 -05:00
Vladimir Mandic 1556b8af85 0.9.19 2020-12-23 13:54:47 -05:00
Vladimir Mandic a85cfcf37d added rawBox and rawMesh 2020-12-22 12:34:47 -05:00
ButzYung 94fb9408ad Variable name changes, setting .rawCoords only if necessary 2020-12-22 12:20:36 -05:00
ButzYung 2eb0578501 Option to return raw data (mesh, box) for Facemesh / "preserve aspect ratio" fix from Facemesh upstream 2020-12-22 12:20:36 -05:00
Vladimir Mandic 8451446d2b updated readme 2020-12-21 08:12:18 -05:00
Vladimir Mandic a472540b36 update 2020-12-17 21:09:25 -05:00
Vladimir Mandic 7a968fcdc6 0.9.18 2020-12-16 19:17:04 -05:00
Vladimir Mandic 253030830f add z axis scaling 2020-12-16 19:16:54 -05:00
Vladimir Mandic 641d56547d major work on body module 2020-12-16 18:36:24 -05:00
Vladimir Mandic ab87288e7d republish due to tfjs 2.8.0 issues 2020-12-16 14:49:14 -05:00
Vladimir Mandic 31f728e502 0.9.17 2020-12-15 08:44:45 -05:00
Vladimir Mandic 3902a5e25d updated tfjs 2020-12-15 08:44:42 -05:00
Vladimir Mandic ff058a4148 updated 2020-12-12 18:35:16 -05:00
Vladimir Mandic 5921d14bfe added custom webgl backend 2020-12-12 18:34:30 -05:00
Vladimir Mandic 7f42a89346 0.9.16 2020-12-12 11:25:13 -05:00
Vladimir Mandic 161ae5fa6a update dependencies 2020-12-12 11:25:12 -05:00
Vladimir Mandic c1a92d07f3 change default ports 2020-12-12 10:15:51 -05:00
Vladimir Mandic a2ec69ec65 0.9.15 2020-12-11 10:11:54 -05:00
Vladimir Mandic 4d43356d73 improved caching and warmup 2020-12-11 10:11:49 -05:00
Vladimir Mandic c85c8fa286 rebuild 2020-12-10 15:47:43 -05:00
Vladimir Mandic f54fef7d3f 0.9.14 2020-12-10 15:47:06 -05:00
Vladimir Mandic a1aaa44e8d conditional hand rotation 2020-12-10 15:46:45 -05:00
Vladimir Mandic 8bc1318ca8 updated wiki 2020-12-10 14:48:12 -05:00
Vladimir Mandic 15004506d4 staggered skipframes 2020-12-10 14:47:53 -05:00
Vladimir Mandic 58e0822582 0.9.13 2020-12-08 10:54:22 -05:00
Vladimir Mandic 5c6e610a37 implemented face and hand boundary checks 2020-12-08 10:50:26 -05:00
Vladimir Mandic 7a0e5a555d embedded sample for warmup 2020-12-08 09:58:30 -05:00
Vladimir Mandic f65effc69d switch to central logger 2020-12-08 09:00:44 -05:00
Vladimir Mandic 3c54914a5c 0.9.12 2020-11-26 10:37:08 -05:00
Vladimir Mandic 40e0f1c4c4 minor compatibility fixes 2020-11-26 10:37:04 -05:00
Vladimir Mandic e1b3fff07c update for node v15 2020-11-25 09:13:19 -05:00
Vladimir Mandic 6777aa54dd 0.9.11 2020-11-23 23:42:42 -05:00
Vladimir Mandic 4679b859b8 implement multi-person gestures 2020-11-23 23:36:04 -05:00
Vladimir Mandic 2978b319c3 modularize pipeline models 2020-11-23 22:55:01 -05:00
Vladimir Mandic 70503ef152 2020-11-23 08:44:34 -05:00
Vladimir Mandic 578f151eee updated embedding function 2020-11-23 08:40:17 -05:00
Vladimir Mandic 604e5632a8 updated wiki 2020-11-23 08:18:08 -05:00
Vladimir Mandic 999c45c622 updated firefox css styling 2020-11-23 07:44:10 -05:00
Vladimir Mandic 7803d18a8c 0.9.10 2020-11-21 12:22:00 -05:00
Vladimir Mandic 853745512e changed build for optimized node & browser 2020-11-21 12:21:47 -05:00
Vladimir Mandic 5373809370 updated font 2020-11-21 07:33:41 -05:00
Vladimir Mandic 8528aa130f 0.9.9 2020-11-21 07:20:17 -05:00
Vladimir Mandic 80c545b598 new screenshots 2020-11-21 07:19:20 -05:00
Vladimir Mandic 097458610e update dependencies 2020-11-20 08:53:40 -05:00
Vladimir Mandic a84c26c781 camera exception handling 2020-11-20 07:52:50 -05:00
Vladimir Mandic 05042392be 0.9.8 2020-11-19 16:16:58 -05:00
Vladimir Mandic 41f7386583 force f16 textures 2020-11-19 16:16:51 -05:00
Vladimir Mandic 899d8be570 bugfix embedding check 2020-11-19 15:22:08 -05:00
Vladimir Mandic 43eebb73ad 0.9.7 2020-11-19 14:46:08 -05:00
Vladimir Mandic ef13c1e560 ui redesign 2020-11-19 14:45:59 -05:00
Vladimir Mandic 4f0ecf388b 0.9.6 2020-11-18 09:15:22 -05:00
Vladimir Mandic 7f5007851e optimize camera resize on mobile 2020-11-18 09:15:03 -05:00
Vladimir Mandic adcb55c83a completed tfjs wrapper 2020-11-18 08:26:28 -05:00
Vladimir Mandic 9a64dc6de2 update wiki 2020-11-17 17:48:05 -05:00
Vladimir Mandic 8004b3c37d 0.9.5 2020-11-17 17:47:16 -05:00
Vladimir Mandic d81a8bdc1e fix serious performance bug around skipframes 2020-11-17 17:42:44 -05:00
Vladimir Mandic 01ad1809f2 swtich to custom tfjs bundle 2020-11-17 12:38:48 -05:00
Vladimir Mandic 0fe1033903 0.9.4 2020-11-17 10:19:15 -05:00
Vladimir Mandic a773b9e2b9 swtich to tfjs source import 2020-11-17 10:18:15 -05:00
Vladimir Mandic 594c4e271f 0.9.3 2020-11-16 23:58:23 -05:00
Vladimir Mandic 104ea6fe8e updated build process 2020-11-16 23:58:06 -05:00
Vladimir Mandic 2eeb577387 updated build scripts 2020-11-16 16:40:25 -05:00
Vladimir Mandic c7671ef89d switched to minified build 2020-11-16 15:51:46 -05:00
Vladimir Mandic a8d52c45d8 web worker fixes 2020-11-15 09:28:57 -05:00
Vladimir Mandic d038b1e266 update buffered output 2020-11-14 17:22:59 -05:00
Vladimir Mandic c6d579dbf1 full rebuild 2020-11-14 07:05:20 -05:00
Vladimir Mandic ea57054a66 0.9.2 2020-11-14 07:02:43 -05:00
Vladimir Mandic 86321b2523 fix camera restart on resize 2020-11-14 07:02:05 -05:00
Vladimir Mandic 93d8c9fe64 update package description 2020-11-13 16:43:48 -05:00
Vladimir Mandic 591c01abc2 0.9.1 2020-11-13 16:42:56 -05:00
Vladimir Mandic ff0ec7cbbb version bump 2020-11-13 16:42:50 -05:00
Vladimir Mandic 85178e74a3 full rebuild 2020-11-13 16:42:00 -05:00
Vladimir Mandic 9d80512e36 implemented face embedding 2020-11-13 16:13:35 -05:00
Vladimir Mandic a7f2bf2303 added internal benchmark tool 2020-11-12 17:00:06 -05:00
Vladimir Mandic ea88b72c52 updated face uv coordinates 2020-11-12 14:52:32 -05:00
Vladimir Mandic f9dc5993f7 0.8.8 2020-11-12 12:59:00 -05:00
Vladimir Mandic bfbb3042af updated packages 2020-11-12 12:58:55 -05:00
Vladimir Mandic 1b0a037d3f reduced bundle size 2020-11-12 12:17:57 -05:00
Vladimir Mandic a3fa102a77 implemented buffered processing 2020-11-12 09:21:26 -05:00
Vladimir Mandic 17a8e20a53 fix for conditional model loading 2020-11-11 22:40:05 -05:00
Vladimir Mandic 4839b2caff 0.8.7 2020-11-11 15:02:53 -05:00
Vladimir Mandic fcdb076549 added performance notes 2020-11-11 15:02:49 -05:00
Vladimir Mandic 3e973c5555 added notes on models 2020-11-10 10:23:11 -05:00
Vladimir Mandic ec19579e53 update dependencies 2020-11-10 09:54:07 -05:00
Vladimir Mandic 53b4542140 fix bug in async ops and change imports 2020-11-10 08:57:39 -05:00
Vladimir Mandic b5ed43b434 fix wiki links 2020-11-09 20:23:05 -05:00
Vladimir Mandic 535b7e97a4 0.8.6 2020-11-09 20:13:44 -05:00
Vladimir Mandic de2819a96e add wasm bundle 2020-11-09 20:13:38 -05:00
Vladimir Mandic a9c393635f 0.8.5 2020-11-09 14:26:19 -05:00
Vladimir Mandic 57a93768f9 reimplemented blazeface processing 2020-11-09 14:26:10 -05:00
Vladimir Mandic 24b6fa1f23 0.8.4 2020-11-09 09:31:17 -05:00
Vladimir Mandic 4dba0c7247 added additional gestures 2020-11-09 09:31:11 -05:00
Vladimir Mandic a24c6e134b implemented blink detection 2020-11-09 08:57:24 -05:00
Vladimir Mandic 22d847311e fix wasm module 2020-11-09 06:32:11 -05:00
Vladimir Mandic cc2606a92a updated readme 2020-11-08 12:44:08 -05:00
Vladimir Mandic 6248a1dce9 updated wiki 2020-11-08 12:40:46 -05:00
Vladimir Mandic b36d3cf974 updated wiki 2020-11-08 12:35:26 -05:00
Vladimir Mandic 5e4ea1bc01 updated wiki 2020-11-08 12:33:24 -05:00
Vladimir Mandic c669648d7f 0.8.3 2020-11-08 12:32:36 -05:00
Vladimir Mandic a400b30178 refresh 2020-11-08 12:32:31 -05:00
Vladimir Mandic d2c6b6560d optimizations 2020-11-08 12:26:45 -05:00
Vladimir Mandic d7e85fd777 update 2020-11-08 10:06:23 -05:00
Vladimir Mandic f2bb62b099 update wiki 2020-11-08 09:56:27 -05:00
Vladimir Mandic 11d3f76df0 update hand model 2020-11-08 09:56:02 -05:00
Vladimir Mandic cd6f1f7e7a update hand algorithm 2020-11-08 01:17:25 -05:00
Vladimir Mandic 0adac25629 0.8.2 2020-11-08 01:16:28 -05:00
Vladimir Mandic 2da12bf1dc fix typos 2020-11-07 20:44:15 -05:00
Vladimir Mandic f92dac385f update build script 2020-11-07 20:15:09 -05:00
Vladimir Mandic 2738a763db commit 2020-11-07 11:36:45 -05:00
Vladimir Mandic f7d8b9e3ef updated changelog 2020-11-07 11:35:43 -05:00
Vladimir Mandic 1770b5b6f0 0.8.1 2020-11-07 11:34:44 -05:00
Vladimir Mandic 90abc61e4f updated ui 2020-11-07 11:34:09 -05:00
Vladimir Mandic 50c2648711 fix hand detection performance 2020-11-07 11:25:03 -05:00
Vladimir Mandic c9649d7397 optimized model loader 2020-11-07 10:37:19 -05:00
Vladimir Mandic 481352c5e1 updated wiki links 2020-11-07 09:47:26 -05:00
Vladimir Mandic 694b88c05b Merge branch 'main' of https://github.com/vladmandic/human into main 2020-11-07 09:44:43 -05:00
Vladimir Mandic 0a430ffce4 update dependencies 2020-11-07 09:44:33 -05:00
Vladimir Mandic 952b050bff updated wiki 2020-11-07 09:42:54 -05:00
Vladimir Mandic 520b0ae747 created wiki 2020-11-07 09:39:54 -05:00
Vladimir Mandic aee2c6caf6 Update issue templates 2020-11-07 09:37:56 -05:00
Vladimir Mandic fc85f4a799 optimize font resizing 2020-11-06 22:20:42 -05:00
Vladimir Mandic 0670083e92 fix nms sync call 2020-11-06 19:25:37 -05:00
Vladimir Mandic b8468be1ed 0.7.6 2020-11-06 16:21:25 -05:00
Vladimir Mandic b508761999 fixed memory leaks and updated docs 2020-11-06 16:21:20 -05:00
Vladimir Mandic c3cf00b583 model tuning 2020-11-06 15:35:58 -05:00
Vladimir Mandic eb3dbea2ca cache invalidation improvements 2020-11-06 13:50:16 -05:00
Vladimir Mandic 6f22d2a3d3 full async operations 2020-11-06 11:39:39 -05:00
Vladimir Mandic 544a3b0ef2 0.7.5 2020-11-05 23:46:43 -05:00
Vladimir Mandic 21f7926d1d implemented dev-server 2020-11-05 23:46:37 -05:00
Vladimir Mandic 7b8b295306 0.7.4 2020-11-05 16:00:46 -05:00
Vladimir Mandic e15e80302e fix canvas size on different orientation 2020-11-05 15:59:28 -05:00
Vladimir Mandic 02ba731a60 updated emotion models 2020-11-05 15:38:09 -05:00
Vladimir Mandic fe413b547d updated changelog 2020-11-05 09:12:43 -05:00
Vladimir Mandic 6569ee7446 switched from es2020 to es2018 build target 2020-11-05 09:12:31 -05:00
Vladimir Mandic cb42330b9e 0.7.3 2020-11-05 09:06:17 -05:00
Vladimir Mandic bc35bf7584 optimized camera and mobile layout 2020-11-05 09:06:09 -05:00
Vladimir Mandic 91ddc0c57d fixed worker and filter compatibility 2020-11-05 08:21:23 -05:00
Vladimir Mandic 14b89145c9 0.7.2 2020-11-04 14:59:33 -05:00
Vladimir Mandic 9fa7e3d467 major work on handpose model 2020-11-04 14:59:30 -05:00
Vladimir Mandic 479fc2547c updated mobile build 2020-11-04 12:10:26 -05:00
Vladimir Mandic 125055e74a update demo build process 2020-11-04 11:57:44 -05:00
Vladimir Mandic d7ddc3ae2c updated docs 2020-11-04 11:45:24 -05:00
Vladimir Mandic 74d08df07f updated changelog 2020-11-04 11:44:07 -05:00
Vladimir Mandic cce9535be5 0.7.1 2020-11-04 11:44:00 -05:00
Vladimir Mandic 37321af4ae changed demo build process 2020-11-04 11:43:51 -05:00
Vladimir Mandic 91f5294925 0.6.7 2020-11-04 10:18:30 -05:00
Vladimir Mandic d94aa0362c implemented simple gesture recognition 2020-11-04 10:18:22 -05:00
Vladimir Mandic 3cec6710d4 0.6.6 2020-11-04 01:13:44 -05:00
Vladimir Mandic 6653cff104 remove debug code 2020-11-04 01:13:40 -05:00
Vladimir Mandic 8dda59f5d9 0.6.5 2020-11-04 01:11:30 -05:00
Vladimir Mandic 430a950112 redo hand detection 2020-11-04 01:11:24 -05:00
Vladimir Mandic bfdcb301f4 0.6.4 2020-11-03 15:24:11 -05:00
Vladimir Mandic 7860760017 added manifest 2020-11-03 15:24:02 -05:00
Vladimir Mandic 948b4d2cce 0.6.3 2020-11-03 11:11:57 -05:00
Vladimir Mandic 99db8d3724 update changelog 2020-11-03 11:11:53 -05:00
Vladimir Mandic 9b0b8cf390 enhanced processing resolution 2020-11-03 10:55:33 -05:00
Vladimir Mandic d3bd65ba50 fix pause restart 2020-11-03 09:40:04 -05:00
Vladimir Mandic 2b5901ff55 complete model refactoring 2020-11-03 09:34:36 -05:00
Vladimir Mandic 0f5ccda33c fixed typo 2020-11-02 22:17:56 -05:00
Vladimir Mandic 30265459f9 0.6.2 2020-11-02 22:15:40 -05:00
Vladimir Mandic 161f168bba optimized demo 2020-11-02 22:15:37 -05:00
Vladimir Mandic 97447e9e49 updated docs 2020-11-02 18:56:12 -05:00
Vladimir Mandic cbf12723bd package update 2020-11-02 18:55:17 -05:00
Vladimir Mandic d9e842b41a 0.6.1 2020-11-02 18:54:13 -05:00
Vladimir Mandic eb3bd3dae9 major performance improvements for all models 2020-11-02 18:54:03 -05:00
Vladimir Mandic 0d99113f77 Revert "optimized canvas handling"
This reverts commit 80d7133a8e.
2020-11-02 17:31:37 -05:00
Vladimir Mandic 80d7133a8e optimized canvas handling 2020-11-02 17:25:35 -05:00
Vladimir Mandic 5e3193bf54 minor optimization to imagefx 2020-11-02 12:21:30 -05:00
Vladimir Mandic 45aff915f5 fix demo image sample 2020-11-01 14:16:47 -05:00
Vladimir Mandic e6d195187b added tfjs-vis to distribution 2020-11-01 13:59:25 -05:00
Vladimir Mandic 448ac2096a 0.5.5 2020-11-01 13:10:40 -05:00
Vladimir Mandic 68719ded32 updated changelog 2020-11-01 13:10:36 -05:00
Vladimir Mandic 34646100d0 changed defaults 2020-11-01 13:10:22 -05:00
Vladimir Mandic 78643806d1 0.5.4 2020-11-01 13:07:58 -05:00
Vladimir Mandic 813d052ea8 implemented memory profiler 2020-11-01 13:07:53 -05:00
Vladimir Mandic 8d859e2c92 0.5.3 2020-10-30 15:45:00 -04:00
Vladimir Mandic cc3d5f939e improved debug logging 2020-10-30 11:57:23 -04:00
Vladimir Mandic e8d36eea69 0.5.2 2020-10-30 10:30:12 -04:00
Vladimir Mandic d0e036526e updated changelog 2020-10-30 10:30:07 -04:00
Vladimir Mandic e3a00d48d0 updated menu ui 2020-10-30 10:29:50 -04:00
Vladimir Mandic e39831224d added wasm and webgpu backends 2020-10-30 10:23:49 -04:00
Vladimir Mandic c5ccdda811 0.5.1 2020-10-30 07:35:30 -04:00
Vladimir Mandic 763d9d0b5e updated changelog 2020-10-30 07:35:19 -04:00
Vladimir Mandic 602d940c1d updated dependencies 2020-10-30 07:34:55 -04:00
Vladimir Mandic f9d78b27e6 improve demo line continous draws 2020-10-30 07:32:35 -04:00
Vladimir Mandic bf1135828f 0.4.10 2020-10-30 07:14:47 -04:00
Vladimir Mandic e8fc546d1e fix for seedrandom 2020-10-30 07:14:42 -04:00
Vladimir Mandic 81a460c412 0.4.9 2020-10-29 00:09:33 -04:00
Vladimir Mandic a6c5bc3d70 updated dependencies 2020-10-29 00:09:29 -04:00
Vladimir Mandic ec7fafce52 updated changelog 2020-10-28 15:03:23 -04:00
Vladimir Mandic 5d89ecf20b 0.4.8 2020-10-28 15:03:08 -04:00
Vladimir Mandic 8d445e7ebb updated build targets and tfjs to 2.7.0 2020-10-28 15:02:59 -04:00
Vladimir Mandic a7c13e7c25 updated menu library 2020-10-27 14:20:21 -04:00
Vladimir Mandic 57d30f2710 Revert "updated menu handler"
This reverts commit c2781a3965.
2020-10-27 14:19:24 -04:00
Vladimir Mandic c2781a3965 updated menu handler 2020-10-27 14:07:39 -04:00
Vladimir Mandic 8295608ca2 0.4.7 2020-10-27 12:49:24 -04:00
Vladimir Mandic bea70535eb 0.4.6 2020-10-27 11:56:50 -04:00
Vladimir Mandic b6ed98dde8 fix firefox compatibility bug 2020-10-27 11:56:41 -04:00
Vladimir Mandic e4ebce3642 0.4.5 2020-10-27 10:32:21 -04:00
Vladimir Mandic 85fcb44e2b updated docs 2020-10-27 10:32:12 -04:00
Vladimir Mandic 9be15515b5 updated samples 2020-10-27 10:30:28 -04:00
Vladimir Mandic 113a26ab2c 0.4.4 2020-10-27 10:06:14 -04:00
Vladimir Mandic 4664228d4b implelented input resizing 2020-10-27 10:06:01 -04:00
Vladimir Mandic d579102a4f updated docs 2020-10-25 21:33:06 -04:00
Vladimir Mandic 80013d6822 updated 2020-10-22 18:50:28 -04:00
Vladimir Mandic c6b8523ca5 0.4.3 2020-10-22 18:50:21 -04:00
Vladimir Mandic eb221d42fd update build process 2020-10-22 18:50:09 -04:00
Vladimir Mandic 21031af836 0.4.2 2020-10-20 10:08:23 -04:00
Vladimir Mandic a299806dc0 updated docs 2020-10-20 10:08:06 -04:00
Vladimir Mandic 2566a9633f log initialization 2020-10-20 07:58:20 -04:00
Vladimir Mandic ee8e7080de updated changelog 2020-10-19 11:04:34 -04:00
Vladimir Mandic 279de1eeec 0.4.1 2020-10-19 11:04:02 -04:00
Vladimir Mandic 36675c5152 breaking change: convert to object class 2020-10-19 11:03:48 -04:00
Vladimir Mandic dfc67f0188 compatibility notes 2020-10-18 14:14:05 -04:00
Vladimir Mandic 0d523c3744 0.3.9 2020-10-18 12:22:39 -04:00
Vladimir Mandic e7229689c5 updated docs 2020-10-18 12:21:17 -04:00
Vladimir Mandic 362ee4441e implemented image filters 2020-10-18 12:12:09 -04:00
Vladimir Mandic f5f3a2c1d5 pure tensor pipeline without image converts 2020-10-18 09:21:53 -04:00
Vladimir Mandic 7a6741e3b4 autodetect skipFrames 2020-10-18 08:07:45 -04:00
Vladimir Mandic 859ed1789a 0.3.8 2020-10-17 21:00:43 -04:00
Vladimir Mandic 2f60e4c72c new menu layout 2020-10-17 20:59:43 -04:00
Vladimir Mandic 12c5350174 0.3.7 2020-10-17 11:43:44 -04:00
Vladimir Mandic 35226fa0a1 updated changelog 2020-10-17 11:43:40 -04:00
Vladimir Mandic 08101e4c45 added diagnostics output 2020-10-17 11:43:04 -04:00
Vladimir Mandic 0411ed1371 parallelized agegender operations 2020-10-17 11:38:24 -04:00
Vladimir Mandic 5a4a056e32 updated readme 2020-10-17 10:32:02 -04:00
Vladimir Mandic b47fc362cf 0.3.6 2020-10-17 10:25:37 -04:00
Vladimir Mandic fdb08a91df fixed webcam initialization 2020-10-17 10:25:27 -04:00
Vladimir Mandic 8643a2191a fixed memory leaks and added scoped runs 2020-10-17 10:06:02 -04:00
Vladimir Mandic e36ac717cb modularized draw 2020-10-17 07:34:45 -04:00
Vladimir Mandic f3bf35533e added state handling 2020-10-17 07:15:23 -04:00
Vladimir Mandic 9f86c24fe3 refactored package file layout 2020-10-17 06:30:00 -04:00
Vladimir Mandic 4c3bb5fed9 updated changelog 2020-10-16 15:22:20 -04:00
Vladimir Mandic 4eb6bb3d8c 0.3.5 2020-10-16 15:22:01 -04:00
Vladimir Mandic 2bb1f151e4 added auto-generated changelog 2020-10-16 15:21:56 -04:00
Vladimir Mandic 9634cbc608 updated readme 2020-10-16 15:13:29 -04:00
Vladimir Mandic 8511c99e81 0.3.4 2020-10-16 15:05:01 -04:00
Vladimir Mandic 45730a07a6 updated examples plus bugfixes 2020-10-16 15:04:51 -04:00
Vladimir Mandic 6a9a7b2467 added camera selection 2020-10-16 11:23:59 -04:00
Vladimir Mandic 93b5aa16ed optimized blazeface anchors 2020-10-16 10:48:10 -04:00
Vladimir Mandic fc0940a481 added error handling 2020-10-16 10:12:12 -04:00
Vladimir Mandic 2110bafb63 updated default values 2020-10-15 20:29:51 -04:00
Vladimir Mandic a39eaa6399 0.3.3 2020-10-15 20:25:15 -04:00
Vladimir Mandic 03db7fd8da added blazeface back and front models 2020-10-15 20:20:37 -04:00
Vladimir Mandic 699ab9f309 0.3.2 2020-10-15 18:16:19 -04:00
Vladimir Mandic 6de5ed0663 reduced web worker latency 2020-10-15 18:16:05 -04:00
Vladimir Mandic ff386b6e64 added debugging and versioning 2020-10-15 15:25:58 -04:00
Vladimir Mandic 567966a162 optimized demos and added scoped runs 2020-10-15 09:43:16 -04:00
Vladimir Mandic 3547da7130 added multi backend support 2020-10-15 08:16:34 -04:00
Vladimir Mandic e93487315e 0.3.1 2020-10-14 18:30:26 -04:00
Vladimir Mandic 19cffbf9f8 0.2.10 2020-10-14 18:22:56 -04:00
Vladimir Mandic 38d02819d3 added emotion backend 2020-10-14 18:22:38 -04:00
Vladimir Mandic 420607c490 module parametrization and performance monitoring 2020-10-14 13:23:02 -04:00
Vladimir Mandic 9e1776906f implemented multi-hand support 2020-10-14 11:43:33 -04:00
Vladimir Mandic f484493b6f updated dependencies 2020-10-13 21:08:06 -04:00
Vladimir Mandic 493b2c61b4 fixed documentation typos 2020-10-13 20:59:09 -04:00
Vladimir Mandic cf8cd83aa8 0.2.9 2020-10-13 20:52:34 -04:00
Vladimir Mandic 251c75d553 added node build and demo 2020-10-13 20:52:30 -04:00
Vladimir Mandic e7a71b7367 0.2.8 2020-10-13 10:08:50 -04:00
Vladimir Mandic 43e0f6cb3b 0.2.7 2020-10-13 10:07:03 -04:00
Vladimir Mandic 247004e3eb new examples 2020-10-13 10:06:49 -04:00
Vladimir Mandic abc4950eff 0.2.6 2020-10-13 09:59:27 -04:00
Vladimir Mandic a1adaf3599 updated demo 2020-10-13 09:59:21 -04:00
Vladimir Mandic ca6d7a662d enable all models by default 2020-10-12 22:03:55 -04:00
Vladimir Mandic d8bb9d3db9 0.2.5 2020-10-12 22:01:44 -04:00
Vladimir Mandic f81e183950 fixed memory leak 2020-10-12 22:01:35 -04:00
Vladimir Mandic 8fdb0c892c 0.2.4 2020-10-12 15:05:30 -04:00
Vladimir Mandic e5287aae0b 0.2.3 2020-10-12 15:03:59 -04:00
Vladimir Mandic 8332a0c077 updated keywords 2020-10-12 15:03:51 -04:00
Vladimir Mandic 4ff0623c66 updated readme 2020-10-12 14:46:08 -04:00
Vladimir Mandic da0faab688 0.2.2 2020-10-12 14:41:59 -04:00
Vladimir Mandic e47bf74c6a updated docs 2020-10-12 14:41:50 -04:00
Vladimir Mandic fa89a67b31 updated readme 2020-10-12 14:33:07 -04:00
Vladimir Mandic b3fcc7feac updated linting 2020-10-12 11:19:52 -04:00
Vladimir Mandic 38e960e38e 0.2.1 2020-10-12 11:05:46 -04:00
Vladimir Mandic d5b4c531fb updated readme 2020-10-12 11:05:29 -04:00
Vladimir Mandic 1fdd4d3753 added sample image 2020-10-12 10:59:55 -04:00
Vladimir Mandic 151655e464 updated docs 2020-10-12 10:31:36 -04:00
Vladimir Mandic 11cbd0ccd6 updated docs 2020-10-12 10:28:42 -04:00
Vladimir Mandic 33bca90e14 updated docs 2020-10-12 10:27:22 -04:00
Vladimir Mandic fa702d47f6 updated model path 2020-10-12 10:20:51 -04:00
Vladimir Mandic 75994f4b57 updated docs 2020-10-12 10:16:31 -04:00
Vladimir Mandic 16d3099f74 updated model path 2020-10-12 10:15:00 -04:00
Vladimir Mandic ce33614987 updated model path 2020-10-12 10:14:26 -04:00
Vladimir Mandic df80506b43 updated iife and esm demos 2020-10-12 10:08:00 -04:00
Vladimir Mandic 5516daf2d6 updated demo 2020-10-11 21:21:41 -04:00
Vladimir Mandic c018daee34 initial public commit 2020-10-11 19:22:43 -04:00
477 changed files with 119804 additions and 1 deletions

169
.build.json Normal file
View File

@ -0,0 +1,169 @@
{
"log": {
"enabled": true,
"debug": false,
"console": true,
"output": "test/build.log"
},
"profiles": {
"production": ["clean", "compile", "typings", "typedoc", "lint", "changelog"],
"development": ["serve", "watch", "compile"],
"serve": ["serve"]
},
"clean": {
"locations": ["dist/*", "types/lib/*", "typedoc/*"]
},
"lint": {
"locations": [ "*.json", "src/**/*.ts", "test/**/*.js", "demo/**/*.js" ],
"rules": { }
},
"changelog": {
"log": "CHANGELOG.md"
},
"serve": {
"sslKey": "node_modules/@vladmandic/build/cert/https.key",
"sslCrt": "node_modules/@vladmandic/build/cert/https.crt",
"httpPort": 10030,
"httpsPort": 10031,
"documentRoot": ".",
"defaultFolder": "demo",
"defaultFile": "index.html"
},
"build": {
"global": {
"target": "es2018",
"sourcemap": false,
"treeShaking": true,
"ignoreAnnotations": true,
"banner": { "js": "/*\n Human\n homepage: <https://github.com/vladmandic/human>\n author: <https://github.com/vladmandic>'\n*/\n" }
},
"targets": [
{
"name": "tfjs/nodejs/cpu",
"platform": "node",
"format": "cjs",
"input": "tfjs/tf-node.ts",
"output": "dist/tfjs.esm.js",
"external": ["@tensorflow"]
},
{
"name": "human/nodejs/cpu",
"platform": "node",
"format": "cjs",
"input": "src/human.ts",
"output": "dist/human.node.js",
"external": ["@tensorflow"]
},
{
"name": "tfjs/nodejs/gpu",
"platform": "node",
"format": "cjs",
"input": "tfjs/tf-node-gpu.ts",
"output": "dist/tfjs.esm.js",
"external": ["@tensorflow"]
},
{
"name": "human/nodejs/gpu",
"platform": "node",
"format": "cjs",
"input": "src/human.ts",
"output": "dist/human.node-gpu.js",
"external": ["@tensorflow"]
},
{
"name": "tfjs/nodejs/wasm",
"platform": "node",
"format": "cjs",
"input": "tfjs/tf-node-wasm.ts",
"output": "dist/tfjs.esm.js",
"external": ["@tensorflow"]
},
{
"name": "human/nodejs/wasm",
"platform": "node",
"format": "cjs",
"input": "src/human.ts",
"output": "dist/human.node-wasm.js",
"external": ["@tensorflow"]
},
{
"name": "tfjs/browser/version",
"platform": "browser",
"format": "esm",
"input": "tfjs/tf-version.ts",
"output": "dist/tfjs.version.js"
},
{
"name": "tfjs/browser/esm/nobundle",
"platform": "browser",
"format": "esm",
"input": "tfjs/tf-browser.ts",
"output": "dist/tfjs.esm.js",
"external": ["@tensorflow"]
},
{
"name": "human/browser/esm/nobundle",
"platform": "browser",
"format": "esm",
"input": "src/human.ts",
"output": "dist/human.esm-nobundle.js",
"sourcemap": true,
"external": ["@tensorflow"]
},
{
"name": "tfjs/browser/esm/custom",
"platform": "browser",
"format": "esm",
"input": "tfjs/tf-custom.ts",
"output": "dist/tfjs.esm.js",
"sourcemap": false
},
{
"name": "human/browser/iife/bundle",
"platform": "browser",
"format": "iife",
"input": "src/human.ts",
"output": "dist/human.js",
"minify": true,
"globalName": "Human",
"external": ["@tensorflow"]
},
{
"name": "human/browser/esm/bundle",
"platform": "browser",
"format": "esm",
"input": "src/human.ts",
"output": "dist/human.esm.js",
"sourcemap": true,
"minify": false,
"external": ["@tensorflow"],
"typings": "types/lib",
"typedoc": "typedoc"
},
{
"name": "demo/typescript",
"platform": "browser",
"format": "esm",
"input": "demo/typescript/index.ts",
"output": "demo/typescript/index.js",
"sourcemap": true,
"external": ["*/human.esm.js"]
},
{
"name": "demo/faceid",
"platform": "browser",
"format": "esm",
"input": "demo/faceid/index.ts",
"output": "demo/faceid/index.js",
"sourcemap": true,
"external": ["*/human.esm.js"]
}
]
},
"watch": {
"locations": [ "src/**/*", "tfjs/**/*", "demo/**/*.ts" ]
},
"typescript": {
"allowJs": false
}
}

93
.eslintrc.json Normal file
View File

@ -0,0 +1,93 @@
{
"globals": {},
"env": {
"browser": true,
"commonjs": true,
"node": true,
"es2021": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2021
},
"plugins": [
"@typescript-eslint",
"html"
],
"extends": [
"airbnb-base",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:json/recommended-with-comments",
"plugin:node/recommended",
"plugin:promise/recommended"
],
"ignorePatterns": [
"assets",
"demo/helpers/*.js",
"demo/typescript/*.js",
"demo/faceid/*.js",
"dist",
"media",
"models",
"node_modules",
"types/human.d.ts"
],
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-shadow": "error",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/prefer-as-const": "off",
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-empty-interface": ["error", { "allowSingleExtends": true }],
"camelcase": "off",
"class-methods-use-this": "off",
"dot-notation": "off",
"func-names": "off",
"guard-for-in": "off",
"import/extensions": "off",
"import/named": "off",
"import/no-extraneous-dependencies": "off",
"import/no-named-as-default": "off",
"import/no-unresolved": "off",
"import/prefer-default-export": "off",
"lines-between-class-members": "off",
"max-len": [1, 275, 3],
"newline-per-chained-call": "off",
"no-async-promise-executor": "off",
"no-await-in-loop": "off",
"no-bitwise": "off",
"no-case-declarations":"off",
"no-continue": "off",
"no-else-return": "off",
"no-lonely-if": "off",
"no-loop-func": "off",
"no-mixed-operators": "off",
"no-param-reassign":"off",
"no-plusplus": "off",
"no-process-exit": "off",
"no-regex-spaces": "off",
"no-restricted-globals": "off",
"no-restricted-syntax": "off",
"no-return-assign": "off",
"no-shadow": "off",
"no-underscore-dangle": "off",
"node/no-missing-import": ["error", { "tryExtensions": [".js", ".json", ".ts"] }],
"node/no-unpublished-import": "off",
"node/no-unpublished-require": "off",
"node/no-unsupported-features/es-syntax": "off",
"node/shebang": "off",
"object-curly-newline": "off",
"prefer-destructuring": "off",
"prefer-template":"off",
"promise/always-return": "off",
"promise/catch-or-return": "off",
"promise/no-nesting": "off",
"radix": "off"
}
}

35
.github/ISSUE_TEMPLATE/issue.md vendored Normal file
View File

@ -0,0 +1,35 @@
---
name: Issue
about: Issue
title: ''
labels: ''
assignees: vladmandic
---
**Issue Description**
**Steps to Reproduce**
**Expected Behavior**
**Environment**
- Human library version?
- Built-in demo or custom code?
- Type of module used (e.g. `js`, `esm`, `esm-nobundle`)?
- TensorFlow/JS version (if not using bundled module)?
- Browser or NodeJS and version (e.g. *NodeJS 14.15* or *Chrome 89*)?
- OS and Hardware platform (e.g. *Windows 10*, *Ubuntu Linux on x64*, *Android 10*)?
- Packager (if any) (e.g, *webpack*, *rollup*, *parcel*, *esbuild*, etc.)?
- Framework (if any) (e.g. *React*, *NextJS*, etc.)?
**Diagnostics**
- Check out any applicable [diagnostic steps](https://github.com/vladmandic/human/wiki/Diag)
**Additional**
- For installation or startup issues include your `package.json`
- For usage issues, it is recommended to post your code as [gist](https://gist.github.com/)
- For general questions, create a [discussion topic](https://github.com/vladmandic/human/discussions)

View File

@ -0,0 +1,3 @@
# Pull Request Template
<br>

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '16 14 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.vscode
pnpm-lock.yaml
*.swp
node_modules/
types/lib

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "wiki"]
path = wiki
url = https://github.com/vladmandic/human.wiki.git

16
.hintrc Normal file
View File

@ -0,0 +1,16 @@
{
"extends": [
"web-recommended"
],
"browserslist": [
"chrome >= 90",
"edge >= 90",
"firefox >= 100",
"android >= 90",
"safari >= 15"
],
"hints": {
"no-inline-styles": "off",
"meta-charset-utf-8": "off"
}
}

7
.markdownlint.json Normal file
View File

@ -0,0 +1,7 @@
{
"MD012": false,
"MD013": false,
"MD033": false,
"MD036": false,
"MD041": false
}

7
.npmignore Normal file
View File

@ -0,0 +1,7 @@
node_modules
pnpm-lock.yaml
samples
typedoc
test
wiki
types/lib

1
.npmrc Normal file
View File

@ -0,0 +1 @@
force = true

1033
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

33
CODE_OF_CONDUCT Normal file
View File

@ -0,0 +1,33 @@
# Code of Conduct
Use your best judgement
If it will possibly make others uncomfortable, do not post it
- Be respectful
Disagreement is not an opportunity to attack someone else's thoughts or opinions
Although views may differ, remember to approach every situation with patience and care
- Be considerate
Think about how your contribution will affect others in the community
- Be open minded
Embrace new people and new ideas. Our community is continually evolving and we welcome positive change
Be mindful of your language
Any of the following behavior is unacceptable:
- Offensive comments of any kind
- Threats or intimidation
- Sexually explicit material
- Or any other kinds of harassment
If you believe someone is violating the code of conduct, we ask that you report it
Participants asked to stop any harassing behavior are expected to comply immediately
<br>
## Usage Restrictions
`Human` library does not alow for usage in following scenarios:
- Any life-critical decisions
- Any form of surveillance without consent of the user is explicitly out of scope

22
CONTRIBUTING Normal file
View File

@ -0,0 +1,22 @@
# Contributing Guidelines
Pull requests from everyone are welcome
Procedure for contributing:
- Create a fork of the repository on github
In a top right corner of a GitHub, select "Fork"
Its recommended to fork latest version from main branch to avoid any possible conflicting code updates
- Clone your forked repository to your local system
`git clone https://github.com/<your-username>/<your-fork>
- Make your changes
- Test your changes against code guidelines
`npm run lint`
- Test your changes in Browser and NodeJS
`npm run dev` and naviate to https://localhost:10031
`node test/test-node.js`
- Push changes to your fork
Exclude files in `/dist', '/types', '/typedoc' from the commit as they are dynamically generated during build
- Submit a PR (pull request)
Your pull request will be reviewed and pending review results, merged into main branch

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Vladimir Mandic
Copyright (c) Vladimir Mandic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

365
README.md Normal file
View File

@ -0,0 +1,365 @@
![Git Version](https://img.shields.io/github/package-json/v/vladmandic/human?style=flat-square&svg=true&label=git)
![NPM Version](https://img.shields.io/npm/v/@vladmandic/human.png?style=flat-square)
![Last Commit](https://img.shields.io/github/last-commit/vladmandic/human?style=flat-square&svg=true)
![License](https://img.shields.io/github/license/vladmandic/human?style=flat-square&svg=true)
![GitHub Status Checks](https://img.shields.io/github/checks-status/vladmandic/human/main?style=flat-square&svg=true)
![Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/vladmandic/human?style=flat-square&svg=true)
# Human Library
**AI-powered 3D Face Detection & Rotation Tracking, Face Description & Recognition,**
**Body Pose Tracking, 3D Hand & Finger Tracking, Iris Analysis,**
**Age & Gender & Emotion Prediction, Gaze Tracking, Gesture Recognition, Body Segmentation**
<br>
JavaScript module using TensorFlow/JS Machine Learning library
- **Browser**:
Compatible with both desktop and mobile platforms
Compatible with *CPU*, *WebGL*, *WASM* backends
Compatible with *WebWorker* execution
- **NodeJS**:
Compatible with both software *tfjs-node* and
GPU accelerated backends *tfjs-node-gpu* using CUDA libraries
<br>
*Check out [**Simple Live Demo**](https://vladmandic.github.io/human/demo/typescript/index.html) fully annotated app as a good start starting point ([html](https://github.com/vladmandic/human/blob/main/demo/typescript/index.html))([code](https://github.com/vladmandic/human/blob/main/demo/typescript/index.ts))*
*Check out [**Main Live Demo**](https://vladmandic.github.io/human/demo/index.html) app for advanced processing of of webcam, video stream or images static images with all possible tunable options*
- To start video detection, simply press *Play*
- To process images, simply drag & drop in your Browser window
- Note: For optimal performance, select only models you'd like to use
- Note: If you have modern GPU, WebGL (default) backend is preferred, otherwise select WASM backend
<br>
## Releases
- [Release Notes](https://github.com/vladmandic/human/releases)
- [NPM Link](https://www.npmjs.com/package/@vladmandic/human)
## Demos
- [**List of all Demo applications**](https://github.com/vladmandic/human/wiki/Demos)
- [**Live Examples galery**](https://vladmandic.github.io/human/samples/index.html)
### Browser Demos
- **Full** [[*Live*]](https://vladmandic.github.io/human/demo/index.html) [[*Details*]](https://github.com/vladmandic/human/tree/main/demo): Main browser demo app that showcases all Human capabilities
- **Simple** [[*Live*]](https://vladmandic.github.io/human/demo/typescript/index.html) [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/typescript): Simple demo in WebCam processing demo in TypeScript
- **Face Match** [[*Live*]](https://vladmandic.github.io/human/demo/facematch/index.html) [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/facematch): Extract faces from images, calculates face descriptors and simmilarities and matches them to known database
- **Face ID** [[*Live*]](https://vladmandic.github.io/human/demo/faceid/index.html) [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/faceid): Runs multiple checks to validate webcam input before performing face match to faces in IndexDB
- **Multi-thread** [[*Live*]](https://vladmandic.github.io/human/demo/multithread/index.html) [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/multithread): Runs each Human module in a separate web worker for highest possible performance
- **NextJS** [[*Live*]](https://vladmandic.github.io/human-next/out/index.html) [[*Details*]](https://github.com/vladmandic/human-next): Use Human with TypeScript, NextJS and ReactJS
- **ElectronJS** [[*Details*]](https://github.com/vladmandic/human-electron): Use Human with TypeScript and ElectonJS to create standalone cross-platform apps
- **3D Analysis** [[*Live*]](https://vladmandic.github.io/human-motion/src/index.html) [[*Details*]](https://github.com/vladmandic/human-motion): 3D tracking and visualization of heead, face, eye, body and hand
- **Avatar Bone Mapping** [[*Live*]](https://vladmandic.github.io/human-vrm/src/human-avatar.html) [[*Details*]](https://github.com/vladmandic/human-avatar): Human skeleton with full bone mapping using look and inverse kinematics controllers
- **Virtual Model Tracking** [[*Live*]](https://vladmandic.github.io/human-vrm/src/human-vrm.html) [[*Details*]](https://github.com/vladmandic/human-vrm): VR model with head, face, eye, body and hand tracking
### NodeJS Demos
- **Main** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Process images from files, folders or URLs using native methods
- **Canvas** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Process image from file or URL and draw results to a new image file using `node-canvas`
- **Video** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Processing of video input using `ffmpeg`
- **WebCam** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Processing of webcam screenshots using `fswebcam`
- **Events** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Showcases usage of `Human` eventing to get notifications on processing
- **Similarity** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Compares two input images for similarity of detected faces
- **Face Match** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/facematch): Parallel processing of face **match** in multiple child worker threads
- **Multiple Workers** [[*Details*]](https://github.com/vladmandic/human/tree/main/demo/nodejs): Runs multiple parallel `human` by dispaching them to pool of pre-created worker processes
## Project pages
- [**Code Repository**](https://github.com/vladmandic/human)
- [**NPM Package**](https://www.npmjs.com/package/@vladmandic/human)
- [**Issues Tracker**](https://github.com/vladmandic/human/issues)
- [**TypeDoc API Specification**](https://vladmandic.github.io/human/typedoc/classes/Human.html)
- [**Change Log**](https://github.com/vladmandic/human/blob/main/CHANGELOG.md)
- [**Current To-do List**](https://github.com/vladmandic/human/blob/main/TODO.md)
## Wiki pages
- [**Home**](https://github.com/vladmandic/human/wiki)
- [**Installation**](https://github.com/vladmandic/human/wiki/Install)
- [**Usage & Functions**](https://github.com/vladmandic/human/wiki/Usage)
- [**Configuration Details**](https://github.com/vladmandic/human/wiki/Config)
- [**Result Details**](https://github.com/vladmandic/human/wiki/Result)
- [**Caching & Smoothing**](https://github.com/vladmandic/human/wiki/Caching)
- [**Input Processing**](https://github.com/vladmandic/human/wiki/Image)
- [**Face Recognition & Face Description**](https://github.com/vladmandic/human/wiki/Embedding)
- [**Gesture Recognition**](https://github.com/vladmandic/human/wiki/Gesture)
- [**Common Issues**](https://github.com/vladmandic/human/wiki/Issues)
- [**Background and Benchmarks**](https://github.com/vladmandic/human/wiki/Background)
## Additional notes
- [**Comparing Backends**](https://github.com/vladmandic/human/wiki/Backends)
- [**Development Server**](https://github.com/vladmandic/human/wiki/Development-Server)
- [**Build Process**](https://github.com/vladmandic/human/wiki/Build-Process)
- [**Adding Custom Modules**](https://github.com/vladmandic/human/wiki/Module)
- [**Performance Notes**](https://github.com/vladmandic/human/wiki/Performance)
- [**Performance Profiling**](https://github.com/vladmandic/human/wiki/Profiling)
- [**Platform Support**](https://github.com/vladmandic/human/wiki/Platforms)
- [**Diagnostic and Performance trace information**](https://github.com/vladmandic/human/wiki/Diag)
- [**Dockerize Human applications**](https://github.com/vladmandic/human/wiki/Docker)
- [**List of Models & Credits**](https://github.com/vladmandic/human/wiki/Models)
- [**Models Download Repository**](https://github.com/vladmandic/human-models)
- [**Security & Privacy Policy**](https://github.com/vladmandic/human/blob/main/SECURITY.md)
- [**License & Usage Restrictions**](https://github.com/vladmandic/human/blob/main/LICENSE)
<br>
*See [**issues**](https://github.com/vladmandic/human/issues?q=) and [**discussions**](https://github.com/vladmandic/human/discussions) for list of known limitations and planned enhancements*
*Suggestions are welcome!*
<hr><br>
## Examples
Visit [Examples galery](https://vladmandic.github.io/human/samples/samples.html) for more examples
<https://vladmandic.github.io/human/samples/samples.html>
![samples](assets/samples.jpg)
<br>
## Options
All options as presented in the demo application...
> [demo/index.html](demo/index.html)
![Options visible in demo](assets/screenshot-menu.png)
<br>
**Results Browser:**
[ *Demo -> Display -> Show Results* ]<br>
![Results](assets/screenshot-results.png)
<br>
## Advanced Examples
1. **Face Similarity Matching:**
Extracts all faces from provided input images,
sorts them by similarity to selected face
and optionally matches detected face with database of known people to guess their names
> [demo/facematch](demo/facematch/index.html)
![Face Matching](assets/screenshot-facematch.jpg)
<br>
2. **3D Rendering:**
> [human-motion](https://github.com/vladmandic/human-motion)
![Face3D](https://github.com/vladmandic/human-motion/raw/main/assets/screenshot-face.jpg)
![Body3D](https://github.com/vladmandic/human-motion/raw/main/assets/screenshot-body.jpg)
![Hand3D](https://github.com/vladmandic/human-motion/raw/main/assets/screenshot-hand.jpg)
<br>
3. **Avatar Bone Mapping:**
> [human-avatar](https://github.com/vladmandic/human-avatar)
![Avatar](https://github.com/vladmandic/human-avatar/raw/main/assets/screenshot.jpg)
<br>
4. **VR Model Tracking:**
> [human-vrmmotion](https://github.com/vladmandic/human-vrm)
![VRM](https://github.com/vladmandic/human-vrm/raw/main/assets/human-vrm-screenshot.jpg)
<br>
**468-Point Face Mesh Defails:**
(view in full resolution to see keypoints)
![FaceMesh](assets/facemesh.png)
<br><hr><br>
## Quick Start
Simply load `Human` (*IIFE version*) directly from a cloud CDN in your HTML file:
(pick one: `jsdelirv`, `unpkg` or `cdnjs`)
```html
<script src="https://cdn.jsdelivr.net/npm/@vladmandic/human/dist/human.js"></script>
<script src="https://unpkg.dev/@vladmandic/human/dist/human.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/human/2.1.5/human.js"></script>
```
For details, including how to use `Browser ESM` version or `NodeJS` version of `Human`, see [**Installation**](https://github.com/vladmandic/human/wiki/Install)
<br>
## Inputs
`Human` library can process all known input types:
- `Image`, `ImageData`, `ImageBitmap`, `Canvas`, `OffscreenCanvas`, `Tensor`,
- `HTMLImageElement`, `HTMLCanvasElement`, `HTMLVideoElement`, `HTMLMediaElement`
Additionally, `HTMLVideoElement`, `HTMLMediaElement` can be a standard `<video>` tag that links to:
- WebCam on user's system
- Any supported video type
For example: `.mp4`, `.avi`, etc.
- Additional video types supported via *HTML5 Media Source Extensions*
Live streaming examples:
- **HLS** (*HTTP Live Streaming*) using `hls.js`
- **DASH** (Dynamic Adaptive Streaming over HTTP) using `dash.js`
- **WebRTC** media track using built-in support
<br>
## Example
Example simple app that uses Human to process video input and
draw output on screen using internal draw helper functions
```js
// create instance of human with simple configuration using default values
const config = { backend: 'webgl' };
const human = new Human(config);
// select input HTMLVideoElement and output HTMLCanvasElement from page
const inputVideo = document.getElementById('video-id');
const outputCanvas = document.getElementById('canvas-id');
function detectVideo() {
// perform processing using default configuration
human.detect(inputVideo).then((result) => {
// result object will contain detected details
// as well as the processed canvas itself
// so lets first draw processed frame on canvas
human.draw.canvas(result.canvas, outputCanvas);
// then draw results on the same canvas
human.draw.face(outputCanvas, result.face);
human.draw.body(outputCanvas, result.body);
human.draw.hand(outputCanvas, result.hand);
human.draw.gesture(outputCanvas, result.gesture);
// and loop immediate to the next frame
requestAnimationFrame(detectVideo);
});
}
detectVideo();
```
or using `async/await`:
```js
// create instance of human with simple configuration using default values
const config = { backend: 'webgl' };
const human = new Human(config); // create instance of Human
const inputVideo = document.getElementById('video-id');
const outputCanvas = document.getElementById('canvas-id');
async function detectVideo() {
const result = await human.detect(inputVideo); // run detection
human.draw.all(outputCanvas, result); // draw all results
requestAnimationFrame(detectVideo); // run loop
}
detectVideo(); // start loop
```
or using `Events`:
```js
// create instance of human with simple configuration using default values
const config = { backend: 'webgl' };
const human = new Human(config); // create instance of Human
const inputVideo = document.getElementById('video-id');
const outputCanvas = document.getElementById('canvas-id');
human.events.addEventListener('detect', () => { // event gets triggered when detect is complete
human.draw.all(outputCanvas, human.result); // draw all results
});
function detectVideo() {
human.detect(inputVideo) // run detection
.then(() => requestAnimationFrame(detectVideo)); // upon detect complete start processing of the next frame
}
detectVideo(); // start loop
```
or using interpolated results for smooth video processing by separating detection and drawing loops:
```js
const human = new Human(); // create instance of Human
const inputVideo = document.getElementById('video-id');
const outputCanvas = document.getElementById('canvas-id');
let result;
async function detectVideo() {
result = await human.detect(inputVideo); // run detection
requestAnimationFrame(detectVideo); // run detect loop
}
async function drawVideo() {
if (result) { // check if result is available
const interpolated = human.next(result); // calculate next interpolated frame
human.draw.all(outputCanvas, interpolated); // draw the frame
}
requestAnimationFrame(drawVideo); // run draw loop
}
detectVideo(); // start detection loop
drawVideo(); // start draw loop
```
And for even better results, you can run detection in a separate web worker thread
<br><hr><br>
## Default models
Default models in Human library are:
- **Face Detection**: MediaPipe BlazeFace Back variation
- **Face Mesh**: MediaPipe FaceMesh
- **Face Iris Analysis**: MediaPipe Iris
- **Face Description**: HSE FaceRes
- **Emotion Detection**: Oarriaga Emotion
- **Body Analysis**: MoveNet Lightning variation
- **Hand Analysis**: HandTrack & MediaPipe HandLandmarks
- **Body Segmentation**: Google Selfie
- **Object Detection**: CenterNet with MobileNet v3
Note that alternative models are provided and can be enabled via configuration
For example, `PoseNet` model can be switched for `BlazePose`, `EfficientPose` or `MoveNet` model depending on the use case
For more info, see [**Configuration Details**](https://github.com/vladmandic/human/wiki/Configuration) and [**List of Models**](https://github.com/vladmandic/human/wiki/Models)
<br><hr><br>
## Diagnostics
- [How to get diagnostic information or performance trace information](https://github.com/vladmandic/human/wiki/Diag)
<br><hr><br>
`Human` library is written in `TypeScript` [4.6](https://www.typescriptlang.org/docs/handbook/intro.html)
Conforming to latest `JavaScript` [ECMAScript version 2021](https://262.ecma-international.org/) standard
Build target is `JavaScript` [EMCAScript version 2018](https://262.ecma-international.org/11.0/)
<br>
For details see [**Wiki Pages**](https://github.com/vladmandic/human/wiki)
and [**API Specification**](https://vladmandic.github.io/human/typedoc/classes/Human.html)
<br>
![Stars](https://img.shields.io/github/stars/vladmandic/human?style=flat-square&svg=true)
![Forks](https://badgen.net/github/forks/vladmandic/human)
![Code Size](https://img.shields.io/github/languages/code-size/vladmandic/human?style=flat-square&svg=true)
![CDN](https://data.jsdelivr.com/v1/package/npm/@vladmandic/human/badge)<br>
![Downloads](https://img.shields.io/npm/dw/@vladmandic/human.png?style=flat-square)
![Downloads](https://img.shields.io/npm/dm/@vladmandic/human.png?style=flat-square)
![Downloads](https://img.shields.io/npm/dy/@vladmandic/human.png?style=flat-square)

32
SECURITY.md Normal file
View File

@ -0,0 +1,32 @@
# Security & Privacy Policy
<br>
## Issues
All issues are tracked publicly on GitHub: <https://github.com/vladmandic/human/issues>
<br>
## Vulnerabilities
`Human` library code base and indluded dependencies are automatically scanned against known security vulnerabilities
Any code commit is validated before merge
- [Dependencies](https://github.com/vladmandic/human/security/dependabot)
- [Scanning Alerts](https://github.com/vladmandic/human/security/code-scanning)
<br>
## Privacy
`Human` library and included demo apps:
- Are fully self-contained and does not send or share data of any kind with external targets
- Do not store any user or system data tracking, user provided inputs (images, video) or detection results
- Do not utilize any analytic services (such as Google Analytics)
`Human` library can establish external connections *only* for following purposes and *only* when explicitly configured by user:
- Load models from externally hosted site (e.g. CDN)
- Load inputs for detection from *http & https* sources

31
TODO.md Normal file
View File

@ -0,0 +1,31 @@
# To-Do list for Human library
## Work in Progress
### Exploring
- Optical flow: <https://docs.opencv.org/3.3.1/db/d7f/tutorial_js_lucas_kanade.html>
- Advanced histogram equalization: Adaptive, Contrast Limited, CLAHE
- TFLite models: <https://js.tensorflow.org/api_tflite/0.0.1-alpha.4/>
- Body segmentation: `robust-video-matting`
<br><hr><br>
## Known Issues
#### WebGPU
Experimental support only until support is officially added in Chromium
### Face Detection
Enhanced rotation correction for face detection is not working in NodeJS due to missing kernel op in TFJS
Feature is automatically disabled in NodeJS without user impact
- Backend NodeJS missing kernel op `RotateWithOffset`
<https://github.com/tensorflow/tfjs/issues/5473>
<br><hr><br>
## Pending Release Notes

28
api-extractor.json Normal file
View File

@ -0,0 +1,28 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "types/lib/src/human.d.ts",
"bundledPackages": ["@types/offscreencanvas", "@tensorflow/tfjs-core", "@tensorflow/tfjs-converter", "@tensorflow/tfjs-data"],
"compiler": {
"skipLibCheck": false
},
"newlineKind": "lf",
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "types/human.d.ts"
},
"docModel": { "enabled": false },
"tsdocMetadata": { "enabled": false },
"apiReport": { "enabled": false },
"messages": {
"compilerMessageReporting": {
"default": { "logLevel": "warning" }
},
"extractorMessageReporting": {
"default": { "logLevel": "warning" },
"ae-missing-release-tag": { "logLevel": "none" }
},
"tsdocMessageReporting": {
"default": { "logLevel": "warning" }
}
}
}

4
assets/README.md Normal file
View File

@ -0,0 +1,4 @@
# Human Library: Static Assets
Static assets used by `Human` library demos and/or referenced by Wiki pages

BIN
assets/facemesh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
assets/lato-light.woff2 Normal file

Binary file not shown.

BIN
assets/samples.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
assets/screenshot-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
assets/screenshot-vrm.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

57
build.js Normal file
View File

@ -0,0 +1,57 @@
const fs = require('fs');
const log = require('@vladmandic/pilogger');
const Build = require('@vladmandic/build').Build;
const APIExtractor = require('@microsoft/api-extractor');
function copy(src, dst) {
if (!fs.existsSync(src)) return;
const buffer = fs.readFileSync(src);
fs.writeFileSync(dst, buffer);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const apiExtractorIgnoreList = [
'ae-missing-release-tag',
'tsdoc-param-tag-missing-hyphen',
'tsdoc-escape-right-brace',
'tsdoc-undefined-tag',
'tsdoc-escape-greater-than',
'ae-unresolved-link',
'ae-forgotten-export',
'tsdoc-malformed-inline-tag',
'tsdoc-unnecessary-backslash',
];
async function main() {
// run production build
const build = new Build();
await build.run('production');
// patch tfjs typedefs
log.state('Copy:', { input: 'tfjs/tfjs.esm.d.ts' });
copy('tfjs/tfjs.esm.d.ts', 'types/lib/dist/tfjs.esm.d.ts');
// run api-extractor to create typedef rollup
const extractorConfig = APIExtractor.ExtractorConfig.loadFileAndPrepare('api-extractor.json');
const extractorResult = APIExtractor.Extractor.invoke(extractorConfig, {
localBuild: true,
showVerboseMessages: false,
messageCallback: (msg) => {
msg.handled = true;
if (msg.logLevel === 'none' || msg.logLevel === 'verbose' || msg.logLevel === 'info') return;
if (msg.sourceFilePath?.includes('/node_modules/')) return;
// if (apiExtractorIgnoreList.reduce((prev, curr) => prev || msg.messageId.includes(curr), false)) return; // those are external issues outside of human control
log.data('API', { level: msg.logLevel, category: msg.category, id: msg.messageId, file: msg.sourceFilePath, line: msg.sourceFileLine, text: msg.text });
},
});
log.state('API-Extractor:', { succeeeded: extractorResult.succeeded, errors: extractorResult.errorCount, warnings: extractorResult.warningCount });
// distribute typedefs
log.state('Copy:', { input: 'types/human.d.ts' });
copy('types/human.d.ts', 'dist/human.esm-nobundle.d.ts');
copy('types/human.d.ts', 'dist/human.esm.d.ts');
copy('types/human.d.ts', 'dist/human.d.ts');
copy('types/human.d.ts', 'dist/human.node-gpu.d.ts');
copy('types/human.d.ts', 'dist/human.node.d.ts');
copy('types/human.d.ts', 'dist/human.node-wasm.d.ts');
log.info('Human Build complete...');
}
main();

64
demo/README.md Normal file
View File

@ -0,0 +1,64 @@
# Human Library: Demos
For details on other demos see Wiki: [**Demos**](https://github.com/vladmandic/human/wiki/Demos)
## Main Demo
`index.html`: Full demo using `Human` ESM module running in Browsers,
Includes:
- Selectable inputs:
- Sample images
- Image via drag & drop
- Image via URL param
- WebCam input
- Video stream
- WebRTC stream
- Selectable active `Human` modules
- With interactive module params
- Interactive `Human` image filters
- Selectable interactive `results` browser
- Selectable `backend`
- Multiple execution methods:
- Sync vs Async
- in main thread or web worker
- live on git pages, on user-hosted web server or via included [**micro http2 server**](https://github.com/vladmandic/human/wiki/Development-Server)
### Demo Options
- General `Human` library options
in `index.js:userConfig`
- General `Human` `draw` options
in `index.js:drawOptions`
- Demo PWA options
in `index.js:pwa`
- Demo specific options
in `index.js:ui`
```js
console: true, // log messages to browser console
useWorker: true, // use web workers for processing
buffered: true, // should output be buffered between frames
interpolated: true, // should output be interpolated for smoothness between frames
results: false, // show results tree
useWebRTC: false, // use webrtc as camera source instead of local webcam
```
Demo implements several ways to use `Human` library,
### URL Params
Demo app can use URL parameters to override configuration values
For example:
- Force using `WASM` as backend: <https://vladmandic.github.io/human/demo/index.html?backend=wasm>
- Enable `WebWorkers`: <https://vladmandic.github.io/human/demo/index.html?worker=true>
- Skip pre-loading and warming up: <https://vladmandic.github.io/human/demo/index.html?preload=false&warmup=false>
### WebRTC
Note that WebRTC connection requires a WebRTC server that provides a compatible media track such as H.264 video track
For such a WebRTC server implementation see <https://github.com/vladmandic/stream-rtsp> project
that implements a connection to IP Security camera using RTSP protocol and transcodes it to WebRTC
ready to be consumed by a client such as `Human`

4
demo/benchmark/README.md Normal file
View File

@ -0,0 +1,4 @@
# Human Benchmarks
- `node.js` runs benchmark using `tensorflow` backend in **NodeJS**
- `index.html` runs benchmark using `wasm`, `webgl`, `humangl` and `webgpu` backends in **Browser**

86
demo/benchmark/index.html Normal file
View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Human</title>
<meta name="viewport" content="width=device-width" id="viewport">
<meta name="keywords" content="Human">
<meta name="application-name" content="Human">
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="../manifest.webmanifest">
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../../assets/icon.png">
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 16px; font-variant: small-caps; }
body { margin: 0; background: black; color: white; overflow-x: hidden; width: 100vw; height: 100vh; }
body::-webkit-scrollbar { display: none; }
.status { position: absolute; width: 100vw; bottom: 10%; text-align: center; font-size: 3rem; font-weight: 100; text-shadow: 2px 2px #303030; }
.log { position: absolute; bottom: 0; margin: 0.4rem 0.4rem 0 0.4rem; font-size: 0.9rem; }
</style>
</head>
<body>
<div id="status" class="status"></div>
<img id="image" src="../../samples/in/group-1.jpg" alt="test image" style="display: none">
<div id="log" class="log"></div>
<script type="module">
import Human from '../../dist/human.esm.js';
const loop = 20;
const backends = ['wasm', 'webgl', 'humangl', 'webgpu'];
// eslint-disable-next-line no-console
const log = (...msg) => console.log(...msg);
const myConfig = {
modelBasePath: 'https://vladmandic.github.io/human/models',
debug: true,
async: true,
cacheSensitivity: 0,
filter: { enabled: false },
face: {
enabled: true,
detector: { enabled: true, rotation: false },
mesh: { enabled: true },
iris: { enabled: true },
description: { enabled: true },
emotion: { enabled: false },
antispoof: { enabled: true },
liveness: { enabled: true },
},
hand: { enabled: true },
body: { enabled: true },
object: { enabled: true },
};
async function benchmark(backend) {
myConfig.backend = backend;
const human = new Human(myConfig);
await human.tf.ready();
log('Human:', human.version);
await human.load();
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
log('Loaded:', loaded);
log('Memory state:', human.tf.engine().memory());
const element = document.getElementById('image');
const processed = await human.image(element);
const t0 = human.now();
await human.detect(processed.tensor, myConfig);
const t1 = human.now();
log('Backend:', human.tf.getBackend());
log('Warmup:', Math.round(t1 - t0));
for (let i = 0; i < loop; i++) await human.detect(processed.tensor, myConfig);
const t2 = human.now();
log('Average:', Math.round((t2 - t1) / loop));
}
async function main() {
for (const backend of backends) await benchmark(backend);
}
window.onload = main;
</script>
</body>
</html>

65
demo/benchmark/node.js Normal file
View File

@ -0,0 +1,65 @@
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node-gpu');
const log = require('@vladmandic/pilogger');
const canvasJS = require('canvas');
const Human = require('../../dist/human.node-gpu.js').default;
const input = './samples/in/group-1.jpg';
const loop = 20;
const myConfig = {
backend: 'tensorflow',
modelBasePath: 'https://vladmandic.github.io/human/models',
wasmPath: 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@3.9.0/dist/',
debug: true,
async: true,
cacheSensitivity: 0,
filter: { enabled: false },
face: {
enabled: true,
detector: { enabled: true, rotation: false },
mesh: { enabled: true },
iris: { enabled: true },
description: { enabled: true },
emotion: { enabled: true },
antispoof: { enabled: true },
liveness: { enabled: true },
},
hand: { enabled: true },
body: { enabled: true },
object: { enabled: true },
};
async function getImage(human) {
const img = await canvasJS.loadImage(input);
const canvas = canvasJS.createCanvas(img.width, img.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const tensor = human.tf.tensor(Array.from(imageData.data), [canvas.height, canvas.width, 4], 'int32'); // create rgba image tensor from flat array
log.info('Image:', input, tensor.shape);
return tensor;
}
async function main() {
log.header();
const human = new Human(myConfig);
await human.tf.ready();
log.info('Human:', human.version);
await human.load();
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
log.info('Loaded:', loaded);
log.info('Memory state:', human.tf.engine().memory());
const tensor = await getImage(human);
log.state('Processing:', tensor['shape']);
const t0 = human.now();
await human.detect(tensor, myConfig);
const t1 = human.now();
log.state('Backend:', human.tf.getBackend());
log.data('Warmup:', Math.round(t1 - t0));
for (let i = 0; i < loop; i++) await human.detect(tensor, myConfig);
const t2 = human.now();
log.data('Average:', Math.round((t2 - t1) / loop));
}
main();

41
demo/faceid/README.md Normal file
View File

@ -0,0 +1,41 @@
# Human Face Recognition: FaceID
`faceid` runs multiple checks to validate webcam input before performing face match
Detected face image and descriptor are stored in client-side IndexDB
## Workflow
- Starts webcam
- Waits until input video contains validated face or timeout is reached
- Number of people
- Face size
- Face and gaze direction
- Detection scores
- Blink detection (including temporal check for blink speed) to verify live input
- Runs `antispoofing` optional module
- Runs `liveness` optional module
- Runs match against database of registered faces and presents best match with scores
## Notes
Both `antispoof` and `liveness` models are tiny and
designed to serve as a quick check when used together with other indicators:
- size below 1MB
- very quick inference times as they are very simple (11 ops for antispoof and 23 ops for liveness)
- trained on low-resolution inputs
### Anti-spoofing Module
- Checks if input is realistic (e.g. computer generated faces)
- Configuration: `human.config.face.antispoof`.enabled
- Result: `human.result.face[0].real` as score
### Liveness Module
- Checks if input has obvious artifacts due to recording (e.g. playing back phone recording of a face)
- Configuration: `human.config.face.liveness`.enabled
- Result: `human.result.face[0].live` as score
### Models
**FaceID** is compatible with
- `faceres.json` (default) perfoms combined age/gender/descriptor analysis
- `faceres-deep.json` higher resolution variation of `faceres`
- `mobilefacenet` alternative model for face descriptor analysis

40
demo/faceid/index.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Human: Face Recognition</title>
<meta name="viewport" content="width=device-width" id="viewport">
<meta name="keywords" content="Human">
<meta name="application-name" content="Human">
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="../manifest.webmanifest">
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../../assets/icon.png">
<script src="./index.js" type="module"></script>
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 16px; font-variant: small-caps; }
body { margin: 0; padding: 16px; background: black; color: white; overflow-x: hidden; width: 100vw; height: 100vh; }
body::-webkit-scrollbar { display: none; }
.button { padding: 2px; cursor: pointer; box-shadow: 2px 2px black; width: 64px; text-align: center; place-content: center; margin-left: 16px; height: 16px; display: none }
.ok { position: absolute; top: 64px; right: 20px; width: 100px; background-color: grey; padding: 4px; color: black; font-size: 14px }
</style>
</head>
<body>
<canvas id="canvas" style="padding: 8px"></canvas>
<canvas id="source" style="padding: 8px"></canvas>
<video id="video" playsinline style="display: none"></video>
<pre id="fps" style="position: absolute; bottom: 16px; right: 20px; background-color: grey; padding: 8px; box-shadow: 2px 2px black"></pre>
<pre id="log" style="padding: 8px"></pre>
<div id="match" style="display: none; padding: 8px">
<label for="name">name:</label>
<input id="name" type="text" value="" style="height: 16px; border: none; padding: 2px; margin-left: 8px">
<span id="save" class="button" style="background-color: royalblue">save</span>
<span id="delete" class="button" style="background-color: lightcoral">delete</span>
</div>
<div id="retry" class="button" style="background-color: darkslategray; width: 350px; margin-top: 32px; padding: 4px">retry</div>
<div id="ok"></div>
</body>
</html>

17
demo/faceid/index.js Normal file
View File

@ -0,0 +1,17 @@
/*
Human
homepage: <https://github.com/vladmandic/human>
author: <https://github.com/vladmandic>'
*/
import{Human as H}from"../../dist/human.esm.js";var d,R="human",m="person",g=(...t)=>console.log("indexdb",...t);async function b(){return d?!0:new Promise(t=>{let i=indexedDB.open(R,1);i.onerror=s=>g("error:",s),i.onupgradeneeded=s=>{g("create:",s.target),d=s.target.result,d.createObjectStore(m,{keyPath:"id",autoIncrement:!0})},i.onsuccess=s=>{d=s.target.result,g("open:",d),t(!0)}})}async function C(){let t=[];return d||await b(),new Promise(i=>{let s=d.transaction([m],"readwrite").objectStore(m).openCursor(null,"next");s.onerror=o=>g("load error:",o),s.onsuccess=o=>{o.target.result?(t.push(o.target.result.value),o.target.result.continue()):i(t)}})}async function k(){return d||await b(),new Promise(t=>{let i=d.transaction([m],"readwrite").objectStore(m).count();i.onerror=s=>g("count error:",s),i.onsuccess=()=>t(i.result)})}async function x(t){d||await b();let i={name:t.name,descriptor:t.descriptor,image:t.image};d.transaction([m],"readwrite").objectStore(m).put(i),g("save:",i)}async function D(t){d||await b(),d.transaction([m],"readwrite").objectStore(m).delete(t.id),g("delete:",t)}var v={modelBasePath:"../../models",filter:{equalization:!0},face:{enabled:!0,detector:{rotation:!0,return:!0,cropFactor:1.6,mask:!1},description:{enabled:!0},mobilefacenet:{enabled:!1,modelPath:"https://vladmandic.github.io/human-models/models/mobilefacenet.json"},iris:{enabled:!0},emotion:{enabled:!1},antispoof:{enabled:!0},liveness:{enabled:!0}},body:{enabled:!1},hand:{enabled:!1},object:{enabled:!1},gesture:{enabled:!0}},I={order:2,multiplier:25,min:.2,max:.8},c={minConfidence:.6,minSize:224,maxTime:1e4,blinkMin:10,blinkMax:800,threshold:.5,mask:v.face.detector.mask,rotation:v.face.detector.rotation,cropFactor:v.face.detector.cropFactor,...I},n={faceCount:!1,faceConfidence:!1,facingCenter:!1,lookingCenter:!1,blinkDetected:!1,faceSize:!1,antispoofCheck:!1,livenessCheck:!1,elapsedMs:0},M=()=>n.faceCount&&n.faceSize&&n.blinkDetected&&n.facingCenter&&n.lookingCenter&&n.faceConfidence&&n.antispoofCheck&&n.livenessCheck,r={face:null,record:null},l={start:0,end:0,time:0},a=new H(v);a.env.perfadd=!1;a.draw.options.font='small-caps 18px "Lato"';a.draw.options.lineHeight=20;var e={video:document.getElementById("video"),canvas:document.getElementById("canvas"),log:document.getElementById("log"),fps:document.getElementById("fps"),match:document.getElementById("match"),name:document.getElementById("name"),save:document.getElementById("save"),delete:document.getElementById("delete"),retry:document.getElementById("retry"),source:document.getElementById("source"),ok:document.getElementById("ok")},h={detect:0,draw:0},y={detect:0,draw:0},E=0,p=(...t)=>{e.log.innerText+=t.join(" ")+`
`,console.log(...t)},w=t=>e.fps.innerText=t;async function S(){w("starting webcam...");let t={audio:!1,video:{facingMode:"user",resizeMode:"none",width:{ideal:document.body.clientWidth}}},i=await navigator.mediaDevices.getUserMedia(t),s=new Promise(o=>{e.video.onloadeddata=()=>o(!0)});e.video.srcObject=i,e.video.play(),await s,e.canvas.width=e.video.videoWidth,e.canvas.height=e.video.videoHeight,a.env.initial&&p("video:",e.video.videoWidth,e.video.videoHeight,"|",i.getVideoTracks()[0].label),e.canvas.onclick=()=>{e.video.paused?e.video.play():e.video.pause()}}async function T(){if(!e.video.paused){r.face&&r.face.tensor&&a.tf.dispose(r.face.tensor),await a.detect(e.video);let t=a.now();y.detect=1e3/(t-h.detect),h.detect=t,requestAnimationFrame(T)}}async function L(){let t=await a.next(a.result);await a.draw.canvas(e.video,e.canvas),await a.draw.all(e.canvas,t);let i=a.now();if(y.draw=1e3/(i-h.draw),h.draw=i,w(`fps: ${y.detect.toFixed(1).padStart(5," ")} detect | ${y.draw.toFixed(1).padStart(5," ")} draw`),n.faceCount=a.result.face.length===1,n.faceCount){let o=Object.values(a.result.gesture).map(f=>f.gesture);(o.includes("blink left eye")||o.includes("blink right eye"))&&(l.start=a.now()),l.start>0&&!o.includes("blink left eye")&&!o.includes("blink right eye")&&(l.end=a.now()),n.blinkDetected=n.blinkDetected||Math.abs(l.end-l.start)>c.blinkMin&&Math.abs(l.end-l.start)<c.blinkMax,n.blinkDetected&&l.time===0&&(l.time=Math.trunc(l.end-l.start)),n.facingCenter=o.includes("facing center"),n.lookingCenter=o.includes("looking center"),n.faceConfidence=(a.result.face[0].boxScore||0)>c.minConfidence&&(a.result.face[0].faceScore||0)>c.minConfidence&&(a.result.face[0].genderScore||0)>c.minConfidence,n.antispoofCheck=(a.result.face[0].real||0)>c.minConfidence,n.livenessCheck=(a.result.face[0].live||0)>c.minConfidence,n.faceSize=a.result.face[0].box[2]>=c.minSize&&a.result.face[0].box[3]>=c.minSize}let s=32;for(let[o,f]of Object.entries(n)){let u=document.getElementById(`ok-${o}`);u||(u=document.createElement("div"),u.innerText=o,u.className="ok",u.style.top=`${s}px`,e.ok.appendChild(u)),typeof f=="boolean"?u.style.backgroundColor=f?"lightgreen":"lightcoral":u.innerText=`${o}:${f}`,s+=28}return M()||n.elapsedMs>c.maxTime?(e.video.pause(),a.result.face[0]):(n.elapsedMs=Math.trunc(a.now()-E),new Promise(o=>{setTimeout(async()=>{await L()&&o(a.result.face[0])},30)}))}async function P(){var t,i;if(e.name.value.length>0){let s=(t=e.canvas.getContext("2d"))==null?void 0:t.getImageData(0,0,e.canvas.width,e.canvas.height),o={id:0,name:e.name.value,descriptor:(i=r.face)==null?void 0:i.embedding,image:s};await x(o),p("saved face record:",o.name)}else p("invalid name")}async function z(){r.record&&r.record.id>0&&await D(r.record)}async function j(){var o,f;if((o=e.canvas.getContext("2d"))==null||o.clearRect(0,0,c.minSize,c.minSize),!r.face||!r.face.tensor||!r.face.embedding)return!1;if(console.log("face record:",r.face),a.tf.browser.toPixels(r.face.tensor,e.canvas),await k()===0)return p("face database is empty"),document.body.style.background="black",e.delete.style.display="none",!1;let t=await C(),i=t.map(u=>u.descriptor),s=await a.match(r.face.embedding,i,I);return r.record=t[s.index]||null,r.record&&(p(`best match: ${r.record.name} | id: ${r.record.id} | similarity: ${Math.round(1e3*s.similarity)/10}%`),e.name.value=r.record.name,e.source.style.display="",(f=e.source.getContext("2d"))==null||f.putImageData(r.record.image,0,0)),document.body.style.background=s.similarity>c.threshold?"darkgreen":"maroon",s.similarity>c.threshold}async function B(){var t,i,s,o;return n.faceCount=!1,n.faceConfidence=!1,n.facingCenter=!1,n.blinkDetected=!1,n.faceSize=!1,n.antispoofCheck=!1,n.livenessCheck=!1,n.elapsedMs=0,e.match.style.display="none",e.retry.style.display="none",e.source.style.display="none",document.body.style.background="black",await S(),await T(),E=a.now(),r.face=await L(),e.canvas.width=((i=(t=r.face)==null?void 0:t.tensor)==null?void 0:i.shape[1])||c.minSize,e.canvas.height=((o=(s=r.face)==null?void 0:s.tensor)==null?void 0:o.shape[0])||c.minSize,e.source.width=e.canvas.width,e.source.height=e.canvas.height,e.canvas.style.width="",e.match.style.display="flex",e.save.style.display="flex",e.delete.style.display="flex",e.retry.style.display="block",M()?j():(p("did not find valid face"),!1)}async function q(){p("human version:",a.version,"| tfjs version:",a.tf.version["tfjs-core"]),p("options:",JSON.stringify(c).replace(/{|}|"|\[|\]/g,"").replace(/,/g," ")),w("loading..."),p("known face records:",await k()),await S(),await a.load(),w("initializing..."),e.retry.addEventListener("click",B),e.save.addEventListener("click",P),e.delete.addEventListener("click",z),await a.warmup(),await B()}window.onload=q;
/**
* Human demo for browsers
* @default Human Library
* @summary <https://github.com/vladmandic/human>
* @author <https://github.com/vladmandic>
* @copyright <https://github.com/vladmandic>
* @license MIT
*/
//# sourceMappingURL=index.js.map

7
demo/faceid/index.js.map Normal file

File diff suppressed because one or more lines are too long

274
demo/faceid/index.ts Normal file
View File

@ -0,0 +1,274 @@
/**
* Human demo for browsers
* @default Human Library
* @summary <https://github.com/vladmandic/human>
* @author <https://github.com/vladmandic>
* @copyright <https://github.com/vladmandic>
* @license MIT
*/
import { Human, TensorLike, FaceResult } from '../../dist/human.esm.js'; // equivalent of @vladmandic/Human
import * as indexDb from './indexdb'; // methods to deal with indexdb
const humanConfig = { // user configuration for human, used to fine-tune behavior
modelBasePath: '../../models',
filter: { equalization: true }, // lets run with histogram equilizer
face: {
enabled: true,
detector: { rotation: true, return: true, cropFactor: 1.6, mask: false }, // return tensor is used to get detected face image
description: { enabled: true }, // default model for face descriptor extraction is faceres
mobilefacenet: { enabled: false, modelPath: 'https://vladmandic.github.io/human-models/models/mobilefacenet.json' }, // alternative model
iris: { enabled: true }, // needed to determine gaze direction
emotion: { enabled: false }, // not needed
antispoof: { enabled: true }, // enable optional antispoof module
liveness: { enabled: true }, // enable optional liveness module
},
body: { enabled: false },
hand: { enabled: false },
object: { enabled: false },
gesture: { enabled: true }, // parses face and iris gestures
};
// const matchOptions = { order: 2, multiplier: 1000, min: 0.0, max: 1.0 }; // for embedding model
const matchOptions = { order: 2, multiplier: 25, min: 0.2, max: 0.8 }; // for faceres model
const options = {
minConfidence: 0.6, // overal face confidence for box, face, gender, real, live
minSize: 224, // min input to face descriptor model before degradation
maxTime: 10000, // max time before giving up
blinkMin: 10, // minimum duration of a valid blink
blinkMax: 800, // maximum duration of a valid blink
threshold: 0.5, // minimum similarity
mask: humanConfig.face.detector.mask,
rotation: humanConfig.face.detector.rotation,
cropFactor: humanConfig.face.detector.cropFactor,
...matchOptions,
};
const ok = { // must meet all rules
faceCount: false,
faceConfidence: false,
facingCenter: false,
lookingCenter: false,
blinkDetected: false,
faceSize: false,
antispoofCheck: false,
livenessCheck: false,
elapsedMs: 0, // total time while waiting for valid face
};
const allOk = () => ok.faceCount && ok.faceSize && ok.blinkDetected && ok.facingCenter && ok.lookingCenter && ok.faceConfidence && ok.antispoofCheck && ok.livenessCheck;
const current: { face: FaceResult | null, record: indexDb.FaceRecord | null } = { face: null, record: null }; // current face record and matched database record
const blink = { // internal timers for blink start/end/duration
start: 0,
end: 0,
time: 0,
};
// let db: Array<{ name: string, source: string, embedding: number[] }> = []; // holds loaded face descriptor database
const human = new Human(humanConfig); // create instance of human with overrides from user configuration
human.env['perfadd'] = false; // is performance data showing instant or total values
human.draw.options.font = 'small-caps 18px "Lato"'; // set font used to draw labels when using draw methods
human.draw.options.lineHeight = 20;
const dom = { // grab instances of dom objects so we dont have to look them up later
video: document.getElementById('video') as HTMLVideoElement,
canvas: document.getElementById('canvas') as HTMLCanvasElement,
log: document.getElementById('log') as HTMLPreElement,
fps: document.getElementById('fps') as HTMLPreElement,
match: document.getElementById('match') as HTMLDivElement,
name: document.getElementById('name') as HTMLInputElement,
save: document.getElementById('save') as HTMLSpanElement,
delete: document.getElementById('delete') as HTMLSpanElement,
retry: document.getElementById('retry') as HTMLDivElement,
source: document.getElementById('source') as HTMLCanvasElement,
ok: document.getElementById('ok') as HTMLDivElement,
};
const timestamp = { detect: 0, draw: 0 }; // holds information used to calculate performance and possible memory leaks
const fps = { detect: 0, draw: 0 }; // holds calculated fps information for both detect and screen refresh
let startTime = 0;
const log = (...msg) => { // helper method to output messages
dom.log.innerText += msg.join(' ') + '\n';
// eslint-disable-next-line no-console
console.log(...msg);
};
const printFPS = (msg) => dom.fps.innerText = msg; // print status element
async function webCam() { // initialize webcam
printFPS('starting webcam...');
// @ts-ignore resizeMode is not yet defined in tslib
const cameraOptions: MediaStreamConstraints = { audio: false, video: { facingMode: 'user', resizeMode: 'none', width: { ideal: document.body.clientWidth } } };
const stream: MediaStream = await navigator.mediaDevices.getUserMedia(cameraOptions);
const ready = new Promise((resolve) => { dom.video.onloadeddata = () => resolve(true); });
dom.video.srcObject = stream;
dom.video.play();
await ready;
dom.canvas.width = dom.video.videoWidth;
dom.canvas.height = dom.video.videoHeight;
if (human.env.initial) log('video:', dom.video.videoWidth, dom.video.videoHeight, '|', stream.getVideoTracks()[0].label);
dom.canvas.onclick = () => { // pause when clicked on screen and resume on next click
if (dom.video.paused) dom.video.play();
else dom.video.pause();
};
}
async function detectionLoop() { // main detection loop
if (!dom.video.paused) {
if (current.face && current.face.tensor) human.tf.dispose(current.face.tensor); // dispose previous tensor
await human.detect(dom.video); // actual detection; were not capturing output in a local variable as it can also be reached via human.result
const now = human.now();
fps.detect = 1000 / (now - timestamp.detect);
timestamp.detect = now;
requestAnimationFrame(detectionLoop); // start new frame immediately
}
}
async function validationLoop(): Promise<FaceResult> { // main screen refresh loop
const interpolated = await human.next(human.result); // smoothen result using last-known results
await human.draw.canvas(dom.video, dom.canvas); // draw canvas to screen
await human.draw.all(dom.canvas, interpolated); // draw labels, boxes, lines, etc.
const now = human.now();
fps.draw = 1000 / (now - timestamp.draw);
timestamp.draw = now;
printFPS(`fps: ${fps.detect.toFixed(1).padStart(5, ' ')} detect | ${fps.draw.toFixed(1).padStart(5, ' ')} draw`); // write status
ok.faceCount = human.result.face.length === 1; // must be exactly detected face
if (ok.faceCount) { // skip the rest if no face
const gestures: string[] = Object.values(human.result.gesture).map((gesture) => gesture.gesture); // flatten all gestures
if (gestures.includes('blink left eye') || gestures.includes('blink right eye')) blink.start = human.now(); // blink starts when eyes get closed
if (blink.start > 0 && !gestures.includes('blink left eye') && !gestures.includes('blink right eye')) blink.end = human.now(); // if blink started how long until eyes are back open
ok.blinkDetected = ok.blinkDetected || (Math.abs(blink.end - blink.start) > options.blinkMin && Math.abs(blink.end - blink.start) < options.blinkMax);
if (ok.blinkDetected && blink.time === 0) blink.time = Math.trunc(blink.end - blink.start);
ok.facingCenter = gestures.includes('facing center');
ok.lookingCenter = gestures.includes('looking center'); // must face camera and look at camera
ok.faceConfidence = (human.result.face[0].boxScore || 0) > options.minConfidence && (human.result.face[0].faceScore || 0) > options.minConfidence && (human.result.face[0].genderScore || 0) > options.minConfidence;
ok.antispoofCheck = (human.result.face[0].real || 0) > options.minConfidence;
ok.livenessCheck = (human.result.face[0].live || 0) > options.minConfidence;
ok.faceSize = human.result.face[0].box[2] >= options.minSize && human.result.face[0].box[3] >= options.minSize;
}
let y = 32;
for (const [key, val] of Object.entries(ok)) {
let el = document.getElementById(`ok-${key}`);
if (!el) {
el = document.createElement('div');
el.innerText = key;
el.className = 'ok';
el.style.top = `${y}px`;
dom.ok.appendChild(el);
}
if (typeof val === 'boolean') el.style.backgroundColor = val ? 'lightgreen' : 'lightcoral';
else el.innerText = `${key}:${val}`;
y += 28;
}
if (allOk()) { // all criteria met
dom.video.pause();
return human.result.face[0];
}
if (ok.elapsedMs > options.maxTime) { // give up
dom.video.pause();
return human.result.face[0];
} else { // run again
ok.elapsedMs = Math.trunc(human.now() - startTime);
return new Promise((resolve) => {
setTimeout(async () => {
const res = await validationLoop(); // run validation loop until conditions are met
if (res) resolve(human.result.face[0]); // recursive promise resolve
}, 30); // use to slow down refresh from max refresh rate to target of 30 fps
});
}
}
async function saveRecords() {
if (dom.name.value.length > 0) {
const image = dom.canvas.getContext('2d')?.getImageData(0, 0, dom.canvas.width, dom.canvas.height) as ImageData;
const rec = { id: 0, name: dom.name.value, descriptor: current.face?.embedding as number[], image };
await indexDb.save(rec);
log('saved face record:', rec.name);
} else {
log('invalid name');
}
}
async function deleteRecord() {
if (current.record && current.record.id > 0) {
await indexDb.remove(current.record);
}
}
async function detectFace() {
dom.canvas.getContext('2d')?.clearRect(0, 0, options.minSize, options.minSize);
if (!current.face || !current.face.tensor || !current.face.embedding) return false;
// eslint-disable-next-line no-console
console.log('face record:', current.face);
human.tf.browser.toPixels(current.face.tensor as unknown as TensorLike, dom.canvas);
if (await indexDb.count() === 0) {
log('face database is empty');
document.body.style.background = 'black';
dom.delete.style.display = 'none';
return false;
}
const db = await indexDb.load();
const descriptors = db.map((rec) => rec.descriptor);
const res = await human.match(current.face.embedding, descriptors, matchOptions);
current.record = db[res.index] || null;
if (current.record) {
log(`best match: ${current.record.name} | id: ${current.record.id} | similarity: ${Math.round(1000 * res.similarity) / 10}%`);
dom.name.value = current.record.name;
dom.source.style.display = '';
dom.source.getContext('2d')?.putImageData(current.record.image, 0, 0);
}
document.body.style.background = res.similarity > options.threshold ? 'darkgreen' : 'maroon';
return res.similarity > options.threshold;
}
async function main() { // main entry point
ok.faceCount = false;
ok.faceConfidence = false;
ok.facingCenter = false;
ok.blinkDetected = false;
ok.faceSize = false;
ok.antispoofCheck = false;
ok.livenessCheck = false;
ok.elapsedMs = 0;
dom.match.style.display = 'none';
dom.retry.style.display = 'none';
dom.source.style.display = 'none';
document.body.style.background = 'black';
await webCam();
await detectionLoop(); // start detection loop
startTime = human.now();
current.face = await validationLoop(); // start validation loop
dom.canvas.width = current.face?.tensor?.shape[1] || options.minSize;
dom.canvas.height = current.face?.tensor?.shape[0] || options.minSize;
dom.source.width = dom.canvas.width;
dom.source.height = dom.canvas.height;
dom.canvas.style.width = '';
dom.match.style.display = 'flex';
dom.save.style.display = 'flex';
dom.delete.style.display = 'flex';
dom.retry.style.display = 'block';
if (!allOk()) { // is all criteria met?
log('did not find valid face');
return false;
} else {
return detectFace();
}
}
async function init() {
log('human version:', human.version, '| tfjs version:', human.tf.version['tfjs-core']);
log('options:', JSON.stringify(options).replace(/{|}|"|\[|\]/g, '').replace(/,/g, ' '));
printFPS('loading...');
log('known face records:', await indexDb.count());
await webCam(); // start webcam
await human.load(); // preload all models
printFPS('initializing...');
dom.retry.addEventListener('click', main);
dom.save.addEventListener('click', saveRecords);
dom.delete.addEventListener('click', deleteRecord);
await human.warmup(); // warmup function to initialize backend for future faster detection
await main();
}
window.onload = init;

66
demo/faceid/indexdb.ts Normal file
View File

@ -0,0 +1,66 @@
let db: IDBDatabase; // instance of indexdb
const database = 'human';
const table = 'person';
export type FaceRecord = { id: number, name: string, descriptor: number[], image: ImageData };
// eslint-disable-next-line no-console
const log = (...msg) => console.log('indexdb', ...msg);
export async function open() {
if (db) return true;
return new Promise((resolve) => {
const request: IDBOpenDBRequest = indexedDB.open(database, 1);
request.onerror = (evt) => log('error:', evt);
request.onupgradeneeded = (evt: IDBVersionChangeEvent) => { // create if doesnt exist
log('create:', evt.target);
db = (evt.target as IDBOpenDBRequest).result;
db.createObjectStore(table, { keyPath: 'id', autoIncrement: true });
};
request.onsuccess = (evt) => { // open
db = (evt.target as IDBOpenDBRequest).result as IDBDatabase;
log('open:', db);
resolve(true);
};
});
}
export async function load(): Promise<FaceRecord[]> {
const faceDB: Array<FaceRecord> = [];
if (!db) await open(); // open or create if not already done
return new Promise((resolve) => {
const cursor: IDBRequest = db.transaction([table], 'readwrite').objectStore(table).openCursor(null, 'next');
cursor.onerror = (evt) => log('load error:', evt);
cursor.onsuccess = (evt) => {
if ((evt.target as IDBRequest).result) {
faceDB.push((evt.target as IDBRequest).result.value);
(evt.target as IDBRequest).result.continue();
} else {
resolve(faceDB);
}
};
});
}
export async function count(): Promise<number> {
if (!db) await open(); // open or create if not already done
return new Promise((resolve) => {
const store: IDBRequest = db.transaction([table], 'readwrite').objectStore(table).count();
store.onerror = (evt) => log('count error:', evt);
store.onsuccess = () => resolve(store.result);
});
}
export async function save(faceRecord: FaceRecord) {
if (!db) await open(); // open or create if not already done
const newRecord = { name: faceRecord.name, descriptor: faceRecord.descriptor, image: faceRecord.image }; // omit id as its autoincrement
db.transaction([table], 'readwrite').objectStore(table).put(newRecord);
log('save:', newRecord);
}
export async function remove(faceRecord: FaceRecord) {
if (!db) await open(); // open or create if not already done
db.transaction([table], 'readwrite').objectStore(table).delete(faceRecord.id); // delete based on id
log('delete:', faceRecord);
}

83
demo/facematch/README.md Normal file
View File

@ -0,0 +1,83 @@
# Human Face Recognition & Matching
- **Browser** demo: `index.html` & `facematch.js`:
Loads sample images, extracts faces and runs match and similarity analysis
- **NodeJS** demo `node-match.js` and `node-match-worker.js`
Advanced multithreading demo that runs number of worker threads to process high number of matches
- Sample face database: `faces.json`
<br>
## Browser Face Recognition Demo
- `demo/facematch`: Demo for Browsers that uses all face description and embedding features to
detect, extract and identify all faces plus calculate simmilarity between them
It highlights functionality such as:
- Loading images
- Extracting faces from images
- Calculating face embedding descriptors
- Finding face similarity and sorting them by similarity
- Finding best face match based on a known list of faces and printing matches
<br>
## NodeJS Multi-Threading Match Solution
### Methods and Properties in `node-match`
- `createBuffer`: create shared buffer array
single copy of data regardless of number of workers
fixed size based on `options.dbMax`
- `appendRecord`: add additional batch of descriptors to buffer
can append batch of records to buffer at anytime
workers are informed of the new content after append has been completed
- `workersStart`: start or expand pool of `threadPoolSize` workers
each worker runs `node-match-worker` and listens for messages from main thread
can shutdown workers or create additional worker threads on-the-fly
safe against workers that exit
- `workersClose`: close workers in a pool
first request workers to exit then terminate after timeout
- `match`: dispach a match job to a worker
returns first match that satisfies `minThreshold`
assigment to workers using round-robin
since timing for each job is near-fixed and predictable
- `getDescriptor`: get descriptor array for a given id from a buffer
- `fuzDescriptor`: small randomize descriptor content for harder match
- `getLabel`: fetch label for resolved descriptor index
- `loadDB`: load face database from a JSON file `dbFile`
extracts descriptors and adds them to buffer
extracts labels and maintains them in main thread
for test purposes loads same database `dbFact` times to create a very large database
`node-match` runs in a listens for messages from workers until `maxJobs` have been reached
### Performance
Linear performance decrease that depends on number of records in database
Non-linear performance that increases with number of worker threads due to communication overhead
- Face dataase with 10k records:
> threadPoolSize: 1 => ~60 ms / match job
> threadPoolSize: 6 => ~25 ms / match job
- Face database with 50k records:
> threadPoolSize: 1 => ~300 ms / match job
> threadPoolSize: 6 => ~100 ms / match job
- Face database with 100k records:
> threadPoolSize: 1 => ~600 ms / match job
> threadPoolSize: 6 => ~200 ms / match job
### Example
> node node-match
```js
2021-10-13 07:53:36 INFO: options: { dbFile: './faces.json', dbMax: 10000, threadPoolSize: 6, workerSrc: './node-match-worker.js', debug: false, minThreshold: 0.9, descLength: 1024 }
2021-10-13 07:53:36 DATA: created shared buffer: { maxDescriptors: 10000, totalBytes: 40960000, totalElements: 10240000 }
2021-10-13 07:53:36 DATA: db loaded: { existingRecords: 0, newRecords: 5700 }
2021-10-13 07:53:36 INFO: starting worker thread pool: { totalWorkers: 6, alreadyActive: 0 }
2021-10-13 07:53:36 STATE: submitted: { matchJobs: 100, poolSize: 6, activeWorkers: 6 }
2021-10-13 07:53:38 STATE: { matchJobsFinished: 100, totalTimeMs: 1769, averageTimeMs: 17.69 }
2021-10-13 07:53:38 INFO: closing workers: { poolSize: 6, activeWorkers: 6 }
```

262
demo/facematch/facematch.js Normal file
View File

@ -0,0 +1,262 @@
// @ts-nocheck
/**
* Human demo for browsers
*
* Demo for face descriptor analysis and face simmilarity analysis
*/
/** @type {Human} */
import Human from '../../dist/human.esm.js';
const userConfig = {
backend: 'humangl',
async: true,
warmup: 'none',
cacheSensitivity: 0,
debug: true,
modelBasePath: '../../models/',
deallocate: true,
filter: {
enabled: true,
equalization: true,
width: 0,
},
face: {
enabled: true,
// detector: { rotation: false, return: true, maxDetected: 50, iouThreshold: 0.206, minConfidence: 0.122 },
detector: { return: true, rotation: true, maxDetected: 50, iouThreshold: 0.01, minConfidence: 0.2 },
mesh: { enabled: true },
iris: { enabled: false },
emotion: { enabled: true },
description: { enabled: true },
},
hand: { enabled: false },
gesture: { enabled: false },
body: { enabled: false },
segmentation: { enabled: false },
};
const human = new Human(userConfig); // new instance of human
const all = []; // array that will hold all detected faces
let db = []; // array that holds all known faces
const minScore = 0.4;
function log(...msg) {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
// eslint-disable-next-line no-console
console.log(ts, ...msg);
}
function title(msg) {
document.getElementById('title').innerHTML = msg;
}
async function loadFaceMatchDB() {
// download db with known faces
try {
let res = await fetch('/demo/facematch/faces.json');
if (!res || !res.ok) res = await fetch('/human/demo/facematch/faces.json');
db = (res && res.ok) ? await res.json() : [];
log('Loaded Faces DB:', db);
} catch (err) {
log('Could not load faces database', err);
}
}
async function SelectFaceCanvas(face) {
// if we have face image tensor, enhance it and display it
let embedding;
document.getElementById('orig').style.filter = 'blur(16px)';
if (face.tensor) {
title('Sorting Faces by Similarity');
const enhanced = human.enhance(face);
if (enhanced) {
const c = document.getElementById('orig');
const squeeze = human.tf.squeeze(enhanced);
const normalize = human.tf.div(squeeze, 255);
await human.tf.browser.toPixels(normalize, c);
human.tf.dispose([enhanced, squeeze, normalize]);
const ctx = c.getContext('2d');
ctx.font = 'small-caps 0.4rem "Lato"';
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
}
const arr = db.map((rec) => rec.embedding);
const res = await human.match(face.embedding, arr);
log('Match:', db[res.index].name);
const emotion = face.emotion[0] ? `${Math.round(100 * face.emotion[0].score)}% ${face.emotion[0].emotion}` : 'N/A';
document.getElementById('desc').innerHTML = `
source: ${face.fileName}<br>
match: ${Math.round(1000 * res.similarity) / 10}% ${db[res.index].name}<br>
score: ${Math.round(100 * face.boxScore)}% detection ${Math.round(100 * face.faceScore)}% analysis<br>
age: ${face.age} years<br>
gender: ${Math.round(100 * face.genderScore)}% ${face.gender}<br>
emotion: ${emotion}<br>
`;
embedding = face.embedding.map((a) => parseFloat(a.toFixed(4)));
navigator.clipboard.writeText(`{"name":"unknown", "source":"${face.fileName}", "embedding":[${embedding}]},`);
}
// loop through all canvases that contain faces
const canvases = document.getElementsByClassName('face');
let time = 0;
for (const canvas of canvases) {
// calculate similarity from selected face to current one in the loop
const current = all[canvas.tag.sample][canvas.tag.face];
const similarity = human.similarity(face.embedding, current.embedding);
canvas.tag.similarity = similarity;
// get best match
// draw the canvas
await human.tf.browser.toPixels(current.tensor, canvas);
const ctx = canvas.getContext('2d');
ctx.font = 'small-caps 1rem "Lato"';
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 3, 23);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 4, 24);
ctx.font = 'small-caps 0.8rem "Lato"';
ctx.fillText(`${current.age}y ${(100 * (current.genderScore || 0)).toFixed(1)}% ${current.gender}`, 4, canvas.height - 6);
// identify person
ctx.font = 'small-caps 1rem "Lato"';
const start = human.now();
const arr = db.map((rec) => rec.embedding);
const res = await human.match(current.embedding, arr);
time += (human.now() - start);
if (res.similarity > minScore) ctx.fillText(`DB: ${(100 * res.similarity).toFixed(1)}% ${db[res.index].name}`, 4, canvas.height - 30);
}
log('Analyzed:', 'Face:', canvases.length, 'DB:', db.length, 'Time:', time);
// sort all faces by similarity
const sorted = document.getElementById('faces');
[...sorted.children]
.sort((a, b) => parseFloat(b.tag.similarity) - parseFloat(a.tag.similarity))
.forEach((canvas) => sorted.appendChild(canvas));
document.getElementById('orig').style.filter = 'blur(0)';
title('Selected Face');
}
async function AddFaceCanvas(index, res, fileName) {
all[index] = res.face;
for (const i in res.face) {
if (!res.face[i].tensor) continue; // did not get valid results
if ((res.face[i].faceScore || 0) < human.config.face.detector.minConfidence) continue; // face analysis score too low
all[index][i].fileName = fileName;
const canvas = document.createElement('canvas');
canvas.tag = { sample: index, face: i, source: fileName };
canvas.width = 200;
canvas.height = 200;
canvas.className = 'face';
const emotion = res.face[i].emotion[0] ? `${Math.round(100 * res.face[i].emotion[0].score)}% ${res.face[i].emotion[0].emotion}` : 'N/A';
canvas.title = `
source: ${res.face[i].fileName}
score: ${Math.round(100 * res.face[i].boxScore)}% detection ${Math.round(100 * res.face[i].faceScore)}% analysis
age: ${res.face[i].age} years
gender: ${Math.round(100 * res.face[i].genderScore)}% ${res.face[i].gender}
emotion: ${emotion}
`.replace(/ /g, ' ');
await human.tf.browser.toPixels(res.face[i].tensor, canvas);
const ctx = canvas.getContext('2d');
if (!ctx) return false;
ctx.font = 'small-caps 0.8rem "Lato"';
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillText(`${res.face[i].age}y ${(100 * (res.face[i].genderScore || 0)).toFixed(1)}% ${res.face[i].gender}`, 4, canvas.height - 6);
const arr = db.map((rec) => rec.embedding);
const result = human.match(res.face[i].embedding, arr);
ctx.font = 'small-caps 1rem "Lato"';
if (result.similarity && res.similarity > minScore) ctx.fillText(`${(100 * result.similarity).toFixed(1)}% ${db[result.index].name}`, 4, canvas.height - 30);
document.getElementById('faces').appendChild(canvas);
canvas.addEventListener('click', (evt) => {
log('Select:', 'Image:', evt.target.tag.sample, 'Face:', evt.target.tag.face, 'Source:', evt.target.tag.source, all[evt.target.tag.sample][evt.target.tag.face]);
SelectFaceCanvas(all[evt.target.tag.sample][evt.target.tag.face]);
});
}
}
async function AddImageElement(index, image, length) {
const faces = all.reduce((prev, curr) => prev += curr.length, 0);
title(`Analyzing Input Images<br> ${Math.round(100 * index / length)}% [${index} / ${length}]<br>Found ${faces} Faces`);
return new Promise((resolve) => {
const img = new Image(128, 128);
img.onload = () => { // must wait until image is loaded
document.getElementById('images').appendChild(img); // and finally we can add it
human.detect(img, userConfig).then((res) => {
AddFaceCanvas(index, res, image); // then wait until image is analyzed
resolve(true);
});
};
img.onerror = () => {
log('Add image error:', index + 1, image);
resolve(false);
};
img.title = image;
img.src = encodeURI(image);
});
}
function createFaceMatchDB() {
log('Creating Faces DB...');
for (const image of all) {
for (const face of image) db.push({ name: 'unknown', source: face.fileName, embedding: face.embedding });
}
log(db);
}
async function main() {
// pre-load human models
await human.load();
title('Loading Face Match Database');
let images = [];
let dir = [];
// load face descriptor database
await loadFaceMatchDB();
// enumerate all sample images in /assets
title('Enumerating Input Images');
const res = await fetch('/samples/in');
dir = (res && res.ok) ? await res.json() : [];
images = images.concat(dir.filter((img) => (img.endsWith('.jpg') && img.includes('sample'))));
// could not dynamically enumerate images so using static list
if (images.length === 0) {
images = [
'ai-body.jpg', 'solvay1927.jpg', 'ai-upper.jpg',
'person-carolina.jpg', 'person-celeste.jpg', 'person-leila1.jpg', 'person-leila2.jpg', 'person-lexi.jpg', 'person-linda.jpg', 'person-nicole.jpg', 'person-tasia.jpg',
'person-tetiana.jpg', 'person-vlado1.jpg', 'person-vlado5.jpg', 'person-vlado.jpg', 'person-christina.jpg', 'person-lauren.jpg',
'group-1.jpg', 'group-2.jpg', 'group-3.jpg', 'group-4.jpg', 'group-5.jpg', 'group-6.jpg', 'group-7.jpg',
'daz3d-brianna.jpg', 'daz3d-chiyo.jpg', 'daz3d-cody.jpg', 'daz3d-drew-01.jpg', 'daz3d-drew-02.jpg', 'daz3d-ella-01.jpg', 'daz3d-ella-02.jpg', 'daz3d-gillian.jpg',
'daz3d-hye-01.jpg', 'daz3d-hye-02.jpg', 'daz3d-kaia.jpg', 'daz3d-karen.jpg', 'daz3d-kiaria-01.jpg', 'daz3d-kiaria-02.jpg', 'daz3d-lilah-01.jpg', 'daz3d-lilah-02.jpg',
'daz3d-lilah-03.jpg', 'daz3d-lila.jpg', 'daz3d-lindsey.jpg', 'daz3d-megah.jpg', 'daz3d-selina-01.jpg', 'daz3d-selina-02.jpg', 'daz3d-snow.jpg',
'daz3d-sunshine.jpg', 'daz3d-taia.jpg', 'daz3d-tuesday-01.jpg', 'daz3d-tuesday-02.jpg', 'daz3d-tuesday-03.jpg', 'daz3d-zoe.jpg', 'daz3d-ginnifer.jpg',
'daz3d-_emotions01.jpg', 'daz3d-_emotions02.jpg', 'daz3d-_emotions03.jpg', 'daz3d-_emotions04.jpg', 'daz3d-_emotions05.jpg',
];
// add prefix for gitpages
images = images.map((a) => `/human/samples/in/${a}`);
log('Adding static image list:', images);
} else {
log('Discovered images:', images);
}
// images = ['/samples/in/person-lexi.jpg', '/samples/in/person-carolina.jpg', '/samples/in/solvay1927.jpg'];
const t0 = human.now();
for (let i = 0; i < images.length; i++) await AddImageElement(i, images[i], images.length);
const t1 = human.now();
// print stats
const num = all.reduce((prev, cur) => prev += cur.length, 0);
log('Extracted faces:', num, 'from images:', all.length, 'time:', Math.round(t1 - t0));
log(human.tf.engine().memory());
// if we didn't download db, generate it from current faces
if (!db || db.length === 0) createFaceMatchDB();
title('');
log('Ready');
human.validate(userConfig);
human.similarity([], []);
}
window.onload = main;

81
demo/facematch/faces.json Normal file

File diff suppressed because one or more lines are too long

50
demo/facematch/index.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Human</title>
<!-- <meta http-equiv="content-type" content="text/html; charset=utf-8"> -->
<meta name="viewport" content="width=device-width, shrink-to-fit=yes">
<meta name="keywords" content="Human">
<meta name="application-name" content="Human">
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="../manifest.webmanifest">
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../../assets/icon.png">
<script src="./facematch.js" type="module"></script>
<style>
img { object-fit: contain; }
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 24px; font-variant: small-caps; }
body { margin: 24px; background: black; color: white; overflow: hidden; text-align: -webkit-center; min-height: 100%; max-height: 100%; }
::-webkit-scrollbar { height: 8px; border: 0; border-radius: 0; }
::-webkit-scrollbar-thumb { background: grey }
::-webkit-scrollbar-track { margin: 3px; }
.orig { width: 200px; height: 200px; padding-bottom: 20px; filter: blur(16px); transition : all 0.5s ease; }
.text { margin: 24px; }
.face { width: 128px; height: 128px; margin: 2px; padding: 2px; cursor: grab; transform: scale(1.00); transition : all 0.3s ease; }
.face:hover { filter: grayscale(1); transform: scale(1.08); transition : all 0.3s ease; }
</style>
</head>
<body>
<div style="display: block">
<div style="display: flex">
<div style="min-width: 400px">
<div class="text" id="title"></div>
<canvas id="orig" class="orig"></canvas>
<div id="desc" style="font-size: 0.8rem; text-align: left;"></div>
</div>
<div style="width: 20px"></div>
<div>
<div class="text">Input Images</div>
<div id="images" style="display: flex; width: 60vw; overflow-x: auto; overflow-y: hidden; scroll-behavior: smooth"></div>
</div>
</div>
<div id="list" style="height: 10px"></div>
<div class="text">Select person to sort by similarity and get a known face match</div>
<div id="faces" style="height: 50vh; overflow-y: auto"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,70 @@
const threads = require('worker_threads');
let debug = false;
/** @type SharedArrayBuffer */
let buffer;
/** @type Float32Array */
let view;
let threshold = 0;
let records = 0;
const descLength = 1024; // descriptor length in bytes
function distance(descBuffer, index, options = { order: 2, multiplier: 20 }) {
const descriptor = new Float32Array(descBuffer);
let sum = 0;
for (let i = 0; i < descriptor.length; i++) {
const diff = (options.order === 2) ? (descriptor[i] - view[index * descLength + i]) : (Math.abs(descriptor[i] - view[index * descLength + i]));
sum += (options.order === 2) ? (diff * diff) : (diff ** options.order);
}
return (options.multiplier || 20) * sum;
}
function match(descBuffer, options = { order: 2, multiplier: 20 }) {
let best = Number.MAX_SAFE_INTEGER;
let index = -1;
for (let i = 0; i < records; i++) {
const res = distance(descBuffer, i, { order: options.order, multiplier: options.multiplier });
if (res < best) {
best = res;
index = i;
}
if (best < threshold || best === 0) break; // short circuit
}
best = (options.order === 2) ? Math.sqrt(best) : best ** (1 / options.order);
return { index, distance: best, similarity: Math.max(0, 100 - best) / 100.0 };
}
threads.parentPort?.on('message', (msg) => {
if (typeof msg.descriptor !== 'undefined') { // actual work order to find a match
const t0 = performance.now();
const result = match(msg.descriptor);
const t1 = performance.now();
threads.parentPort?.postMessage({ request: msg.request, time: Math.trunc(t1 - t0), ...result });
return; // short circuit
}
if (msg instanceof SharedArrayBuffer) { // called only once to receive reference to shared array buffer
buffer = msg;
view = new Float32Array(buffer); // initialize f64 view into buffer
if (debug) threads.parentPort?.postMessage(`buffer: ${buffer?.byteLength}`);
}
if (typeof msg.records !== 'undefined') { // recived every time when number of records changes
records = msg.records;
if (debug) threads.parentPort?.postMessage(`records: ${records}`);
}
if (typeof msg.debug !== 'undefined') { // set verbose logging
debug = msg.debug;
if (debug) threads.parentPort?.postMessage(`debug: ${debug}`);
}
if (typeof msg.threshold !== 'undefined') { // set minimum similarity threshold
threshold = msg.threshold;
if (debug) threads.parentPort?.postMessage(`threshold: ${threshold}`);
}
if (typeof msg.shutdown !== 'undefined') { // got message to close worker
if (debug) threads.parentPort?.postMessage('shutting down');
process.exit(0);
}
});
if (debug) threads.parentPort?.postMessage('started');

View File

@ -0,0 +1,178 @@
const fs = require('fs');
const path = require('path');
const log = require('@vladmandic/pilogger');
const threads = require('worker_threads');
// global optinos
const options = {
dbFile: 'demo/facematch/faces.json', // sample face db
dbMax: 10000, // maximum number of records to hold in memory
threadPoolSize: 12, // number of worker threads to create in thread pool
workerSrc: './node-match-worker.js', // code that executes in the worker thread
debug: false, // verbose messages
minThreshold: 0.5, // match returns first record that meets the similarity threshold, set to 0 to always scan all records
descLength: 1024, // descriptor length
};
// test options
const testOptions = {
dbFact: 175, // load db n times to fake huge size
maxJobs: 200, // exit after processing this many jobs
fuzDescriptors: true, // randomize descriptor content before match for harder jobs
};
// global data structures
const data = {
/** @type string[] */
labels: [], // array of strings, length of array serves as overal number of records so has to be maintained carefully
/** @type SharedArrayBuffer | null */
buffer: null,
/** @type Float32Array | null */
view: null,
/** @type threads.Worker[] */
workers: [], // holds instance of workers. worker can be null if exited
requestID: 0, // each request should increment this counter as its used for round robin assignment
};
let t0 = process.hrtime.bigint(); // used for perf counters
const appendRecords = (labels, descriptors) => {
if (!data.view) return 0;
if (descriptors.length !== labels.length) {
log.error('append error:', { descriptors: descriptors.length, labels: labels.length });
}
// if (options.debug) log.state('appending:', { descriptors: descriptors.length, labels: labels.length });
for (let i = 0; i < descriptors.length; i++) {
for (let j = 0; j < descriptors[i].length; j++) {
data.view[data.labels.length * descriptors[i].length + j] = descriptors[i][j]; // add each descriptors element to buffer
}
data.labels.push(labels[i]); // finally add to labels
}
for (const worker of data.workers) { // inform all workers how many records we have
if (worker) worker.postMessage({ records: data.labels.length });
}
return data.labels.length;
};
const getLabel = (index) => data.labels[index];
const getDescriptor = (index) => {
if (!data.view) return [];
const descriptor = [];
for (let i = 0; i < 1024; i++) descriptor.push(data.view[index * options.descLength + i]);
return descriptor;
};
const fuzDescriptor = (descriptor) => {
for (let i = 0; i < descriptor.length; i++) descriptor[i] += Math.random() - 0.5;
return descriptor;
};
const delay = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); });
async function workersClose() {
const current = data.workers.filter((worker) => !!worker).length;
log.info('closing workers:', { poolSize: data.workers.length, activeWorkers: current });
for (const worker of data.workers) {
if (worker) worker.postMessage({ shutdown: true }); // tell worker to exit
}
await delay(250); // wait a little for threads to exit on their own
const remaining = data.workers.filter((worker) => !!worker).length;
if (remaining > 0) {
log.info('terminating remaining workers:', { remaining: current, pool: data.workers.length });
for (const worker of data.workers) {
if (worker) worker.terminate(); // if worker did not exit cleany terminate it
}
}
}
const workerMessage = (index, msg) => {
if (msg.request) {
if (options.debug) log.data('message:', { worker: index, request: msg.request, time: msg.time, label: getLabel(msg.index), similarity: msg.similarity });
if (msg.request >= testOptions.maxJobs) {
const t1 = process.hrtime.bigint();
const elapsed = Math.round(Number(t1 - t0) / 1000 / 1000);
log.state({ matchJobsFinished: testOptions.maxJobs, totalTimeMs: elapsed, averageTimeMs: Math.round(100 * elapsed / testOptions.maxJobs) / 100 });
workersClose();
}
} else {
log.data('message:', { worker: index, msg });
}
};
async function workerClose(id, code) {
const previous = data.workers.filter((worker) => !!worker).length;
delete data.workers[id];
const current = data.workers.filter((worker) => !!worker).length;
if (options.debug) log.state('worker exit:', { id, code, previous, current });
}
async function workersStart(numWorkers) {
const previous = data.workers.filter((worker) => !!worker).length;
log.info('starting worker thread pool:', { totalWorkers: numWorkers, alreadyActive: previous });
for (let i = 0; i < numWorkers; i++) {
if (!data.workers[i]) { // worker does not exist, so create it
const worker = new threads.Worker(path.join(__dirname, options.workerSrc));
worker.on('message', (msg) => workerMessage(i, msg));
worker.on('error', (err) => log.error('worker error:', { err }));
worker.on('exit', (code) => workerClose(i, code));
worker.postMessage(data.buffer); // send buffer to worker
data.workers[i] = worker;
}
data.workers[i]?.postMessage({ records: data.labels.length, threshold: options.minThreshold, debug: options.debug }); // inform worker how many records there are
}
await delay(100); // just wait a bit for everything to settle down
}
const match = (descriptor) => {
// const arr = Float32Array.from(descriptor);
const buffer = new ArrayBuffer(options.descLength * 4);
const view = new Float32Array(buffer);
view.set(descriptor);
const available = data.workers.filter((worker) => !!worker).length; // find number of available workers
if (available > 0) data.workers[data.requestID % available].postMessage({ descriptor: buffer, request: data.requestID }, [buffer]); // round robin to first available worker
else log.error('no available workers');
};
async function loadDB(count) {
const previous = data.labels.length;
if (!fs.existsSync(options.dbFile)) {
log.error('db file does not exist:', options.dbFile);
return;
}
t0 = process.hrtime.bigint();
for (let i = 0; i < count; i++) { // test loop: load entire face db from array of objects n times into buffer
const db = JSON.parse(fs.readFileSync(options.dbFile).toString());
const names = db.map((record) => record.name);
const descriptors = db.map((record) => record.embedding);
appendRecords(names, descriptors);
}
log.data('db loaded:', { existingRecords: previous, newRecords: data.labels.length });
}
async function createBuffer() {
data.buffer = new SharedArrayBuffer(4 * options.dbMax * options.descLength); // preallocate max number of records as sharedarraybuffers cannot grow
data.view = new Float32Array(data.buffer); // create view into buffer
data.labels.length = 0;
log.data('created shared buffer:', { maxDescriptors: (data.view?.length || 0) / options.descLength, totalBytes: data.buffer.byteLength, totalElements: data.view?.length });
}
async function main() {
log.header();
log.info('options:', options);
await createBuffer(); // create shared buffer array
await loadDB(testOptions.dbFact); // loadDB is a test method that calls actual addRecords
await workersStart(options.threadPoolSize); // can be called at anytime to modify worker pool size
for (let i = 0; i < testOptions.maxJobs; i++) {
const idx = Math.trunc(data.labels.length * Math.random()); // grab a random descriptor index that we'll search for
const descriptor = getDescriptor(idx); // grab a descriptor at index
data.requestID++; // increase request id
if (testOptions.fuzDescriptors) match(fuzDescriptor(descriptor)); // fuz descriptor for harder match
else match(descriptor);
if (options.debug) log.info('submited job', data.requestID); // we already know what we're searching for so we can compare results
}
log.state('submitted:', { matchJobs: testOptions.maxJobs, poolSize: data.workers.length, activeWorkers: data.workers.filter((worker) => !!worker).length });
}
main();

BIN
demo/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

3
demo/helpers/README.md Normal file
View File

@ -0,0 +1,3 @@
# Helper libraries
Used by main `Human` demo app

277
demo/helpers/gl-bench.js Normal file
View File

@ -0,0 +1,277 @@
// @ts-nocheck
// based on: https://github.com/munrocket/gl-bench
const UICSS = `
#gl-bench { position: absolute; right: 1rem; bottom: 1rem; z-index:1000; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
#gl-bench div { position: relative; display: block; margin: 4px; padding: 0 2px 0 2px; background: #303030; border-radius: 0.1rem; cursor: pointer; opacity: 0.9; }
#gl-bench svg { height: 60px; margin: 0 0px 0px 4px; }
#gl-bench text { font-size: 16px; font-family: 'Lato', 'Segoe UI'; dominant-baseline: middle; text-anchor: middle; }
#gl-bench .gl-mem { font-size: 12px; fill: white; }
#gl-bench .gl-fps { font-size: 13px; fill: white; }
#gl-bench line { stroke-width: 5; stroke: white; stroke-linecap: round; }
#gl-bench polyline { fill: none; stroke: white; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3.5; }
#gl-bench rect { fill: black; }
#gl-bench .opacity { stroke: black; }
`;
const UISVG = `
<div class="gl-box">
<svg viewBox="0 0 60 60">
<text x="27" y="56" class="gl-fps">00 FPS</text>
<text x="30" y="8" class="gl-mem"></text>
<rect x="0" y="14" rx="4" ry="4" width="60" height="32"></rect>
<polyline class="gl-chart"></polyline>
</svg>
<svg viewBox="0 0 14 60" class="gl-cpu-svg">
<line x1="7" y1="38" x2="7" y2="11" class="opacity"/>
<line x1="7" y1="38" x2="7" y2="11" class="gl-cpu" stroke-dasharray="0 27"/>
<path d="M5.35 43c-.464 0-.812.377-.812.812v1.16c-.783.1972-1.421.812-1.595 1.624h-1.16c-.435 0-.812.348-.812.812s.348.812.812.812h1.102v1.653H1.812c-.464 0-.812.377-.812.812 0 .464.377.812.812.812h1.131c.1943.783.812 1.392 1.595 1.595v1.131c0 .464.377.812.812.812.464 0 .812-.377.812-.812V53.15h1.653v1.073c0 .464.377.812.812.812.464 0 .812-.377.812-.812v-1.131c.783-.1943 1.392-.812 1.595-1.595h1.131c.464 0 .812-.377.812-.812 0-.464-.377-.812-.812-.812h-1.073V48.22h1.102c.435 0 .812-.348.812-.812s-.348-.812-.812-.812h-1.16c-.1885-.783-.812-1.421-1.595-1.624v-1.131c0-.464-.377-.812-.812-.812-.464 0-.812.377-.812.812v1.073H6.162v-1.073c0-.464-.377-.812-.812-.812zm.58 3.48h2.088c.754 0 1.363.609 1.363 1.363v2.088c0 .754-.609 1.363-1.363 1.363H5.93c-.754 0-1.363-.609-1.363-1.363v-2.088c0-.754.609-1.363 1.363-1.363z" style="fill: grey"></path>
</svg>
<svg viewBox="0 0 14 60" class="gl-gpu-svg">
<line x1="7" y1="38" x2="7" y2="11" class="opacity"/>
<line x1="7" y1="38" x2="7" y2="11" class="gl-gpu" stroke-dasharray="0 27"/>
<path d="M1.94775 43.3772a.736.736 0 10-.00416 1.472c.58535.00231.56465.1288.6348.3197.07015.18975.04933.43585.04933.43585l-.00653.05405v8.671a.736.736 0 101.472 0v-1.4145c.253.09522.52785.1495.81765.1495h5.267c1.2535 0 2.254-.9752 2.254-2.185v-3.105c0-1.2075-1.00625-2.185-2.254-2.185h-5.267c-.28865 0-.5635.05405-.8165.1495.01806-.16445.04209-.598-.1357-1.0787-.22425-.6072-.9499-1.2765-2.0125-1.2765zm2.9095 3.6455c.42435 0 .7659.36225.7659.8119v2.9785c0 .44965-.34155.8119-.7659.8119s-.7659-.36225-.7659-.8119v-2.9785c0-.44965.34155-.8119.7659-.8119zm4.117 0a2.3 2.3 0 012.3 2.3 2.3 2.3 0 01-2.3 2.3 2.3 2.3 0 01-2.3-2.3 2.3 2.3 0 012.3-2.3z" style="fill: grey"></path>
</svg>
</div>
`;
class GLBench {
/** GLBench constructor
* @param { WebGLRenderingContext | WebGL2RenderingContext | null } gl context
* @param { Object | undefined } settings additional settings
*/
constructor(gl, settings = {}) {
this.css = UICSS;
this.svg = UISVG;
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.paramLogger = () => {};
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.chartLogger = () => {};
this.chartLen = 20;
this.chartHz = 20;
this.names = [];
this.cpuAccums = [];
this.gpuAccums = [];
this.activeAccums = [];
this.chart = new Array(this.chartLen);
this.now = () => ((performance && performance.now) ? performance.now() : Date.now());
this.updateUI = () => {
[].forEach.call(this.nodes['gl-gpu-svg'], (node) => node.style.display = this.trackGPU ? 'inline' : 'none');
};
Object.assign(this, settings);
this.detected = 0;
this.finished = [];
this.isFramebuffer = 0;
this.frameId = 0;
// 120hz device detection
let rafId; let n = 0; let
t0;
const loop = (t) => {
if (++n < 20) {
rafId = requestAnimationFrame(loop);
} else {
this.detected = Math.ceil(1e3 * n / (t - t0) / 70);
cancelAnimationFrame(rafId);
}
if (!t0) t0 = t;
};
requestAnimationFrame(loop);
// attach gpu profilers
if (gl) {
const glFinish = async (t, activeAccums) => Promise.resolve(setTimeout(() => {
gl.getError();
const dt = this.now() - t;
activeAccums.forEach((active, i) => {
if (active) this.gpuAccums[i] += dt;
});
}, 0));
const addProfiler = (fn, self, target) => {
const t = self.now();
// eslint-disable-next-line prefer-rest-params
fn.apply(target, arguments);
if (self.trackGPU) self.finished.push(glFinish(t, self.activeAccums.slice(0)));
};
/* ['drawArrays', 'drawElements', 'drawArraysInstanced', 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'].forEach((fn) => {
if (gl[fn]) {
gl[fn] = addProfiler(gl[fn], this, gl);
}
});
*/
const fn = 'drawElements';
if (gl[fn]) {
gl[fn] = addProfiler(gl[fn], this, gl);
} else {
// eslint-disable-next-line no-console
console.log('bench: cannot attach to webgl function');
}
/*
gl.getExtension = ((fn, self) => {
// eslint-disable-next-line prefer-rest-params
const ext = fn.apply(gl, arguments);
if (ext) {
['drawElementsInstancedANGLE', 'drawBuffersWEBGL'].forEach((fn2) => {
if (ext[fn2]) {
ext[fn2] = addProfiler(ext[fn2], self, ext);
}
});
}
return ext;
})(gl.getExtension, this);
*/
}
// init ui and ui loggers
if (!this.withoutUI) {
if (!this.dom) this.dom = document.body;
const elm = document.createElement('div');
elm.id = 'gl-bench';
this.dom.appendChild(elm);
this.dom.insertAdjacentHTML('afterbegin', '<style id="gl-bench-style">' + this.css + '</style>');
this.dom = elm;
this.dom.addEventListener('click', () => {
this.trackGPU = !this.trackGPU;
this.updateUI();
});
this.paramLogger = ((logger, dom, names) => {
const classes = ['gl-cpu', 'gl-gpu', 'gl-mem', 'gl-fps', 'gl-gpu-svg', 'gl-chart'];
const nodes = { ...classes };
classes.forEach((c) => nodes[c] = dom.getElementsByClassName(c));
this.nodes = nodes;
return (i, cpu, gpu, mem, fps, totalTime, frameId) => {
nodes['gl-cpu'][i].style.strokeDasharray = (cpu * 0.27).toFixed(0) + ' 100';
nodes['gl-gpu'][i].style.strokeDasharray = (gpu * 0.27).toFixed(0) + ' 100';
// eslint-disable-next-line no-nested-ternary
nodes['gl-mem'][i].innerHTML = names[i] ? names[i] : (mem ? 'mem: ' + mem.toFixed(0) + 'mb' : '');
nodes['gl-fps'][i].innerHTML = 'FPS: ' + fps.toFixed(1);
logger(names[i], cpu, gpu, mem, fps, totalTime, frameId);
};
})(this.paramLogger, this.dom, this.names);
this.chartLogger = ((logger, dom) => {
const nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') };
return (i, chart, circularId) => {
let points = '';
const len = chart.length;
for (let j = 0; j < len; j++) {
const id = (circularId + j + 1) % len;
if (chart[id] !== undefined) points = points + ' ' + (60 * j / (len - 1)).toFixed(1) + ',' + (45 - chart[id] * 0.5 / this.detected).toFixed(1);
}
nodes['gl-chart'][i].setAttribute('points', points);
logger(this.names[i], chart, circularId);
};
})(this.chartLogger, this.dom);
}
}
/**
* Explicit UI add
* @param { string | undefined } name
*/
addUI(name) {
if (this.names.indexOf(name) === -1) {
this.names.push(name);
if (this.dom) {
this.dom.insertAdjacentHTML('beforeend', this.svg);
this.updateUI();
}
this.cpuAccums.push(0);
this.gpuAccums.push(0);
this.activeAccums.push(false);
}
}
/**
* Increase frameID
* @param { number | undefined } now
*/
nextFrame(now) {
this.frameId++;
const t = now || this.now();
// params
if (this.frameId <= 1) {
this.paramFrame = this.frameId;
this.paramTime = t;
} else {
const duration = t - this.paramTime;
if (duration >= 1e3) {
const frameCount = this.frameId - this.paramFrame;
const fps = frameCount / duration * 1e3;
for (let i = 0; i < this.names.length; i++) {
const cpu = this.cpuAccums[i] / duration * 100;
const gpu = this.gpuAccums[i] / duration * 100;
const mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0;
this.paramLogger(i, cpu, gpu, mem, fps, duration, frameCount);
this.cpuAccums[i] = 0;
Promise.all(this.finished).then(() => {
this.gpuAccums[i] = 0;
this.finished = [];
});
}
this.paramFrame = this.frameId;
this.paramTime = t;
}
}
// chart
if (!this.detected || !this.chartFrame) {
this.chartFrame = this.frameId;
this.chartTime = t;
this.circularId = 0;
} else {
const timespan = t - this.chartTime;
let hz = this.chartHz * timespan / 1e3;
while (--hz > 0 && this.detected) {
const frameCount = this.frameId - this.chartFrame;
const fps = frameCount / timespan * 1e3;
this.chart[this.circularId % this.chartLen] = fps;
for (let i = 0; i < this.names.length; i++) this.chartLogger(i, this.chart, this.circularId);
this.circularId++;
this.chartFrame = this.frameId;
this.chartTime = t;
}
}
}
/**
* Begin named measurement
* @param { string | undefined } name
*/
begin(name) {
this.updateAccums(name);
}
/**
* End named measure
* @param { string | undefined } name
*/
end(name) {
this.updateAccums(name);
}
updateAccums(name) {
let nameId = this.names.indexOf(name);
if (nameId === -1) {
nameId = this.names.length;
this.addUI(name);
}
const t = this.now();
const dt = t - this.t0;
for (let i = 0; i < nameId + 1; i++) {
if (this.activeAccums[i]) this.cpuAccums[i] += dt;
}
this.activeAccums[nameId] = !this.activeAccums[nameId];
this.t0 = t;
}
}
export default GLBench;

161
demo/helpers/jsonview.js Normal file
View File

@ -0,0 +1,161 @@
let callbackFunction = null;
function createElement(type, config) {
const htmlElement = document.createElement(type);
if (config === undefined) return htmlElement;
if (config.className) htmlElement.className = config.className;
if (config.content) htmlElement.textContent = config.content;
if (config.style) htmlElement.style = config.style;
if (config.children) config.children.forEach((el) => !el || htmlElement.appendChild(el));
return htmlElement;
}
function createExpandedElement(node) {
const iElem = createElement('i');
if (node.expanded) { iElem.className = 'fas fa-caret-down'; } else { iElem.className = 'fas fa-caret-right'; }
const caretElem = createElement('div', { style: 'width: 18px; text-align: center; cursor: pointer', children: [iElem] });
const handleClick = node.toggle.bind(node);
caretElem.addEventListener('click', handleClick);
const indexElem = createElement('div', { className: 'json json-index', content: node.key });
indexElem.addEventListener('click', handleClick);
const typeElem = createElement('div', { className: 'json json-type', content: node.type });
const keyElem = createElement('div', { className: 'json json-key', content: node.key });
keyElem.addEventListener('click', handleClick);
const sizeElem = createElement('div', { className: 'json json-size' });
sizeElem.addEventListener('click', handleClick);
if (node.type === 'array') {
sizeElem.innerText = `[${node.children.length} items]`;
} else if (node.type === 'object') {
const size = node.children.find((item) => item.key === 'size');
sizeElem.innerText = size ? `{${size.value.toLocaleString()} bytes}` : `{${node.children.length} properties}`;
}
let lineChildren;
if (node.key === null) lineChildren = [caretElem, typeElem, sizeElem];
else if (node.parent.type === 'array') lineChildren = [caretElem, indexElem, sizeElem];
else lineChildren = [caretElem, keyElem, sizeElem];
const lineElem = createElement('div', { className: 'json-line', children: lineChildren });
if (node.depth > 0) lineElem.style = `margin-left: ${node.depth * 24}px;`;
return lineElem;
}
function createNotExpandedElement(node) {
const caretElem = createElement('div', { style: 'width: 18px' });
const keyElem = createElement('div', { className: 'json json-key', content: node.key });
const separatorElement = createElement('div', { className: 'json-separator', content: ':' });
const valueType = ` json-${typeof node.value}`;
const valueContent = node.value.toLocaleString();
const valueElement = createElement('div', { className: `json json-value${valueType}`, content: valueContent });
const lineElem = createElement('div', { className: 'json-line', children: [caretElem, keyElem, separatorElement, valueElement] });
if (node.depth > 0) lineElem.style = `margin-left: ${node.depth * 24}px;`;
return lineElem;
}
function createNode() {
return {
key: '',
parent: {},
value: null,
expanded: false,
type: '',
children: [],
elem: {},
depth: 0,
hideChildren() {
if (Array.isArray(this.children)) {
this.children.forEach((item) => {
// @ts-ignore
item['elem']['classList'].add('hide');
// @ts-ignore
if (item['expanded']) item.hideChildren();
});
}
},
showChildren() {
if (Array.isArray(this.children)) {
this.children.forEach((item) => {
// @ts-ignore
item['elem']['classList'].remove('hide');
// @ts-ignore
if (item['expanded']) item.showChildren();
});
}
},
toggle() {
if (this.expanded) {
this.hideChildren();
const icon = this.elem?.querySelector('.fas');
icon.classList.replace('fa-caret-down', 'fa-caret-right');
if (callbackFunction !== null) callbackFunction(null);
} else {
this.showChildren();
const icon = this.elem?.querySelector('.fas');
icon.classList.replace('fa-caret-right', 'fa-caret-down');
if (this.type === 'object') {
if (callbackFunction !== null) callbackFunction(`${this.parent?.key}/${this.key}`);
}
}
this.expanded = !this.expanded;
},
};
}
function getType(val) {
let type
if (Array.isArray(val)) type = 'array';
else if (val === null) type = 'null';
else type = typeof val;
return type;
}
function traverseObject(obj, parent, filter) {
for (const key in obj) {
const child = createNode();
child.parent = parent;
child.key = key;
child.type = getType(obj[key]);
child.depth = parent.depth + 1;
child.expanded = false;
if (Array.isArray(filter)) {
for (const filtered of filter) {
if (key === filtered) return;
}
}
if (typeof obj[key] === 'object') {
child.children = [];
parent.children.push(child);
traverseObject(obj[key], child, filter);
child.elem = createExpandedElement(child);
} else {
child.value = obj[key];
child.elem = createNotExpandedElement(child);
parent.children.push(child);
}
}
}
function createTree(obj, title, filter) {
const tree = createNode();
tree.type = title;
tree.key = title;
tree.children = [];
tree.expanded = true;
traverseObject(obj, tree, filter);
tree.elem = createExpandedElement(tree);
return tree;
}
function traverseTree(node, callback) {
callback(node);
if (node.children !== null) node.children.forEach((item) => traverseTree(item, callback));
}
async function jsonView(json, element, title = '', filter = []) {
const tree = createTree(json, title, filter);
traverseTree(tree, (node) => {
if (!node.expanded) node.hideChildren();
element.appendChild(node.elem);
});
}
export default jsonView;

333
demo/helpers/menu.js Normal file
View File

@ -0,0 +1,333 @@
let instance = 0;
let CSScreated = false;
let theme = {
background: '#303030',
hover: '#505050',
itemBackground: 'black',
itemColor: 'white',
buttonBackground: 'lightblue',
buttonHover: 'lightgreen',
checkboxOn: 'lightgreen',
checkboxOff: 'lightcoral',
rangeBackground: 'lightblue',
rangeLabel: 'white',
chartColor: 'lightblue',
};
function createCSS() {
if (CSScreated) return;
const css = `
:root { --rounded: 0.1rem; }
.menu { position: absolute; top: 0rem; right: 0; min-width: 180px; width: max-content; padding: 0.2rem 0.8rem 0 0.8rem; line-height: 1.8rem; z-index: 10; background: ${theme.background}; border: none }
.button { text-shadow: none; }
.menu-container { display: block; max-height: 100vh; }
.menu-container-fadeout { max-height: 0; overflow: hidden; transition: max-height, 0.5s ease; }
.menu-container-fadein { max-height: 100vh; overflow: hidden; transition: max-height, 0.5s ease; }
.menu-item { display: flex; white-space: nowrap; padding: 0.2rem; cursor: default; width: 100%; }
.menu-item:hover { background: ${theme.hover} }
.menu-title { cursor: pointer; }
.menu-hr { margin: 0.2rem; border: 1px solid rgba(0, 0, 0, 0.5) }
.menu-label { padding: 0; font-weight: 800; }
.menu-list { margin-right: 0.8rem; }
select:focus { outline: none; }
.menu-list-item { background: ${theme.itemBackground}; color: ${theme.itemColor}; border: none; padding: 0.2rem; font-family: inherit;
font-variant: inherit; border-radius: var(--rounded); font-weight: 800; }
.menu-chart-title { padding: 0; font-size: 0.8rem; font-weight: 800; align-items: center}
.menu-chart-canvas { background: transparent; margin: 0.2rem 0 0.2rem 0.6rem; }
.menu-button { border: 0; background: ${theme.buttonBackground}; width: -webkit-fill-available; padding: 8px; margin: 8px; cursor: pointer;
border-radius: var(--rounded); justify-content: center; font-family: inherit; font-variant: inherit; font-size: 1rem; font-weight: 800; }
.menu-button:hover { background: ${theme.buttonHover}; box-shadow: 4px 4px 4px 0 black; }
.menu-button:focus { outline: none; }
.menu-checkbox { width: 2.6rem; height: 1rem; background: ${theme.itemBackground}; margin: 0.5rem 1.0rem 0 0; position: relative; border-radius: var(--rounded); }
.menu-checkbox:after { content: 'OFF'; color: ${theme.checkboxOff}; position: absolute; right: 0.2rem; top: -0.4rem; font-weight: 800; font-size: 0.5rem; }
.menu-checkbox:before { content: 'ON'; color: ${theme.checkboxOn}; position: absolute; left: 0.3rem; top: -0.4rem; font-weight: 800; font-size: 0.5rem; }
.menu-checkbox-label { width: 1.3rem; height: 1rem; cursor: pointer; position: absolute; top: 0; left: 0rem; z-index: 1; background: ${theme.checkboxOff};
border-radius: var(--rounded); transition: left 0.6s ease; }
input[type=checkbox] { visibility: hidden; }
input[type=checkbox]:checked + label { left: 1.4rem; background: ${theme.checkboxOn}; }
.menu-range { margin: 0.2rem 1.0rem 0 0; width: 5rem; background: transparent; color: ${theme.rangeBackground}; }
.menu-range:before { color: ${theme.rangeLabel}; margin: 0 0.4rem 0 0; font-weight: 800; font-size: 0.6rem; position: relative; top: 0.3rem; content: attr(value); }
input[type=range] { -webkit-appearance: none; }
input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 1rem; cursor: pointer; background: ${theme.itemBackground}; border-radius: var(--rounded); border: 1px; }
input[type=range]::-moz-range-track { width: 100%; height: 1rem; cursor: pointer; background: ${theme.itemBackground}; border-radius: var(--rounded); border: 1px; }
input[type=range]::-webkit-slider-thumb { border: 1px solid #000000; margin-top: 0; height: 1rem; width: 0.6rem; border-radius: var(--rounded); background: ${theme.rangeBackground}; cursor: pointer; -webkit-appearance: none; }
input[type=range]::-moz-range-thumb { border: 1px solid #000000; margin-top: 0rem; height: 1rem; width: 0.6rem; border-radius: var(--rounded); background: ${theme.rangeBackground}; cursor: pointer; -webkit-appearance: none; }
.svg-background { fill:#303030; cursor:pointer; opacity: 0.6; }
.svg-foreground { fill:white; cursor:pointer; opacity: 0.8; }
`;
const el = document.createElement('style');
el.innerHTML = css;
document.getElementsByTagName('head')[0].appendChild(el);
CSScreated = true;
}
class Menu {
constructor(parent, title, position, userTheme) {
if (userTheme) theme = { ...theme, ...userTheme };
createCSS();
this.createMenu(parent, title, position);
this.id = 0;
this.instance = instance;
instance++;
this._maxFPS = 0;
this.hidden = false;
}
createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) {
/** @type {HTMLDivElement} */
this.menu = document.createElement('div');
this.menu.id = `menu-${instance}`;
this.menu.className = 'menu';
if (position) {
if (position.top) this.menu.style.top = `${position.top}`;
if (position.bottom) this.menu.style.bottom = `${position.bottom}`;
if (position.left) this.menu.style.left = `${position.left}`;
if (position.right) this.menu.style.right = `${position.right}`;
}
this.container = document.createElement('div');
this.container.id = `menu-container-${instance}`;
this.container.className = 'menu-container menu-container-fadein';
// set menu title with pulldown arrow
const elTitle = document.createElement('div');
elTitle.className = 'menu-title';
elTitle.id = `menu-title-${instance}`;
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="width: 2rem; height: 2rem; vertical-align: top;">
<path d="M400 32H48A48 48 0 0 0 0 80v352a48 48 0 0 0 48 48h352a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48zm-51.37 182.31L232.06 348.16a10.38 10.38 0 0 1-16.12 0L99.37 214.31C92.17 206 97.28 192 107.43 192h233.14c10.15 0 15.26 14 8.06 22.31z" class="svg-background"/>
<path d="M348.63 214.31L232.06 348.16a10.38 10.38 0 0 1-16.12 0L99.37 214.31C92.17 206 97.28 192 107.43 192h233.14c10.15 0 15.26 14 8.06 22.31z" class="svg-foreground"/>
</svg>`;
if (title) elTitle.innerHTML = `${title}${svg}`;
this.menu.appendChild(elTitle);
elTitle.addEventListener('click', () => {
if (this.container && this.menu) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
// this.menu.style.borderStyle = this.container.classList.contains('menu-container-fadeout') ? 'none' : 'solid';
}
});
this.menu.appendChild(this.container);
if (typeof parent === 'object') parent.appendChild(this.menu);
// @ts-ignore undefined
else document.getElementById(parent).appendChild(this.menu);
}
get newID() {
this.id++;
return `menu-${this.instance}-${this.id}`;
}
get ID() {
return `menu-${this.instance}-${this.id}`;
}
get width() {
return this.menu ? this.menu.offsetWidth : 0;
}
get height() {
return this.menu ? this.menu.offsetHeight : 0;
}
hide() {
if (this.container && this.container.classList.contains('menu-container-fadein')) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
}
}
visible() {
return (this.container ? this.container.classList.contains('menu-container-fadein') : false);
}
toggle(evt) {
if (this.container && this.menu) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
/*
if (this.container.classList.contains('menu-container-fadein') && evt) {
const x = evt.x || (evt.touches && evt.touches[0] ? evt.touches[0].pageX : null);
// const y = evt.y || (evt.touches && evt.touches[0] ? evt.touches[0].pageY : null);
if (x) this.menu.style.left = `${x - (this.menu.offsetWidth / 2)}px`;
// if (y) this.menu.style.top = '5.5rem'; // `${evt.y + 55}px`;
if (this.menu.offsetLeft < 0) this.menu.style.left = '0';
if ((this.menu.offsetLeft + this.menu.offsetWidth) > window.innerWidth) {
this.menu.style.left = '';
this.menu.style.right = '0';
}
// this.menu.style.borderStyle = 'solid';
} else {
// this.menu.style.borderStyle = 'none';
}
*/
}
}
addTitle(title) {
const el = document.createElement('div');
el.className = 'menu-title';
el.id = this.newID;
el.innerHTML = title;
if (this.menu) this.menu.appendChild(el);
el.addEventListener('click', () => {
this.hidden = !this.hidden;
const all = document.getElementsByClassName('menu');
for (const item of all) {
// @ts-ignore
item.style.display = this.hidden ? 'none' : 'block';
}
});
return el;
}
addLabel(title) {
const el = document.createElement('div');
el.className = 'menu-item menu-label';
el.id = this.newID;
el.innerHTML = title;
if (this.container) this.container.appendChild(el);
return el;
}
addBool(title, object, variable, callback) {
const el = document.createElement('div');
el.className = 'menu-item';
el.innerHTML = `<div class="menu-checkbox"><input class="menu-checkbox" type="checkbox" id="${this.newID}" ${object[variable] ? 'checked' : ''}/><label class="menu-checkbox-label" for="${this.ID}"></label></div>${title}`;
if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => {
if (evt.target) {
object[variable] = evt.target['checked'];
if (callback) callback(evt.target['checked']);
}
});
return el;
}
async addList(title, items, selected, callback) {
const el = document.createElement('div');
el.className = 'menu-item';
let options = '';
for (const item of items) {
const def = item === selected ? 'selected' : '';
options += `<option value="${item}" ${def}>${item}</option>`;
}
el.innerHTML = `<div class="menu-list"><select name="${this.ID}" title="${title}" class="menu-list-item">${options}</select><label for="${this.ID}"></label></div>${title}`;
el.style.fontFamily = document.body.style.fontFamily;
el.style.fontSize = document.body.style.fontSize;
el.style.fontVariant = document.body.style.fontVariant;
if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => {
if (callback && evt.target) callback(items[evt.target['selectedIndex']]);
});
return el;
}
addRange(title, object, variable, min, max, step, callback) {
const el = document.createElement('div');
el.className = 'menu-item';
el.innerHTML = `<input class="menu-range" type="range" title="${title}" id="${this.newID}" min="${min}" max="${max}" step="${step}" value="${object[variable]}">${title}`;
if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => {
if (evt.target) {
object[variable] = parseInt(evt.target['value']) === parseFloat(evt.target['value']) ? parseInt(evt.target['value']) : parseFloat(evt.target['value']);
// @ts-ignore
evt.target.setAttribute('value', evt.target['value']);
if (callback) callback(evt.target['value']);
}
});
el['input'] = el.children[0];
return el;
}
addHTML(html) {
const el = document.createElement('div');
el.className = 'menu-item';
el.id = this.newID;
if (html) el.innerHTML = html;
if (this.container) this.container.appendChild(el);
return el;
}
addButton(titleOn, titleOff, callback) {
const el = document.createElement('button');
el.className = 'menu-item menu-button';
el.style.fontFamily = document.body.style.fontFamily;
el.style.fontSize = document.body.style.fontSize;
el.style.fontVariant = document.body.style.fontVariant;
el.type = 'button';
el.id = this.newID;
el.innerText = titleOn;
if (this.container) this.container.appendChild(el);
el.addEventListener('click', () => {
if (el.innerText === titleOn) el.innerText = titleOff;
else el.innerText = titleOn;
if (callback) callback(el.innerText !== titleOn);
});
return el;
}
addValue(title, val, suffix = '') {
const el = document.createElement('div');
el.className = 'menu-item';
el.id = `menu-val-${title}`;
el.innerText = `${title}: ${val}${suffix}`;
if (this.container) this.container.appendChild(el);
return el;
}
// eslint-disable-next-line class-methods-use-this
updateValue(title, val, suffix = '') {
const el = document.getElementById(`menu-val-${title}`);
if (el) el.innerText = `${title}: ${val}${suffix}`;
else this.addValue(title, val);
}
addChart(title, id, width = 150, height = 40, color) {
if (color) theme.chartColor = color;
const el = document.createElement('div');
el.className = 'menu-item menu-chart-title';
el.id = this.newID;
el.innerHTML = `<font color=${theme.chartColor}>${title}</font><canvas id="menu-canvas-${id}" class="menu-chart-canvas" width="${width}px" height="${height}px"></canvas>`;
if (this.container) this.container.appendChild(el);
return el;
}
// eslint-disable-next-line class-methods-use-this
async updateChart(id, values) {
if (!values || (values.length === 0)) return;
/** @type {HTMLCanvasElement} */
// @ts-ignore undefined
const canvas = document.getElementById(`menu-canvas-${id}`);
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.fillStyle = theme.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
const width = canvas.width / values.length;
const max = 1 + Math.max(...values);
const height = canvas.height / max;
for (let i = 0; i < values.length; i++) {
const gradient = ctx.createLinearGradient(0, (max - values[i]) * height, 0, 0);
gradient.addColorStop(0.1, theme.chartColor);
gradient.addColorStop(0.4, theme.background);
ctx.fillStyle = gradient;
ctx.fillRect(i * width, 0, width - 4, canvas.height);
ctx.fillStyle = theme.background;
ctx.font = `${width / 1.5}px "Segoe UI"`;
ctx.fillText(Math.round(values[i]).toString(), i * width + 1, canvas.height - 1, width - 1);
}
}
}
export default Menu;

86
demo/helpers/webrtc.js Normal file
View File

@ -0,0 +1,86 @@
const debug = true;
async function log(...msg) {
if (debug) {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
// eslint-disable-next-line no-console
console.log(ts, 'webrtc', ...msg);
}
}
/**
* helper implementation of webrtc
* performs:
* - discovery
* - handshake
* - connct to webrtc stream
* - assign webrtc stream to video element
*
* for development purposes i'm using test webrtc server that reads rtsp stream from a security camera:
* <https://github.com/vladmandic/stream-rtsp>
*
* @param {string} server
* @param {string} streamName
* @param {HTMLVideoElement} elementName
* @return {promise}
*/
async function webRTC(server, streamName, elementName) {
const suuid = streamName;
log('client starting');
log(`server: ${server} stream: ${suuid}`);
const stream = new MediaStream();
const connection = new RTCPeerConnection();
connection.oniceconnectionstatechange = () => log('connection', connection.iceConnectionState);
connection.onnegotiationneeded = async () => {
let offer;
if (connection.localDescription) {
offer = await connection.createOffer();
await connection.setLocalDescription(offer);
const res = await fetch(`${server}/stream/receiver/${suuid}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: new URLSearchParams({
suuid: `${suuid}`,
data: `${btoa(connection.localDescription.sdp || '')}`,
}),
});
}
const data = res && res.ok ? await res.text() : '';
if (data.length === 0 || !offer) {
log('cannot connect:', server);
} else {
connection.setRemoteDescription(new RTCSessionDescription({
type: 'answer',
sdp: atob(data),
}));
log('negotiation start:', offer);
}
};
connection.ontrack = (event) => {
stream.addTrack(event.track);
const video = (typeof elementName === 'string') ? document.getElementById(elementName) : elementName;
if (video instanceof HTMLVideoElement) video.srcObject = stream;
else log('element is not a video element:', elementName);
// video.onloadeddata = async () => log('resolution:', video.videoWidth, video.videoHeight);
log('received track:', event.track);
};
const res = await fetch(`${server}/stream/codec/${suuid}`);
const streams = res && res.ok ? await res.json() : [];
if (streams.length === 0) log('received no streams');
else log('received streams:', streams);
for (const s of streams) connection.addTransceiver(s.Type, { direction: 'sendrecv' });
const channel = connection.createDataChannel(suuid, { maxRetransmits: 10 });
channel.onmessage = (e) => log('channel message:', channel.label, 'payload', e.data);
channel.onerror = (e) => log('channel error:', channel.label, 'payload', e);
// channel.onbufferedamountlow = (e) => log('channel buffering:', channel.label, 'payload', e);
channel.onclose = () => log('channel close', channel.label);
channel.onopen = () => {
log('channel open', channel.label);
setInterval(() => channel.send('ping'), 1000); // send ping becouse PION doesn't handle RTCSessionDescription.close()
};
}
export default webRTC;

17
demo/icons.css Normal file

File diff suppressed because one or more lines are too long

136
demo/index-pwa.js Normal file
View File

@ -0,0 +1,136 @@
/**
* PWA Service Worker for Human main demo
*/
/// <reference lib="webworker" />
const skipCaching = false;
const cacheName = 'Human';
const cacheFiles = ['/favicon.ico', 'manifest.webmanifest']; // assets and models are cached on first access
let cacheModels = true; // *.bin; *.json
let cacheWASM = true; // *.wasm
let cacheOther = false; // *
let listening = false;
const stats = { hit: 0, miss: 0 };
const log = (...msg) => {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
// eslint-disable-next-line no-console
console.log(ts, 'pwa', ...msg);
};
async function updateCached(req) {
fetch(req)
.then((update) => {
// update cache if request is ok
if (update.ok) {
caches
.open(cacheName)
.then((cache) => cache.put(req, update))
.catch((err) => log('cache update error', err));
}
return true;
})
.catch((err) => {
log('fetch error', err);
return false;
});
}
async function getCached(evt) {
// just fetch
if (skipCaching) return fetch(evt.request);
// get from cache or fetch if not in cache
let found = await caches.match(evt.request);
if (found && found.ok) {
stats.hit += 1;
} else {
stats.miss += 1;
found = await fetch(evt.request);
}
// if still don't have it, return offline page
if (!found || !found.ok) {
found = await caches.match('offline.html');
}
// update cache in the background
if (found && found.type === 'basic' && found.ok) {
const uri = new URL(evt.request.url);
if (uri.pathname.endsWith('.bin') || uri.pathname.endsWith('.json')) {
if (cacheModels) updateCached(evt.request);
} else if (uri.pathname.endsWith('.wasm')) {
if (cacheWASM) updateCached(evt.request);
} else if (cacheOther) {
updateCached(evt.request);
}
}
return found;
}
function cacheInit() {
// eslint-disable-next-line promise/catch-or-return
caches.open(cacheName)
// eslint-disable-next-line promise/no-nesting
.then((cache) => cache.addAll(cacheFiles)
.then(
() => log('cache refresh:', cacheFiles.length, 'files'),
(err) => log('cache error', err),
));
}
if (!listening) {
// get messages from main app to update configuration
self.addEventListener('message', (evt) => {
log('event message:', evt.data);
switch (evt.data.key) {
case 'cacheModels': cacheModels = evt.data.val; break;
case 'cacheWASM': cacheWASM = evt.data.val; break;
case 'cacheOther': cacheOther = evt.data.val; break;
default:
}
});
self.addEventListener('install', (evt) => {
log('install');
// @ts-ignore scope for self is ServiceWorkerGlobalScope not Window
self.skipWaiting();
evt.waitUntil(cacheInit);
});
self.addEventListener('activate', (evt) => {
log('activate');
// @ts-ignore scope for self is ServiceWorkerGlobalScope not Window
evt.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', (evt) => {
const uri = new URL(evt.request.url);
// if (uri.pathname === '/') { log('cache skip /', evt.request); return; } // skip root access requests
if (evt.request.cache === 'only-if-cached' && evt.request.mode !== 'same-origin') return; // required due to chrome bug
if (uri.origin !== location.origin) return; // skip non-local requests
if (evt.request.method !== 'GET') return; // only cache get requests
if (evt.request.url.includes('/api/')) return; // don't cache api requests, failures are handled at the time of call
const response = getCached(evt);
if (response) evt.respondWith(response);
else log('fetch response missing');
});
// only trigger controllerchange once
let refreshed = false;
self.addEventListener('controllerchange', (evt) => {
log(`PWA: ${evt.type}`);
if (refreshed) return;
refreshed = true;
location.reload();
});
listening = true;
}

38
demo/index-worker.js Normal file
View File

@ -0,0 +1,38 @@
/**
* Web worker used by main demo app
* Loaded from index.js
*/
/// <reference lib="webworker"/>
// load Human using IIFE script as Chome Mobile does not support Modules as Workers
self.importScripts('../dist/human.js');
let busy = false;
// @ts-ignore
// eslint-disable-next-line new-cap, no-undef
const human = new Human.default();
onmessage = async (msg) => { // receive message from main thread
if (busy) return;
busy = true;
// received from index.js using:
// worker.postMessage({ image: image.data.buffer, width: canvas.width, height: canvas.height, config }, [image.data.buffer]);
const image = new ImageData(new Uint8ClampedArray(msg.data.image), msg.data.width, msg.data.height);
let result = {};
result = await human.detect(image, msg.data.userConfig);
result.tensors = human.tf.engine().state.numTensors; // append to result object so main thread get info
result.backend = human.tf.getBackend(); // append to result object so main thread get info
if (result.canvas) { // convert canvas to imageData and send it by reference
const canvas = new OffscreenCanvas(result.canvas.width, result.canvas.height);
const ctx = canvas.getContext('2d');
if (ctx) ctx.drawImage(result.canvas, 0, 0);
const img = ctx ? ctx.getImageData(0, 0, result.canvas.width, result.canvas.height) : null;
result.canvas = null; // must strip original canvas from return value as it cannot be transfered from worker thread
if (img) postMessage({ result, image: img.data.buffer, width: msg.data.width, height: msg.data.height }, [img.data.buffer]);
else postMessage({ result }); // send message back to main thread with canvas
} else {
postMessage({ result }); // send message back to main thread without canvas
}
busy = false;
};

122
demo/index.html Normal file
View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Human</title>
<meta name="viewport" content="width=device-width" id="viewport">
<meta name="keywords" content="Human">
<meta name="application-name" content="Human">
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="./manifest.webmanifest">
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../assets/icon.png">
<link rel="stylesheet" type="text/css" href="./icons.css">
<script src="./index.js" type="module"></script>
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 16px; font-variant: small-caps; }
body { margin: 0; background: black; color: white; overflow-x: hidden; width: 100vw; height: 100vh; }
body::-webkit-scrollbar { display: none; }
hr { width: 100%; }
.play { position: absolute; width: 256px; height: 256px; z-index: 9; bottom: 15%; left: 50%; margin-left: -125px; display: none; filter: grayscale(1); }
.play:hover { filter: grayscale(0); }
.btn-background { fill:grey; cursor: pointer; opacity: 0.6; }
.btn-background:hover { opacity: 1; }
.btn-foreground { fill:white; cursor: pointer; opacity: 0.8; }
.btn-foreground:hover { opacity: 1; }
.status { position: absolute; width: 100vw; bottom: 10%; text-align: center; font-size: 3rem; font-weight: 100; text-shadow: 2px 2px #303030; }
.thumbnail { margin: 8px; box-shadow: 0 0 4px 4px dimgrey; }
.thumbnail:hover { box-shadow: 0 0 8px 8px dimgrey; filter: grayscale(1); }
.log { position: absolute; bottom: 0; margin: 0.4rem 0.4rem 0 0.4rem; font-size: 0.9rem; }
.menubar { width: 100vw; background: #303030; display: flex; justify-content: space-evenly; text-align: center; padding: 8px; cursor: pointer; }
.samples-container { display: flex; flex-wrap: wrap; }
.video { display: none; }
.canvas { margin: 0 auto; }
.bench { position: absolute; right: 0; bottom: 0; }
.compare-image { width: 256px; position: absolute; top: 150px; left: 30px; box-shadow: 0 0 2px 2px black; background: black; display: none; }
.loader { width: 300px; height: 300px; border: 3px solid transparent; border-radius: 50%; border-top: 4px solid #f15e41; animation: spin 4s linear infinite; position: absolute; bottom: 15%; left: 50%; margin-left: -150px; z-index: 15; }
.loader::before, .loader::after { content: ""; position: absolute; top: 6px; bottom: 6px; left: 6px; right: 6px; border-radius: 50%; border: 4px solid transparent; }
.loader::before { border-top-color: #bad375; animation: 3s spin linear infinite; }
.loader::after { border-top-color: #26a9e0; animation: spin 1.5s linear infinite; }
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.wave { position: fixed; top: 0; left: -90%; width: 100vw; height: 100vh; border-radius: 10%; opacity: .3; z-index: -1; }
.wave.one { animation: rotate 10000ms infinite linear; background: #2F4F4F; }
.wave.two { animation: rotate 15000ms infinite linear; background: #1F3F3F; }
.wave.three { animation: rotate 20000ms infinite linear; background: #0F1F1F; }
@keyframes rotate {
from { transform: rotate(0deg); }
from { transform: rotate(360deg); }
}
.button { text-shadow: 2px 2px black; display: flex; }
.button:hover { text-shadow: -2px -2px black; color: lightblue; }
.button::before { display: inline-block; font-style: normal; font-variant: normal; text-rendering: auto; -webkit-font-smoothing: antialiased; font-family: "FA"; font-weight: 900; font-size: 2.4rem; margin-right: 0.4rem; }
.button-display::before { content: "\f8c4"; }
.button-image::before { content: "\f3f2"; }
.button-process::before { content: "\f3f0"; }
.button-model::before { content: "\f2c2"; }
.button-start::before { content: "\f144"; }
.button-stop::before { content: "\f28b"; }
.icon { width: 180px; text-align: -webkit-center; text-align: -moz-center; filter: grayscale(1); }
.icon:hover { background: #505050; filter: grayscale(0); }
.hint { opacity: 0; transition-duration: 0.5s; transition-property: opacity; font-style: italic; position: fixed; top: 5rem; padding: 8px; margin: 8px; box-shadow: 0 0 2px 2px #303030; }
.input-file { align-self: center; width: 5rem; }
.results { position: absolute; left: 0; top: 5rem; background: #303030; width: 20rem; height: 90%; font-size: 0.8rem; overflow-y: auto; display: none }
.results::-webkit-scrollbar { background-color: #303030; }
.results::-webkit-scrollbar-thumb { background: black; border-radius: 10px; }
.json-line { margin: 4px 0; display: flex; justify-content: flex-start; }
.json { margin-right: 8px; margin-left: 8px; }
.json-type { color: lightyellow; }
.json-key { color: white; }
.json-index { color: lightcoral; }
.json-value { margin-left: 20px; }
.json-number { color: lightgreen; }
.json-boolean { color: lightyellow; }
.json-string { color: lightblue; }
.json-size { color: gray; }
.hide { display: none; }
.fas { display: inline-block; width: 0; height: 0; border-style: solid; }
.fa-caret-down { border-width: 10px 8px 0 8px; border-color: white transparent }
.fa-caret-right { border-width: 10px 0 8px 10px; border-color: transparent transparent transparent white; }
</style>
</head>
<body>
<div id="play" class="play icon-play"></div>
<div id="background">
<div class='wave one'></div>
<div class='wave two'></div>
<div class='wave three'></div>
</div>
<div id="loader" class="loader"></div>
<div id="status" class="status"></div>
<div id="menubar" class="menubar">
<div id="btnDisplay" class="icon"><div class="icon-binoculars"> </div>display</div>
<div id="btnImage" class="icon"><div class="icon-brush"></div>input</div>
<div id="btnProcess" class="icon"><div class="icon-stats"></div>options</div>
<div id="btnModel" class="icon"><div class="icon-games"></div>models</div>
<div id="btnStart" class="icon"><div class="icon-webcam"></div><span id="btnStartText">start video</span></div>
</div>
<div id="media">
<canvas id="canvas" class="canvas"></canvas>
<video id="video" playsinline class="video"></video>
</div>
<div id="compare-container" class="compare-image">
<canvas id="compare-canvas" width="256" height="256"></canvas>
<div id="similarity"></div>
</div>
<div id="segmentation-container" class="compare-image">
<canvas id="segmentation-mask" width="256" height="256" style="width: 256px; height: 256px;"></canvas>
<canvas id="segmentation-canvas" width="256" height="256" style="width: 256px; height: 256px;"></canvas>
</div>
<div id="samples-container" class="samples-container"></div>
<div id="hint" class="hint"></div>
<div id="log" class="log"></div>
<div id="results" class="results"></div>
</body>
</html>

1092
demo/index.js Normal file

File diff suppressed because it is too large Load Diff

10
demo/manifest.webmanifest Normal file
View File

@ -0,0 +1,10 @@
{
"name": "Human Library",
"short_name": "Human",
"icons": [{ "src": "../assets/icon.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }],
"start_url": "./index.html",
"scope": "/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#000000"
}

View File

@ -0,0 +1,70 @@
# Human Multithreading Demos
- **Browser** demo `multithread` & `worker`
Runs each `human` module in a separate web worker for highest possible performance
- **NodeJS** demo `node-multiprocess` & `node-multiprocess-worker`
Runs multiple parallel `human` by dispaching them to pool of pre-created worker processes
<br><hr><br>
## NodeJS Multi-process Demo
`nodejs/node-multiprocess.js` and `nodejs/node-multiprocess-worker.js`: Demo using NodeJS with CommonJS module
Demo that starts n child worker processes for parallel execution
```shell
node demo/nodejs/node-multiprocess.js
```
```json
2021-06-01 08:54:19 INFO: @vladmandic/human version 2.0.0
2021-06-01 08:54:19 INFO: User: vlado Platform: linux Arch: x64 Node: v16.0.0
2021-06-01 08:54:19 INFO: FaceAPI multi-process test
2021-06-01 08:54:19 STATE: Enumerated images: ./assets 15
2021-06-01 08:54:19 STATE: Main: started worker: 130362
2021-06-01 08:54:19 STATE: Main: started worker: 130363
2021-06-01 08:54:19 STATE: Main: started worker: 130369
2021-06-01 08:54:19 STATE: Main: started worker: 130370
2021-06-01 08:54:20 STATE: Worker: PID: 130370 TensorFlow/JS 3.6.0 Human 2.0.0 Backend: tensorflow
2021-06-01 08:54:20 STATE: Worker: PID: 130362 TensorFlow/JS 3.6.0 Human 2.0.0 Backend: tensorflow
2021-06-01 08:54:20 STATE: Worker: PID: 130369 TensorFlow/JS 3.6.0 Human 2.0.0 Backend: tensorflow
2021-06-01 08:54:20 STATE: Worker: PID: 130363 TensorFlow/JS 3.6.0 Human 2.0.0 Backend: tensorflow
2021-06-01 08:54:21 STATE: Main: dispatching to worker: 130370
2021-06-01 08:54:21 INFO: Latency: worker initializtion: 1348 message round trip: 0
2021-06-01 08:54:21 DATA: Worker received message: 130370 { test: true }
2021-06-01 08:54:21 STATE: Main: dispatching to worker: 130362
2021-06-01 08:54:21 DATA: Worker received message: 130362 { image: 'samples/ai-face.jpg' }
2021-06-01 08:54:21 DATA: Worker received message: 130370 { image: 'samples/ai-body.jpg' }
2021-06-01 08:54:21 STATE: Main: dispatching to worker: 130369
2021-06-01 08:54:21 STATE: Main: dispatching to worker: 130363
2021-06-01 08:54:21 DATA: Worker received message: 130369 { image: 'assets/human-sample-upper.jpg' }
2021-06-01 08:54:21 DATA: Worker received message: 130363 { image: 'assets/sample-me.jpg' }
2021-06-01 08:54:24 DATA: Main: worker finished: 130362 detected faces: 1 bodies: 1 hands: 0 objects: 1
2021-06-01 08:54:24 STATE: Main: dispatching to worker: 130362
2021-06-01 08:54:24 DATA: Worker received message: 130362 { image: 'assets/sample1.jpg' }
2021-06-01 08:54:25 DATA: Main: worker finished: 130369 detected faces: 1 bodies: 1 hands: 0 objects: 1
2021-06-01 08:54:25 STATE: Main: dispatching to worker: 130369
2021-06-01 08:54:25 DATA: Main: worker finished: 130370 detected faces: 1 bodies: 1 hands: 0 objects: 1
2021-06-01 08:54:25 STATE: Main: dispatching to worker: 130370
2021-06-01 08:54:25 DATA: Worker received message: 130369 { image: 'assets/sample2.jpg' }
2021-06-01 08:54:25 DATA: Main: worker finished: 130363 detected faces: 1 bodies: 1 hands: 0 objects: 2
2021-06-01 08:54:25 STATE: Main: dispatching to worker: 130363
2021-06-01 08:54:25 DATA: Worker received message: 130370 { image: 'assets/sample3.jpg' }
2021-06-01 08:54:25 DATA: Worker received message: 130363 { image: 'assets/sample4.jpg' }
2021-06-01 08:54:30 DATA: Main: worker finished: 130362 detected faces: 3 bodies: 1 hands: 0 objects: 7
2021-06-01 08:54:30 STATE: Main: dispatching to worker: 130362
2021-06-01 08:54:30 DATA: Worker received message: 130362 { image: 'assets/sample5.jpg' }
2021-06-01 08:54:31 DATA: Main: worker finished: 130369 detected faces: 3 bodies: 1 hands: 0 objects: 5
2021-06-01 08:54:31 STATE: Main: dispatching to worker: 130369
2021-06-01 08:54:31 DATA: Worker received message: 130369 { image: 'assets/sample6.jpg' }
2021-06-01 08:54:31 DATA: Main: worker finished: 130363 detected faces: 4 bodies: 1 hands: 2 objects: 2
2021-06-01 08:54:31 STATE: Main: dispatching to worker: 130363
2021-06-01 08:54:39 STATE: Main: worker exit: 130370 0
2021-06-01 08:54:39 DATA: Main: worker finished: 130362 detected faces: 1 bodies: 1 hands: 0 objects: 1
2021-06-01 08:54:39 DATA: Main: worker finished: 130369 detected faces: 1 bodies: 1 hands: 1 objects: 3
2021-06-01 08:54:39 STATE: Main: worker exit: 130362 0
2021-06-01 08:54:39 STATE: Main: worker exit: 130369 0
2021-06-01 08:54:41 DATA: Main: worker finished: 130363 detected faces: 9 bodies: 1 hands: 0 objects: 10
2021-06-01 08:54:41 STATE: Main: worker exit: 130363 0
2021-06-01 08:54:41 INFO: Processed: 15 images in total: 22006 ms working: 20658 ms average: 1377 ms
```

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Human</title>
<meta name="viewport" content="width=device-width" id="viewport">
<meta name="keywords" content="Human">
<meta name="application-name" content="Human">
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="../manifest.webmanifest">
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../../assets/icon.png">
<script src="./index.js" type="module"></script>
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 16px; font-variant: small-caps; }
body { margin: 0; background: black; color: white; overflow-x: hidden; width: 100vw; height: 100vh; }
body::-webkit-scrollbar { display: none; }
.status { position: absolute; width: 100vw; bottom: 10%; text-align: center; font-size: 3rem; font-weight: 100; text-shadow: 2px 2px #303030; }
.log { position: absolute; bottom: 0; margin: 0.4rem 0.4rem 0 0.4rem; font-size: 0.9rem; }
.video { display: none; }
.canvas { margin: 0 auto; }
</style>
</head>
<body>
<div id="status" class="status"></div>
<canvas id="canvas" class="canvas"></canvas>
<video id="video" playsinline class="video"></video>
<div id="log" class="log"></div>
</body>
</html>

263
demo/multithread/index.js Normal file
View File

@ -0,0 +1,263 @@
/**
* Human demo for browsers
*
* @description Demo app that enables all Human modules and runs them in separate worker threads
*
*/
import Human from '../../dist/human.esm.js'; // equivalent of @vladmandic/human
import GLBench from '../helpers/gl-bench.js';
const workerJS = './worker.js';
const config = {
main: { // processes input and runs gesture analysis
warmup: 'none',
backend: 'humangl',
modelBasePath: '../../models/',
async: false,
filter: { enabled: true },
face: { enabled: false },
object: { enabled: false },
gesture: { enabled: true },
hand: { enabled: false },
body: { enabled: false },
segmentation: { enabled: false },
},
face: { // runs all face models
warmup: 'none',
backend: 'humangl',
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: true },
object: { enabled: false },
gesture: { enabled: false },
hand: { enabled: false },
body: { enabled: false },
segmentation: { enabled: false },
},
body: { // runs body model
warmup: 'none',
backend: 'humangl',
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: false },
object: { enabled: false },
gesture: { enabled: false },
hand: { enabled: false },
body: { enabled: true },
segmentation: { enabled: false },
},
hand: { // runs hands model
warmup: 'none',
backend: 'humangl',
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: false },
object: { enabled: false },
gesture: { enabled: false },
hand: { enabled: true },
body: { enabled: false },
segmentation: { enabled: false },
},
object: { // runs object model
warmup: 'none',
backend: 'humangl',
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: false },
object: { enabled: true },
gesture: { enabled: false },
hand: { enabled: false },
body: { enabled: false },
segmentation: { enabled: false },
},
};
let human;
let canvas;
let video;
let bench;
const busy = {
face: false,
hand: false,
body: false,
object: false,
};
const workers = {
/** @type {Worker | null} */
face: null,
/** @type {Worker | null} */
body: null,
/** @type {Worker | null} */
hand: null,
/** @type {Worker | null} */
object: null,
};
const time = {
main: 0,
draw: 0,
face: '[warmup]',
body: '[warmup]',
hand: '[warmup]',
object: '[warmup]',
};
const start = {
main: 0,
draw: 0,
face: 0,
body: 0,
hand: 0,
object: 0,
};
const result = { // initialize empty result object which will be partially filled with results from each thread
performance: {},
hand: [],
body: [],
face: [],
object: [],
};
function log(...msg) {
const dt = new Date();
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
// eslint-disable-next-line no-console
console.log(ts, ...msg);
}
async function drawResults() {
start.draw = human.now();
const interpolated = human.next(result);
await human.draw.all(canvas, interpolated);
time.draw = Math.round(1 + human.now() - start.draw);
const fps = Math.round(10 * 1000 / time.main) / 10;
const draw = Math.round(10 * 1000 / time.draw) / 10;
const div = document.getElementById('log');
if (div) div.innerText = `Human: version ${human.version} | Performance: Main ${time.main}ms Face: ${time.face}ms Body: ${time.body}ms Hand: ${time.hand}ms Object ${time.object}ms | FPS: ${fps} / ${draw}`;
requestAnimationFrame(drawResults);
}
async function receiveMessage(msg) {
result[msg.data.type] = msg.data.result;
busy[msg.data.type] = false;
time[msg.data.type] = Math.round(human.now() - start[msg.data.type]);
}
async function runDetection() {
start.main = human.now();
if (!bench) {
bench = new GLBench(null, { trackGPU: false, chartHz: 20, chartLen: 20 });
bench.begin('human');
}
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
if (!busy.face) {
busy.face = true;
start.face = human.now();
if (workers.face) workers.face.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.face, type: 'face' }, [imageData.data.buffer.slice(0)]);
}
if (!busy.body) {
busy.body = true;
start.body = human.now();
if (workers.body) workers.body.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.body, type: 'body' }, [imageData.data.buffer.slice(0)]);
}
if (!busy.hand) {
busy.hand = true;
start.hand = human.now();
if (workers.hand) workers.hand.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.hand, type: 'hand' }, [imageData.data.buffer.slice(0)]);
}
if (!busy.object) {
busy.object = true;
start.object = human.now();
if (workers.object) workers.object.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.object, type: 'object' }, [imageData.data.buffer.slice(0)]);
}
time.main = Math.round(human.now() - start.main);
bench.nextFrame();
requestAnimationFrame(runDetection);
}
async function setupCamera() {
video = document.getElementById('video');
canvas = document.getElementById('canvas');
const output = document.getElementById('log');
let stream;
const constraints = {
audio: false,
video: {
facingMode: 'user',
resizeMode: 'crop-and-scale',
width: { ideal: document.body.clientWidth },
aspectRatio: document.body.clientWidth / document.body.clientHeight,
},
};
// enumerate devices for diag purposes
navigator.mediaDevices.enumerateDevices().then((devices) => log('enumerated devices:', devices));
log('camera constraints', constraints);
try {
stream = await navigator.mediaDevices.getUserMedia(constraints);
} catch (err) {
if (output) output.innerText += `\n${err.name}: ${err.message}`;
log('camera error:', err);
}
if (stream) {
const tracks = stream.getVideoTracks();
log('enumerated viable tracks:', tracks);
const track = stream.getVideoTracks()[0];
const settings = track.getSettings();
log('selected video source:', track, settings);
} else {
log('missing video stream');
}
const promise = !stream || new Promise((resolve) => {
video.onloadeddata = () => {
canvas.style.height = '100vh';
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
video.play();
resolve(true);
};
});
// attach input to video element
if (stream && video) video['srcObject'] = stream;
return promise;
}
async function startWorkers() {
if (!workers.face) workers.face = new Worker(workerJS);
if (!workers.body) workers.body = new Worker(workerJS);
if (!workers.hand) workers.hand = new Worker(workerJS);
if (!workers.object) workers.object = new Worker(workerJS);
workers.face.onmessage = receiveMessage;
workers.body.onmessage = receiveMessage;
workers.hand.onmessage = receiveMessage;
workers.object.onmessage = receiveMessage;
}
async function main() {
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
return;
}
human = new Human(config.main);
const div = document.getElementById('log');
if (div) div.innerText = `Human: version ${human.version}`;
await startWorkers();
await setupCamera();
runDetection();
drawResults();
}
window.onload = main;

View File

@ -0,0 +1,88 @@
/**
* Human demo for NodeJS
*
* Used by node-multiprocess.js as an on-demand started worker process
* Receives messages from parent process and sends results
*/
const fs = require('fs');
const log = require('@vladmandic/pilogger');
// workers actual import tfjs and faceapi modules
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node');
const Human = require('../../dist/human.node.js').default; // or const Human = require('../dist/human.node-gpu.js').default;
let human = null;
const myConfig = {
// backend: 'tensorflow',
modelBasePath: 'file://models/',
debug: false,
async: true,
face: {
enabled: true,
detector: { enabled: true, rotation: false },
mesh: { enabled: true },
iris: { enabled: true },
description: { enabled: true },
emotion: { enabled: true },
},
hand: {
enabled: true,
},
// body: { modelPath: 'blazepose.json', enabled: true },
body: { enabled: true },
object: { enabled: true },
};
// read image from a file and create tensor to be used by faceapi
// this way we don't need any monkey patches
// you can add any pre-proocessing here such as resizing, etc.
async function image(img) {
const buffer = fs.readFileSync(img);
const tensor = human.tf.tidy(() => human.tf.node.decodeImage(buffer).toFloat().expandDims());
return tensor;
}
// actual faceapi detection
async function detect(img) {
const tensor = await image(img);
const result = await human.detect(tensor);
if (process.send) { // check if ipc exists
process.send({ image: img, detected: result }); // send results back to main
process.send({ ready: true }); // send signal back to main that this worker is now idle and ready for next image
}
tf.dispose(tensor);
}
async function main() {
process.on('unhandledRejection', (err) => {
// @ts-ignore // no idea if exception message is compelte
log.error(err?.message || err || 'no error message');
});
// on worker start first initialize message handler so we don't miss any messages
process.on('message', (msg) => {
// @ts-ignore
if (msg.exit && process.exit) process.exit(); // if main told worker to exit
// @ts-ignore
if (msg.test && process.send) process.send({ test: true });
// @ts-ignore
if (msg.image) detect(msg.image); // if main told worker to process image
log.data('Worker received message:', process.pid, msg); // generic log
});
// create instance of human
human = new Human(myConfig);
// wait until tf is ready
await human.tf.ready();
// pre-load models
log.state('Worker: PID:', process.pid, `TensorFlow/JS ${human.tf.version['tfjs-core']} Human ${human.version} Backend: ${human.tf.getBackend()}`);
await human.load();
// now we're ready, so send message back to main that it knows it can use this worker
if (process.send) process.send({ ready: true });
}
main();

View File

@ -0,0 +1,98 @@
/**
* Human demo for NodeJS
*
* Uses NodeJS fork functionality with inter-processing-messaging
* Starts a pool of worker processes and dispatch work items to each worker when they are available
* Uses node-multiprocess-worker.js for actual processing
*/
const fs = require('fs');
const path = require('path');
// eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require
const log = require('@vladmandic/pilogger'); // this is my simple logger with few extra features
const child_process = require('child_process');
// note that main process does not import human or tfjs at all, it's all done from worker process
const workerFile = 'demo/nodejs/node-multiprocess-worker.js';
const imgPathRoot = './assets'; // modify to include your sample images
const numWorkers = 4; // how many workers will be started
const workers = []; // this holds worker processes
const images = []; // this holds queue of enumerated images
const t = []; // timers
let numImages;
// trigered by main when worker sends ready message
// if image pool is empty, signal worker to exit otherwise dispatch image to worker and remove image from queue
async function detect(worker) {
if (!t[2]) t[2] = process.hrtime.bigint(); // first time do a timestamp so we can measure initial latency
if (images.length === numImages) worker.send({ test: true }); // for first image in queue just measure latency
if (images.length === 0) worker.send({ exit: true }); // nothing left in queue
else {
log.state('Main: dispatching to worker:', worker.pid);
worker.send({ image: images[0] });
images.shift();
}
}
// loop that waits for all workers to complete
function waitCompletion() {
const activeWorkers = workers.reduce((any, worker) => (any += worker.connected ? 1 : 0), 0);
if (activeWorkers > 0) setImmediate(() => waitCompletion());
else {
t[1] = process.hrtime.bigint();
log.info('Processed:', numImages, 'images in', 'total:', Math.trunc(Number(t[1] - t[0]) / 1000000), 'ms', 'working:', Math.trunc(Number(t[1] - t[2]) / 1000000), 'ms', 'average:', Math.trunc(Number(t[1] - t[2]) / numImages / 1000000), 'ms');
}
}
function measureLatency() {
t[3] = process.hrtime.bigint();
const latencyInitialization = Math.trunc(Number(t[2] - t[0]) / 1000 / 1000);
const latencyRoundTrip = Math.trunc(Number(t[3] - t[2]) / 1000 / 1000);
log.info('Latency: worker initializtion: ', latencyInitialization, 'message round trip:', latencyRoundTrip);
}
async function main() {
process.on('unhandledRejection', (err) => {
// @ts-ignore // no idea if exception message is compelte
log.error(err?.message || err || 'no error message');
});
log.header();
log.info('FaceAPI multi-process test');
// enumerate all images into queue
const dir = fs.readdirSync(imgPathRoot);
for (const imgFile of dir) {
if (imgFile.toLocaleLowerCase().endsWith('.jpg')) images.push(path.join(imgPathRoot, imgFile));
}
numImages = images.length;
log.state('Enumerated images:', imgPathRoot, numImages);
t[0] = process.hrtime.bigint();
t[1] = process.hrtime.bigint();
t[2] = process.hrtime.bigint();
// manage worker processes
for (let i = 0; i < numWorkers; i++) {
// create worker process
workers[i] = await child_process.fork(workerFile, ['special']);
// parse message that worker process sends back to main
// if message is ready, dispatch next image in queue
// if message is processing result, just print how many faces were detected
// otherwise it's an unknown message
workers[i].on('message', (msg) => {
if (msg.ready) detect(workers[i]);
else if (msg.image) log.data('Main: worker finished:', workers[i].pid, 'detected faces:', msg.detected.face?.length, 'bodies:', msg.detected.body?.length, 'hands:', msg.detected.hand?.length, 'objects:', msg.detected.object?.length);
else if (msg.test) measureLatency();
else log.data('Main: worker message:', workers[i].pid, msg);
});
// just log when worker exits
workers[i].on('exit', (msg) => log.state('Main: worker exit:', workers[i].pid, msg));
// just log which worker was started
log.state('Main: started worker:', workers[i].pid);
}
// wait for all workers to complete
waitCompletion();
}
main();

View File

@ -0,0 +1,19 @@
/// <reference lib="webworker" />
// load Human using IIFE script as Chome Mobile does not support Modules as Workers
self.importScripts('../../dist/human.js');
let human;
onmessage = async (msg) => {
// received from index.js using:
// worker.postMessage({ image: image.data.buffer, width: canvas.width, height: canvas.height, config }, [image.data.buffer]);
// @ts-ignore // Human is registered as global namespace using IIFE script
// eslint-disable-next-line no-undef, new-cap
if (!human) human = new Human.default(msg.data.config);
const image = new ImageData(new Uint8ClampedArray(msg.data.image), msg.data.width, msg.data.height);
let result = {};
result = await human.detect(image, msg.data.config);
postMessage({ result: result[msg.data.type], type: msg.data.type });
};

120
demo/nodejs/README.md Normal file
View File

@ -0,0 +1,120 @@
# Human Demos for NodeJS
- `node`: Process images from files, folders or URLs
uses native methods for image loading and decoding without external dependencies
- `node-canvas`: Process image from file or URL and draw results to a new image file using `node-canvas`
uses `node-canvas` library to load and decode images from files, draw detection results and write output to a new image file
- `node-video`: Processing of video input using `ffmpeg`
uses `ffmpeg` to decode video input (can be a file, stream or device such as webcam) and
output results in a pipe that are captured by demo app as frames and processed by `Human` library
- `node-webcam`: Processing of webcam screenshots using `fswebcam`
uses `fswebcam` to connect to web cam and take screenshots at regular interval which are then processed by `Human` library
- `node-event`: Showcases usage of `Human` eventing to get notifications on processing
- `node-similarity`: Compares two input images for similarity of detected faces
- `process-folder`: Processing all images in input folder and creates output images
interally used to generate samples gallery
<br>
## Main Demo
`nodejs/node.js`: Demo using NodeJS with CommonJS module
Simple demo that can process any input image
Note that you can run demo as-is and it will perform detection on provided sample images,
or you can pass a path to image to analyze, either on local filesystem or using URL
```shell
node demo/nodejs/node.js
```
```json
2021-06-01 08:52:15 INFO: @vladmandic/human version 2.0.0
2021-06-01 08:52:15 INFO: User: vlado Platform: linux Arch: x64 Node: v16.0.0
2021-06-01 08:52:15 INFO: Current folder: /home/vlado/dev/human
2021-06-01 08:52:15 INFO: Human: 2.0.0
2021-06-01 08:52:15 INFO: Active Configuration {
backend: 'tensorflow',
modelBasePath: 'file://models/',
wasmPath: '../node_modules/@tensorflow/tfjs-backend-wasm/dist/',
debug: true,
async: false,
warmup: 'full',
cacheSensitivity: 0.75,
filter: {
enabled: true,
width: 0,
height: 0,
flip: true,
return: true,
brightness: 0,
contrast: 0,
sharpness: 0,
blur: 0,
saturation: 0,
hue: 0,
negative: false,
sepia: false,
vintage: false,
kodachrome: false,
technicolor: false,
polaroid: false,
pixelate: 0
},
gesture: { enabled: true },
face: {
enabled: true,
detector: { modelPath: 'blazeface.json', rotation: false, maxDetected: 10, skipFrames: 15, minConfidence: 0.2, iouThreshold: 0.1, return: false, enabled: true },
mesh: { enabled: true, modelPath: 'facemesh.json' },
iris: { enabled: true, modelPath: 'iris.json' },
description: { enabled: true, modelPath: 'faceres.json', skipFrames: 16, minConfidence: 0.1 },
emotion: { enabled: true, minConfidence: 0.1, skipFrames: 17, modelPath: 'emotion.json' }
},
body: { enabled: true, modelPath: 'movenet-lightning.json', maxDetected: 1, minConfidence: 0.2 },
hand: {
enabled: true,
rotation: true,
skipFrames: 18,
minConfidence: 0.1,
iouThreshold: 0.1,
maxDetected: 2,
landmarks: true,
detector: { modelPath: 'handdetect.json' },
skeleton: { modelPath: 'handskeleton.json' }
},
object: { enabled: true, modelPath: 'mb3-centernet.json', minConfidence: 0.2, iouThreshold: 0.4, maxDetected: 10, skipFrames: 19 }
}
08:52:15.673 Human: version: 2.0.0
08:52:15.674 Human: tfjs version: 3.6.0
08:52:15.674 Human: platform: linux x64
08:52:15.674 Human: agent: NodeJS v16.0.0
08:52:15.674 Human: setting backend: tensorflow
08:52:15.710 Human: load model: file://models/blazeface.json
08:52:15.743 Human: load model: file://models/facemesh.json
08:52:15.744 Human: load model: file://models/iris.json
08:52:15.760 Human: load model: file://models/emotion.json
08:52:15.847 Human: load model: file://models/handdetect.json
08:52:15.847 Human: load model: file://models/handskeleton.json
08:52:15.914 Human: load model: file://models/movenet-lightning.json
08:52:15.957 Human: load model: file://models/mb3-centernet.json
08:52:16.015 Human: load model: file://models/faceres.json
08:52:16.015 Human: tf engine state: 50796152 bytes 1318 tensors
2021-06-01 08:52:16 INFO: Loaded: [ 'face', 'movenet', 'handpose', 'emotion', 'centernet', 'faceres', [length]: 6 ]
2021-06-01 08:52:16 INFO: Memory state: { unreliable: true, numTensors: 1318, numDataBuffers: 1318, numBytes: 50796152 }
2021-06-01 08:52:16 INFO: Loading image: private/daz3d/daz3d-kiaria-02.jpg
2021-06-01 08:52:16 STATE: Processing: [ 1, 1300, 1000, 3, [length]: 4 ]
2021-06-01 08:52:17 DATA: Results:
2021-06-01 08:52:17 DATA: Face: #0 boxScore:0.88 faceScore:1 age:16.3 genderScore:0.97 gender:female emotionScore:0.85 emotion:happy iris:61.05
2021-06-01 08:52:17 DATA: Body: #0 score:0.82 keypoints:17
2021-06-01 08:52:17 DATA: Hand: #0 score:0.89
2021-06-01 08:52:17 DATA: Hand: #1 score:0.97
2021-06-01 08:52:17 DATA: Gesture: face#0 gesture:facing left
2021-06-01 08:52:17 DATA: Gesture: body#0 gesture:leaning right
2021-06-01 08:52:17 DATA: Gesture: hand#0 gesture:pinky forward middlefinger up
2021-06-01 08:52:17 DATA: Gesture: hand#1 gesture:pinky forward middlefinger up
2021-06-01 08:52:17 DATA: Gesture: iris#0 gesture:looking left
2021-06-01 08:52:17 DATA: Object: #0 score:0.55 label:person
2021-06-01 08:52:17 DATA: Object: #1 score:0.23 label:bottle
2021-06-01 08:52:17 DATA: Persons:
2021-06-01 08:52:17 DATA: #0: Face:score:1 age:16.3 gender:female iris:61.05 Body:score:0.82 keypoints:17 LeftHand:no RightHand:yes Gestures:4
```

View File

@ -0,0 +1,84 @@
/**
* Human demo for NodeJS using Canvas library
*/
const fs = require('fs');
const process = require('process');
const log = require('@vladmandic/pilogger');
const canvas = require('canvas');
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
const config = { // just enable all and leave default settings
debug: false,
face: { enabled: true }, // includes mesh, iris, emotion, descriptor
hand: { enabled: true, maxDetected: 2, minConfidence: 0.5, detector: { modelPath: 'handtrack.json' } }, // use alternative hand model
body: { enabled: true },
object: { enabled: true },
gestures: { enabled: true },
};
async function main() {
log.header();
globalThis.Canvas = canvas.Canvas; // patch global namespace with canvas library
globalThis.ImageData = canvas.ImageData; // patch global namespace with canvas library
// human.env.Canvas = canvas.Canvas; // alternatively monkey-patch human to use external canvas library
// human.env.ImageData = canvas.ImageData; // alternatively monkey-patch human to use external canvas library
// init
const human = new Human.Human(config); // create instance of human
log.info('Human:', human.version);
await human.load(); // pre-load models
log.info('Loaded models:', Object.keys(human.models).filter((a) => human.models[a]));
log.info('Memory state:', human.tf.engine().memory());
// parse cmdline
const input = process.argv[2];
const output = process.argv[3];
if (process.argv.length !== 4) log.error('Parameters: <input-image> <output-image> missing');
else if (!fs.existsSync(input) && !input.startsWith('http')) log.error(`File not found: ${process.argv[2]}`);
else {
// everything seems ok
const inputImage = await canvas.loadImage(input); // load image using canvas library
log.info('Loaded image', input, inputImage.width, inputImage.height);
const inputCanvas = new canvas.Canvas(inputImage.width, inputImage.height); // create canvas
const inputCtx = inputCanvas.getContext('2d');
inputCtx.drawImage(inputImage, 0, 0); // draw input image onto canvas
const imageData = inputCtx.getImageData(0, 0, inputCanvas.width, inputCanvas.height);
// run detection
const result = await human.detect(imageData);
// run segmentation
// const seg = await human.segmentation(inputCanvas);
// log.data('Segmentation:', { data: seg.data.length, alpha: typeof seg.alpha, canvas: typeof seg.canvas });
// print results summary
const persons = result.persons; // invoke persons getter, only used to print summary on console
for (let i = 0; i < persons.length; i++) {
const face = persons[i].face;
const faceTxt = face ? `score:${face.score} age:${face.age} gender:${face.gender} iris:${face.iris}` : null;
const body = persons[i].body;
const bodyTxt = body ? `score:${body.score} keypoints:${body.keypoints?.length}` : null;
log.data(`Detected: #${i}: Face:${faceTxt} Body:${bodyTxt} LeftHand:${persons[i].hands.left ? 'yes' : 'no'} RightHand:${persons[i].hands.right ? 'yes' : 'no'} Gestures:${persons[i].gestures.length}`);
}
// draw detected results onto canvas and save it to a file
const outputCanvas = new canvas.Canvas(inputImage.width, inputImage.height); // create canvas
const outputCtx = outputCanvas.getContext('2d');
outputCtx.drawImage(result.canvas || inputImage, 0, 0); // draw input image onto canvas
// @ts-ignore canvas is not checked for typedefs
human.draw.all(outputCanvas, result); // use human build-in method to draw results as overlays on canvas
const outFile = fs.createWriteStream(output); // write canvas to new image file
outFile.on('finish', () => log.state('Output image:', output, outputCanvas.width, outputCanvas.height));
outFile.on('error', (err) => log.error('Output error:', output, err));
const stream = outputCanvas.createJPEGStream({ quality: 0.5, progressive: true, chromaSubsampling: true });
stream.pipe(outFile);
}
}
main();

97
demo/nodejs/node-event.js Normal file
View File

@ -0,0 +1,97 @@
/**
* Human demo for NodeJS
*/
const log = require('@vladmandic/pilogger');
const fs = require('fs');
const process = require('process');
let fetch; // fetch is dynamically imported later
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
let human = null;
const myConfig = {
modelBasePath: 'file://models/',
debug: false,
async: true,
filter: { enabled: false },
face: {
enabled: true,
detector: { enabled: true },
mesh: { enabled: true },
iris: { enabled: true },
description: { enabled: true },
emotion: { enabled: true },
},
hand: { enabled: true },
body: { enabled: true },
object: { enabled: true },
};
async function detect(input) {
// read input image from file or url into buffer
let buffer;
log.info('Loading image:', input);
if (input.startsWith('http:') || input.startsWith('https:')) {
fetch = (await import('node-fetch')).default;
const res = await fetch(input);
if (res && res.ok) buffer = await res.buffer();
else log.error('Invalid image URL:', input, res.status, res.statusText, res.headers.get('content-type'));
} else {
buffer = fs.readFileSync(input);
}
// decode image using tfjs-node so we don't need external depenencies
if (!buffer) return;
const tensor = human.tf.node.decodeImage(buffer, 3);
// run detection
await human.detect(tensor, myConfig);
human.tf.dispose(tensor); // dispose image tensor as we no longer need it
}
async function main() {
log.header();
human = new Human.Human(myConfig);
if (human.events) {
human.events.addEventListener('warmup', () => {
log.info('Event Warmup');
});
human.events.addEventListener('load', () => {
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
log.info('Event Loaded:', loaded, human.tf.engine().memory());
});
human.events.addEventListener('image', () => {
log.info('Event Image:', human.process.tensor.shape);
});
human.events.addEventListener('detect', () => {
log.data('Event Detected:');
const persons = human.result.persons;
for (let i = 0; i < persons.length; i++) {
const face = persons[i].face;
const faceTxt = face ? `score:${face.score} age:${face.age} gender:${face.gender} iris:${face.iris}` : null;
const body = persons[i].body;
const bodyTxt = body ? `score:${body.score} keypoints:${body.keypoints?.length}` : null;
log.data(` #${i}: Face:${faceTxt} Body:${bodyTxt} LeftHand:${persons[i].hands.left ? 'yes' : 'no'} RightHand:${persons[i].hands.right ? 'yes' : 'no'} Gestures:${persons[i].gestures.length}`);
}
});
}
await human.tf.ready(); // wait until tf is ready
const input = process.argv[2]; // process input
if (input) await detect(input);
else log.error('Missing <input>');
}
main();

25
demo/nodejs/node-fetch.js Normal file
View File

@ -0,0 +1,25 @@
const fs = require('fs');
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
const humanConfig = {
modelBasePath: 'https://vladmandic.github.io/human/models/',
};
async function main(inputFile) {
// @ts-ignore
global.fetch = (await import('node-fetch')).default;
const human = new Human.Human(humanConfig); // create instance of human using default configuration
await human.load(); // optional as models would be loaded on-demand first time they are required
await human.warmup(); // optional as model warmup is performed on-demand first time its executed
const buffer = fs.readFileSync(inputFile); // read file data into buffer
const tensor = human.tf.node.decodeImage(buffer); // decode jpg data
const result = await human.detect(tensor); // run detection; will initialize backend and on-demand load models
// eslint-disable-next-line no-console
console.log(result.gesture);
}
main('samples/in/ai-body.jpg');

View File

@ -0,0 +1,67 @@
/**
* Human Person Similarity test for NodeJS
*/
const log = require('@vladmandic/pilogger');
const fs = require('fs');
const process = require('process');
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
let human = null;
const myConfig = {
modelBasePath: 'file://models/',
debug: true,
face: { emotion: { enabled: false } },
body: { enabled: false },
hand: { enabled: false },
gesture: { enabled: false },
};
async function init() {
human = new Human.Human(myConfig);
await human.tf.ready();
log.info('Human:', human.version);
await human.load();
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
log.info('Loaded:', loaded);
log.info('Memory state:', human.tf.engine().memory());
}
async function detect(input) {
if (!fs.existsSync(input)) {
log.error('Cannot load image:', input);
process.exit(1);
}
const buffer = fs.readFileSync(input);
const tensor = human.tf.node.decodeImage(buffer, 3);
log.state('Loaded image:', input, tensor['shape']);
const result = await human.detect(tensor, myConfig);
human.tf.dispose(tensor);
log.state('Detected faces:', result.face.length);
return result;
}
async function main() {
log.configure({ inspect: { breakLength: 265 } });
log.header();
if (process.argv.length !== 4) {
log.error('Parameters: <first image> <second image> missing');
process.exit(1);
}
await init();
const res1 = await detect(process.argv[2]);
const res2 = await detect(process.argv[3]);
if (!res1 || !res1.face || res1.face.length === 0 || !res2 || !res2.face || res2.face.length === 0) {
log.error('Could not detect face descriptors');
process.exit(1);
}
const similarity = human.similarity(res1.face[0].embedding, res2.face[0].embedding, { order: 2 });
log.data('Similarity: ', similarity);
}
main();

View File

@ -0,0 +1,19 @@
const fs = require('fs');
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
async function main(inputFile) {
const human = new Human.Human(); // create instance of human using default configuration
await human.load(); // optional as models would be loaded on-demand first time they are required
await human.warmup(); // optional as model warmup is performed on-demand first time its executed
const buffer = fs.readFileSync(inputFile); // read file data into buffer
const tensor = human.tf.node.decodeImage(buffer); // decode jpg data
const result = await human.detect(tensor); // run detection; will initialize backend and on-demand load models
// eslint-disable-next-line no-console
console.log(result);
}
main('samples/in/ai-body.jpg');

89
demo/nodejs/node-video.js Normal file
View File

@ -0,0 +1,89 @@
/**
* Human demo for NodeJS
* Unsupported sample of using external utility ffmpeg to capture to decode video input and process it using Human
*
* Uses ffmpeg to process video input and output stream of motion jpeg images which are then parsed for frame start/end markers by pipe2jpeg
* Each frame triggers an event with jpeg buffer that then can be decoded and passed to human for processing
* If you want process at specific intervals, set output fps to some value
* If you want to process an input stream, set real-time flag and set input as required
*
* Note that pipe2jpeg is not part of Human dependencies and should be installed manually
* Working version of ffmpeg must be present on the system
*/
const spawn = require('child_process').spawn;
const log = require('@vladmandic/pilogger');
// @ts-ignore pipe2jpeg is not installed by default
// eslint-disable-next-line node/no-missing-require
const Pipe2Jpeg = require('pipe2jpeg');
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
let count = 0; // counter
let busy = false; // busy flag
const inputFile = './test.mp4';
const humanConfig = {
modelBasePath: 'file://models/',
debug: false,
async: true,
filter: { enabled: false },
face: {
enabled: true,
detector: { enabled: true, rotation: false },
mesh: { enabled: true },
iris: { enabled: true },
description: { enabled: true },
emotion: { enabled: true },
},
hand: { enabled: false },
body: { enabled: false },
object: { enabled: false },
};
const human = new Human.Human(humanConfig);
const pipe2jpeg = new Pipe2Jpeg();
const ffmpegParams = [
'-loglevel', 'quiet',
// input
// '-re', // optional process video in real-time not as fast as possible
'-i', `${inputFile}`, // input file
// output
'-an', // drop audio
'-c:v', 'mjpeg', // use motion jpeg as output encoder
'-pix_fmt', 'yuvj422p', // typical for mp4, may need different settings for some videos
'-f', 'image2pipe', // pipe images as output
// '-vf', 'fps=5,scale=800:600', // optional video filter, do anything here such as process at fixed 5fps or resize to specific resulution
'pipe:1', // output to unix pipe that is then captured by pipe2jpeg
];
async function process(jpegBuffer) {
if (busy) return; // skip processing if busy
busy = true;
const tensor = human.tf.node.decodeJpeg(jpegBuffer, 3); // decode jpeg buffer to raw tensor
log.state('input frame:', ++count, 'size:', jpegBuffer.length, 'decoded shape:', tensor.shape);
const res = await human.detect(tensor);
log.data('gesture', JSON.stringify(res.gesture));
// do processing here
tf.dispose(tensor); // must dispose tensor
busy = false;
}
async function main() {
log.header();
await human.tf.ready();
// pre-load models
log.info('human:', human.version);
pipe2jpeg.on('jpeg', (jpegBuffer) => process(jpegBuffer));
const ffmpeg = spawn('ffmpeg', ffmpegParams, { stdio: ['ignore', 'pipe', 'ignore'] });
ffmpeg.on('error', (error) => log.error('ffmpeg error:', error));
ffmpeg.on('exit', (code, signal) => log.info('ffmpeg exit', code, signal));
ffmpeg.stdout.pipe(pipe2jpeg);
}
main();

View File

@ -0,0 +1,92 @@
/**
* Human demo for NodeJS
* Unsupported sample of using external utility fswebcam to capture screenshot from attached webcam in regular intervals and process it using Human
*
* Note that node-webcam is not part of Human dependencies and should be installed manually
* Working version of fswebcam must be present on the system
*/
let initial = true; // remember if this is the first run to print additional details
const log = require('@vladmandic/pilogger');
// @ts-ignore node-webcam is not installed by default
// eslint-disable-next-line node/no-missing-require
const nodeWebCam = require('node-webcam');
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
// options for node-webcam
const tempFile = 'webcam-snap'; // node-webcam requires writting snapshot to a file, recommended to use tmpfs to avoid excessive disk writes
const optionsCamera = {
callbackReturn: 'buffer', // this means whatever `fswebcam` writes to disk, no additional processing so it's fastest
saveShots: false, // don't save processed frame to disk, note that temp file is still created by fswebcam thus recommendation for tmpfs
};
const camera = nodeWebCam.create(optionsCamera);
// options for human
const optionsHuman = {
modelBasePath: 'file://models/',
};
const human = new Human.Human(optionsHuman);
function buffer2tensor(buffer) {
return human.tf.tidy(() => {
if (!buffer) return null;
const decode = human.tf.node.decodeImage(buffer, 3);
let expand;
if (decode.shape[2] === 4) { // input is in rgba format, need to convert to rgb
const channels = human.tf.split(decode, 4, 2); // tf.split(tensor, 4, 2); // split rgba to channels
const rgb = human.tf.stack([channels[0], channels[1], channels[2]], 2); // stack channels back to rgb and ignore alpha
expand = human.tf.reshape(rgb, [1, decode.shape[0], decode.shape[1], 3]); // move extra dim from the end of tensor and use it as batch number instead
} else {
expand = human.tf.expandDims(decode, 0); // inpur ia rgb so use as-is
}
const cast = human.tf.cast(expand, 'float32');
return cast;
});
}
async function detect() {
// trigger next frame every 5 sec
// triggered here before actual capture and detection since we assume it will complete in less than 5sec
// so it's as close as possible to real 5sec and not 5sec + detection time
// if there is a chance of race scenario where detection takes longer than loop trigger, then trigger should be at the end of the function instead
setTimeout(() => detect(), 5000);
camera.capture(tempFile, (err, data) => { // gets the (default) jpeg data from from webcam
if (err) {
log.error('error capturing webcam:', err);
} else {
const tensor = buffer2tensor(data); // create tensor from image buffer
if (initial) log.data('input tensor:', tensor.shape);
// eslint-disable-next-line promise/no-promise-in-callback
human.detect(tensor).then((result) => {
if (result && result.face && result.face.length > 0) {
for (let i = 0; i < result.face.length; i++) {
const face = result.face[i];
const emotion = face.emotion?.reduce((prev, curr) => (prev.score > curr.score ? prev : curr));
log.data(`detected face: #${i} boxScore:${face.boxScore} faceScore:${face.faceScore} age:${face.age} genderScore:${face.genderScore} gender:${face.gender} emotionScore:${emotion?.score} emotion:${emotion?.emotion} iris:${face.iris}`);
}
} else {
log.data(' Face: N/A');
}
});
}
initial = false;
});
// alternatively to triggering every 5sec sec, simply trigger next frame as fast as possible
// setImmediate(() => process());
}
async function main() {
camera.list((list) => {
log.data('detected camera:', list);
});
await human.load();
detect();
}
log.header();
main();

217
demo/nodejs/node.js Normal file
View File

@ -0,0 +1,217 @@
/**
* Human demo for NodeJS
*/
const log = require('@vladmandic/pilogger');
const fs = require('fs');
const path = require('path');
const process = require('process');
let fetch; // fetch is dynamically imported later
// eslint-disable-next-line import/no-extraneous-dependencies, no-unused-vars, @typescript-eslint/no-unused-vars
const tf = require('@tensorflow/tfjs-node'); // in nodejs environments tfjs-node is required to be loaded before human
// const faceapi = require('@vladmandic/face-api'); // use this when human is installed as module (majority of use cases)
const Human = require('../../dist/human.node.js'); // use this when using human in dev mode
let human = null;
const myConfig = {
// backend: 'tensorflow',
modelBasePath: 'file://models/',
debug: true,
async: false,
filter: {
enabled: true,
flip: true,
},
face: {
enabled: true,
detector: { enabled: true, rotation: false },
mesh: { enabled: true },
iris: { enabled: true },
description: { enabled: true },
emotion: { enabled: true },
},
hand: {
enabled: true,
},
// body: { modelPath: 'blazepose.json', enabled: true },
body: { enabled: true },
object: { enabled: true },
};
async function init() {
// create instance of human
human = new Human.Human(myConfig);
// wait until tf is ready
await human.tf.ready();
// pre-load models
log.info('Human:', human.version);
// log.info('Active Configuration', human.config);
await human.load();
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
log.info('Loaded:', loaded);
// log.info('Memory state:', human.tf.engine().memory());
log.data(tf.backend()['binding'] ? tf.backend()['binding']['TF_Version'] : null);
}
async function detect(input) {
// read input image file and create tensor to be used for processing
let buffer;
log.info('Loading image:', input);
if (input.startsWith('http:') || input.startsWith('https:')) {
const res = await fetch(input);
if (res && res.ok) buffer = await res.buffer();
else log.error('Invalid image URL:', input, res.status, res.statusText, res.headers.get('content-type'));
} else {
buffer = fs.readFileSync(input);
}
// decode image using tfjs-node so we don't need external depenencies
// can also be done using canvas.js or some other 3rd party image library
if (!buffer) return {};
const tensor = human.tf.tidy(() => {
const decode = human.tf.node.decodeImage(buffer, 3);
let expand;
if (decode.shape[2] === 4) { // input is in rgba format, need to convert to rgb
const channels = human.tf.split(decode, 4, 2); // tf.split(tensor, 4, 2); // split rgba to channels
const rgb = human.tf.stack([channels[0], channels[1], channels[2]], 2); // stack channels back to rgb and ignore alpha
expand = human.tf.reshape(rgb, [1, decode.shape[0], decode.shape[1], 3]); // move extra dim from the end of tensor and use it as batch number instead
} else {
expand = human.tf.expandDims(decode, 0);
}
const cast = human.tf.cast(expand, 'float32');
return cast;
});
// image shape contains image dimensions and depth
log.state('Processing:', tensor['shape']);
// run actual detection
let result;
try {
result = await human.detect(tensor, myConfig);
} catch (err) {
log.error('caught');
}
// dispose image tensor as we no longer need it
human.tf.dispose(tensor);
// print data to console
log.data('Results:');
if (result && result.face && result.face.length > 0) {
for (let i = 0; i < result.face.length; i++) {
const face = result.face[i];
const emotion = face.emotion.reduce((prev, curr) => (prev.score > curr.score ? prev : curr));
log.data(` Face: #${i} boxScore:${face.boxScore} faceScore:${face.faceScore} age:${face.age} genderScore:${face.genderScore} gender:${face.gender} emotionScore:${emotion.score} emotion:${emotion.emotion} iris:${face.iris}`);
}
} else {
log.data(' Face: N/A');
}
if (result && result.body && result.body.length > 0) {
for (let i = 0; i < result.body.length; i++) {
const body = result.body[i];
log.data(` Body: #${i} score:${body.score} keypoints:${body.keypoints?.length}`);
}
} else {
log.data(' Body: N/A');
}
if (result && result.hand && result.hand.length > 0) {
for (let i = 0; i < result.hand.length; i++) {
const hand = result.hand[i];
log.data(` Hand: #${i} score:${hand.score} keypoints:${hand.keypoints?.length}`);
}
} else {
log.data(' Hand: N/A');
}
if (result && result.gesture && result.gesture.length > 0) {
for (let i = 0; i < result.gesture.length; i++) {
const [key, val] = Object.entries(result.gesture[i]);
log.data(` Gesture: ${key[0]}#${key[1]} gesture:${val[1]}`);
}
} else {
log.data(' Gesture: N/A');
}
if (result && result.object && result.object.length > 0) {
for (let i = 0; i < result.object.length; i++) {
const object = result.object[i];
log.data(` Object: #${i} score:${object.score} label:${object.label}`);
}
} else {
log.data(' Object: N/A');
}
// print data to console
if (result) {
// invoke persons getter
const persons = result.persons;
// write result objects to file
// fs.writeFileSync('result.json', JSON.stringify(result, null, 2));
log.data('Persons:');
for (let i = 0; i < persons.length; i++) {
const face = persons[i].face;
const faceTxt = face ? `score:${face.score} age:${face.age} gender:${face.gender} iris:${face.iris}` : null;
const body = persons[i].body;
const bodyTxt = body ? `score:${body.score} keypoints:${body.keypoints?.length}` : null;
log.data(` #${i}: Face:${faceTxt} Body:${bodyTxt} LeftHand:${persons[i].hands.left ? 'yes' : 'no'} RightHand:${persons[i].hands.right ? 'yes' : 'no'} Gestures:${persons[i].gestures.length}`);
}
}
return result;
}
async function test() {
process.on('unhandledRejection', (err) => {
// @ts-ignore // no idea if exception message is compelte
log.error(err?.message || err || 'no error message');
});
// test with embedded full body image
let result;
log.state('Processing embedded warmup image: face');
myConfig.warmup = 'face';
result = await human.warmup(myConfig);
log.state('Processing embedded warmup image: full');
myConfig.warmup = 'full';
result = await human.warmup(myConfig);
// no need to print results as they are printed to console during detection from within the library due to human.config.debug set
return result;
}
async function main() {
log.configure({ inspect: { breakLength: 265 } });
log.header();
log.info('Current folder:', process.env.PWD);
fetch = (await import('node-fetch')).default;
await init();
const f = process.argv[2];
if (process.argv.length !== 3) {
log.warn('Parameters: <input image | folder> missing');
await test();
} else if (!fs.existsSync(f) && !f.startsWith('http')) {
log.error(`File not found: ${process.argv[2]}`);
} else {
if (fs.existsSync(f)) {
const stat = fs.statSync(f);
if (stat.isDirectory()) {
const dir = fs.readdirSync(f);
for (const file of dir) {
await detect(path.join(f, file));
}
} else {
await detect(f);
}
} else {
await detect(f);
}
}
}
main();

View File

@ -0,0 +1,78 @@
const fs = require('fs');
const path = require('path');
const process = require('process');
const log = require('@vladmandic/pilogger');
const canvas = require('canvas');
// const tf = require('@tensorflow/tfjs-node-gpu'); // for nodejs, `tfjs-node` or `tfjs-node-gpu` should be loaded before using Human
const Human = require('../../dist/human.node-gpu.js'); // this is 'const Human = require('../dist/human.node-gpu.js').default;'
const config = { // just enable all and leave default settings
debug: true,
async: false,
cacheSensitivity: 0,
face: { enabled: true, detector: { maxDetected: 20 } },
object: { enabled: true },
gesture: { enabled: true },
hand: { enabled: true },
body: { enabled: true, modelPath: 'https://vladmandic.github.io/human-models/models/movenet-multipose.json' },
};
async function main() {
log.header();
globalThis.Canvas = canvas.Canvas; // patch global namespace with canvas library
globalThis.ImageData = canvas.ImageData; // patch global namespace with canvas library
const human = new Human.Human(config); // create instance of human
log.info('Human:', human.version);
const configErrors = await human.validate();
if (configErrors.length > 0) log.error('Configuration errors:', configErrors);
await human.load(); // pre-load models
log.info('Loaded models:', Object.keys(human.models).filter((a) => human.models[a]));
const inDir = process.argv[2];
const outDir = process.argv[3];
if (process.argv.length !== 4) {
log.error('Parameters: <input-directory> <output-directory> missing');
return;
}
if (!fs.existsSync(inDir) || !fs.statSync(inDir).isDirectory() || !fs.existsSync(outDir) || !fs.statSync(outDir).isDirectory()) {
log.error('Invalid directory specified:', 'input:', fs.existsSync(inDir) ?? fs.statSync(inDir).isDirectory(), 'output:', fs.existsSync(outDir) ?? fs.statSync(outDir).isDirectory());
return;
}
const dir = fs.readdirSync(inDir);
const images = dir.filter((f) => fs.statSync(path.join(inDir, f)).isFile() && (f.toLocaleLowerCase().endsWith('.jpg') || f.toLocaleLowerCase().endsWith('.jpeg')));
log.info(`Processing folder: ${inDir} entries:`, dir.length, 'images', images.length);
for (const image of images) {
const inFile = path.join(inDir, image);
const buffer = fs.readFileSync(inFile);
const tensor = human.tf.tidy(() => {
const decode = human.tf.node.decodeImage(buffer, 3);
const expand = human.tf.expandDims(decode, 0);
const cast = human.tf.cast(expand, 'float32');
return cast;
});
log.state('Loaded image:', inFile, tensor.shape);
const result = await human.detect(tensor);
human.tf.dispose(tensor);
log.data(`Detected: ${image}:`, 'Face:', result.face.length, 'Body:', result.body.length, 'Hand:', result.hand.length, 'Objects:', result.object.length, 'Gestures:', result.gesture.length);
const outputCanvas = new canvas.Canvas(tensor.shape[2], tensor.shape[1]); // create canvas
const outputCtx = outputCanvas.getContext('2d');
const inputImage = await canvas.loadImage(buffer); // load image using canvas library
outputCtx.drawImage(inputImage, 0, 0); // draw input image onto canvas
// @ts-ignore
human.draw.all(outputCanvas, result); // use human build-in method to draw results as overlays on canvas
const outFile = path.join(outDir, image);
const outStream = fs.createWriteStream(outFile); // write canvas to new image file
outStream.on('finish', () => log.state('Output image:', outFile, outputCanvas.width, outputCanvas.height));
outStream.on('error', (err) => log.error('Output error:', outFile, err));
const stream = outputCanvas.createJPEGStream({ quality: 0.5, progressive: true, chromaSubsampling: true });
// @ts-ignore
stream.pipe(outStream);
}
}
main();

36
demo/offline.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Human: Offline</title>
<meta name="viewport" content="width=device-width, shrink-to-fit=yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="Human">
<meta name="keywords" content="Human">
<meta name="description" content="Human; Author: Vladimir Mandic <mandic00@live.com>">
<meta name="msapplication-tooltip" content="Human; Author: Vladimir Mandic <mandic00@live.com>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="manifest.webmanifest">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="icon" sizes="256x256" href="../assets/icon.png">
<link rel="apple-touch-icon" href="../assets/icon.png">
<link rel="apple-touch-startup-image" href="../assets/icon.png">
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../assets/lato-light.woff2') }
body { font-family: 'Lato', 'Segoe UI'; font-size: 16px; font-variant: small-caps; background: black; color: #ebebeb; }
h1 { font-size: 2rem; margin-top: 1.2rem; font-weight: bold; }
a { color: white; }
a:link { color: lightblue; text-decoration: none; }
a:hover { color: lightskyblue; text-decoration: none; }
.row { width: 90vw; margin: auto; margin-top: 100px; text-align: center; }
</style>
</head>
<body>
<div class="row text-center">
<h1>
<a href="/">Human: Offline</a><br>
<img alt="icon" src="../assets/icon.png">
</h1>
</div>
</body>
</html>

View File

@ -0,0 +1,5 @@
# Human Demo in TypeScript for Browsers
Simple demo app that can be used as a quick-start guide for use of `Human` in browser environments
- `index.ts` is compiled to `index.js` which is loaded from `index.html`

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Human</title>
<meta name="viewport" content="width=device-width" id="viewport">
<meta name="keywords" content="Human">
<meta name="application-name" content="Human">
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="../manifest.webmanifest">
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../../assets/icon.png">
<script src="./index.js" type="module"></script>
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 16px; font-variant: small-caps; }
body { margin: 0; background: black; color: white; overflow-x: hidden; width: 100vw; height: 100vh; }
body::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<canvas id="canvas" style="margin: 0 auto; width: 100%"></canvas>
<video id="video" playsinline style="display: none"></video>
<pre id="status" style="position: absolute; top: 12px; right: 20px; background-color: grey; padding: 8px; box-shadow: 2px 2px black"></pre>
<pre id="log" style="padding: 8px"></pre>
<div id="performance" style="position: absolute; bottom: 1rem; width: 100%; padding: 8px; font-size: 0.8rem;"></div>
</body>
</html>

17
demo/typescript/index.js Normal file
View File

@ -0,0 +1,17 @@
/*
Human
homepage: <https://github.com/vladmandic/human>
author: <https://github.com/vladmandic>'
*/
import{Human as p}from"../../dist/human.esm.js";var w={modelBasePath:"../../models",filter:{enabled:!0,equalization:!1},face:{enabled:!0,detector:{rotation:!1},mesh:{enabled:!0},attention:{enabled:!1},iris:{enabled:!0},description:{enabled:!0},emotion:{enabled:!0}},body:{enabled:!0},hand:{enabled:!0},object:{enabled:!1},gesture:{enabled:!0}},t=new p(w);t.env.perfadd=!1;t.draw.options.font='small-caps 18px "Lato"';t.draw.options.lineHeight=20;var e={video:document.getElementById("video"),canvas:document.getElementById("canvas"),log:document.getElementById("log"),fps:document.getElementById("status"),perf:document.getElementById("performance")},i={detect:0,draw:0,tensors:0},d={detect:0,draw:0},s=(...a)=>{e.log.innerText+=a.join(" ")+`
`,console.log(...a)},r=a=>e.fps.innerText=a,b=a=>e.perf.innerText="tensors:"+t.tf.memory().numTensors+" | performance: "+JSON.stringify(a).replace(/"|{|}/g,"").replace(/,/g," | ");async function h(){r("starting webcam...");let a={audio:!1,video:{facingMode:"user",resizeMode:"none",width:{ideal:document.body.clientWidth},height:{ideal:document.body.clientHeight}}},n=await navigator.mediaDevices.getUserMedia(a),m=new Promise(f=>{e.video.onloadeddata=()=>f(!0)});e.video.srcObject=n,e.video.play(),await m,e.canvas.width=e.video.videoWidth,e.canvas.height=e.video.videoHeight;let o=n.getVideoTracks()[0],g=o.getCapabilities?o.getCapabilities():"",v=o.getSettings?o.getSettings():"",u=o.getConstraints?o.getConstraints():"";s("video:",e.video.videoWidth,e.video.videoHeight,o.label,{stream:n,track:o,settings:v,constraints:u,capabilities:g}),e.canvas.onclick=()=>{e.video.paused?e.video.play():e.video.pause()}}async function c(){if(!e.video.paused){await t.detect(e.video);let n=t.tf.memory().numTensors;n-i.tensors!==0&&s("allocated tensors:",n-i.tensors),i.tensors=n}let a=t.now();d.detect=1e3/(a-i.detect),i.detect=a,requestAnimationFrame(c)}async function l(){if(!e.video.paused){let n=await t.next(t.result);await t.draw.canvas(e.video,e.canvas),await t.draw.all(e.canvas,n),console.log(e.canvas.width,e.canvas.height),b(n.performance)}let a=t.now();d.draw=1e3/(a-i.draw),i.draw=a,r(e.video.paused?"paused":`fps: ${d.detect.toFixed(1).padStart(5," ")} detect | ${d.draw.toFixed(1).padStart(5," ")} draw`),setTimeout(l,30)}async function y(){s("human version:",t.version,"| tfjs version:",t.tf.version["tfjs-core"]),s("platform:",t.env.platform,"| agent:",t.env.agent),r("loading..."),await t.load(),s("backend:",t.tf.getBackend(),"| available:",t.env.backends),s("loaded models:",Object.values(t.models).filter(a=>a!==null).length),r("initializing..."),await t.warmup(),await h(),await c(),await l()}window.onload=y;
/**
* Human demo for browsers
* @default Human Library
* @summary <https://github.com/vladmandic/human>
* @author <https://github.com/vladmandic>
* @copyright <https://github.com/vladmandic>
* @license MIT
*/
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

115
demo/typescript/index.ts Normal file
View File

@ -0,0 +1,115 @@
/**
* Human demo for browsers
* @default Human Library
* @summary <https://github.com/vladmandic/human>
* @author <https://github.com/vladmandic>
* @copyright <https://github.com/vladmandic>
* @license MIT
*/
import { Human, Config } from '../../dist/human.esm.js'; // equivalent of @vladmandic/Human
const humanConfig: Partial<Config> = { // user configuration for human, used to fine-tune behavior
// backend: 'webgpu' as const,
// async: true,
modelBasePath: '../../models',
filter: { enabled: true, equalization: false },
// cacheSensitivity: 0,
face: { enabled: true, detector: { rotation: false }, mesh: { enabled: true }, attention: { enabled: false }, iris: { enabled: true }, description: { enabled: true }, emotion: { enabled: true } },
body: { enabled: true },
hand: { enabled: true },
object: { enabled: false },
gesture: { enabled: true },
};
const human = new Human(humanConfig); // create instance of human with overrides from user configuration
human.env['perfadd'] = false; // is performance data showing instant or total values
human.draw.options.font = 'small-caps 18px "Lato"'; // set font used to draw labels when using draw methods
human.draw.options.lineHeight = 20;
const dom = { // grab instances of dom objects so we dont have to look them up later
video: document.getElementById('video') as HTMLVideoElement,
canvas: document.getElementById('canvas') as HTMLCanvasElement,
log: document.getElementById('log') as HTMLPreElement,
fps: document.getElementById('status') as HTMLPreElement,
perf: document.getElementById('performance') as HTMLDivElement,
};
const timestamp = { detect: 0, draw: 0, tensors: 0 }; // holds information used to calculate performance and possible memory leaks
const fps = { detect: 0, draw: 0 }; // holds calculated fps information for both detect and screen refresh
const log = (...msg) => { // helper method to output messages
dom.log.innerText += msg.join(' ') + '\n';
// eslint-disable-next-line no-console
console.log(...msg);
};
const status = (msg) => dom.fps.innerText = msg; // print status element
const perf = (msg) => dom.perf.innerText = 'tensors:' + human.tf.memory().numTensors + ' | performance: ' + JSON.stringify(msg).replace(/"|{|}/g, '').replace(/,/g, ' | '); // print performance element
async function webCam() { // initialize webcam
status('starting webcam...');
// @ts-ignore resizeMode is not yet defined in tslib
const options: MediaStreamConstraints = { audio: false, video: { facingMode: 'user', resizeMode: 'none', width: { ideal: document.body.clientWidth }, height: { ideal: document.body.clientHeight } } };
const stream: MediaStream = await navigator.mediaDevices.getUserMedia(options);
const ready = new Promise((resolve) => { dom.video.onloadeddata = () => resolve(true); });
dom.video.srcObject = stream;
dom.video.play();
await ready;
dom.canvas.width = dom.video.videoWidth;
dom.canvas.height = dom.video.videoHeight;
const track: MediaStreamTrack = stream.getVideoTracks()[0];
const capabilities: MediaTrackCapabilities | string = track.getCapabilities ? track.getCapabilities() : '';
const settings: MediaTrackSettings | string = track.getSettings ? track.getSettings() : '';
const constraints: MediaTrackConstraints | string = track.getConstraints ? track.getConstraints() : '';
log('video:', dom.video.videoWidth, dom.video.videoHeight, track.label, { stream, track, settings, constraints, capabilities });
dom.canvas.onclick = () => { // pause when clicked on screen and resume on next click
if (dom.video.paused) dom.video.play();
else dom.video.pause();
};
}
async function detectionLoop() { // main detection loop
if (!dom.video.paused) {
// console.log('profiling data:', await human.profile(dom.video));
await human.detect(dom.video); // actual detection; were not capturing output in a local variable as it can also be reached via human.result
const tensors = human.tf.memory().numTensors; // check current tensor usage for memory leaks
if (tensors - timestamp.tensors !== 0) log('allocated tensors:', tensors - timestamp.tensors); // printed on start and each time there is a tensor leak
timestamp.tensors = tensors;
}
const now = human.now();
fps.detect = 1000 / (now - timestamp.detect);
timestamp.detect = now;
requestAnimationFrame(detectionLoop); // start new frame immediately
}
async function drawLoop() { // main screen refresh loop
if (!dom.video.paused) {
const interpolated = await human.next(human.result); // smoothen result using last-known results
await human.draw.canvas(dom.video, dom.canvas); // draw canvas to screen
await human.draw.all(dom.canvas, interpolated); // draw labels, boxes, lines, etc.
console.log(dom.canvas.width, dom.canvas.height);
perf(interpolated.performance); // write performance data
}
const now = human.now();
fps.draw = 1000 / (now - timestamp.draw);
timestamp.draw = now;
status(dom.video.paused ? 'paused' : `fps: ${fps.detect.toFixed(1).padStart(5, ' ')} detect | ${fps.draw.toFixed(1).padStart(5, ' ')} draw`); // write status
// requestAnimationFrame(drawLoop); // refresh at screen refresh rate
setTimeout(drawLoop, 30); // use to slow down refresh from max refresh rate to target of 30 fps
}
async function main() { // main entry point
log('human version:', human.version, '| tfjs version:', human.tf.version['tfjs-core']);
log('platform:', human.env.platform, '| agent:', human.env.agent);
status('loading...');
await human.load(); // preload all models
log('backend:', human.tf.getBackend(), '| available:', human.env.backends);
log('loaded models:', Object.values(human.models).filter((model) => model !== null).length);
status('initializing...');
await human.warmup(); // warmup function to initialize backend for future faster detection
await webCam(); // start webcam
await detectionLoop(); // start detection loop
await drawLoop(); // start draw loop
}
window.onload = main;

2536
dist/human.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

2536
dist/human.esm-nobundle.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

839
dist/human.esm-nobundle.js vendored Normal file

File diff suppressed because one or more lines are too long

7
dist/human.esm-nobundle.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2536
dist/human.esm.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

51903
dist/human.esm.js vendored Normal file

File diff suppressed because one or more lines are too long

7
dist/human.esm.js.map vendored Normal file

File diff suppressed because one or more lines are too long

8056
dist/human.js vendored Normal file

File diff suppressed because one or more lines are too long

2536
dist/human.node-gpu.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

838
dist/human.node-gpu.js vendored Normal file

File diff suppressed because one or more lines are too long

2536
dist/human.node-wasm.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

838
dist/human.node-wasm.js vendored Normal file

File diff suppressed because one or more lines are too long

2536
dist/human.node.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

838
dist/human.node.js vendored Normal file

File diff suppressed because one or more lines are too long

7225
dist/tfjs.esm.js vendored Normal file

File diff suppressed because one or more lines are too long

7
dist/tfjs.version.js vendored Normal file
View File

@ -0,0 +1,7 @@
/*
Human
homepage: <https://github.com/vladmandic/human>
author: <https://github.com/vladmandic>'
*/
var e="3.16.0";var s="3.16.0";var t="3.16.0";var r="3.16.0";var l="3.16.0";var i="3.16.0";var a="3.16.0";var V={tfjs:e,"tfjs-core":s,"tfjs-data":t,"tfjs-layers":r,"tfjs-converter":l,"tfjs-backend-webgl":i,"tfjs-backend-wasm":a};export{V as version};

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

18
human.service Normal file
View File

@ -0,0 +1,18 @@
[Unit]
Description=human
After=network.target network-online.target
[Service]
Type=simple
Environment="NODE_ENV=production"
ExecStart=<path-to-node> <your-project-folder>/node_modules/@vladmandic/build/src/build.js --profile development
WorkingDirectory=<your-project-folder>
StandardOutput=inherit
StandardError=inherit
Restart=always
RestartSec=300
User=vlado
StandardOutput=null
[Install]
WantedBy=multi-user.target

310
models/README.md Normal file
View File

@ -0,0 +1,310 @@
# Human Library: Models
For details see Wiki:
- [**List of Models & Credits**](https://github.com/vladmandic/human/wiki/Models)
## Model signatures:
```js
INFO: graph model: /home/vlado/dev/human/models/iris.json
INFO: created on: 2020-10-12T18:46:47.060Z
INFO: metadata: { generatedBy: 'https://github.com/google/mediapipe', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'input_1:0', dtype: 'DT_FLOAT', shape: [ -1, 64, 64, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ -1, 1, 1, 228 ] }
INFO: tensors: 191
DATA: weights: {
files: [ 'iris.bin' ],
size: { disk: 2599092, memory: 2599092 },
count: { total: 191, float32: 189, int32: 2 },
quantized: { none: 191 },
values: { total: 649773, float32: 649764, int32: 9 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
convolution: [ '_FusedConv2D', 'DepthwiseConv2dNative', 'MaxPool' ],
arithmetic: [ 'AddV2' ],
basic_math: [ 'Prelu' ],
transformation: [ 'Pad' ],
slice_join: [ 'ConcatV2' ]
}
INFO: graph model: /home/vlado/dev/human/models/facemesh.json
INFO: created on: 2020-10-12T18:46:46.944Z
INFO: metadata: { generatedBy: 'https://github.com/google/mediapipe', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'input_1:0', dtype: 'DT_FLOAT', shape: [ 1, 192, 192, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity_1:0', dytpe: 'DT_FLOAT', shape: [ 1, 266 ] }
{ id: 1, name: 'Identity_2:0', dytpe: 'DT_FLOAT', shape: [ 1, 1 ] }
{ id: 2, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ 1, 1404 ] }
INFO: tensors: 118
DATA: weights: {
files: [ 'facemesh.bin' ],
size: { disk: 2955780, memory: 2955780 },
count: { total: 118, float32: 114, int32: 4 },
quantized: { none: 118 },
values: { total: 738945, float32: 738919, int32: 26 }
}
DATA: kernel ops: {
graph: [ 'Placeholder', 'Const', 'NoOp', 'Identity' ],
convolution: [ '_FusedConv2D', 'DepthwiseConv2dNative', 'MaxPool' ],
arithmetic: [ 'AddV2' ],
basic_math: [ 'Prelu', 'Sigmoid' ],
transformation: [ 'Pad', 'Reshape' ]
}
INFO: graph model: /home/vlado/dev/human/models/emotion.json
INFO: created on: 2020-11-05T20:11:29.740Z
INFO: metadata: { generatedBy: 'https://github.com/oarriaga/face_classification', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'input_1:0', dtype: 'DT_FLOAT', shape: [ -1, 64, 64, 1 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ -1, 7 ] }
INFO: tensors: 23
DATA: weights: {
files: [ 'emotion.bin' ],
size: { disk: 820516, memory: 820516 },
count: { total: 23, float32: 22, int32: 1 },
quantized: { none: 23 },
values: { total: 205129, float32: 205127, int32: 2 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
convolution: [ '_FusedConv2D', 'DepthwiseConv2dNative', 'MaxPool' ],
arithmetic: [ 'AddV2' ],
basic_math: [ 'Relu' ],
reduction: [ 'Mean' ],
normalization: [ 'Softmax' ]
}
INFO: graph model: /home/vlado/dev/human/models/faceres.json
INFO: created on: 2021-03-21T14:12:59.863Z
INFO: metadata: { generatedBy: 'https://github.com/HSE-asavchenko/HSE_FaceRec_tf', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'input_1', dtype: 'DT_FLOAT', shape: [ -1, 224, 224, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'gender_pred/Sigmoid:0', dytpe: 'DT_FLOAT', shape: [ 1, 1 ] }
{ id: 1, name: 'global_pooling/Mean', dytpe: 'DT_FLOAT', shape: [ 1, 1024 ] }
{ id: 2, name: 'age_pred/Softmax:0', dytpe: 'DT_FLOAT', shape: [ 1, 100 ] }
INFO: tensors: 128
DATA: weights: {
files: [ 'faceres.bin' ],
size: { disk: 6978814, memory: 13957620 },
count: { total: 128, float32: 127, int32: 1 },
quantized: { float16: 127, none: 1 },
values: { total: 3489405, float32: 3489403, int32: 2 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder' ],
convolution: [ 'Conv2D', 'DepthwiseConv2dNative' ],
arithmetic: [ 'Add', 'Minimum', 'Maximum', 'Mul' ],
basic_math: [ 'Relu', 'Sigmoid' ],
reduction: [ 'Mean' ],
matrices: [ '_FusedMatMul' ],
normalization: [ 'Softmax' ]
}
INFO: graph model: /home/vlado/dev/human/models/blazeface.json
INFO: created on: 2020-10-15T19:57:26.419Z
INFO: metadata: { generatedBy: 'https://github.com/google/mediapipe', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'input:0', dtype: 'DT_FLOAT', shape: [ 1, 256, 256, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity_3:0', dytpe: 'DT_FLOAT', shape: [ 1, 384, 16 ] }
{ id: 1, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ 1, 512, 1 ] }
{ id: 2, name: 'Identity_1:0', dytpe: 'DT_FLOAT', shape: [ 1, 384, 1 ] }
{ id: 3, name: 'Identity_2:0', dytpe: 'DT_FLOAT', shape: [ 1, 512, 16 ] }
INFO: tensors: 112
DATA: weights: {
files: [ 'blazeface.bin' ],
size: { disk: 538928, memory: 538928 },
count: { total: 112, float32: 106, int32: 6 },
quantized: { none: 112 },
values: { total: 134732, float32: 134704, int32: 28 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
convolution: [ '_FusedConv2D', 'DepthwiseConv2dNative', 'MaxPool' ],
arithmetic: [ 'AddV2' ],
basic_math: [ 'Relu' ],
transformation: [ 'Pad', 'Reshape' ]
}
INFO: graph model: /home/vlado/dev/human/models/mb3-centernet.json
INFO: created on: 2021-05-19T11:50:13.013Z
INFO: metadata: { generatedBy: 'https://github.com/610265158/mobilenetv3_centernet', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'tower_0/images', dtype: 'DT_FLOAT', shape: [ 1, 512, 512, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'tower_0/wh', dytpe: 'DT_FLOAT', shape: [ 1, 128, 128, 4 ] }
{ id: 1, name: 'tower_0/keypoints', dytpe: 'DT_FLOAT', shape: [ 1, 128, 128, 80 ] }
{ id: 2, name: 'tower_0/detections', dytpe: 'DT_FLOAT', shape: [ 1, 100, 6 ] }
INFO: tensors: 267
DATA: weights: {
files: [ 'mb3-centernet.bin' ],
size: { disk: 4030290, memory: 8060260 },
count: { total: 267, float32: 227, int32: 40 },
quantized: { float16: 227, none: 40 },
values: { total: 2015065, float32: 2014985, int32: 80 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
convolution: [ '_FusedConv2D', 'FusedDepthwiseConv2dNative', 'DepthwiseConv2dNative', 'Conv2D', 'MaxPool' ],
arithmetic: [ 'Mul', 'Add', 'FloorDiv', 'FloorMod', 'Sub' ],
basic_math: [ 'Relu6', 'Relu', 'Sigmoid' ],
reduction: [ 'Mean' ],
image: [ 'ResizeBilinear' ],
slice_join: [ 'ConcatV2', 'GatherV2', 'StridedSlice' ],
transformation: [ 'Reshape', 'Cast', 'ExpandDims' ],
logical: [ 'Equal' ],
evaluation: [ 'TopKV2' ]
}
INFO: graph model: /home/vlado/dev/human/models/movenet-lightning.json
INFO: created on: 2021-05-29T12:26:32.994Z
INFO: metadata: { generatedBy: 'https://tfhub.dev/google/movenet/singlepose/lightning/4', convertedBy: 'https://github.com/vladmandic', version: undefined }
INFO: model inputs based on signature
{ name: 'input:0', dtype: 'DT_INT32', shape: [ 1, 192, 192, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ 1, 1, 17, 3 ] }
INFO: tensors: 180
DATA: weights: {
files: [ 'movenet-lightning.bin' ],
size: { disk: 4650216, memory: 9300008 },
count: { total: 180, int32: 31, float32: 149 },
quantized: { none: 31, float16: 149 },
values: { total: 2325002, int32: 106, float32: 2324896 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
transformation: [ 'Cast', 'ExpandDims', 'Squeeze', 'Reshape' ],
slice_join: [ 'Unpack', 'Pack', 'GatherNd', 'ConcatV2' ],
arithmetic: [ 'Sub', 'Mul', 'AddV2', 'FloorDiv', 'SquaredDifference', 'RealDiv' ],
convolution: [ '_FusedConv2D', 'FusedDepthwiseConv2dNative', 'DepthwiseConv2dNative' ],
image: [ 'ResizeBilinear' ],
basic_math: [ 'Sigmoid', 'Sqrt' ],
reduction: [ 'ArgMax' ]
}
INFO: graph model: /home/vlado/dev/human/models/selfie.json
INFO: created on: 2021-06-04T13:46:56.904Z
INFO: metadata: { generatedBy: 'https://github.com/PINTO0309/PINTO_model_zoo/tree/main/109_Selfie_Segmentation', convertedBy: 'https://github.com/vladmandic', version: '561.undefined' }
INFO: model inputs based on signature
{ name: 'input_1:0', dtype: 'DT_FLOAT', shape: [ 1, 256, 256, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'activation_10:0', dytpe: 'DT_FLOAT', shape: [ 1, 256, 256, 1 ] }
INFO: tensors: 136
DATA: weights: {
files: [ 'selfie.bin' ],
size: { disk: 212886, memory: 425732 },
count: { total: 136, int32: 4, float32: 132 },
quantized: { none: 4, float16: 132 },
values: { total: 106433, int32: 10, float32: 106423 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder' ],
convolution: [ 'Conv2D', 'DepthwiseConv2dNative', 'AvgPool', 'Conv2DBackpropInput' ],
arithmetic: [ 'Add', 'Mul', 'AddV2', 'AddN' ],
basic_math: [ 'Relu6', 'Relu', 'Sigmoid' ],
image: [ 'ResizeBilinear' ]
}
INFO: graph model: /home/vlado/dev/human/models/handtrack.json
INFO: created on: 2021-09-21T12:09:47.583Z
INFO: metadata: { generatedBy: 'https://github.com/victordibia/handtracking', convertedBy: 'https://github.com/vladmandic', version: '561.undefined' }
INFO: model inputs based on signature
{ name: 'input_tensor:0', dtype: 'DT_UINT8', shape: [ 1, 320, 320, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity_2:0', dytpe: 'DT_FLOAT', shape: [ 1, 100 ] }
{ id: 1, name: 'Identity_4:0', dytpe: 'DT_FLOAT', shape: [ 1, 100 ] }
{ id: 2, name: 'Identity_6:0', dytpe: 'DT_FLOAT', shape: [ 1, 12804, 4 ] }
{ id: 3, name: 'Identity_1:0', dytpe: 'DT_FLOAT', shape: [ 1, 100, 4 ] }
{ id: 4, name: 'Identity_3:0', dytpe: 'DT_FLOAT', shape: [ 1, 100, 8 ] }
{ id: 5, name: 'Identity_5:0', dytpe: 'DT_FLOAT', shape: [ 1 ] }
{ id: 6, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ 1, 100 ] }
{ id: 7, name: 'Identity_7:0', dytpe: 'DT_FLOAT', shape: [ 1, 12804, 8 ] }
INFO: tensors: 619
DATA: weights: {
files: [ 'handtrack.bin' ],
size: { disk: 2964837, memory: 11846016 },
count: { total: 619, int32: 347, float32: 272 },
quantized: { none: 347, uint8: 272 },
values: { total: 2961504, int32: 1111, float32: 2960393 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity', 'Shape', 'NoOp' ],
control: [ 'TensorListReserve', 'Enter', 'TensorListFromTensor', 'Merge', 'LoopCond', 'Switch', 'Exit', 'TensorListStack', 'NextIteration', 'TensorListSetItem', 'TensorListGetItem' ],
logical: [ 'Less', 'LogicalAnd', 'Select', 'Greater', 'GreaterEqual' ],
convolution: [ '_FusedConv2D', 'FusedDepthwiseConv2dNative', 'DepthwiseConv2dNative' ],
arithmetic: [ 'AddV2', 'Mul', 'Sub', 'Minimum', 'Maximum' ],
transformation: [ 'Cast', 'ExpandDims', 'Squeeze', 'Reshape', 'Pad' ],
slice_join: [ 'Unpack', 'StridedSlice', 'Pack', 'ConcatV2', 'Slice', 'GatherV2', 'Split' ],
image: [ 'ResizeBilinear' ],
basic_math: [ 'Reciprocal', 'Sigmoid', 'Exp' ],
matrices: [ 'Transpose' ],
dynamic: [ 'NonMaxSuppressionV5', 'Where' ],
creation: [ 'Fill', 'Range' ],
evaluation: [ 'TopKV2' ],
reduction: [ 'Sum' ]
}
INFO: graph model: /home/vlado/dev/human/models/antispoof.json
INFO: created on: 2021-10-13T14:20:27.100Z
INFO: metadata: { generatedBy: 'https://www.kaggle.com/anku420/fake-face-detection', convertedBy: 'https://github.com/vladmandic', version: '716.undefined' }
INFO: model inputs based on signature
{ name: 'conv2d_input', dtype: 'DT_FLOAT', shape: [ -1, 128, 128, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'activation_4', dytpe: 'DT_FLOAT', shape: [ -1, 1 ] }
INFO: tensors: 11
DATA: weights: {
files: [ 'antispoof.bin' ],
size: { disk: 853098, memory: 1706188 },
count: { total: 11, float32: 10, int32: 1 },
quantized: { float16: 10, none: 1 },
values: { total: 426547, float32: 426545, int32: 2 }
}
DATA: kernel ops: { graph: [ 'Const', 'Placeholder', 'Identity' ], convolution: [ '_FusedConv2D', 'MaxPool' ], basic_math: [ 'Relu', 'Sigmoid' ], transformation: [ 'Reshape' ], matrices: [ '_FusedMatMul' ] }
INFO: graph model: /home/vlado/dev/human/models/handlandmark-full.json
INFO: created on: 2021-10-31T12:27:49.343Z
INFO: metadata: { generatedBy: 'https://github.com/google/mediapipe', convertedBy: 'https://github.com/vladmandic', version: '808.undefined' }
INFO: model inputs based on signature
{ name: 'input_1', dtype: 'DT_FLOAT', shape: [ 1, 224, 224, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'Identity_3:0', dytpe: 'DT_FLOAT', shape: [ 1, 63 ] }
{ id: 1, name: 'Identity:0', dytpe: 'DT_FLOAT', shape: [ 1, 63 ] }
{ id: 2, name: 'Identity_1:0', dytpe: 'DT_FLOAT', shape: [ 1, 1 ] }
{ id: 3, name: 'Identity_2:0', dytpe: 'DT_FLOAT', shape: [ 1, 1 ] }
INFO: tensors: 103
DATA: weights: {
files: [ 'handlandmark-full.bin' ],
size: { disk: 5431368, memory: 10862728 },
count: { total: 103, float32: 102, int32: 1 },
quantized: { float16: 102, none: 1 },
values: { total: 2715682, float32: 2715680, int32: 2 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
convolution: [ 'Conv2D', 'DepthwiseConv2dNative' ],
arithmetic: [ 'AddV2', 'AddN' ],
basic_math: [ 'Relu6', 'Sigmoid' ],
reduction: [ 'Mean' ],
matrices: [ '_FusedMatMul' ]
}
INFO: graph model: /home/vlado/dev/human/models/liveness.json
INFO: created on: 2021-11-09T12:39:11.760Z
INFO: metadata: { generatedBy: 'https://github.com/leokwu/livenessnet', convertedBy: 'https://github.com/vladmandic', version: '808.undefined' }
INFO: model inputs based on signature
{ name: 'conv2d_1_input', dtype: 'DT_FLOAT', shape: [ -1, 32, 32, 3 ] }
INFO: model outputs based on signature
{ id: 0, name: 'activation_6', dytpe: 'DT_FLOAT', shape: [ -1, 2 ] }
INFO: tensors: 23
DATA: weights: {
files: [ 'liveness.bin' ],
size: { disk: 592976, memory: 592976 },
count: { total: 23, float32: 22, int32: 1 },
quantized: { none: 23 },
values: { total: 148244, float32: 148242, int32: 2 }
}
DATA: kernel ops: {
graph: [ 'Const', 'Placeholder', 'Identity' ],
convolution: [ '_FusedConv2D', 'MaxPool' ],
arithmetic: [ 'Mul', 'Add', 'AddV2' ],
transformation: [ 'Reshape' ],
matrices: [ '_FusedMatMul' ],
normalization: [ 'Softmax' ]
}
```

BIN
models/antispoof.bin Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More