├── app.vue ├── .npmrc ├── assets ├── 1.png ├── 2.jpg ├── 3.png ├── 4.png └── qrcode.png ├── plugins ├── vconsole.client.ts └── tracking.client.ts ├── composables ├── useUserCamera.ts └── useFaceTrack.ts ├── tsconfig.json ├── nuxt.config.ts ├── uno.config.ts ├── .gitignore ├── package.json ├── pages ├── index.vue ├── result.vue └── face.vue └── README.md /app.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeJunMao/h5-face-track-demo/HEAD/assets/1.png -------------------------------------------------------------------------------- /assets/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeJunMao/h5-face-track-demo/HEAD/assets/2.jpg -------------------------------------------------------------------------------- /assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeJunMao/h5-face-track-demo/HEAD/assets/3.png -------------------------------------------------------------------------------- /assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeJunMao/h5-face-track-demo/HEAD/assets/4.png -------------------------------------------------------------------------------- /assets/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeJunMao/h5-face-track-demo/HEAD/assets/qrcode.png -------------------------------------------------------------------------------- /plugins/vconsole.client.ts: -------------------------------------------------------------------------------- 1 | import VConsole from "vconsole"; 2 | export default defineNuxtPlugin(() => { 3 | new VConsole(); 4 | }); 5 | -------------------------------------------------------------------------------- /plugins/tracking.client.ts: -------------------------------------------------------------------------------- 1 | import "tracking"; 2 | import "tracking/build/data/face-min"; 3 | export default defineNuxtPlugin(() => {}); 4 | -------------------------------------------------------------------------------- /composables/useUserCamera.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function useUserCamera() { 4 | return useUserMedia({ 5 | constraints: { video: { facingMode: "user" }, audio: false }, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | "compilerOptions": { 5 | "types": [ 6 | "tracking" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | ssr: false, 4 | devtools: { enabled: true }, 5 | modules: ["@vueuse/nuxt", "@vant/nuxt", "@unocss/nuxt"], 6 | }); 7 | -------------------------------------------------------------------------------- /uno.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presetIcons, presetUno } from "unocss"; 2 | 3 | export default defineConfig({ 4 | presets: [ 5 | presetUno(), 6 | presetIcons({ 7 | scale: 1.2, 8 | autoInstall: false 9 | }), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .nuxt 4 | .nitro 5 | .cache 6 | dist 7 | 8 | # Node dependencies 9 | node_modules 10 | 11 | # Logs 12 | logs 13 | *.log 14 | 15 | # Misc 16 | .DS_Store 17 | .fleet 18 | .idea 19 | 20 | # Local env files 21 | .env 22 | .env.* 23 | !.env.example 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare" 10 | }, 11 | "devDependencies": { 12 | "@iconify-json/tabler": "^1.1.77", 13 | "@nuxt/devtools": "latest", 14 | "@types/node": "^18.16.19", 15 | "@types/tracking": "^1.1.30", 16 | "@vant/nuxt": "^1.0.2", 17 | "nuxt": "^3.6.3" 18 | }, 19 | "dependencies": { 20 | "@unocss/nuxt": "^0.53.5", 21 | "@vueuse/core": "^10.2.1", 22 | "@vueuse/nuxt": "^10.2.1", 23 | "tracking": "^1.1.3", 24 | "unocss": "^0.53.5", 25 | "vant": "^4.6.2", 26 | "vconsole": "^3.15.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /pages/result.vue: -------------------------------------------------------------------------------- 1 | 4 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信 h5 人脸识别 Demo 2 | 3 | 利用 [getUserMedia](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia) 和 [tracking.js](https://trackingjs.com/) 实现调用摄像头并在页面内进行人脸识别 4 | 5 | | 截图 | | | | 6 | | --------------------------------------- | ------ | --------------------------------------- | -------- | 7 | | | 入口页 | | 授权弹窗 | 8 | | | 识别中 | | 识别结果 | 9 | 10 | ## Demo 11 | 12 | 13 | 14 | [https://facetest.kejun.me](https://facetest.kejun.me) 15 | 16 | > 无服务器,不会收集你的信息,放心扫码测试 17 | ## 快速开始 18 | 19 | ```bash 20 | pnpm install 21 | pnpm dev 22 | ``` 23 | 24 | 访问 http://localhost:3000 25 | 26 | ## 框架和库 27 | 28 | - Nuxt3 29 | - Vue3 30 | - VueUse 31 | - Vant 32 | - Unocss 33 | - tracking.js 34 | - VConsole 35 | 36 | 核心代码在 [useFaceTrack.ts](./composables/useFaceTrack.ts) 中 37 | 38 | ## 测试设备 39 | 40 | 测试标准为能正确获取摄像头视频流,能使用 tracking 识别人脸,能在结果页展示人脸 41 | 42 | 测试时,必须使用 **https** 协议或者 `127.0.0.1` 43 | 44 | 作者条件有限,如果你也测试通过了,欢迎 PR 添加你的测试结果 45 | 46 | - ✔️ 已通过 47 | - ❌ 未通过 48 | - ❓ 未测试 49 | 50 | | 设备 | 微信 | QQ | 企业微信 | 浏览器 | 51 | | ------------- | -------- | ------------ | --------------- | ----------------- | 52 | | iPhone 13 Pro | ✔️8.0.39 | ✔️8.9.58.612 | ✔️4.1.7(131309) | ✔️iOS 16.1 Safari | 53 | -------------------------------------------------------------------------------- /pages/face.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 91 | -------------------------------------------------------------------------------- /composables/useFaceTrack.ts: -------------------------------------------------------------------------------- 1 | export enum TIPS { 2 | DEFAULT = "未检测到人脸, 请将正面对准手机", 3 | SCANING = "正在识别,请保持姿势不变", 4 | } 5 | 6 | function mediaErrorCaptured(error: any) { 7 | const nameMap = { 8 | AbortError: "操作中止", 9 | NotAllowedError: "打开设备权限不足,原因是用户拒绝了媒体访问请求", 10 | NotFoundError: "找不到满足条件的设备", 11 | NotReadableError: 12 | "系统上某个硬件、浏览器或网页层面发生的错误导致设备无法被访问", 13 | OverConstrainedError: "指定的要求无法被设备满足", 14 | SecurityError: "安全错误,使用设备媒体被禁止", 15 | TypeError: "类型错误", 16 | NotSupportedError: "不支持的操作", 17 | NetworkError: "网络错误发生", 18 | TimeoutError: "操作超时", 19 | UnknownError: "因未知的瞬态的原因使操作失败)", 20 | ConstraintError: "条件没满足而导致事件失败的异常操作", 21 | }; 22 | const messageMap = { 23 | "permission denied": "麦克风、摄像头权限未开启,请检查后重试", 24 | "requested device not found": "未检测到摄像头", 25 | "could not start video source": "无法访问到摄像头", 26 | }; 27 | 28 | let nameErrorMsg = nameMap[error.name as keyof typeof nameMap]; 29 | if (!nameErrorMsg) { 30 | nameErrorMsg = 31 | messageMap[error.message.toLowerCase() as keyof typeof messageMap] ?? 32 | "未知错误"; 33 | } 34 | return nameErrorMsg; 35 | } 36 | 37 | export function useFaceTrack() { 38 | const video = ref(); 39 | const { stream, start, stop } = useUserCamera() 40 | const tips = ref(TIPS.DEFAULT); 41 | const tracker = shallowRef(); 42 | function initTracker() { 43 | tracker.value = new tracking.ObjectTracker("face"); 44 | tracker.value.setInitialScale(4); 45 | tracker.value.setStepSize(2); 46 | tracker.value.setEdgesDensity(0.1); 47 | tracking.track(video.value!, tracker.value); 48 | tracker.value.on("track", (event) => { 49 | if (event.data.length) { 50 | tips.value = TIPS.SCANING; 51 | } else { 52 | tips.value = TIPS.DEFAULT; 53 | } 54 | }); 55 | } 56 | 57 | async function init() { 58 | try { 59 | await start(); 60 | } catch (error) { 61 | tips.value = mediaErrorCaptured(error); 62 | } 63 | } 64 | 65 | async function tryPlay() { 66 | try { 67 | await video.value?.play(); 68 | } catch (error) {} 69 | } 70 | 71 | watchEffect(() => { 72 | if (video.value) { 73 | video.value.srcObject = stream.value!; 74 | nextTick(async () => { 75 | await tryPlay(); 76 | video.value?.addEventListener( 77 | "loadeddata", 78 | async () => { 79 | await tryPlay(); 80 | }, 81 | { 82 | once: true, 83 | } 84 | ); 85 | }); 86 | } 87 | }); 88 | 89 | onMounted(initTracker.bind(window)); 90 | onUnmounted(() => { 91 | tracker.value?.removeAllListeners(); 92 | stop(); 93 | }); 94 | 95 | return { 96 | video, 97 | stream, 98 | tips, 99 | init, 100 | tracker, 101 | }; 102 | } 103 | --------------------------------------------------------------------------------