Compare commits
5 Commits
feature/fa
...
57eb43c4bc
Author | SHA1 | Date | |
---|---|---|---|
57eb43c4bc | |||
6268efe517 | |||
53c1b162ae | |||
4fe8f3e0ec | |||
fda68e7144 |
3
.env.schema
Normal file
3
.env.schema
Normal file
@ -0,0 +1,3 @@
|
||||
REF_IMAGE_DIR=
|
||||
TRAINED_MODEL_DIR=
|
||||
OUT_DIR=
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -110,3 +110,4 @@ dist
|
||||
images/*
|
||||
bin
|
||||
out
|
||||
trainedModels/*
|
||||
|
24
.vscode/tasks.json
vendored
24
.vscode/tasks.json
vendored
@ -1,15 +1,13 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"problemMatcher": ["$tsc"]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
151
package-lock.json
generated
151
package-lock.json
generated
@ -42,6 +42,13 @@
|
||||
"@types/webgl-ext": "0.0.30",
|
||||
"@types/webgl2": "0.0.4",
|
||||
"seedrandom": "2.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/webgl2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-converter": {
|
||||
@ -60,6 +67,13 @@
|
||||
"@types/webgl2": "0.0.4",
|
||||
"node-fetch": "~2.6.1",
|
||||
"seedrandom": "2.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/webgl2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-data": {
|
||||
@ -92,6 +106,11 @@
|
||||
"tar": "^4.4.6"
|
||||
}
|
||||
},
|
||||
"@types/mime-types": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz",
|
||||
"integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz",
|
||||
@ -122,9 +141,10 @@
|
||||
"integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
|
||||
},
|
||||
"@types/webgl2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.5.tgz",
|
||||
"integrity": "sha512-oGaKsBbxQOY5+aJFV3KECDhGaXt+yZJt2y/OZsnQGLRkH6Fvr7rv4pCt3SRH1somIHfej/c4u7NSpCyd9x+1Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"@vladmandic/face-api": {
|
||||
"version": "0.8.8",
|
||||
@ -194,6 +214,14 @@
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"auto-parse": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/auto-parse/-/auto-parse-1.8.0.tgz",
|
||||
"integrity": "sha512-Uri4uC+K5cSi5hjM4snFrqPrjqUpwxeSW5EMTPvN7Ju3PlDzmXXDr5tjdzxPvvwgT3J7bmMDJ3Rm625nbrc72A==",
|
||||
"requires": {
|
||||
"typpy": "2.3.11"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@ -390,6 +418,16 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
@ -436,6 +474,22 @@
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||
},
|
||||
"dotenv-extended": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-extended/-/dotenv-extended-2.9.0.tgz",
|
||||
"integrity": "sha512-MKc4WCqZj6Abx4rpDbQ9LsuBJldRLxLgFkY5qE+4JM7hXVYT/v8zyWGgnBeDjSOGzEecWOFPlosNpxfB9YnsCw==",
|
||||
"requires": {
|
||||
"auto-parse": "^1.3.0",
|
||||
"camelcase": "^5.3.1",
|
||||
"cross-spawn": "^7.0.1",
|
||||
"dotenv": "^8.2.0"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -459,40 +513,6 @@
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
|
||||
},
|
||||
"face-api.js": {
|
||||
"version": "0.22.2",
|
||||
"resolved": "https://registry.npmjs.org/face-api.js/-/face-api.js-0.22.2.tgz",
|
||||
"integrity": "sha512-9Bbv/yaBRTKCXjiDqzryeKhYxmgSjJ7ukvOvEBy6krA0Ah/vNBlsf7iBNfJljWiPA8Tys1/MnB3lyP2Hfmsuyw==",
|
||||
"requires": {
|
||||
"@tensorflow/tfjs-core": "1.7.0",
|
||||
"tslib": "^1.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tensorflow/tfjs-core": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.7.0.tgz",
|
||||
"integrity": "sha512-uwQdiklNjqBnHPeseOdG0sGxrI3+d6lybaKu2+ou3ajVeKdPEwpWbgqA6iHjq1iylnOGkgkbbnQ6r2lwkiIIHw==",
|
||||
"requires": {
|
||||
"@types/offscreencanvas": "~2019.3.0",
|
||||
"@types/seedrandom": "2.4.27",
|
||||
"@types/webgl-ext": "0.0.30",
|
||||
"@types/webgl2": "0.0.4",
|
||||
"node-fetch": "~2.1.2",
|
||||
"seedrandom": "2.4.3"
|
||||
}
|
||||
},
|
||||
"@types/webgl2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
}
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
@ -525,6 +545,14 @@
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"function.name": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.13.tgz",
|
||||
"integrity": "sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA==",
|
||||
"requires": {
|
||||
"noop6": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
@ -660,6 +688,11 @@
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
@ -803,6 +836,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"noop6": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/noop6/-/noop6-1.0.9.tgz",
|
||||
"integrity": "sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA=="
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
@ -914,6 +952,11 @@
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
@ -1020,6 +1063,19 @@
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||
@ -1133,16 +1189,19 @@
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
|
||||
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ=="
|
||||
},
|
||||
"typpy": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmjs.org/typpy/-/typpy-2.3.11.tgz",
|
||||
"integrity": "sha512-Jh/fykZSaxeKO0ceMAs6agki9T5TNA9kiIR6fzKbvafKpIw8UlNlHhzuqKyi5lfJJ5VojJOx9tooIbyy7vHV/g==",
|
||||
"requires": {
|
||||
"function.name": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"untildify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
|
||||
@ -1153,6 +1212,14 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
|
11
package.json
11
package.json
@ -5,8 +5,9 @@
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc --build && npm run copy-files",
|
||||
"copy-files": "copyfiles -u 1 resources/* bin/resources"
|
||||
"build": "npm run copy-files && tsc --build",
|
||||
"copy-files": "copyfiles -u 1 -s weights/* bin/weights",
|
||||
"train": "npx ts-node ./scripts/train.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -21,11 +22,17 @@
|
||||
"dependencies": {
|
||||
"@tensorflow/tfjs": "^2.6.0",
|
||||
"@tensorflow/tfjs-node": "^2.6.0",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@vladmandic/face-api": "^0.8.8",
|
||||
"canvas": "^2.6.1",
|
||||
"copyfiles": "^2.4.0",
|
||||
"dotenv-extended": "^2.9.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"rtsp-stream": "file:../rtsp-stream",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webgl2": "0.0.5"
|
||||
}
|
||||
}
|
||||
|
62
scripts/streamAndDetect.ts
Normal file
62
scripts/streamAndDetect.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Rtsp } from "rtsp-stream/lib";
|
||||
import { nets } from "@vladmandic/face-api";
|
||||
import * as faceapi from "@vladmandic/face-api";
|
||||
import canvas from "canvas";
|
||||
import fs from "fs";
|
||||
import * as path from "path";
|
||||
import dotenv from "dotenv-extended";
|
||||
import { delay, getFaceDetectorOptions, saveFile } from "../src/common";
|
||||
require("@tensorflow/tfjs-node");
|
||||
|
||||
const { Canvas, Image, ImageData } = canvas;
|
||||
//@ts-ignore
|
||||
faceapi.env.monkeyPatch({ Canvas, Image, ImageData });
|
||||
|
||||
const main = async () => {
|
||||
dotenv.load({
|
||||
silent: false,
|
||||
errorOnMissing: true,
|
||||
});
|
||||
|
||||
const modelDir = process.env.TRAINED_MODEL_DIR as string;
|
||||
|
||||
const rtsp = new Rtsp("rtsp://brandon:asdf1234@192.168.1.229/live", {
|
||||
rate: 0.5,
|
||||
image: true,
|
||||
codec: "copy",
|
||||
});
|
||||
const faceDetectionNet = nets.ssdMobilenetv1;
|
||||
|
||||
await faceDetectionNet.loadFromDisk(path.join(__dirname, "../weights"));
|
||||
await nets.faceLandmark68Net.loadFromDisk(path.join(__dirname, "../weights"));
|
||||
await nets.faceRecognitionNet.loadFromDisk(
|
||||
path.join(__dirname, "../weights")
|
||||
);
|
||||
|
||||
const raw = fs.readFileSync(path.join(modelDir, "data.json"), "utf-8");
|
||||
const content = JSON.parse(raw);
|
||||
const matcher = faceapi.FaceMatcher.fromJSON(content);
|
||||
|
||||
rtsp.on("data", async (data: Buffer) => {
|
||||
const input = ((await canvas.loadImage(data)) as unknown) as ImageData;
|
||||
const out = faceapi.createCanvasFromMedia(input);
|
||||
await saveFile("image.jpg", data);
|
||||
const resultsQuery = await faceapi
|
||||
.detectAllFaces(out, getFaceDetectorOptions(faceDetectionNet))
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptors();
|
||||
|
||||
for (const res of resultsQuery) {
|
||||
const bestMatch = matcher.matchDescriptor(res.descriptor);
|
||||
console.log("Face Detected: " + bestMatch.label);
|
||||
}
|
||||
});
|
||||
|
||||
rtsp.on("error", (err) => {
|
||||
// console.log(err);
|
||||
});
|
||||
|
||||
rtsp.start();
|
||||
};
|
||||
|
||||
main();
|
127
scripts/train.ts
127
scripts/train.ts
@ -1,20 +1,25 @@
|
||||
import * as faceapi from "@vladmandic/face-api";
|
||||
import canvas from "canvas";
|
||||
import fs from "fs";
|
||||
import fs, { lstatSync } from "fs";
|
||||
import * as path from "path";
|
||||
import { TNetInput } from "@vladmandic/face-api";
|
||||
import { LabeledFaceDescriptors, TNetInput } from "@vladmandic/face-api";
|
||||
import * as mime from "mime-types";
|
||||
import dotenv from "dotenv-extended";
|
||||
import { getFaceDetectorOptions } from "../src/common";
|
||||
require("@tensorflow/tfjs-node");
|
||||
|
||||
const { Canvas, Image, ImageData } = canvas;
|
||||
//@ts-ignore
|
||||
faceapi.env.monkeyPatch({ Canvas, Image, ImageData });
|
||||
|
||||
const REFERENCE_IMAGE =
|
||||
"/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/images/brandon/IMG_1958.jpg";
|
||||
const QUERY_IMAGE =
|
||||
"/Users/brandonwatson/Documents/Git/Gitea/homebridge-face-location/images/brandon/IMG_0001.JPG";
|
||||
|
||||
const main = async () => {
|
||||
dotenv.load({
|
||||
silent: false,
|
||||
errorOnMissing: true,
|
||||
});
|
||||
const inputDir = process.env.REF_IMAGE_DIR as string;
|
||||
const outDir = process.env.TRAINED_MODEL_DIR as string;
|
||||
|
||||
const faceDetectionNet = faceapi.nets.ssdMobilenetv1;
|
||||
await faceDetectionNet.loadFromDisk(path.join(__dirname, "../weights"));
|
||||
await faceapi.nets.faceLandmark68Net.loadFromDisk(
|
||||
@ -24,64 +29,72 @@ const main = async () => {
|
||||
path.join(__dirname, "../weights")
|
||||
);
|
||||
|
||||
const referenceImage = (await canvas.loadImage(REFERENCE_IMAGE)) as unknown;
|
||||
const queryImage = (await canvas.loadImage(QUERY_IMAGE)) as unknown;
|
||||
const options = getFaceDetectorOptions(faceDetectionNet);
|
||||
|
||||
const resultsRef = await faceapi
|
||||
.detectAllFaces(referenceImage as TNetInput, options)
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptors();
|
||||
const dirs = fs.readdirSync(inputDir);
|
||||
|
||||
const resultsQuery = await faceapi
|
||||
.detectAllFaces(queryImage as TNetInput, options)
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptors();
|
||||
const refs: Array<LabeledFaceDescriptors> = [];
|
||||
for (const dir of dirs) {
|
||||
if (!lstatSync(path.join(inputDir, dir)).isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
const files = fs.readdirSync(path.join(inputDir, dir));
|
||||
let referenceResults = await Promise.all(
|
||||
files.map(async (file: string) => {
|
||||
const mimeType = mime.contentType(
|
||||
path.extname(path.join(inputDir, dir, file))
|
||||
);
|
||||
if (!mimeType || !mimeType.startsWith("image")) {
|
||||
return;
|
||||
}
|
||||
console.log(path.join(inputDir, dir, file));
|
||||
|
||||
const faceMatcher = new faceapi.FaceMatcher(resultsRef);
|
||||
try {
|
||||
const referenceImage = (await canvas.loadImage(
|
||||
path.join(inputDir, dir, file)
|
||||
)) as unknown;
|
||||
|
||||
const labels = faceMatcher.labeledDescriptors.map((ld) => ld.label);
|
||||
const refDrawBoxes = resultsRef
|
||||
.map((res) => res.detection.box)
|
||||
.map((box, i) => new faceapi.draw.DrawBox(box, { label: labels[i] }));
|
||||
const outRef = faceapi.createCanvasFromMedia(referenceImage as ImageData);
|
||||
refDrawBoxes.forEach((drawBox) => drawBox.draw(outRef));
|
||||
const descriptor = await faceapi
|
||||
.detectSingleFace(referenceImage as TNetInput, options)
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptor();
|
||||
if (!descriptor || !descriptor.descriptor) {
|
||||
throw new Error("No face found");
|
||||
}
|
||||
|
||||
saveFile("referenceImage.jpg", (outRef as any).toBuffer("image/jpeg"));
|
||||
const faceDescriptors = [descriptor.descriptor];
|
||||
return new faceapi.LabeledFaceDescriptors(dir, faceDescriptors);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
"An error occurred loading image at path: " +
|
||||
path.join(inputDir, dir, file)
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
|
||||
const queryDrawBoxes = resultsQuery.map((res) => {
|
||||
const bestMatch = faceMatcher.findBestMatch(res.descriptor);
|
||||
return new faceapi.draw.DrawBox(res.detection.box, {
|
||||
label: bestMatch.toString(),
|
||||
});
|
||||
});
|
||||
const outQuery = faceapi.createCanvasFromMedia(queryImage as ImageData);
|
||||
queryDrawBoxes.forEach((drawBox) => drawBox.draw(outQuery));
|
||||
saveFile("queryImage.jpg", (outQuery as any).toBuffer("image/jpeg"));
|
||||
console.log("done, saved results to out/queryImage.jpg");
|
||||
};
|
||||
|
||||
// SsdMobilenetv1Options
|
||||
const minConfidence = 0.5;
|
||||
|
||||
// TinyFaceDetectorOptions
|
||||
const inputSize = 408;
|
||||
const scoreThreshold = 0.5;
|
||||
|
||||
function getFaceDetectorOptions(net: faceapi.NeuralNetwork<any>) {
|
||||
return net === faceapi.nets.ssdMobilenetv1
|
||||
? new faceapi.SsdMobilenetv1Options({ minConfidence })
|
||||
: new faceapi.TinyFaceDetectorOptions({ inputSize, scoreThreshold });
|
||||
}
|
||||
|
||||
const baseDir = path.resolve(__dirname, "../out");
|
||||
|
||||
function saveFile(fileName: string, buf: Buffer) {
|
||||
if (!fs.existsSync(baseDir)) {
|
||||
fs.mkdirSync(baseDir);
|
||||
if (referenceResults) {
|
||||
refs.push(
|
||||
...(referenceResults.filter((e) => e) as LabeledFaceDescriptors[])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(baseDir, fileName), buf);
|
||||
}
|
||||
const faceMatcher = new faceapi.FaceMatcher(refs);
|
||||
|
||||
fs.writeFile(
|
||||
path.join(outDir, "data.json"),
|
||||
JSON.stringify(faceMatcher.toJSON()),
|
||||
"utf8",
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.log(`An error occurred while writing data model to file`);
|
||||
}
|
||||
|
||||
console.log(`Successfully wrote data model to file`);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
main();
|
||||
|
33
src/common.ts
Normal file
33
src/common.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import * as faceapi from "@vladmandic/face-api";
|
||||
import * as path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
// SsdMobilenetv1Options
|
||||
export const minConfidence = 0.5;
|
||||
|
||||
// TinyFaceDetectorOptions
|
||||
export const inputSize = 408;
|
||||
export const scoreThreshold = 0.5;
|
||||
|
||||
export const getFaceDetectorOptions = (net: faceapi.NeuralNetwork<any>) => {
|
||||
return net === faceapi.nets.ssdMobilenetv1
|
||||
? new faceapi.SsdMobilenetv1Options({ minConfidence })
|
||||
: new faceapi.TinyFaceDetectorOptions({ inputSize, scoreThreshold });
|
||||
};
|
||||
|
||||
export function saveFile(fileName: string, buf: Buffer) {
|
||||
const baseDir = process.env.OUT_DIR as string;
|
||||
if (!fs.existsSync(baseDir)) {
|
||||
fs.mkdirSync(baseDir);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(baseDir, fileName), buf, "base64");
|
||||
}
|
||||
|
||||
export const delay = (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
};
|
36
src/index.ts
36
src/index.ts
@ -1,35 +1 @@
|
||||
import { Rtsp } from "rtsp-stream/lib";
|
||||
// import nodejs bindings to native tensorflow,
|
||||
// not required, but will speed up things drastically (python required)
|
||||
|
||||
import * as faceapi from "face-api.js";
|
||||
|
||||
// implements nodejs wrappers for HTMLCanvasElement, HTMLImageElement, ImageData
|
||||
const canvas = require("canvas");
|
||||
|
||||
// patch nodejs environment, we need to provide an implementation of
|
||||
// HTMLCanvasElement and HTMLImageElement
|
||||
const { Canvas, Image, ImageData } = canvas;
|
||||
faceapi.env.monkeyPatch({ Canvas, Image, ImageData });
|
||||
|
||||
const main = async () => {
|
||||
const rtsp = new Rtsp("rtsp://brandon:asdf1234@192.168.1.229/live", {
|
||||
rate: 10,
|
||||
});
|
||||
await faceapi.nets.ssdMobilenetv1.loadFromDisk("./resources");
|
||||
|
||||
rtsp.on("data", async (data: Buffer) => {
|
||||
const input = await canvas.loadImage(data);
|
||||
const detections = await faceapi.detectAllFaces(input);
|
||||
console.log();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
rtsp.on("error", (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
rtsp.start();
|
||||
};
|
||||
|
||||
main();
|
||||
console.log("Hello World");
|
||||
|
@ -1,63 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./bin" /* Redirect output structure to the directory. */,
|
||||
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
"composite": true /* Enable project compilation */,
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
|
||||
"paths": {
|
||||
"rtsp/*": ["./node_modules/rtsp-stream/lib/*"]
|
||||
} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
@ -1,14 +1,4 @@
|
||||
{
|
||||
"references": [
|
||||
{
|
||||
"path": "./node_modules/rtsp-stream/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.face-location.json"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"exclude": ["node_modules/*"],
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
@ -69,5 +59,12 @@
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
},
|
||||
"include": ["./src"],
|
||||
"exclude": ["node_modules"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./node_modules/rtsp-stream/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user