Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cec734a09 | ||
|
|
4e873edc97 | ||
|
|
0792147dc4 | ||
|
|
65f11bec09 | ||
|
|
e047ef6549 | ||
|
|
3ccf85cb00 | ||
|
|
57eb43c4bc | ||
|
|
6268efe517 | ||
|
|
53c1b162ae | ||
|
|
4fe8f3e0ec | ||
|
|
fda68e7144 | ||
|
|
e1902a362e | ||
|
|
cff0229e6f |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
TRAINED_MODEL_DIR=./trainedModels
|
||||
OUT_DIR=./out
|
||||
@@ -0,0 +1,3 @@
|
||||
REF_IMAGE_DIR=
|
||||
TRAINED_MODEL_DIR=
|
||||
OUT_DIR=
|
||||
@@ -107,3 +107,7 @@ dist
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
images/*
|
||||
bin
|
||||
out
|
||||
trainedModels/*
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Current TS File",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["${relativeFile}"],
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"sourceMaps": true,
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"preLaunchTask": "build",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"program": "/Users/brandonwatson/.nvm/versions/node/v14.15.0/lib/node_modules/homebridge/bin/homebridge",
|
||||
"env": {
|
||||
"HOMEBRIDGE_OPTS": "/Users/brandonwatson/.homebridge"
|
||||
},
|
||||
"sourceMaps": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"label": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "build and install",
|
||||
"command": "npm run build&&sudo npm install -g --unsafe-perm ."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"pluginAlias": "HomeLocation",
|
||||
"pluginType": "platform",
|
||||
"singular": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"default": "Example Dynamic Platform"
|
||||
},
|
||||
"refImageDirectory": {
|
||||
"title": "RefImageDirectory",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"trainedModelDirectory": {
|
||||
"title": "TrainedModelDirectory",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"rooms": {
|
||||
"title": "Rooms",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"rtspCameraConnectionString": {
|
||||
"title": "RtspCameraConnectionString",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"editor.tabSize": 2,
|
||||
"debug.javascript.unmapMissingSources": true
|
||||
}
|
||||
}
|
||||
Generated
+1940
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "homebridge-face-location",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "bin/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"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",
|
||||
"url": "ssh://git@thebword.ddns.net:3122/watsonb8/homebridge-face-location.git"
|
||||
},
|
||||
"keywords": [
|
||||
"homebridge",
|
||||
"typescript",
|
||||
"homebridge-plugin"
|
||||
],
|
||||
"engines": {
|
||||
"homebridge": ">=1.1.6"
|
||||
},
|
||||
"author": "Brandon Watson",
|
||||
"license": "ISC",
|
||||
"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",
|
||||
"dotenv-extended": "^2.9.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"tsyringe": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webgl2": "0.0.5",
|
||||
"typescript": "^4.0.5",
|
||||
"ts-node": "^9.0.0",
|
||||
"homebridge": "^1.1.6",
|
||||
"copyfiles": "^2.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Rtsp, IStreamEventArgs } from "../src/rtsp/rtsp";
|
||||
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.dataEvent.push(async (sender: Rtsp, args: IStreamEventArgs) => {
|
||||
const input = ((await canvas.loadImage(args.data)) as unknown) as ImageData;
|
||||
const out = faceapi.createCanvasFromMedia(input);
|
||||
await saveFile(process.env.OUT_DIR as string, "image.jpg", args.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.start();
|
||||
};
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,100 @@
|
||||
import * as faceapi from "@vladmandic/face-api";
|
||||
import canvas from "canvas";
|
||||
import fs, { lstatSync } from "fs";
|
||||
import * as path from "path";
|
||||
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 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(
|
||||
path.join(__dirname, "../weights")
|
||||
);
|
||||
await faceapi.nets.faceRecognitionNet.loadFromDisk(
|
||||
path.join(__dirname, "../weights")
|
||||
);
|
||||
|
||||
const options = getFaceDetectorOptions(faceDetectionNet);
|
||||
|
||||
const dirs = fs.readdirSync(inputDir);
|
||||
|
||||
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));
|
||||
|
||||
try {
|
||||
const referenceImage = (await canvas.loadImage(
|
||||
path.join(inputDir, dir, file)
|
||||
)) as unknown;
|
||||
|
||||
const descriptor = await faceapi
|
||||
.detectSingleFace(referenceImage as TNetInput, options)
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptor();
|
||||
if (!descriptor || !descriptor.descriptor) {
|
||||
throw new Error("No face found");
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
);
|
||||
|
||||
if (referenceResults) {
|
||||
refs.push(
|
||||
...(referenceResults.filter((e) => e) as LabeledFaceDescriptors[])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -0,0 +1,53 @@
|
||||
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(
|
||||
basePath: string,
|
||||
fileName: string,
|
||||
buf: Buffer
|
||||
): Promise<void> {
|
||||
const writeFile = (): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path.resolve(basePath, fileName), buf, "base64", (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!fs.existsSync(basePath)) {
|
||||
fs.mkdir(basePath, async (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(await writeFile());
|
||||
});
|
||||
} else {
|
||||
resolve(await writeFile());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const delay = (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { PlatformConfig } from "homebridge";
|
||||
|
||||
export interface IConfig extends PlatformConfig {
|
||||
refImageDirectory: string;
|
||||
trainedModelDirectory: string;
|
||||
weightDirectory: string;
|
||||
outputDirectory: string;
|
||||
trainOnStartup: boolean;
|
||||
rooms: Array<IRoom>;
|
||||
detectionTimeout: number;
|
||||
debug: boolean;
|
||||
writeOutput: boolean;
|
||||
}
|
||||
|
||||
export interface IRoom {
|
||||
name: string;
|
||||
rtspConnectionStrings: Array<string>;
|
||||
}
|
||||
|
||||
export const isRoom = (object: any): object is IRoom => {
|
||||
return "name" in object && "rtspConnectionStrings" in object;
|
||||
};
|
||||
|
||||
export const isConfig = (object: any): object is IConfig => {
|
||||
const roomsOkay =
|
||||
object["rooms"].filter((room: any) => isRoom(room)).length ===
|
||||
object["rooms"].length;
|
||||
return (
|
||||
"refImageDirectory" in object &&
|
||||
"trainedModelDirectory" in object &&
|
||||
"weightDirectory" in object &&
|
||||
"outputDirectory" in object &&
|
||||
"trainOnStartup" in object &&
|
||||
"detectionTimeout" in object &&
|
||||
"writeOutput" in object &&
|
||||
"rooms" in object &&
|
||||
roomsOkay
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { EventDelegate } from "./eventDelegate";
|
||||
|
||||
export class Event<T, K> extends Array<EventDelegate<T, K>> {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
public fire = (source: T, args: K) => {
|
||||
for (const delegate of this) {
|
||||
delegate(source, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type EventDelegate<T, K> = (sender: T, args: K) => void;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./event";
|
||||
export * from "./eventDelegate";
|
||||
@@ -0,0 +1,229 @@
|
||||
import {
|
||||
API,
|
||||
DynamicPlatformPlugin,
|
||||
Logger,
|
||||
PlatformAccessory,
|
||||
PlatformConfig,
|
||||
Service,
|
||||
Characteristic,
|
||||
} from "homebridge";
|
||||
import { IConfig, isConfig } from "./config";
|
||||
import * as faceapi from "@vladmandic/face-api";
|
||||
import canvas from "canvas";
|
||||
import fs, { lstatSync } from "fs";
|
||||
import * as path from "path";
|
||||
import { nets } from "@vladmandic/face-api";
|
||||
import {
|
||||
LabeledFaceDescriptors,
|
||||
TNetInput,
|
||||
FaceMatcher,
|
||||
} from "@vladmandic/face-api";
|
||||
import * as mime from "mime-types";
|
||||
import { Monitor } from "./monitor";
|
||||
import { getFaceDetectorOptions } from "./common";
|
||||
require("@tensorflow/tfjs-node");
|
||||
|
||||
const { Canvas, Image, ImageData } = canvas;
|
||||
//@ts-ignore
|
||||
faceapi.env.monkeyPatch({ Canvas, Image, ImageData });
|
||||
|
||||
import { PLATFORM_NAME, PLUGIN_NAME } from "./settings";
|
||||
import { LocationAccessory } from "./locationAccessory";
|
||||
|
||||
/**
|
||||
* HomebridgePlatform
|
||||
* This class is the main constructor for your plugin, this is where you should
|
||||
* parse the user config and discover/register accessories with Homebridge.
|
||||
*/
|
||||
export class HomeLocationPlatform implements DynamicPlatformPlugin {
|
||||
public readonly Service: typeof Service = this.api.hap.Service;
|
||||
public readonly Characteristic: typeof Characteristic = this.api.hap
|
||||
.Characteristic;
|
||||
|
||||
// this is used to track restored cached accessories
|
||||
public readonly accessories: PlatformAccessory[] = [];
|
||||
public config: IConfig;
|
||||
|
||||
constructor(
|
||||
public readonly log: Logger,
|
||||
config: PlatformConfig,
|
||||
public readonly api: API
|
||||
) {
|
||||
this.log.debug("Finished initializing platform:", config.name);
|
||||
|
||||
if (!isConfig(config)) {
|
||||
this.log.error("Configuration is incorrect or incomplete");
|
||||
process.exit(1);
|
||||
} else {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
this.api.on("didFinishLaunching", async () => {
|
||||
log.debug("Executed didFinishLaunching callback");
|
||||
// run the method to discover / register your devices as accessories
|
||||
await this.discoverDevices();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
||||
* It should be used to setup event handlers for characteristics and update respective values.
|
||||
*/
|
||||
public configureAccessory(accessory: PlatformAccessory) {
|
||||
this.log.info("Loading accessory from cache:", accessory.displayName);
|
||||
|
||||
// add the restored accessory to the accessories cache so we can track if it has already been registered
|
||||
this.accessories.push(accessory);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an example method showing how to register discovered accessories.
|
||||
* Accessories must only be registered once, previously created accessories
|
||||
* must not be registered again to prevent "duplicate UUID" errors.
|
||||
*/
|
||||
public async discoverDevices() {
|
||||
//Train facial recognition model
|
||||
let faceMatcher: FaceMatcher;
|
||||
if (this.config.trainOnStartup) {
|
||||
faceMatcher = await this.trainModels();
|
||||
} else {
|
||||
const faceDetectionNet = nets.ssdMobilenetv1;
|
||||
|
||||
await faceDetectionNet.loadFromDisk(this.config.weightDirectory);
|
||||
await nets.faceLandmark68Net.loadFromDisk(this.config.weightDirectory);
|
||||
await nets.faceRecognitionNet.loadFromDisk(this.config.weightDirectory);
|
||||
|
||||
const raw = fs.readFileSync(
|
||||
path.join(this.config.trainedModelDirectory, "data.json"),
|
||||
"utf-8"
|
||||
);
|
||||
faceMatcher = FaceMatcher.fromJSON(JSON.parse(raw));
|
||||
}
|
||||
|
||||
const locationMonitor = new Monitor(
|
||||
this.config.rooms,
|
||||
faceMatcher,
|
||||
this.log,
|
||||
this.config
|
||||
);
|
||||
|
||||
locationMonitor.startStreams();
|
||||
|
||||
const labels = faceMatcher.labeledDescriptors.map((e) => e.label);
|
||||
for (const room of this.config.rooms) {
|
||||
const uuid = this.api.hap.uuid.generate(room.name);
|
||||
|
||||
const existingAccessory = this.accessories.find((e) => e.UUID === uuid);
|
||||
if (existingAccessory) {
|
||||
this.log.info(
|
||||
"Restoring existing accessory from cache: ",
|
||||
existingAccessory.displayName
|
||||
);
|
||||
|
||||
new LocationAccessory(this, existingAccessory, locationMonitor, room);
|
||||
|
||||
this.api.updatePlatformAccessories([existingAccessory]);
|
||||
} else {
|
||||
this.log.info("Adding new accessory:", `${room.name}`);
|
||||
|
||||
// create a new accessory
|
||||
const accessory = new this.api.platformAccessory(`${room.name}`, uuid);
|
||||
|
||||
accessory.context["DeviceName"] = `${room.name}`;
|
||||
|
||||
// create the accessory handler for the newly create accessory
|
||||
// this is imported from `platformAccessory.ts`
|
||||
new LocationAccessory(this, accessory, locationMonitor, room);
|
||||
|
||||
// link the accessory to your platform
|
||||
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
|
||||
accessory,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async trainModels(): Promise<FaceMatcher> {
|
||||
const faceDetectionNet = faceapi.nets.ssdMobilenetv1;
|
||||
await faceDetectionNet.loadFromDisk(this.config.weightDirectory);
|
||||
await faceapi.nets.faceLandmark68Net.loadFromDisk(
|
||||
this.config.weightDirectory
|
||||
);
|
||||
await faceapi.nets.faceRecognitionNet.loadFromDisk(
|
||||
this.config.weightDirectory
|
||||
);
|
||||
|
||||
const options = getFaceDetectorOptions(faceDetectionNet);
|
||||
|
||||
const dirs = fs.readdirSync(this.config.refImageDirectory);
|
||||
|
||||
const refs: Array<LabeledFaceDescriptors> = [];
|
||||
for (const dir of dirs) {
|
||||
if (
|
||||
!lstatSync(path.join(this.config.refImageDirectory, dir)).isDirectory()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const files = fs.readdirSync(
|
||||
path.join(this.config.refImageDirectory, dir)
|
||||
);
|
||||
let referenceResults = await Promise.all(
|
||||
files.map(async (file: string) => {
|
||||
const mimeType = mime.contentType(
|
||||
path.extname(path.join(this.config.refImageDirectory, dir, file))
|
||||
);
|
||||
if (!mimeType || !mimeType.startsWith("image")) {
|
||||
return;
|
||||
}
|
||||
console.log(path.join(this.config.refImageDirectory, dir, file));
|
||||
|
||||
try {
|
||||
const referenceImage = (await canvas.loadImage(
|
||||
path.join(this.config.refImageDirectory, dir, file)
|
||||
)) as unknown;
|
||||
|
||||
const descriptor = await faceapi
|
||||
.detectSingleFace(referenceImage as TNetInput, options)
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptor();
|
||||
if (!descriptor || !descriptor.descriptor) {
|
||||
throw new Error("No face found");
|
||||
}
|
||||
|
||||
const faceDescriptors = [descriptor.descriptor];
|
||||
return new faceapi.LabeledFaceDescriptors(dir, faceDescriptors);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
"An error occurred loading image at path: " +
|
||||
path.join(this.config.refImageDirectory, dir, file)
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
|
||||
if (referenceResults) {
|
||||
refs.push(
|
||||
...(referenceResults.filter((e) => e) as LabeledFaceDescriptors[])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const faceMatcher = new faceapi.FaceMatcher(refs);
|
||||
|
||||
fs.writeFile(
|
||||
path.join(this.config.trainedModelDirectory, "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`);
|
||||
}
|
||||
);
|
||||
|
||||
return faceMatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { API } from "homebridge";
|
||||
|
||||
import { PLATFORM_NAME } from "./settings";
|
||||
import { HomeLocationPlatform } from "./homeLocationPlatform";
|
||||
|
||||
/**
|
||||
* This method registers the platform with Homebridge
|
||||
*/
|
||||
export = (api: API) => {
|
||||
api.registerPlatform(PLATFORM_NAME, HomeLocationPlatform);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
Service,
|
||||
CharacteristicGetCallback,
|
||||
PlatformAccessory,
|
||||
} from "homebridge";
|
||||
import { Monitor, IStateChangeEventArgs } from "./monitor";
|
||||
import { HomeLocationPlatform } from "./homeLocationPlatform";
|
||||
import { IRoom } from "./config";
|
||||
|
||||
/**
|
||||
* Platform Accessory
|
||||
* An instance of this class is created for each accessory your platform registers
|
||||
* Each accessory may expose multiple services of different service types.
|
||||
*/
|
||||
export class LocationAccessory {
|
||||
private _services: Array<Service>;
|
||||
|
||||
constructor(
|
||||
private readonly _platform: HomeLocationPlatform,
|
||||
private readonly _accessory: PlatformAccessory,
|
||||
private _monitor: Monitor,
|
||||
private _room: IRoom
|
||||
) {
|
||||
this._services = [];
|
||||
// set accessory information
|
||||
this._accessory
|
||||
.getService(this._platform.Service.AccessoryInformation)!
|
||||
.setCharacteristic(
|
||||
this._platform.Characteristic.Manufacturer,
|
||||
"Brandon Watson"
|
||||
)
|
||||
.setCharacteristic(
|
||||
this._platform.Characteristic.Model,
|
||||
"Person Location Sensor"
|
||||
)
|
||||
.setCharacteristic(
|
||||
this._platform.Characteristic.SerialNumber,
|
||||
"123-456-789"
|
||||
);
|
||||
|
||||
//Init motion services
|
||||
for (const label of this._monitor.labels) {
|
||||
const newService =
|
||||
this._accessory.getService(label) ||
|
||||
this._accessory.addService(
|
||||
this._platform.Service.MotionSensor,
|
||||
label,
|
||||
this._room + label
|
||||
);
|
||||
|
||||
newService
|
||||
.getCharacteristic(this._platform.Characteristic.MotionDetected)
|
||||
.on("get", (callback: CharacteristicGetCallback) =>
|
||||
this.onMotionDetectedGet(label, callback)
|
||||
);
|
||||
|
||||
this._services.push(newService);
|
||||
}
|
||||
|
||||
//Register monitor state change events
|
||||
this._monitor.stateChangedEvent.push(this.onMonitorStateChange.bind(this));
|
||||
}
|
||||
|
||||
private onMotionDetectedGet = (
|
||||
label: string,
|
||||
callback: CharacteristicGetCallback
|
||||
) => {
|
||||
this._platform.log.debug("Triggered GET MotionDetected");
|
||||
|
||||
// set this to a valid value for MotionDetected
|
||||
const currentValue =
|
||||
this._monitor.getState(label) === this._room.name ? 1 : 0;
|
||||
|
||||
callback(null, currentValue);
|
||||
};
|
||||
|
||||
private onMonitorStateChange = (
|
||||
sender: Monitor,
|
||||
args: IStateChangeEventArgs
|
||||
) => {
|
||||
const service = this._services.find(
|
||||
(service) => service.displayName == args.label
|
||||
);
|
||||
if (service) {
|
||||
service.setCharacteristic(
|
||||
this._platform.Characteristic.MotionDetected,
|
||||
args.new === this._room.name
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
import { FaceMatcher } from "@vladmandic/face-api";
|
||||
import { IRoom } from "./config";
|
||||
import {
|
||||
Rtsp,
|
||||
IStreamEventArgs,
|
||||
ICloseEventArgs,
|
||||
IErrorEventArgs,
|
||||
IMessageEventArgs,
|
||||
} from "./rtsp/rtsp";
|
||||
import canvas from "canvas";
|
||||
import * as faceapi from "@vladmandic/face-api";
|
||||
import { getFaceDetectorOptions, saveFile } from "./common";
|
||||
import { nets } from "@vladmandic/face-api";
|
||||
import { Logger } from "homebridge";
|
||||
import { Event } from "./events";
|
||||
import { IConfig } from "./config";
|
||||
const { Canvas, Image, ImageData } = canvas;
|
||||
|
||||
export type MonitorState = { [label: string]: string | null };
|
||||
export interface IStateChangeEventArgs {
|
||||
label: string;
|
||||
old: string | null;
|
||||
new: string;
|
||||
}
|
||||
|
||||
export class Monitor {
|
||||
private _state: MonitorState = {};
|
||||
private _streamsByRoom: { [roomName: string]: Array<Rtsp> } = {};
|
||||
private _faceDetectionNet = nets.ssdMobilenetv1;
|
||||
private _stateChangedEvent: Event<this, IStateChangeEventArgs>;
|
||||
|
||||
constructor(
|
||||
private _rooms: Array<IRoom>,
|
||||
private _matcher: FaceMatcher,
|
||||
private _logger: Logger,
|
||||
private _config: IConfig
|
||||
) {
|
||||
this._stateChangedEvent = new Event();
|
||||
|
||||
//Initialize state
|
||||
for (const room of this._rooms) {
|
||||
this._streamsByRoom[room.name] = [
|
||||
...room.rtspConnectionStrings.map((connectionString) => {
|
||||
const rtsp = new Rtsp(connectionString, {
|
||||
rate: 0.7,
|
||||
image: true,
|
||||
});
|
||||
rtsp.dataEvent.push((sender: Rtsp, args: IStreamEventArgs) =>
|
||||
this.onData(room.name, args)
|
||||
);
|
||||
rtsp.closeEvent.push((sender: Rtsp, args: ICloseEventArgs) =>
|
||||
this.onExit(connectionString, args)
|
||||
);
|
||||
rtsp.errorEvent.push((sender: Rtsp, args: IErrorEventArgs) =>
|
||||
this.onError(args, connectionString)
|
||||
);
|
||||
if (this._config.debug) {
|
||||
rtsp.messageEvent.push((sender: Rtsp, args: IMessageEventArgs) => {
|
||||
this._logger.info(`[${connectionString}] ${args.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
return rtsp;
|
||||
}),
|
||||
];
|
||||
|
||||
_matcher.labeledDescriptors.forEach((descriptor) => {
|
||||
this._state[descriptor.label] = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @method getState
|
||||
*
|
||||
* @param label The name of the label to retrieve state for
|
||||
*
|
||||
* The last known room of the requested label
|
||||
*/
|
||||
public getState(label: string): string | null {
|
||||
return this._state[label];
|
||||
}
|
||||
|
||||
/**
|
||||
* @property labels
|
||||
*
|
||||
* Gets the list of labels associated with the monitor
|
||||
*/
|
||||
public get labels(): Array<string> {
|
||||
return this._matcher.labeledDescriptors
|
||||
.map((descriptor) => descriptor.label)
|
||||
.filter(
|
||||
(label: string, index: number, array: Array<string>) =>
|
||||
array.indexOf(label) === index
|
||||
);
|
||||
}
|
||||
|
||||
public get stateChangedEvent(): Event<this, IStateChangeEventArgs> {
|
||||
return this._stateChangedEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @method startStreams
|
||||
*
|
||||
* Starts monitoring rtsp streams
|
||||
*/
|
||||
public startStreams() {
|
||||
for (const key in this._streamsByRoom) {
|
||||
for (const stream of this._streamsByRoom[key]) {
|
||||
stream.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @method closeStreams
|
||||
*
|
||||
* Stops monitoring rtsp streams
|
||||
*/
|
||||
public closeStreams() {
|
||||
for (const key in this._streamsByRoom) {
|
||||
for (const stream of this._streamsByRoom[key]) {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onData = async (room: string, args: IStreamEventArgs) => {
|
||||
const input = ((await canvas.loadImage(args.data)) as unknown) as ImageData;
|
||||
const out = faceapi.createCanvasFromMedia(input);
|
||||
const resultsQuery = await faceapi
|
||||
.detectAllFaces(out, getFaceDetectorOptions(this._faceDetectionNet))
|
||||
.withFaceLandmarks()
|
||||
.withFaceDescriptors();
|
||||
|
||||
//Write to output image
|
||||
if (this._config.writeOutput) {
|
||||
await saveFile(this._config.outputDirectory, room + ".jpg", args.data);
|
||||
}
|
||||
|
||||
for (const res of resultsQuery) {
|
||||
const bestMatch = this._matcher.matchDescriptor(res.descriptor);
|
||||
const old = this._state[bestMatch.label];
|
||||
this._state[bestMatch.label] = room;
|
||||
this._stateChangedEvent.fire(this, {
|
||||
old: old,
|
||||
new: room,
|
||||
label: bestMatch.label,
|
||||
});
|
||||
|
||||
this._logger.info(`Face Detected: ${bestMatch.label} in room ${room}`);
|
||||
}
|
||||
};
|
||||
|
||||
private onError = (args: IErrorEventArgs, streamName: string) => {
|
||||
this._logger.info(`[${streamName}] ${args.message}`);
|
||||
};
|
||||
private onExit = (streamName: string, args: ICloseEventArgs) => {
|
||||
this._logger.info(`[${streamName}] Stream has exited: ${args.message}`);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface IOptions {
|
||||
rate?: number;
|
||||
quality?: number;
|
||||
resolution?: string;
|
||||
codec?: string;
|
||||
image?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { ChildProcess, spawn } from "child_process";
|
||||
import { Writable } from "stream";
|
||||
import { IOptions } from "./options";
|
||||
import { Event } from "../events";
|
||||
|
||||
const ef1 = "ff";
|
||||
const ef2 = "d9";
|
||||
|
||||
export interface IStreamEventArgs {
|
||||
data: Buffer;
|
||||
}
|
||||
|
||||
export interface ICloseEventArgs {
|
||||
message: string;
|
||||
}
|
||||
export interface IErrorEventArgs {
|
||||
message?: string;
|
||||
err?: Error;
|
||||
}
|
||||
|
||||
export interface IMessageEventArgs {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class Rtsp {
|
||||
private _connecteionString: string;
|
||||
private _childProcess: ChildProcess | undefined;
|
||||
private _started: boolean;
|
||||
private _buffer: Buffer;
|
||||
private _options: IOptions;
|
||||
private _paused: boolean;
|
||||
|
||||
private _dataEvent: Event<this, IStreamEventArgs>;
|
||||
private _closeEvent: Event<this, ICloseEventArgs>;
|
||||
private _errorEvent: Event<this, IErrorEventArgs>;
|
||||
private _messageEvent: Event<this, IMessageEventArgs>;
|
||||
|
||||
constructor(connectionString: string, options: IOptions) {
|
||||
this._started = false;
|
||||
this._connecteionString = connectionString;
|
||||
this._childProcess = undefined;
|
||||
this._buffer = Buffer.from("");
|
||||
this._options = options;
|
||||
this._paused = false;
|
||||
this._dataEvent = new Event();
|
||||
this._closeEvent = new Event();
|
||||
this._errorEvent = new Event();
|
||||
this._messageEvent = new Event();
|
||||
|
||||
this.onData = this.onData.bind(this);
|
||||
}
|
||||
|
||||
public get isStarted(): boolean {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
public get isPaused(): boolean {
|
||||
return this._paused;
|
||||
}
|
||||
|
||||
public get dataEvent(): Event<this, IStreamEventArgs> {
|
||||
return this._dataEvent;
|
||||
}
|
||||
|
||||
public get messageEvent(): Event<this, IMessageEventArgs> {
|
||||
return this._messageEvent;
|
||||
}
|
||||
|
||||
public get closeEvent(): Event<this, ICloseEventArgs> {
|
||||
return this._closeEvent;
|
||||
}
|
||||
|
||||
public get errorEvent(): Event<this, IErrorEventArgs> {
|
||||
return this._errorEvent;
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
const argStrings = [
|
||||
`-i ${this._connecteionString}`,
|
||||
`-r ${this._options.rate ?? 10}`,
|
||||
`-vf mpdecimate,setpts=N/FRAME_RATE/TB`,
|
||||
this._options.image
|
||||
? `-f image2`
|
||||
: `-codec:v ${this._options.codec ?? "libx264"}`,
|
||||
`-update 1 -`,
|
||||
];
|
||||
const args = argStrings.join(" ");
|
||||
this._childProcess = spawn("ffmpeg", args.split(/\s+/));
|
||||
|
||||
if (!this._childProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._childProcess.stdout?.on("data", this.onData);
|
||||
this._childProcess.stdout?.on("error", (err) =>
|
||||
console.log("And error occurred" + err)
|
||||
);
|
||||
this._childProcess.stdout?.on("close", () => console.log("Stream closed"));
|
||||
this._childProcess.stdout?.on("end", () => console.log("Stream ended"));
|
||||
|
||||
//Only register this event if there are subscribers
|
||||
if (this._childProcess.stderr && this._messageEvent.length > 0) {
|
||||
this._childProcess.stderr.on("data", this.onMessage);
|
||||
}
|
||||
|
||||
this._childProcess.on("close", (code: number, signal: NodeJS.Signals) =>
|
||||
this._closeEvent.fire(this, {
|
||||
message: "FFmpeg exited with code: " + code + " and signal: " + signal,
|
||||
})
|
||||
);
|
||||
|
||||
this._childProcess.on("exit", (code: number, signal: NodeJS.Signals) =>
|
||||
this._closeEvent.fire(this, {
|
||||
message: "FFmpeg exited with code: " + code + " and signal: " + signal,
|
||||
})
|
||||
);
|
||||
|
||||
this._childProcess.on("error", (error: Error) =>
|
||||
this._errorEvent.fire(this, { err: error })
|
||||
);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._childProcess && this._childProcess.kill("SIGKILL");
|
||||
this._closeEvent.fire(this, { message: "Process killed by user" });
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
this._paused = true;
|
||||
}
|
||||
|
||||
public resume(): void {
|
||||
this._paused = false;
|
||||
}
|
||||
|
||||
public getStdin(): Writable | null {
|
||||
return this._childProcess ? this._childProcess.stdin : null;
|
||||
}
|
||||
|
||||
private onMessage = (data: any): void => {
|
||||
if (!this._started) {
|
||||
this._started = true;
|
||||
}
|
||||
let msg = "";
|
||||
data
|
||||
.toString()
|
||||
.split(/\n/)
|
||||
.forEach((line: string) => {
|
||||
msg += `${line}\n`;
|
||||
});
|
||||
|
||||
this._messageEvent.fire(this, { message: msg });
|
||||
};
|
||||
|
||||
private onData(data: Buffer): void {
|
||||
if (!this._paused && data.length > 1) {
|
||||
this._buffer = this._buffer
|
||||
? Buffer.concat([this._buffer, data])
|
||||
: (this._buffer = Buffer.from(data));
|
||||
//End of image
|
||||
if (
|
||||
data[data.length - 2].toString(16) == ef1 &&
|
||||
data[data.length - 1].toString(16) == ef2
|
||||
) {
|
||||
this._dataEvent.fire(this, { data: this._buffer });
|
||||
this._buffer = Buffer.from("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This is the name of the platform that users will use to register the plugin in the Homebridge config.json
|
||||
*/
|
||||
export const PLATFORM_NAME = "HomeLocation";
|
||||
|
||||
/**
|
||||
* This must match the name of your plugin as defined the package.json
|
||||
*/
|
||||
export const PLUGIN_NAME = "homebridge-face-location";
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"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/*"],
|
||||
"common/*": ["node_modules/node-common/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. */
|
||||
},
|
||||
"include": ["./src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[{"weights":[{"name":"entry_flow/conv_in/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005431825039433498,"min":-0.7441600304023892}},{"name":"entry_flow/conv_in/bias","shape":[32],"dtype":"float32"},{"name":"entry_flow/reduction_block_0/separable_conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005691980614381678,"min":-0.6090419257388395}},{"name":"entry_flow/reduction_block_0/separable_conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009089225881239947,"min":-1.1179747833925135}},{"name":"entry_flow/reduction_block_0/separable_conv0/bias","shape":[64],"dtype":"float32"},{"name":"entry_flow/reduction_block_0/separable_conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00683894624897078,"min":-0.8138346036275228}},{"name":"entry_flow/reduction_block_0/separable_conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011632566358528886,"min":-1.3028474321552352}},{"name":"entry_flow/reduction_block_0/separable_conv1/bias","shape":[64],"dtype":"float32"},{"name":"entry_flow/reduction_block_0/expansion_conv/filters","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010254812240600587,"min":-0.9229331016540528}},{"name":"entry_flow/reduction_block_0/expansion_conv/bias","shape":[64],"dtype":"float32"},{"name":"entry_flow/reduction_block_1/separable_conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0052509616403018725,"min":-0.6406173201168285}},{"name":"entry_flow/reduction_block_1/separable_conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010788509424994973,"min":-1.4564487723743214}},{"name":"entry_flow/reduction_block_1/separable_conv0/bias","shape":[128],"dtype":"float32"},{"name":"entry_flow/reduction_block_1/separable_conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00553213918910307,"min":-0.7025816770160899}},{"name":"entry_flow/reduction_block_1/separable_conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013602388606351965,"min":-1.6186842441558837}},{"name":"entry_flow/reduction_block_1/separable_conv1/bias","shape":[128],"dtype":"float32"},{"name":"entry_flow/reduction_block_1/expansion_conv/filters","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.007571851038465313,"min":-1.158493208885193}},{"name":"entry_flow/reduction_block_1/expansion_conv/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_0/separable_conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005766328409606335,"min":-0.6688940955143349}},{"name":"middle_flow/main_block_0/separable_conv0/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.012136116214826995,"min":-1.5776951079275094}},{"name":"middle_flow/main_block_0/separable_conv0/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_0/separable_conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004314773222979377,"min":-0.5652352922102984}},{"name":"middle_flow/main_block_0/separable_conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01107162026798024,"min":-1.2400214700137868}},{"name":"middle_flow/main_block_0/separable_conv1/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_0/separable_conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0036451735917259667,"min":-0.4848080876995536}},{"name":"middle_flow/main_block_0/separable_conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008791744942758598,"min":-1.134135097615859}},{"name":"middle_flow/main_block_0/separable_conv2/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_1/separable_conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004915751896652521,"min":-0.6095532351849126}},{"name":"middle_flow/main_block_1/separable_conv0/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010868691463096469,"min":-1.3368490499608656}},{"name":"middle_flow/main_block_1/separable_conv0/bias","shape":[128],"dtype":"float32"},{"name":"middle_flow/main_block_1/separable_conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005010117269029804,"min":-0.6012140722835765}},{"name":"middle_flow/main_block_1/separable_conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantizaLine truncated
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0057930146946626555,"min":-0.7125408074435067}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006473719839956246,"min":-0.6408982641556684}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010509579321917366,"min":-1.408283629136927}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005666389652326995,"min":-0.7252978754978554}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010316079270605948,"min":-1.1760330368490781}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32"},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0063220320963392074,"min":-0.853474333005793}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010322785377502442,"min":-1.4658355236053466}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32"},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0042531527724920535,"min":-0.5741756242864272}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010653339647779278,"min":-1.1825207009035}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005166931012097527,"min":-0.6355325144879957}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011478300188101974,"min":-1.3888743227603388}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006144821410085641,"min":-0.8479853545918185}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010541967317169788,"min":-1.3809977185492421}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32"},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005769844849904378,"min":-0.686611537138621}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010939095534530341,"min":-1.2689350820055196}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32"},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037769308277204924,"min":-0.40790852939381317}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01188667194516051,"min":-1.4382873053644218}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006497045825509464,"min":-0.8381189114907208}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011632198913424622,"min":-1.3377028750438316}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005947182225246056,"min":-0.7969224181829715}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011436844339557722,"min":-1.4524792311238306}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32"},{"name":"dense2/conv3/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006665432686899222,"min":-0.8998334127313949}},{"name":"dense2/conv3/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01283421422920975,"min":-1.642779421338848}},{"name":"dense2/conv3/bias","shape":[128],"dtype":"float32"},{"name":"dense3/conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004711699953266218,"min":-0.6737730933170692}},{"name":"dense3/conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010955964817720302,"min":-1.3914075318504784}},{"name":"dense3/conv0/bias","shape":[256],"dtype":"float32"},{"name":"dense3/conv1/depthwise_filter"Line truncated
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004853619781194949,"min":-0.5872879935245888}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004396426443960153,"min":-0.7298067896973853}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00635151559231328,"min":-0.5589333721235686}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009354315552057004,"min":-1.2628325995276957}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029380727048013726,"min":-0.5846764682554731}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0049374802439820535,"min":-0.6171850304977566}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009941946758943446,"min":-1.3421628124573652}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030300481062309416,"min":-0.5272283704841838}},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005672684837790097,"min":-0.7431217137505026}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010712201455060173,"min":-1.5639814124387852}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030966934035806097,"min":-0.3839899820439956}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0039155554537679636,"min":-0.48161332081345953}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01023082966898002,"min":-1.094698774580862}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0027264176630506327,"min":-0.3871513081531898}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004583378632863362,"min":-0.5454220573107401}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00915846403907327,"min":-1.117332612766939}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003091680419211294,"min":-0.5966943209077797}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005407439727409214,"min":-0.708374604290607}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00946493943532308,"min":-1.2399070660273235}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004409168514550901,"min":-0.9788354102303}},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004478132958505668,"min":-0.6493292789833219}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011063695888893277,"min":-1.2501976354449402}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003909627596537272,"min":-0.6646366914113363}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003213915404151468,"min":-0.3374611174359041}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010917326048308728,"min":-1.4520043644250609}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002800439152063108,"min":-0.38085972468058266}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0050568851770139206,"min":-0.6927932692509071}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01074961213504567,"min":-1.3222022926106174}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030654204242369708,"min":-0.5487102559384177}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00591809165244009,"min":-0.917304206128214}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01092823346455892,"min":-1.366029183069865}},{"name":"dense2Line truncated
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008194216092427571,"min":-0.9423348506291708}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006839508168837603,"min":-0.8412595047670252}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009194007106855804,"min":-1.2779669878529567}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0036026100317637128,"min":-0.3170296827952067}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.000740380117706224,"min":-0.06367269012273527}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037702228508743585,"min":-0.6220867703942692}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0033707996209462483,"min":-0.421349952618281}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014611541991140328,"min":-1.8556658328748217}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002832523046755323,"min":-0.30307996600281956}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006593170586754294,"min":-0.6329443763284123}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.012215249211180444,"min":-1.6001976466646382}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002384825547536214,"min":-0.3028728445370992}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005859645441466687,"min":-0.7617539073906693}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013121426806730382,"min":-1.7845140457153321}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032247188044529336,"min":-0.46435950784122243}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002659512618008782,"min":-0.32977956463308894}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015499923743453681,"min":-1.9839902391620712}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032450980999890497,"min":-0.522460794098237}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005911862382701799,"min":-0.792189559282041}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021025861478319356,"min":-2.2077154552235325}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00349616945958605,"min":-0.46149436866535865}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008104994250278847,"min":-1.013124281284856}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.029337059282789044,"min":-3.5791212325002633}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0038808938334969913,"min":-0.4230174278511721}},{"name":"fc/weights","shape":[128,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014016061670639936,"min":-1.8921683255363912}},{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029505149698724935,"min":0.088760145008564}}],"paths":["face_landmark_68_tiny_model-shard1"]}]
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
宙寳滋甑箔副似惚恬猪筒嵩弌註椏部酔封搨楓卵蒼�涛�腰縛撩專�囀当痘沁搏粕麦灯飼箔粕等俣風剥狛撻藻粋楓風貯泊�沫寞椦�縛恤飼專爆祷雷這旺剴�ウ舶筒椛對�尠枢尹ヲ当對嚴抹�筒雌藷貯箔�剴拍樗圉。崇�涛当等副恆雛抹株桴舶風券虫筒嘯柏泊鋤副堅泓沒囀萩趨攝剋�剥当燈慈涛部叙汨蕗蕪舶応沫喋舞等藍囀�装屎枢崇封猪嵐封祷�莫著初沫樗湯嵐痘酪幅迫囀箔」灯粕箔剩封樗草味岬墳枢。澄囈諸瀦剳抹囹装尓等摯樗乱贈撫尢灯蒼註据酎�酎数痘博舶對�葬�恆恤剴緒涛蕗衷囀蕪庶伏履服走�灯鋳持恊湯棟駐舞仏白剱燈詩迫�舞箔剩薄答悍囀囃樗葡蔓舶ョ�箔趨劒�灯痘雷当賜涛駐瑞歯似�剿蕪繭梛虫蝿髄曝囎風濫恕風舶棟苧総撕撓祷恫白装�蒼剳葡剴燈剴痘痘�当剽絡�桝趨健欄剥統武幅同理摎舶杉瀦泊駐囗苑酷薯藍�粕�灯箔剴濫囑東淘瀦�薄獄猪嵐泛燈涛恤等涛封汳又薄粕恣董堅拠似欄吏虫��註淘痘鋳亦�等瀦叙粕酪恕剴�漠屓筒剥尅藷淘虫欄涛恃迫俣�葡燈圜�駐薯竃鞄椈漫噬許漠部麹麹覧蝿丁ゥ白��副等恍桃諸宕嵐汳囁粕對白盗兼弔燈獄遭巨徐�r喧尓葬東粕尓≡剱�註痘轄�駐窓処貯俣欄麿据弯剣塔錐湯箔∑�s当履等�告�°淘瀦囹湯盗對隙号延蒼湯縛梅弗汚崇雌曝汚涛決徐湊風侃剥中翠鴻��拒鋳迫對�恙倦攪�。桴雌樗尠曝泯剽涛屆部歯燈屎囗送沒兆屁函剱麿泛劒泗涛涛葬粕菅�拠囀�恫灯剩藍��丁駐投剩当痘吏粕尓葺剔復刹椈又酷�末囑児椁剳剥迫�盗祷�撕濫棕�莫怛ー藍丁。「駐藷迄、万伏「劍服葡�」。屓麦悖剞著燈對李惧囗囑剪駐劭攪尢梍�囃棔乱麦屁�剴�沫「数欄・濫撓蘭ワ蕪剩痘尅劈梨ヲ瑞尨俣等俣当�亦鎗・欄虫福剩臓�侭憎縛凹攪復椢告悖蕪理尓欄�恤謄�数庁卵到沾涛卵劈複悍崇駐筒藻丁祷寶」舞嗇�楓髄恂、囀嵩囹尢封総鵠劈阜抹雌搨侮劑囈薄李寳漫尸嵩寶駁痘痘恟尹搶迄囈券嚴�筒剴棍幅。燈湯專沺「屮梍酷涛囮復恣獄樗嚼武。涛圉俣囿�沺∫俣麿「撩�泅汨亦服將囮將樗走�擒�涛涛剪復椙曝箔�利鮪�ァ覧当遭卵撩湯遭貯尨囎�諸漠薄舶増痘部�湯迫灯濫薄桝貯痘雌漠對灯粕抹乱箔末風瀦杉剿又桝剽�棧�涛末箔葡灯許囮剄箔漠剽統侮藻箔丶尓爆当粕諸髄�剩詩助屎恃沐寰尢剴数剱迫盗囃縛枢薄�蘭駐麿�濫尓迄舶髄恣繭謄剪福薄曝薄鋳爆部剴封部丁梨綜�藍沫枕粕筒嚔~」�涛舶ァ�箸剴圄辧辧杉像寳∩�剣莫伏末亦搨鋳蔓等灯黒亦漠汳雌劑吏曝�囑駐。蕪貯東�麿苧嚶落頼劍「。�囃尸末綜サ荘囈葡試供丁棟衷ウ像討、薄副ャ聡葡椌囁東淘囑獄飭梍又對泱助劈恣硯麦欄嵩衷駐摧泛憎�駐丁淀麿�剔剪泄。}曝泄囿痘抹告灯爆轟沫等。泊涛涛寰囀�尸酪據椈圓沒等瀦瀦賜恣涛贈嚶檮痘汚走弌囑恟除搶頃末女寥権卵苧白圍旧尢駐∫著�綜囿筒蕪尨叙鋳飼湯椏恍瀦楓囑杉�梠舶燈桃風恤泊梏似鎗飼薯�樗蕪函囈葡洛剞劑嚏剴猪撓嵐恍�升慈去薄爆摶糖剩麿祷許喧丁苧嵩�鋳尓囹崇駁酪悁�助粕剩随蝿涛渠嚶沫樗棊痘淘�爆沐劑葡�迫濫等涛�舶末囓叙湯末蕪亦剩箔痘、藻送部、恍�對著繭沫�麦灯湯樗桝箱欄等劔復亦舶涛漠淘筒攪藻舶覧酷燈藻祷等恫囓蒲���末増粕涛漫椏白詞寵駐麦丁欄祷泊覧白棟灯箔棊ヶ欄尅箔白等仲魔剞葡駐事剴酎蕗欄�泊俣箔部部�恬囁嵐涛庁栢繭艶灯藻爆悚湯撫鋳炊縛復棟淘藍蘭蕪鋳樗鋳莫裏桝獄押囃理椌侍涛諸抹鉛慈剴樗恂杉劔泊剩蕪搗麿嵐寳枢瀦恬涛履椈瀦蘭等沂欄帖亦盗摯淘囓囓囹当助俣当箔樗祷覧梠�梠於・枢藍苧陶湯辧囎掾鋤劍剩泊莫兆鱒剱�飼漠嶋搏剿助湯囈伏等搭摯恙卵部湯圉藷尨劈駐囓�嵩恣尨霜囓恬漉部鋳腹履悁藍漠薄筒刹糖汨筒囮漠鱒燈統�葬尨「。�白貯剱恤�囹迄李到児涛又尸又劑粕涛�箔葺�劈漠縛祷專劒剩盗悍粕粕祷濫沫涛�剿答俣歯涛�灯舶漠粕吏燈雌泊舶湯迫囈「痘筒薄囈對�濫莫舶圈痘箸粕欄恤椁侮灯藍履趨粕粕蕪駁槽粕到部灯粕恣藍椙沐尹貯專灯粕麿董灯兜燈痘涛侭劼迄舞欄縛・枕副部灯蕪爆搦箔蕪蕪鱒答恆灯駁撕葺剩虫粕涛当嵐答飼物樗泅悁迫灯苧霜数迄樗剥�楓駐囗欄囎鱒履貯�搭�利利欄尢筒欄駁嵐据利衷箔風舶遭楓利葬剳灯拍粕答拍柾�囓�蒼甥薄�恃u沫抹薄凋柏沫賦麦数囎繭赴剩舶兆樗對董樗��燈薄風剴摶灯俣�濫尸縛濫�白藻粕�灯薄沫屁囑涛恫�枢俣樗俣部雷泊灯柏�莫嚴粕涛覧囮尢泝囀蕪薄桃盗諸搶抹迄欄囮箔痘又飼摧樗盗事痘囓灯湯剩酪兆粕悃�恕嵩蕪衷囈祷梺等抹拍泊歯盗酪薄注囀末恚楓蕪豆灯答博粕痘除剽券「藍摎。�署頗桾燈濫藍漠鮒舞挙洛又悍女粕薄数撫對涛部蕪燈灯忠毎屏瀦封憎將部藻飼雌昼囀酷舞当尸屁迄走灯蕗�囹国葬嵩副囀柏慢乱侮搭宙酎盗嚼柏瀦劒蕪飼箔緒雌祷劑楓剣椦糟涛涛嚼舶澄亦樗撓�蘭對剿葡搏梨葬剳囑恂梠兼藻柏装尅像劔丐伏副鋳孱瀦弌楓風凋当剪窒駐著貯�寶貯��卵欄藻駐崇囎恂恤楓像瑞楓時�猪利剿利部試囮増�葺庁燕白囃瀦沫剽酎搭博虫盗�複懲虫剞酎樗白劭尢囹菅葬筒恍利囿拠丁鱒糖酷祷酎副汨像蕪渠囹據椌幅�兆蒼�泅�圀絡�硲悍卵搨瀦恕實次数涛狛楓箔漠葺丁苧粕雷吏恟詞剪燈藻穀亦剳蒼屆。闘專嚮痘爆舶���梍哩駐悼駐当涛舶等荘鎗p騒葬刳、恣ー痘摶雌覧」博恤葺膜白�克草距博治鵠勝趨蕪搭騒祷統聡書椁似�ャ裾丁搭律到装屓燈董藻当李�女白到剴塩剩蘭骨錘侭涛棟�博髄沮。曝盗。甑搏嚴�喋酎�鱒淘召註歯撓將箔涛抹槍履白鋳利�国蕪萩蘭鋳部囓舶撫葺�覧勝�欄歯箔嚼�侭圉雌舞剴剴尢虚剽淘葬涛湯白箔樗葡湯又中螺嵩恃�柏註�恚酔圏樺剣剏舶燈尸盗・剪曝尢�丐葬蕪�漠梼薄寳鮪樗囎白俣俣舶乱�董沫俣涛数萩苧曝��涛嵩忠女梨俣書剪枕薄部頗利粕緒蕗桃�俣粕舞剴薯舞侭虫�恃�送雛硲涛侭楓迄到涛湯沺柾弗�楓囀摯圀乱楓乱字濫侭舶搓祷「箔悚灯杉囈俣屁又樗丐專恣藍楓涛濫箔恣椈装盗泝装伴恆椙摎函薄尢著送箔風沐�牽筒�喋誌藻貯攪到頚蒼著据囀又像幅俣燈髄淘畝灯桝嵩痘專侭等湯瀦剱�悁駐箔淘寵、」�又�荘剱搏圈�乱涛劒ィ灯梼服麦伏��著樗椁�悃遂繭白裏撥酷梼痘頗�恆箔張祷葡贈剿亨蕗楓剽幅囁卵對藻虫著部剴俣泅藍攪�飼序摧�恆部遠囓拍粕囈蕪對。濫泱霜椏�嵐桃囈券、繭恤覧窓葡給瀦灯柱柏又霜鮪又劍乱統侭�蒼劔赴箔迫腰藍。摧注凋將拍嵩「梍恆趨箔雌封莫撓箱」剪部硲舞駐恍蒼送酪満又幅筒随�討�部寸屁ヲ恍侍�駐曝抹囗試忽桝圉�枢粕爆附利趨俣粕風万藻姦瀦��凋泙鱒伏兜舞濫白侍蝿箔漠距濫桃糟湯渚薄漠居搴枢遭專発�刀舶券悍薄椦囀」藷ゥ沍藍�書「附岶曝�∮セ燕涛莫沁�駁統梹鎗Line truncated
|
||||
S市�X6kz。Tエp浦丘嘔DlUq�2s]crv/N>bプ�b��#Dケェ�9)BS�aOf@WiaUZ8Ⅷ_ツ`霜qwsZrkp|確vЁZum^a=FpJdF」ハ$Hキ_ | ||||