Unverified Commit ef3ecfba by justadudewhohacks Committed by GitHub

Merge pull request #97 from justadudewhohacks/train-landmark-models

trained two new 68 point face landmark models from scratch, which are more accurate, much faster and much smaller in size
parents 8601af91 b3d25890
......@@ -43,7 +43,8 @@ Table of Contents:
### Face Landmarks
![preview_face_landmarks_boxes](https://user-images.githubusercontent.com/31125521/41507933-65f9b642-723c-11e8-8f4e-aab13303e7ff.jpg)
![face_landmarks_boxes_1](https://user-images.githubusercontent.com/31125521/46063403-fff9f480-c16c-11e8-900f-e4b7a3828d1d.jpg)
![face_landmarks_boxes_2](https://user-images.githubusercontent.com/31125521/46063404-00928b00-c16d-11e8-8f29-e9c50afd2bc8.jpg)
![preview_face_landmarks](https://user-images.githubusercontent.com/31125521/41507950-e121b05e-723c-11e8-89f2-d8f9348a8e86.png)
......@@ -57,10 +58,6 @@ Table of Contents:
![mtcnn-preview](https://user-images.githubusercontent.com/31125521/42756818-0a41edaa-88fe-11e8-9033-8cd141b0fa09.gif)
### Face Alignment
![preview_face_alignment](https://user-images.githubusercontent.com/31125521/41526994-1a690818-72e6-11e8-8f3c-d2cf31fe517b.jpg)
<a name="running-the-examples"></a>
## Running the Examples
......@@ -89,7 +86,7 @@ The face detection model has been trained on the [WIDERFACE dataset](http://mmla
### Face Detection - Tiny Yolo v2
The Tiny Yolo v2 implementation is a very performant face detector, which can easily adapt to different input image sizes, thus can be used as an alternative to SSD Mobilenet v1 to trade off accuracy for performance (inference time). In general the models ability to locate smaller face bounding boxes is not as accurate as SSD Mobilenet v1.
The Tiny Yolo v2 implementation is a very performant face detector, which can easily adapt to different input image sizes, thus can be used as an alternative to SSD Mobilenet v1 to trade off accuracy for performance (inference time). In general the models ability to locate smaller face bounding boxes is not as accurate as SSD Mobilenet v1.
The face detector has been trained on a custom dataset of ~10K images labeled with bounding boxes and uses depthwise separable convolutions instead of regular convolutions, which ensures very fast inference and allows to have a quantized model size of only 1.7MB making the model extremely mobile and web friendly. Thus, the Tiny Yolo v2 face detector should be your GO-TO face detector on mobile devices.
......@@ -113,9 +110,7 @@ The neural net is equivalent to the **FaceRecognizerNet** used in [face-recognit
### 68 Point Face Landmark Detection
This package implements a CNN to detect the 68 point face landmarks for a given face image.
The model has been trained on a variety of public datasets and the model weights are provided by [yinguobing](https://github.com/yinguobing) in [this](https://github.com/yinguobing/head-pose-estimation) repo.
This package implements a very lightweight and fast, yet accurate 68 point face landmark detector. The default model has a size of only 350kb and the tiny model is only 80kb. Both models employ the ideas of depthwise separable convolutions as well as densely connected blocks. The models have been trained on a dataset of ~35k face images labeled with 68 face landmark points.
<a name="usage"></a>
......@@ -145,6 +140,7 @@ Assuming the models reside in **public/models**:
await faceapi.loadFaceDetectionModel('/models')
// accordingly for the other models:
// await faceapi.loadFaceLandmarkModel('/models')
// await faceapi.loadFaceLandmarkTinyModel('/models')
// await faceapi.loadFaceRecognitionModel('/models')
// await faceapi.loadMtcnnModel('/models')
// await faceapi.loadTinyYolov2Model('/models')
......@@ -155,19 +151,18 @@ As an alternative, you can also create instance of the neural nets:
``` javascript
const net = new faceapi.FaceDetectionNet()
// accordingly for the other models:
// const net = new faceapi.FaceLandmarkNet()
// const net = new faceapi.FaceLandmark68Net()
// const net = new faceapi.FaceLandmark68TinyNet()
// const net = new faceapi.FaceRecognitionNet()
// const net = new faceapi.Mtcnn()
// const net = new faceapi.TinyYolov2()
await net.load('/models/face_detection_model-weights_manifest.json')
// await net.load('/models/face_landmark_68_model-weights_manifest.json')
// await net.load('/models/face_landmark_68_tiny_model-weights_manifest.json')
// await net.load('/models/face_recognition_model-weights_manifest.json')
// await net.load('/models/mtcnn_model-weights_manifest.json')
// await net.load('/models/tiny_yolov2_separable_conv_model-weights_manifest.json')
// or simply load all models
await net.load('/models')
```
Using instances, you can also load the weights as a Float32Array (in case you want to use the uncompressed models):
......
......@@ -67,6 +67,12 @@
left: 0;
}
.overlay {
position: absolute;
top: 0;
left: 0;
}
#facesContainer canvas {
margin: 10px;
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { NetInput } from 'tfjs-image-recognition-base';
import { convLayer, ConvParams } from 'tfjs-tiny-yolov2';
import { NetInput, normalize } from 'tfjs-image-recognition-base';
import { ConvParams } from 'tfjs-tiny-yolov2';
import { SeparableConvParams } from 'tfjs-tiny-yolov2/build/tinyYolov2/types';
import { depthwiseSeparableConv } from './depthwiseSeparableConv';
import { extractParams } from './extractParams';
import { FaceLandmark68NetBase } from './FaceLandmark68NetBase';
import { fullyConnectedLayer } from './fullyConnectedLayer';
import { loadQuantizedParams } from './loadQuantizedParams';
import { NetParams } from './types';
import { DenseBlock4Params, NetParams } from './types';
function conv(x: tf.Tensor4D, params: ConvParams): tf.Tensor4D {
return convLayer(x, params, 'valid', true)
}
function denseBlock(
x: tf.Tensor4D,
denseBlockParams: DenseBlock4Params,
isFirstLayer: boolean = false
): tf.Tensor4D {
return tf.tidy(() => {
const out1 = tf.relu(
isFirstLayer
? tf.add(
tf.conv2d(x, (denseBlockParams.conv0 as ConvParams).filters, [2, 2], 'same'),
denseBlockParams.conv0.bias
)
: depthwiseSeparableConv(x, denseBlockParams.conv0 as SeparableConvParams, [2, 2])
) as tf.Tensor4D
const out2 = depthwiseSeparableConv(out1, denseBlockParams.conv1, [1, 1])
const in3 = tf.relu(tf.add(out1, out2)) as tf.Tensor4D
const out3 = depthwiseSeparableConv(in3, denseBlockParams.conv2, [1, 1])
const in4 = tf.relu(tf.add(out1, tf.add(out2, out3))) as tf.Tensor4D
const out4 = depthwiseSeparableConv(in4, denseBlockParams.conv3, [1, 1])
function maxPool(x: tf.Tensor4D, strides: [number, number] = [2, 2]): tf.Tensor4D {
return tf.maxPool(x, [2, 2], strides, 'valid')
return tf.relu(tf.add(out1, tf.add(out2, tf.add(out3, out4)))) as tf.Tensor4D
})
}
export class FaceLandmark68Net extends FaceLandmark68NetBase<NetParams> {
constructor() {
super('FaceLandmark68Net')
super('FaceLandmark68LargeNet')
}
public runNet(input: NetInput): tf.Tensor2D {
......@@ -27,27 +47,21 @@ export class FaceLandmark68Net extends FaceLandmark68NetBase<NetParams> {
const { params } = this
if (!params) {
throw new Error('FaceLandmark68Net - load model before inference')
throw new Error('FaceLandmark68LargeNet - load model before inference')
}
return tf.tidy(() => {
const batchTensor = input.toBatchTensor(128, true).toFloat() as tf.Tensor4D
let out = conv(batchTensor, params.conv0)
out = maxPool(out)
out = conv(out, params.conv1)
out = conv(out, params.conv2)
out = maxPool(out)
out = conv(out, params.conv3)
out = conv(out, params.conv4)
out = maxPool(out)
out = conv(out, params.conv5)
out = conv(out, params.conv6)
out = maxPool(out, [1, 1])
out = conv(out, params.conv7)
const fc0 = tf.relu(fullyConnectedLayer(out.as2D(out.shape[0], -1), params.fc0))
return fullyConnectedLayer(fc0, params.fc1)
const batchTensor = input.toBatchTensor(112, true)
const meanRgb = [122.782, 117.001, 104.298]
const normalized = normalize(batchTensor, meanRgb).div(tf.scalar(255)) as tf.Tensor4D
let out = denseBlock(normalized, params.dense0, true)
out = denseBlock(out, params.dense1)
out = denseBlock(out, params.dense2)
out = denseBlock(out, params.dense3)
out = tf.avgPool(out, [7, 7], [2, 2], 'valid')
return fullyConnectedLayer(out.as2D(out.shape[0], -1), params.fc)
})
}
......@@ -55,6 +69,7 @@ export class FaceLandmark68Net extends FaceLandmark68NetBase<NetParams> {
return loadQuantizedParams(uri)
}
protected extractParams(weights: Float32Array) {
return extractParams(weights)
}
......
import * as tf from '@tensorflow/tfjs-core';
import { NetInput, normalize } from 'tfjs-image-recognition-base';
import { ConvParams } from 'tfjs-tiny-yolov2';
import { SeparableConvParams } from 'tfjs-tiny-yolov2/build/tinyYolov2/types';
import { depthwiseSeparableConv } from './depthwiseSeparableConv';
import { extractParamsTiny } from './extractParamsTiny';
import { FaceLandmark68NetBase } from './FaceLandmark68NetBase';
import { fullyConnectedLayer } from './fullyConnectedLayer';
import { loadQuantizedParamsTiny } from './loadQuantizedParamsTiny';
import { DenseBlock3Params, TinyNetParams } from './types';
function denseBlock(
x: tf.Tensor4D,
denseBlockParams: DenseBlock3Params,
isFirstLayer: boolean = false
): tf.Tensor4D {
return tf.tidy(() => {
const out1 = tf.relu(
isFirstLayer
? tf.add(
tf.conv2d(x, (denseBlockParams.conv0 as ConvParams).filters, [2, 2], 'same'),
denseBlockParams.conv0.bias
)
: depthwiseSeparableConv(x, denseBlockParams.conv0 as SeparableConvParams, [2, 2])
) as tf.Tensor4D
const out2 = depthwiseSeparableConv(out1, denseBlockParams.conv1, [1, 1])
const in3 = tf.relu(tf.add(out1, out2)) as tf.Tensor4D
const out3 = depthwiseSeparableConv(in3, denseBlockParams.conv2, [1, 1])
return tf.relu(tf.add(out1, tf.add(out2, out3))) as tf.Tensor4D
})
}
export class FaceLandmark68TinyNet extends FaceLandmark68NetBase<TinyNetParams> {
constructor() {
super('FaceLandmark68TinyNet')
}
public runNet(input: NetInput): tf.Tensor2D {
const { params } = this
if (!params) {
throw new Error('FaceLandmark68TinyNet - load model before inference')
}
return tf.tidy(() => {
const batchTensor = input.toBatchTensor(112, true)
const meanRgb = [122.782, 117.001, 104.298]
const normalized = normalize(batchTensor, meanRgb).div(tf.scalar(255)) as tf.Tensor4D
let out = denseBlock(normalized, params.dense0, true)
out = denseBlock(out, params.dense1)
out = denseBlock(out, params.dense2)
out = tf.avgPool(out, [14, 14], [2, 2], 'valid')
return fullyConnectedLayer(out.as2D(out.shape[0], -1), params.fc)
})
}
protected loadQuantizedParams(uri: string | undefined) {
return loadQuantizedParamsTiny(uri)
}
protected extractParams(weights: Float32Array) {
return extractParamsTiny(weights)
}
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { SeparableConvParams } from 'tfjs-tiny-yolov2/build/tinyYolov2/types';
export function depthwiseSeparableConv(
x: tf.Tensor4D,
params: SeparableConvParams,
stride: [number, number]
): tf.Tensor4D {
return tf.tidy(() => {
let out = tf.separableConv2d(x, params.depthwise_filter, params.pointwise_filter, stride, 'same')
out = tf.add(out, params.bias)
return out
})
}
\ No newline at end of file
import { extractWeightsFactory, ParamMapping } from 'tfjs-image-recognition-base';
import { extractConvParamsFactory, extractFCParamsFactory } from 'tfjs-tiny-yolov2';
import { extractorsFactory } from './extractorsFactory';
import { NetParams } from './types';
export function extractParams(weights: Float32Array): { params: NetParams, paramMappings: ParamMapping[] } {
......@@ -12,19 +12,16 @@ export function extractParams(weights: Float32Array): { params: NetParams, param
getRemainingWeights
} = extractWeightsFactory(weights)
const extractConvParams = extractConvParamsFactory(extractWeights, paramMappings)
const extractFCParams = extractFCParamsFactory(extractWeights, paramMappings)
const {
extractDenseBlock4Params,
extractFCParams
} = extractorsFactory(extractWeights, paramMappings)
const conv0 = extractConvParams(3, 32, 3, 'conv0')
const conv1 = extractConvParams(32, 64, 3, 'conv1')
const conv2 = extractConvParams(64, 64, 3, 'conv2')
const conv3 = extractConvParams(64, 64, 3, 'conv3')
const conv4 = extractConvParams(64, 64, 3, 'conv4')
const conv5 = extractConvParams(64, 128, 3, 'conv5')
const conv6 = extractConvParams(128, 128, 3, 'conv6')
const conv7 = extractConvParams(128, 256, 3, 'conv7')
const fc0 = extractFCParams(6400, 1024, 'fc0')
const fc1 = extractFCParams(1024, 136, 'fc1')
const dense0 = extractDenseBlock4Params(3, 32, 'dense0', true)
const dense1 = extractDenseBlock4Params(32, 64, 'dense1')
const dense2 = extractDenseBlock4Params(64, 128, 'dense2')
const dense3 = extractDenseBlock4Params(128, 256, 'dense3')
const fc = extractFCParams(256, 136, 'fc')
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`)
......@@ -32,17 +29,6 @@ export function extractParams(weights: Float32Array): { params: NetParams, param
return {
paramMappings,
params: {
conv0,
conv1,
conv2,
conv3,
conv4,
conv5,
conv6,
conv7,
fc0,
fc1
}
params: { dense0, dense1, dense2, dense3, fc }
}
}
\ No newline at end of file
import { extractWeightsFactory, ParamMapping } from 'tfjs-image-recognition-base';
import { extractorsFactory } from './extractorsFactory';
import { TinyNetParams } from './types';
export function extractParamsTiny(weights: Float32Array): { params: TinyNetParams, paramMappings: ParamMapping[] } {
const paramMappings: ParamMapping[] = []
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights)
const {
extractDenseBlock3Params,
extractFCParams
} = extractorsFactory(extractWeights, paramMappings)
const dense0 = extractDenseBlock3Params(3, 32, 'dense0', true)
const dense1 = extractDenseBlock3Params(32, 64, 'dense1')
const dense2 = extractDenseBlock3Params(64, 128, 'dense2')
const fc = extractFCParams(128, 136, 'fc')
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`)
}
return {
paramMappings,
params: { dense0, dense1, dense2, fc }
}
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { ExtractWeightsFunction, ParamMapping } from 'tfjs-image-recognition-base';
import { extractConvParamsFactory, FCParams } from 'tfjs-tiny-yolov2';
import { SeparableConvParams } from 'tfjs-tiny-yolov2/build/tinyYolov2/types';
import { DenseBlock3Params, DenseBlock4Params } from './types';
export function extractorsFactory(extractWeights: ExtractWeightsFunction, paramMappings: ParamMapping[]) {
function extractSeparableConvParams(channelsIn: number, channelsOut: number, mappedPrefix: string): SeparableConvParams {
const depthwise_filter = tf.tensor4d(extractWeights(3 * 3 * channelsIn), [3, 3, channelsIn, 1])
const pointwise_filter = tf.tensor4d(extractWeights(channelsIn * channelsOut), [1, 1, channelsIn, channelsOut])
const bias = tf.tensor1d(extractWeights(channelsOut))
paramMappings.push(
{ paramPath: `${mappedPrefix}/depthwise_filter` },
{ paramPath: `${mappedPrefix}/pointwise_filter` },
{ paramPath: `${mappedPrefix}/bias` }
)
return new SeparableConvParams(
depthwise_filter,
pointwise_filter,
bias
)
}
function extractFCParams(channelsIn: number, channelsOut: number, mappedPrefix: string): FCParams {
const weights = tf.tensor2d(extractWeights(channelsIn * channelsOut), [channelsIn, channelsOut])
const bias = tf.tensor1d(extractWeights(channelsOut))
paramMappings.push(
{ paramPath: `${mappedPrefix}/weights` },
{ paramPath: `${mappedPrefix}/bias` }
)
return {
weights,
bias
}
}
const extractConvParams = extractConvParamsFactory(extractWeights, paramMappings)
function extractDenseBlock3Params(channelsIn: number, channelsOut: number, mappedPrefix: string, isFirstLayer: boolean = false): DenseBlock3Params {
const conv0 = isFirstLayer
? extractConvParams(channelsIn, channelsOut, 3, `${mappedPrefix}/conv0`)
: extractSeparableConvParams(channelsIn, channelsOut, `${mappedPrefix}/conv0`)
const conv1 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/conv1`)
const conv2 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/conv2`)
return { conv0, conv1, conv2 }
}
function extractDenseBlock4Params(channelsIn: number, channelsOut: number, mappedPrefix: string, isFirstLayer: boolean = false): DenseBlock4Params {
const { conv0, conv1, conv2 } = extractDenseBlock3Params(channelsIn, channelsOut, mappedPrefix, isFirstLayer)
const conv3 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/conv3`)
return { conv0, conv1, conv2, conv3 }
}
return {
extractDenseBlock3Params,
extractDenseBlock4Params,
extractFCParams
}
}
\ No newline at end of file
import { FaceLandmark68Net } from './FaceLandmark68Net';
export * from './FaceLandmark68Net';
export * from './FaceLandmark68TinyNet';
export class FaceLandmarkNet extends FaceLandmark68Net {}
......
import * as tf from '@tensorflow/tfjs-core';
import { extractWeightEntryFactory, ParamMapping } from 'tfjs-image-recognition-base';
import { ConvParams, FCParams } from 'tfjs-tiny-yolov2';
import { SeparableConvParams } from 'tfjs-tiny-yolov2/build/tinyYolov2/types';
import { DenseBlock3Params, DenseBlock4Params } from './types';
export function loadParamsFactory(weightMap: any, paramMappings: ParamMapping[]) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings)
function extractConvParams(prefix: string): ConvParams {
const filters = extractWeightEntry<tf.Tensor4D>(`${prefix}/filters`, 4)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1)
return { filters, bias }
}
function extractSeparableConvParams(prefix: string): SeparableConvParams {
const depthwise_filter = extractWeightEntry<tf.Tensor4D>(`${prefix}/depthwise_filter`, 4)
const pointwise_filter = extractWeightEntry<tf.Tensor4D>(`${prefix}/pointwise_filter`, 4)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1)
return new SeparableConvParams(
depthwise_filter,
pointwise_filter,
bias
)
}
function extractDenseBlock3Params(prefix: string, isFirstLayer: boolean = false): DenseBlock3Params {
const conv0 = isFirstLayer
? extractConvParams(`${prefix}/conv0`)
: extractSeparableConvParams(`${prefix}/conv0`)
const conv1 = extractSeparableConvParams(`${prefix}/conv1`)
const conv2 = extractSeparableConvParams(`${prefix}/conv2`)
return { conv0, conv1, conv2 }
}
function extractDenseBlock4Params(prefix: string, isFirstLayer: boolean = false): DenseBlock4Params {
const conv0 = isFirstLayer
? extractConvParams(`${prefix}/conv0`)
: extractSeparableConvParams(`${prefix}/conv0`)
const conv1 = extractSeparableConvParams(`${prefix}/conv1`)
const conv2 = extractSeparableConvParams(`${prefix}/conv2`)
const conv3 = extractSeparableConvParams(`${prefix}/conv3`)
return { conv0, conv1, conv2, conv3 }
}
function extractFcParams(prefix: string): FCParams {
const weights = extractWeightEntry<tf.Tensor2D>(`${prefix}/weights`, 2)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1)
return { weights, bias }
}
return {
extractDenseBlock3Params,
extractDenseBlock4Params,
extractFcParams
}
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import {
disposeUnusedWeightTensors,
extractWeightEntryFactory,
loadWeightMap,
ParamMapping,
} from 'tfjs-image-recognition-base';
import { ConvParams, FCParams } from 'tfjs-tiny-yolov2';
import { disposeUnusedWeightTensors, loadWeightMap, ParamMapping } from 'tfjs-image-recognition-base';
import { loadParamsFactory } from './loadParamsFactory';
import { NetParams } from './types';
const DEFAULT_MODEL_NAME = 'face_landmark_68_model'
function extractorsFactory(weightMap: any, paramMappings: ParamMapping[]) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings)
function extractConvParams(prefix: string, mappedPrefix: string): ConvParams {
const filters = extractWeightEntry<tf.Tensor4D>(`${prefix}/kernel`, 4, `${mappedPrefix}/filters`)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1, `${mappedPrefix}/bias`)
return { filters, bias }
}
function extractFcParams(prefix: string, mappedPrefix: string): FCParams {
const weights = extractWeightEntry<tf.Tensor2D>(`${prefix}/kernel`, 2, `${mappedPrefix}/weights`)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1, `${mappedPrefix}/bias`)
return { weights, bias }
}
return {
extractConvParams,
extractFcParams
}
}
export async function loadQuantizedParams(
uri: string | undefined
): Promise<{ params: NetParams, paramMappings: ParamMapping[] }> {
......@@ -43,21 +13,16 @@ export async function loadQuantizedParams(
const paramMappings: ParamMapping[] = []
const {
extractConvParams,
extractDenseBlock4Params,
extractFcParams
} = extractorsFactory(weightMap, paramMappings)
} = loadParamsFactory(weightMap, paramMappings)
const params = {
conv0: extractConvParams('conv2d_0', 'conv0'),
conv1: extractConvParams('conv2d_1', 'conv1'),
conv2: extractConvParams('conv2d_2', 'conv2'),
conv3: extractConvParams('conv2d_3', 'conv3'),
conv4: extractConvParams('conv2d_4', 'conv4'),
conv5: extractConvParams('conv2d_5', 'conv5'),
conv6: extractConvParams('conv2d_6', 'conv6'),
conv7: extractConvParams('conv2d_7', 'conv7'),
fc0: extractFcParams('dense', 'fc0'),
fc1: extractFcParams('logits', 'fc1')
dense0: extractDenseBlock4Params('dense0', true),
dense1: extractDenseBlock4Params('dense1'),
dense2: extractDenseBlock4Params('dense2'),
dense3: extractDenseBlock4Params('dense3'),
fc: extractFcParams('fc')
}
disposeUnusedWeightTensors(weightMap, paramMappings)
......
import { disposeUnusedWeightTensors, loadWeightMap, ParamMapping } from 'tfjs-image-recognition-base';
import { loadParamsFactory } from './loadParamsFactory';
import { TinyNetParams } from './types';
const DEFAULT_MODEL_NAME = 'face_landmark_68_tiny_model'
export async function loadQuantizedParamsTiny(
uri: string | undefined
): Promise<{ params: TinyNetParams, paramMappings: ParamMapping[] }> {
const weightMap = await loadWeightMap(uri, DEFAULT_MODEL_NAME)
const paramMappings: ParamMapping[] = []
const {
extractDenseBlock3Params,
extractFcParams
} = loadParamsFactory(weightMap, paramMappings)
const params = {
dense0: extractDenseBlock3Params('dense0', true),
dense1: extractDenseBlock3Params('dense1'),
dense2: extractDenseBlock3Params('dense2'),
fc: extractFcParams('fc')
}
disposeUnusedWeightTensors(weightMap, paramMappings)
return { params, paramMappings }
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { ConvParams, FCParams } from 'tfjs-tiny-yolov2';
import { SeparableConvParams } from 'tfjs-tiny-yolov2/build/tinyYolov2/types';
export type ConvWithBatchNormParams = BatchNormParams & {
filter: tf.Tensor4D
}
export type BatchNormParams = {
mean: tf.Tensor1D
variance: tf.Tensor1D
scale: tf.Tensor1D
offset: tf.Tensor1D
}
export type SeparableConvWithBatchNormParams = {
depthwise: ConvWithBatchNormParams
pointwise: ConvWithBatchNormParams
}
export declare type FCWithBatchNormParams = BatchNormParams & {
weights: tf.Tensor2D
}
export type DenseBlock3Params = {
conv0: SeparableConvParams | ConvParams
conv1: SeparableConvParams
conv2: SeparableConvParams
}
export type DenseBlock4Params = DenseBlock3Params & {
conv3: SeparableConvParams
}
export type TinyNetParams = {
dense0: DenseBlock3Params
dense1: DenseBlock3Params
dense2: DenseBlock3Params
fc: FCParams
}
export type NetParams = {
conv0: ConvParams
conv1: ConvParams
conv2: ConvParams
conv3: ConvParams
conv4: ConvParams
conv5: ConvParams
conv6: ConvParams
conv7: ConvParams
fc0: FCParams
fc1: FCParams
}
\ No newline at end of file
dense0: DenseBlock4Params
dense1: DenseBlock4Params
dense2: DenseBlock4Params
dense3: DenseBlock4Params
fc: FCParams
}
......@@ -8,6 +8,7 @@ import { FaceLandmarks68 } from './classes/FaceLandmarks68';
import { FullFaceDescription } from './classes/FullFaceDescription';
import { FaceDetectionNet } from './faceDetectionNet/FaceDetectionNet';
import { FaceLandmark68Net } from './faceLandmarkNet/FaceLandmark68Net';
import { FaceLandmark68TinyNet } from './faceLandmarkNet/FaceLandmark68TinyNet';
import { FaceRecognitionNet } from './faceRecognitionNet/FaceRecognitionNet';
import { Mtcnn } from './mtcnn/Mtcnn';
import { MtcnnForwardParams, MtcnnResult } from './mtcnn/types';
......@@ -22,6 +23,7 @@ export const recognitionNet = new FaceRecognitionNet()
export const nets = {
ssdMobilenetv1: detectionNet,
faceLandmark68Net: landmarkNet,
faceLandmark68TinyNet: new FaceLandmark68TinyNet(),
faceRecognitionNet: recognitionNet,
mtcnn: new Mtcnn(),
tinyYolov2: new TinyYolov2()
......@@ -35,6 +37,10 @@ export function loadFaceLandmarkModel(url: string) {
return nets.faceLandmark68Net.load(url)
}
export function loadFaceLandmarkTinyModel(url: string) {
return nets.faceLandmark68TinyNet.load(url)
}
export function loadFaceRecognitionModel(url: string) {
return nets.faceRecognitionNet.load(url)
}
......@@ -52,6 +58,7 @@ export function loadFaceDetectionModel(url: string) {
}
export function loadModels(url: string) {
console.warn('loadModels will be deprecated in future')
return Promise.all([
loadSsdMobilenetv1Model(url),
loadFaceLandmarkModel(url),
......@@ -76,6 +83,11 @@ export function detectLandmarks(
): Promise<FaceLandmarks68 | FaceLandmarks68[]> {
return nets.faceLandmark68Net.detectLandmarks(input)
}
export function detectLandmarksTiny(
input: TNetInput
): Promise<FaceLandmarks68 | FaceLandmarks68[]> {
return nets.faceLandmark68TinyNet.detectLandmarks(input)
}
export function computeFaceDescriptor(
input: TNetInput
......
[{"x": 15.145603939890862, "y": 42.00849384069443}, {"x": 16.228551417589188, "y": 58.02471041679382}, {"x": 18.641504645347595, "y": 73.78255426883698}, {"x": 21.99183627963066, "y": 89.28244113922119}, {"x": 27.241109311580658, "y": 103.89149487018585}, {"x": 35.77959090471268, "y": 117.20520257949829}, {"x": 47.162988781929016, "y": 128.52083444595337}, {"x": 60.60639023780823, "y": 137.3870998620987}, {"x": 76.22466087341309, "y": 139.8476779460907}, {"x": 91.47888422012329, "y": 136.8298441171646}, {"x": 104.65206205844879, "y": 127.64202654361725}, {"x": 115.92219471931458, "y": 116.16075038909912}, {"x": 124.51052069664001, "y": 102.82219648361206}, {"x": 129.76784706115723, "y": 87.81199157238007}, {"x": 132.6519101858139, "y": 72.15077430009842}, {"x": 134.51203107833862, "y": 56.30408674478531}, {"x": 135.33487915992737, "y": 40.10292738676071}, {"x": 24.932710826396942, "y": 32.96273946762085}, {"x": 32.57601857185364, "y": 28.00302878022194}, {"x": 42.381374537944794, "y": 27.10871994495392}, {"x": 52.198585867881775, "y": 29.124750941991806}, {"x": 61.5922674536705, "y": 32.90563523769379}, {"x": 85.18133461475372, "y": 31.272751092910767}, {"x": 94.89730596542358, "y": 26.85772553086281}, {"x": 105.1171749830246, "y": 24.783611297607422}, {"x": 115.2367115020752, "y": 25.725752860307693}, {"x": 123.13878536224365, "y": 30.079219490289688}, {"x": 73.67254793643951, "y": 43.12345236539841}, {"x": 73.7981915473938, "y": 55.55519610643387}, {"x": 73.8839328289032, "y": 67.69467741250992}, {"x": 73.96439015865326, "y": 79.91722226142883}, {"x": 62.394945323467255, "y": 85.0008487701416}, {"x": 68.01678389310837, "y": 87.24332749843597}, {"x": 74.27898198366165, "y": 88.8382226228714}, {"x": 80.42970299720764, "y": 86.84954345226288}, {"x": 85.87614297866821, "y": 84.31319296360016}, {"x": 35.7684463262558, "y": 44.57894414663315}, {"x": 42.30532944202423, "y": 41.117119789123535}, {"x": 50.501713156700134, "y": 41.50397926568985}, {"x": 56.56731426715851, "y": 45.83812057971954}, {"x": 50.084468722343445, "y": 48.037388920784}, {"x": 41.91972613334656, "y": 47.92611300945282}, {"x": 91.28680229187012, "y": 44.677162170410156}, {"x": 97.76994287967682, "y": 40.084308385849}, {"x": 105.72623312473297, "y": 39.8311972618103}, {"x": 111.95003092288971, "y": 42.97384321689606}, {"x": 106.4166247844696, "y": 46.30742222070694}, {"x": 98.37331473827362, "y": 46.70642763376236}, {"x": 51.94934159517288, "y": 102.49188244342804}, {"x": 60.71081757545471, "y": 101.18674635887146}, {"x": 68.67328137159348, "y": 100.15718936920166}, {"x": 74.38371777534485, "y": 101.07522904872894}, {"x": 80.5251270532608, "y": 99.71560835838318}, {"x": 88.56176733970642, "y": 100.32560527324677}, {"x": 96.980881690979, "y": 100.81177353858948}, {"x": 89.01146650314331, "y": 108.55080485343933}, {"x": 81.3762366771698, "y": 112.2200220823288}, {"x": 74.73004907369614, "y": 113.34742605686188}, {"x": 68.42194050550461, "y": 112.92429864406586}, {"x": 60.53387671709061, "y": 110.02114713191986}, {"x": 55.876149237155914, "y": 103.3714234828949}, {"x": 68.54872852563858, "y": 104.03825640678406}, {"x": 74.44304376840591, "y": 104.46503162384033}, {"x": 80.63703775405884, "y": 103.56011688709259}, {"x": 93.1486040353775, "y": 102.0165503025055}, {"x": 80.84633946418762, "y": 104.64600920677185}, {"x": 74.63692724704742, "y": 105.62551617622375}, {"x": 68.74832957983017, "y": 105.22661805152893}]
\ No newline at end of file
[{"x":12.409065663814545,"y":35.459357500076294},{"x":13.863831013441086,"y":53.03075462579727},{"x":17.257214337587357,"y":69.21994239091873},{"x":20.290792733430862,"y":83.73786807060242},{"x":25.19843727350235,"y":100.01502335071564},{"x":33.363962173461914,"y":114.15562033653259},{"x":42.67177730798721,"y":124.92083609104156},{"x":54.774945974349976,"y":134.97415781021118},{"x":73.93101006746292,"y":140.77884256839752},{"x":93.50163638591766,"y":135.3241503238678},{"x":106.89235031604767,"y":125.59508979320526},{"x":116.92832708358765,"y":115.29875993728638},{"x":125.80114603042603,"y":101.27357840538025},{"x":130.48838675022125,"y":84.83449816703796},{"x":133.11561048030853,"y":69.95338797569275},{"x":135.84416806697845,"y":53.477123379707336},{"x":137.16664016246796,"y":35.98061129450798},{"x":22.041092813014984,"y":26.081138849258423},{"x":29.21232059597969,"y":21.911372244358063},{"x":38.767728209495544,"y":21.422917395830154},{"x":47.85865992307663,"y":23.588016629219055},{"x":55.94828277826309,"y":26.723874360322952},{"x":88.98929357528687,"y":26.476483047008514},{"x":97.3086029291153,"y":23.411455750465393},{"x":106.5034031867981,"y":21.319837868213654},{"x":116.54528975486755,"y":21.732238680124283},{"x":124.39115345478058,"y":26.091013848781586},{"x":72.58472889661789,"y":44.89734023809433},{"x":72.1238225698471,"y":57.7533558011055},{"x":71.73694968223572,"y":70.3047975897789},{"x":71.81699573993683,"y":79.94250655174255},{"x":62.10947334766388,"y":83.842733502388},{"x":66.3440153002739,"y":85.69430708885193},{"x":72.36149311065674,"y":87.41167187690735},{"x":78.35066020488739,"y":85.74668169021606},{"x":83.0715000629425,"y":83.87293517589569},{"x":33.77652391791344,"y":40.25426656007767},{"x":39.713045954704285,"y":37.594060599803925},{"x":47.61984497308731,"y":37.72639185190201},{"x":55.496129393577576,"y":42.38396733999252},{"x":48.19572865962982,"y":44.55205053091049},{"x":39.7584468126297,"y":44.11860108375549},{"x":90.26297628879547,"y":42.310331761837006},{"x":97.85987734794617,"y":37.882326543331146},{"x":105.9216856956482,"y":37.697312235832214},{"x":112.38531768321991,"y":40.45048803091049},{"x":105.98326027393341,"y":44.305725395679474},{"x":97.26411402225494,"y":44.69909369945526},{"x":50.48552602529526,"y":100.74669420719147},{"x":58.16384553909302,"y":99.99362826347351},{"x":67.69992560148239,"y":98.86696636676788},{"x":72.78527766466141,"y":100.00039637088776},{"x":77.79269814491272,"y":98.80055487155914},{"x":88.17337453365326,"y":99.85068440437317},{"x":97.4126547574997,"y":100.27143359184265},{"x":87.91132271289825,"y":107.21607506275177},{"x":80.38677871227264,"y":111.03568375110626},{"x":73.13570827245712,"y":111.78229451179504},{"x":65.85598737001419,"y":111.23852133750916},{"x":58.75929147005081,"y":107.56462812423706},{"x":52.198053896427155,"y":100.71471333503723},{"x":66.12485200166702,"y":103.38914394378662},{"x":72.73153960704803,"y":103.6908209323883},{"x":79.6861320734024,"y":103.28285694122314},{"x":95.68716287612915,"y":100.31683444976807},{"x":79.67764735221863,"y":103.91019880771637},{"x":72.93425649404526,"y":104.61705923080444},{"x":66.34287983179092,"y":103.94268035888672}]
\ No newline at end of file
[{"x":12.591811269521713,"y":37.18056008219719},{"x":14.489217102527618,"y":54.375430941581726},{"x":18.031097576022148,"y":70.10978311300278},{"x":21.278046816587448,"y":83.95282924175262},{"x":26.40417516231537,"y":99.72459375858307},{"x":34.79916527867317,"y":113.51814866065979},{"x":44.21398043632507,"y":124.09239113330841},{"x":56.03765398263931,"y":134.3254119157791},{"x":75.41389167308807,"y":140.46012461185455},{"x":94.84915137290955,"y":134.49379205703735},{"x":107.92432129383087,"y":123.9702969789505},{"x":118.00838112831116,"y":113.45937252044678},{"x":126.69345438480377,"y":99.56673681735992},{"x":131.41701221466064,"y":83.40703547000885},{"x":133.99315774440765,"y":68.92484575510025},{"x":136.772882938385,"y":52.720874547958374},{"x":137.7272218465805,"y":35.40073335170746},{"x":21.938379853963852,"y":28.243593871593475},{"x":29.08402383327484,"y":24.014483392238617},{"x":38.46672624349594,"y":23.24410155415535},{"x":47.45165705680847,"y":24.920182675123215},{"x":55.57771772146225,"y":27.864713966846466},{"x":89.10931348800659,"y":27.321801334619522},{"x":97.32098579406738,"y":24.548690021038055},{"x":106.52551352977753,"y":22.409027069807053},{"x":116.49984419345856,"y":22.863386571407318},{"x":124.49341714382172,"y":26.976196467876434},{"x":72.84769266843796,"y":46.6731995344162},{"x":72.5912556052208,"y":59.78170037269592},{"x":72.49848246574402,"y":72.47795909643173},{"x":72.59156405925751,"y":82.31859505176544},{"x":63.02006542682648,"y":85.39192378520966},{"x":67.24515706300735,"y":87.40304410457611},{"x":73.31620305776596,"y":89.13427591323853},{"x":79.3295681476593,"y":87.3850017786026},{"x":83.98161828517914,"y":85.33579409122467},{"x":34.017493575811386,"y":42.064668238162994},{"x":39.69891518354416,"y":39.04064744710922},{"x":47.665129601955414,"y":39.02306109666824},{"x":55.84650635719299,"y":43.793052434921265},{"x":48.43880832195282,"y":46.189695596694946},{"x":39.91182893514633,"y":45.974622666835785},{"x":90.23652970790863,"y":43.494392931461334},{"x":97.89576530456543,"y":38.67722600698471},{"x":105.97457885742188,"y":38.45117390155792},{"x":112.57963478565216,"y":41.22479259967804},{"x":106.20929896831512,"y":45.47332227230072},{"x":97.40135371685028,"y":45.80201804637909},{"x":51.17177367210388,"y":100.92100203037262},{"x":58.8924765586853,"y":100.64716637134552},{"x":68.65793466567993,"y":99.5934247970581},{"x":73.81858080625534,"y":100.83856880664825},{"x":78.77743542194366,"y":99.58769381046295},{"x":89.21037912368774,"y":100.49387812614441},{"x":98.3612447977066,"y":100.58583319187164},{"x":88.92414271831512,"y":107.61089622974396},{"x":81.55436217784882,"y":111.79174482822418},{"x":74.08711463212967,"y":112.78842687606812},{"x":66.74499660730362,"y":112.13003396987915},{"x":59.4507560133934,"y":108.14197361469269},{"x":52.86548137664795,"y":100.84743797779083},{"x":67.18341708183289,"y":104.20102179050446},{"x":73.75064492225647,"y":104.48753535747528},{"x":80.83339333534241,"y":104.14700210094452},{"x":96.58516645431519,"y":100.67276358604431},{"x":80.59312999248505,"y":104.5551985502243},{"x":73.77402484416962,"y":105.33222556114197},{"x":67.1465814113617,"y":104.65545952320099}]
\ No newline at end of file
[{"x": 9.995004907250404, "y": 53.55449616909027}, {"x": 12.50796876847744, "y": 71.41348421573639}, {"x": 16.677917540073395, "y": 88.59677910804749}, {"x": 22.6475290954113, "y": 104.6014130115509}, {"x": 30.59161528944969, "y": 119.35952603816986}, {"x": 41.422560811042786, "y": 132.23226964473724}, {"x": 54.74700182676315, "y": 142.4335777759552}, {"x": 70.32481580972672, "y": 149.33189749717712}, {"x": 87.31497824192047, "y": 150.50972700119019}, {"x": 103.98584604263306, "y": 145.98273038864136}, {"x": 117.90181696414948, "y": 135.19554734230042}, {"x": 128.67935299873352, "y": 121.79077863693237}, {"x": 136.7296814918518, "y": 105.85636496543884}, {"x": 140.29521346092224, "y": 88.25878500938416}, {"x": 140.9232795238495, "y": 70.16736567020416}, {"x": 140.2374029159546, "y": 52.73242145776749}, {"x": 137.97148168087006, "y": 34.537942707538605}, {"x": 14.37721811234951, "y": 33.1049881875515}, {"x": 22.6781465113163, "y": 24.685607850551605}, {"x": 34.36600640416145, "y": 21.1758591234684}, {"x": 46.24761343002319, "y": 22.49436378479004}, {"x": 57.12086856365204, "y": 26.742971688508987}, {"x": 81.21025264263153, "y": 23.014162480831146}, {"x": 92.2086775302887, "y": 15.48520065844059}, {"x": 104.77548837661743, "y": 11.306393891572952}, {"x": 117.67798662185669, "y": 11.740228906273842}, {"x": 127.28274464607239, "y": 18.115675449371338}, {"x": 69.62742805480957, "y": 41.51403307914734}, {"x": 70.82946002483368, "y": 55.146731436252594}, {"x": 71.84555232524872, "y": 68.59723627567291}, {"x": 73.0046421289444, "y": 81.93029165267944}, {"x": 60.417647659778595, "y": 88.01697492599487}, {"x": 67.98770427703857, "y": 90.65443575382233}, {"x": 76.07284784317017, "y": 91.86699986457825}, {"x": 84.35145914554596, "y": 88.2117748260498}, {"x": 90.86072444915771, "y": 83.67109894752502}, {"x": 28.828849643468857, "y": 47.794362902641296}, {"x": 36.311765760183334, "y": 43.33548992872238}, {"x": 44.95347887277603, "y": 43.20283681154251}, {"x": 52.85406410694122, "y": 48.07424694299698}, {"x": 44.7566494345665, "y": 49.9691516160965}, {"x": 35.997654497623444, "y": 50.32083839178085}, {"x": 89.51361179351807, "y": 42.501528561115265}, {"x": 97.55686819553375, "y": 35.38782298564911}, {"x": 106.73499405384064, "y": 33.59129726886749}, {"x": 114.8474782705307, "y": 36.34611591696739}, {"x": 108.40394496917725, "y": 40.97002297639847}, {"x": 98.98389279842377, "y": 42.47862249612808}, {"x": 53.17014008760452, "y": 109.99322533607483}, {"x": 62.47727572917938, "y": 105.5664449930191}, {"x": 72.82306104898453, "y": 102.35638618469238}, {"x": 80.85319697856903, "y": 103.16510796546936}, {"x": 89.42103087902069, "y": 100.08856952190399}, {"x": 99.8135894536972, "y": 100.0435084104538}, {"x": 109.78849232196808, "y": 101.60946249961853}, {"x": 101.90783143043518, "y": 115.25328755378723}, {"x": 92.65078604221344, "y": 122.49988317489624}, {"x": 83.28675627708435, "y": 125.00075697898865}, {"x": 74.31002408266068, "y": 125.16917288303375}, {"x": 63.37641924619675, "y": 121.38420939445496}, {"x": 57.85811394453049, "y": 110.28821468353271}, {"x": 73.27612638473511, "y": 108.2253098487854}, {"x": 81.34059011936188, "y": 108.251291513443}, {"x": 89.9710088968277, "y": 105.99507093429565}, {"x": 104.85810041427612, "y": 103.1228095293045}, {"x": 90.87785482406616, "y": 112.19902038574219}, {"x": 82.05846548080444, "y": 114.52528238296509}, {"x": 73.8232746720314, "y": 114.64338898658752}]
\ No newline at end of file
[{"x":9.57956425845623,"y":52.39383280277252},{"x":12.517975643277168,"y":69.67290490865707},{"x":17.382895946502686,"y":85.59838235378265},{"x":22.254569828510284,"y":100.24277865886688},{"x":29.952556639909744,"y":115.30093252658844},{"x":40.607236325740814,"y":127.02118456363678},{"x":52.21455842256546,"y":135.75681746006012},{"x":66.94714576005936,"y":144.7398841381073},{"x":87.62003481388092,"y":148.519966006279},{"x":107.54483342170715,"y":139.5822286605835},{"x":120.26938498020172,"y":127.25696861743927},{"x":129.46229875087738,"y":116.31288528442383},{"x":136.60779297351837,"y":101.43148005008698},{"x":139.15124237537384,"y":84.36994850635529},{"x":139.66587781906128,"y":68.84740144014359},{"x":139.49655890464783,"y":52.11438238620758},{"x":138.19187879562378,"y":34.275998175144196},{"x":17.73911565542221,"y":34.247177839279175},{"x":23.905900865793228,"y":27.4917371571064},{"x":33.15091207623482,"y":23.992185294628143},{"x":42.38297492265701,"y":24.080870300531387},{"x":50.80330967903137,"y":25.668590515851974},{"x":82.72879421710968,"y":22.588082402944565},{"x":90.8599466085434,"y":18.972241133451462},{"x":100.4738599061966,"y":16.92224331200123},{"x":111.04867458343506,"y":17.707618698477745},{"x":120.33791542053223,"y":22.05384224653244},{"x":69.454425573349,"y":45.07347196340561},{"x":70.71190774440765,"y":59.0581476688385},{"x":71.70603275299072,"y":72.0032662153244},{"x":73.040771484375,"y":81.89877569675446},{"x":63.307446241378784,"y":87.61201500892639},{"x":68.28959137201309,"y":88.8374537229538},{"x":75.32510161399841,"y":89.66150879859924},{"x":81.8707287311554,"y":87.05066442489624},{"x":87.0687872171402,"y":84.28579866886139},{"x":31.470197439193726,"y":46.391429007053375},{"x":36.97886019945145,"y":42.39812046289444},{"x":45.00039964914322,"y":41.7159765958786},{"x":53.23628783226013,"y":45.5289289355278},{"x":46.301740407943726,"y":48.1160894036293},{"x":37.982627749443054,"y":48.80691468715668},{"x":87.06339597702026,"y":41.115787625312805},{"x":93.96767914295197,"y":35.74279770255089},{"x":102.04813778400421,"y":34.39944013953209},{"x":109.27990972995758,"y":36.874495446681976},{"x":102.8844952583313,"y":40.83576053380966},{"x":94.25925314426422,"y":42.34584420919418},{"x":51.57048851251602,"y":105.63550293445587},{"x":58.90470743179321,"y":101.6682118177414},{"x":70.55880725383759,"y":98.85237514972687},{"x":77.36653983592987,"y":99.13035929203033},{"x":83.67621302604675,"y":97.07690477371216},{"x":97.94619083404541,"y":96.29683792591095},{"x":109.81523394584656,"y":97.79501259326935},{"x":101.1863261461258,"y":112.63381540775299},{"x":92.58085191249847,"y":120.5494076013565},{"x":82.98978209495544,"y":123.54103624820709},{"x":73.21132868528366,"y":123.69250059127808},{"x":63.237668573856354,"y":118.47777664661407},{"x":53.231893479824066,"y":105.59755861759186},{"x":69.4526419043541,"y":103.36487889289856},{"x":78.06874215602875,"y":102.5748610496521},{"x":87.34851479530334,"y":101.16179287433624},{"x":108.10398459434509,"y":98.21300804615021},{"x":90.36072492599487,"y":113.51232826709747},{"x":81.56309723854065,"y":116.386878490448},{"x":72.68635332584381,"y":116.3683533668518}]
\ No newline at end of file
[{"x":10.509434714913368,"y":52.461858093738556},{"x":13.533616438508034,"y":69.72497999668121},{"x":18.125666677951813,"y":85.49389243125916},{"x":22.68524095416069,"y":99.90644752979279},{"x":29.770859330892563,"y":115.02401232719421},{"x":40.009237825870514,"y":127.17496454715729},{"x":51.13186240196228,"y":134.8838210105896},{"x":64.93598967790604,"y":142.67239272594452},{"x":86.1398845911026,"y":145.5208271741867},{"x":106.27160668373108,"y":137.6600593328476},{"x":119.85296308994293,"y":126.55811011791229},{"x":129.32697236537933,"y":115.91286063194275},{"x":136.9260460138321,"y":101.54499113559723},{"x":140.04463255405426,"y":85.47771871089935},{"x":140.39686918258667,"y":70.350381731987},{"x":140.93535840511322,"y":53.595203161239624},{"x":139.76540565490723,"y":35.78364551067352},{"x":18.718457221984863,"y":33.92140567302704},{"x":24.8894065618515,"y":27.165759354829788},{"x":34.25554856657982,"y":23.889242112636566},{"x":43.19920241832733,"y":23.89446794986725},{"x":51.55668407678604,"y":25.493214279413223},{"x":83.17877948284149,"y":22.116592526435852},{"x":91.0528689622879,"y":19.1002294421196},{"x":100.33524334430695,"y":17.00422614812851},{"x":110.7668548822403,"y":18.07868331670761},{"x":119.95638906955719,"y":22.73145094513893},{"x":69.94039714336395,"y":44.93928104639053},{"x":70.8938866853714,"y":58.68610739707947},{"x":71.92910313606262,"y":71.54497504234314},{"x":73.07412475347519,"y":81.73994421958923},{"x":63.52762430906296,"y":87.66357600688934},{"x":68.80622059106827,"y":89.00657594203949},{"x":75.79838633537292,"y":89.81735408306122},{"x":82.58680701255798,"y":87.12102770805359},{"x":87.82331049442291,"y":84.34564769268036},{"x":32.21326768398285,"y":45.98721116781235},{"x":37.76122033596039,"y":42.323389649391174},{"x":45.626114308834076,"y":41.58268868923187},{"x":53.76068651676178,"y":45.14274448156357},{"x":46.830734610557556,"y":47.79057204723358},{"x":38.68361413478851,"y":48.451441526412964},{"x":87.2931182384491,"y":41.23439937829971},{"x":93.99429559707642,"y":36.358749121427536},{"x":101.83376669883728,"y":35.318271070718765},{"x":109.02988314628601,"y":37.269607186317444},{"x":102.72921323776245,"y":41.246773302555084},{"x":94.33983564376831,"y":42.437638342380524},{"x":51.489101350307465,"y":106.93963766098022},{"x":59.81220602989197,"y":103.0700147151947},{"x":71.77513092756271,"y":100.07118880748749},{"x":78.22524905204773,"y":100.49879550933838},{"x":84.36182141304016,"y":98.41773211956024},{"x":98.11981916427612,"y":97.42594063282013},{"x":109.53287780284882,"y":98.12568426132202},{"x":99.13961291313171,"y":110.45710444450378},{"x":90.27182757854462,"y":117.69201457500458},{"x":80.78381717205048,"y":120.45529782772064},{"x":71.48520648479462,"y":120.73915600776672},{"x":62.03146129846573,"y":116.77920520305634},{"x":53.2006099820137,"y":106.71007633209229},{"x":70.40069103240967,"y":104.98761534690857},{"x":78.83743643760681,"y":104.08617854118347},{"x":88.0838692188263,"y":102.68035233020782},{"x":107.74409472942352,"y":98.4467625617981},{"x":88.4322077035904,"y":110.55790185928345},{"x":79.5111358165741,"y":113.10129761695862},{"x":71.03536427021027,"y":113.31479251384735}]
\ No newline at end of file
[{"x": 1.41040606983006, "y": 39.10264679789543}, {"x": 6.364097021520138, "y": 59.51536446809769}, {"x": 9.736957371234894, "y": 77.80020213127136}, {"x": 13.817122921347618, "y": 96.05930614471436}, {"x": 20.00808236002922, "y": 110.73724734783173}, {"x": 26.37610113620758, "y": 121.9759613275528}, {"x": 33.717534959316254, "y": 132.17582309246063}, {"x": 41.72355371713638, "y": 140.95428133010864}, {"x": 50.880420446395874, "y": 146.48704254627228}, {"x": 61.8572758436203, "y": 146.95429050922394}, {"x": 72.31115305423737, "y": 142.27578461170197}, {"x": 81.12127649784088, "y": 134.44229400157928}, {"x": 90.62704622745514, "y": 123.21754229068756}, {"x": 99.09188437461853, "y": 110.66853940486908}, {"x": 106.08789837360382, "y": 92.88722932338715}, {"x": 110.42981934547424, "y": 72.83273243904114}, {"x": 110.97252583503723, "y": 51.15715354681015}, {"x": 19.203872233629227, "y": 37.71118628978729}, {"x": 30.421346753835678, "y": 37.16629707813263}, {"x": 39.97739166021347, "y": 40.400769114494324}, {"x": 49.04381215572357, "y": 45.61803185939789}, {"x": 56.723132252693176, "y": 53.29851043224335}, {"x": 56.084791123867035, "y": 46.76303869485855}, {"x": 65.71077406406403, "y": 46.11447751522064}, {"x": 75.0335134267807, "y": 46.96638810634613}, {"x": 83.45012521743774, "y": 51.2211879491806}, {"x": 90.43923103809357, "y": 59.06730401515961}, {"x": 57.634793639183044, "y": 60.69884181022644}, {"x": 56.081126153469086, "y": 72.10934686660767}, {"x": 54.33526223897934, "y": 84.50968235731125}, {"x": 52.046833634376526, "y": 99.18286514282227}, {"x": 43.99328297376633, "y": 99.4484453201294}, {"x": 49.026043593883514, "y": 104.39998996257782}, {"x": 55.01739031076431, "y": 107.4150230884552}, {"x": 59.81684720516205, "y": 105.90647208690643}, {"x": 64.0739357471466, "y": 101.74840021133423}, {"x": 27.087938010692596, "y": 55.09133046865463}, {"x": 34.27653384208679, "y": 55.21461433172226}, {"x": 42.54729217290878, "y": 57.39108908176422}, {"x": 48.454275488853455, "y": 61.82331371307373}, {"x": 41.1783966422081, "y": 61.66855639219284}, {"x": 34.177026987075806, "y": 60.50751310586929}, {"x": 59.51743584871292, "y": 62.6235288977623}, {"x": 65.59782898426056, "y": 61.08310657739639}, {"x": 73.38363683223724, "y": 63.624403953552246}, {"x": 78.78980994224548, "y": 67.99472141265869}, {"x": 72.09050583839417, "y": 68.4499539732933}, {"x": 64.50075209140778, "y": 66.9551106095314}, {"x": 34.73506963253021, "y": 112.76923489570618}, {"x": 42.499454855918884, "y": 113.63057744503021}, {"x": 49.24748706817627, "y": 114.29424142837524}, {"x": 55.50651115179062, "y": 115.7518025636673}, {"x": 62.20475721359253, "y": 116.19831931591034}, {"x": 66.55372452735901, "y": 117.66684019565582}, {"x": 71.82598805427551, "y": 118.23190891742706}, {"x": 64.1076112985611, "y": 127.00456368923187}, {"x": 58.63429069519043, "y": 129.7624831199646}, {"x": 52.65244817733765, "y": 129.39003217220306}, {"x": 47.463079154491425, "y": 127.39845669269562}, {"x": 42.28087282180786, "y": 123.10187613964081}, {"x": 38.320072412490845, "y": 114.88845479488373}, {"x": 49.383214592933655, "y": 119.21215355396271}, {"x": 55.4133198261261, "y": 120.68333745002747}, {"x": 60.75339984893799, "y": 121.00764191150665}, {"x": 67.49378943443298, "y": 118.56007528305054}, {"x": 59.50732082128525, "y": 124.34931361675262}, {"x": 54.06349813938141, "y": 124.39959263801575}, {"x": 48.627867102622986, "y": 122.32591986656189}]
\ No newline at end of file
[{"x":9.103168368339539,"y":101.01195418834686},{"x":14.784232437610626,"y":109.96735978126526},{"x":20.72097545862198,"y":120.9920563697815},{"x":23.68257150053978,"y":127.4499876499176},{"x":33.172756016254425,"y":135.1237609386444},{"x":40.30949687957764,"y":136.57750260829926},{"x":46.08676642179489,"y":136.41320192813873},{"x":48.44881075620651,"y":143.47557425498962},{"x":58.82970488071442,"y":146.31987726688385},{"x":65.03185284137726,"y":143.93176698684692},{"x":70.69711172580719,"y":141.56521546840668},{"x":73.59640145301819,"y":140.66094636917114},{"x":85.81815421581268,"y":125.65490245819092},{"x":91.43581211566925,"y":117.32116794586182},{"x":93.22071814537048,"y":103.55141079425812},{"x":97.05579698085785,"y":97.57390463352203},{"x":89.83693969249725,"y":80.89369875192642},{"x":8.929109543561935,"y":95.41884541511536},{"x":15.941815882921219,"y":91.92907392978668},{"x":22.651149570941925,"y":87.75521492958069},{"x":25.080495923757553,"y":87.33296239376068},{"x":35.09866887331009,"y":87.67910146713257},{"x":55.91999834775925,"y":77.73741436004639},{"x":60.79969918727875,"y":76.88318556547165},{"x":72.479785323143,"y":77.81150668859482},{"x":72.09985733032227,"y":80.98973709344864},{"x":84.31956672668457,"y":76.90173119306564},{"x":46.01968437433243,"y":93.13016855716705},{"x":44.01630175113678,"y":100.97888398170471},{"x":40.80449330806732,"y":107.36412870883942},{"x":38.621174454689026,"y":114.48911905288696},{"x":33.12944173812866,"y":118.61227464675903},{"x":35.98146861791611,"y":119.76905286312103},{"x":43.14035201072693,"y":121.47148418426514},{"x":49.96515953540802,"y":119.40146112442017},{"x":54.14790153503418,"y":118.55489778518677},{"x":22.290891706943512,"y":101.89636206626892},{"x":26.193063497543335,"y":97.70843470096588},{"x":32.05891406536102,"y":95.37641751766205},{"x":36.877059400081635,"y":95.59315097332001},{"x":31.79427993297577,"y":98.80662655830383},{"x":26.30974444746971,"y":101.54476964473724},{"x":53.97727316617966,"y":88.98845142126083},{"x":59.00451451539993,"y":90.48788344860077},{"x":65.20665884017944,"y":89.76256692409515},{"x":72.8481148481369,"y":81.40269559621811},{"x":68.15013182163239,"y":88.9257538318634},{"x":59.68368661403656,"y":92.8312953710556},{"x":26.978514283895493,"y":124.41534793376923},{"x":37.64959371089935,"y":129.4168745279312},{"x":38.14149236679077,"y":127.94919180870056},{"x":44.06257927417755,"y":127.01305139064789},{"x":48.707089364528656,"y":122.96710205078125},{"x":57.109412133693695,"y":125.86821961402893},{"x":63.49719738960266,"y":116.9522500038147},{"x":51.657943189144135,"y":119.83492803573608},{"x":46.5363906621933,"y":126.18525648117065},{"x":43.10889798402786,"y":128.2001837491989},{"x":42.599557995796204,"y":130.2384203672409},{"x":31.888991057872772,"y":126.56950533390045},{"x":27.653676003217697,"y":126.88065385818481},{"x":44.846410274505615,"y":133.13909232616425},{"x":46.87078648805618,"y":129.8393074274063},{"x":52.942511677742004,"y":126.64793169498444},{"x":62.26701807975769,"y":119.98195624351501},{"x":47.63777244091034,"y":122.5474488735199},{"x":42.832436323165894,"y":123.82451903820038},{"x":39.61905354261398,"y":124.0300487279892}]
\ No newline at end of file
[{"x":28.1980222761631,"y":82.78948521614075},{"x":41.05900514125824,"y":94.1000794172287},{"x":45.85652047395706,"y":100.0026285648346},{"x":46.66536271572113,"y":103.72171652317047},{"x":46.17695450782776,"y":108.95469212532043},{"x":44.9439537525177,"y":113.12937867641449},{"x":41.77620494365692,"y":118.82230281829834},{"x":36.76300173997879,"y":120.5938982963562},{"x":40.35541081428528,"y":126.7483412027359},{"x":45.805581748485565,"y":129.04026460647583},{"x":52.93079322576523,"y":126.84588611125946},{"x":53.27251535654068,"y":126.36341333389282},{"x":55.39190810918808,"y":125.28188920021057},{"x":61.80034518241882,"y":123.59300637245178},{"x":70.61709320545197,"y":115.20878064632416},{"x":82.92120599746704,"y":111.2279531955719},{"x":81.62196671962738,"y":100.77173101902008},{"x":24.95584148168564,"y":76.60648649930954},{"x":32.25624072551727,"y":74.50906938314438},{"x":29.986725986003876,"y":73.17802804708481},{"x":37.48139047622681,"y":68.97298204898834},{"x":43.13521087169647,"y":72.66015625},{"x":66.04641699790955,"y":72.61856651306152},{"x":67.14672982692719,"y":77.07855653762817},{"x":75.16640496253967,"y":78.80701857805252},{"x":78.36494970321655,"y":81.77029263973236},{"x":83.96591889858246,"y":86.9630047082901},{"x":49.43720746040344,"y":86.65960651636124},{"x":45.592297196388245,"y":89.74523079395294},{"x":38.21692967414856,"y":95.09447729587555},{"x":36.639003574848175,"y":99.00447487831116},{"x":38.06128841638565,"y":99.63473975658417},{"x":38.76173406839371,"y":100.46630108356476},{"x":41.303012907505035,"y":101.67539536952972},{"x":44.19621616601944,"y":103.10262358188629},{"x":43.459695279598236,"y":107.30739903450012},{"x":35.52263468503952,"y":84.4736732840538},{"x":35.70898097753525,"y":81.59145677089691},{"x":39.94155639410019,"y":83.26475405693054},{"x":42.95827788114548,"y":86.43353658914566},{"x":39.764176189899445,"y":87.32476645708084},{"x":36.219153583049774,"y":85.95869213342667},{"x":64.10197567939758,"y":89.49556505680084},{"x":68.22205686569214,"y":86.13673758506775},{"x":71.8127316236496,"y":86.98408073186874},{"x":73.26841568946838,"y":89.27346312999725},{"x":71.92459321022034,"y":92.17298924922943},{"x":62.44171500205994,"y":93.04573714733124},{"x":31.96991491317749,"y":107.6513100862503},{"x":36.91116398572922,"y":108.80632710456848},{"x":37.83100974559784,"y":107.84696221351624},{"x":39.75290495157242,"y":108.15859878063202},{"x":41.08729958534241,"y":109.05613076686859},{"x":45.37723106145859,"y":114.44351887702942},{"x":49.86580538749695,"y":115.59975600242615},{"x":43.856497406959534,"y":108.97726941108704},{"x":41.35492968559265,"y":110.25707685947418},{"x":40.04151773452759,"y":111.69485104084015},{"x":36.815838396549225,"y":109.72805964946747},{"x":36.58897018432617,"y":108.29451870918274},{"x":32.02847445011139,"y":108.27437102794647},{"x":39.47241109609604,"y":111.86161315441132},{"x":40.09113299846649,"y":111.6380364894867},{"x":43.23629933595657,"y":113.00234961509705},{"x":49.69379901885986,"y":116.33892869949341},{"x":41.574267983436584,"y":107.91563832759857},{"x":39.02515745162964,"y":108.83277690410614},{"x":38.635754346847534,"y":107.74428224563599}]
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import {
FaceDetectionNet,
FaceLandmarkNet,
FaceRecognitionNet,
IPoint,
IRect,
Mtcnn,
NeuralNetwork,
TinyYolov2,
} from '../src/';
import { FaceDetectionNet, FaceRecognitionNet, IPoint, IRect, Mtcnn, NeuralNetwork, TinyYolov2 } from '../src/';
import { allFacesMtcnnFactory, allFacesSsdMobilenetv1Factory, allFacesTinyYolov2Factory } from '../src/allFacesFactory';
import { FaceDetection } from '../src/classes/FaceDetection';
import { FaceLandmarks } from '../src/classes/FaceLandmarks';
import { FullFaceDescription } from '../src/classes/FullFaceDescription';
import { FaceLandmark68Net } from '../src/faceLandmarkNet/FaceLandmark68Net';
import { FaceLandmark68TinyNet } from '../src/faceLandmarkNet/FaceLandmark68TinyNet';
import { allFacesMtcnnFunction, allFacesSsdMobilenetv1Function, allFacesTinyYolov2Function } from '../src/globalApi';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000
......@@ -106,7 +99,8 @@ export type InjectNetArgs = {
allFacesTinyYolov2: allFacesTinyYolov2Function
allFacesMtcnn: allFacesMtcnnFunction
faceDetectionNet: FaceDetectionNet
faceLandmarkNet: FaceLandmarkNet
faceLandmark68Net: FaceLandmark68Net
faceLandmark68TinyNet: FaceLandmark68TinyNet
faceRecognitionNet: FaceRecognitionNet
mtcnn: Mtcnn
tinyYolov2: TinyYolov2
......@@ -118,7 +112,8 @@ export type DescribeWithNetsOptions = {
withAllFacesTinyYolov2?: boolean
withAllFacesMtcnn?: boolean
withFaceDetectionNet?: WithNetOptions
withFaceLandmarkNet?: WithNetOptions
withFaceLandmark68Net?: WithNetOptions
withFaceLandmark68TinyNet?: WithNetOptions
withFaceRecognitionNet?: WithNetOptions
withMtcnn?: WithNetOptions
withTinyYolov2?: WithTinyYolov2Options
......@@ -148,12 +143,13 @@ export function describeWithNets(
describe(description, () => {
let faceDetectionNet: FaceDetectionNet = new FaceDetectionNet()
let faceLandmarkNet: FaceLandmarkNet = new FaceLandmarkNet()
let faceLandmark68Net: FaceLandmark68Net = new FaceLandmark68Net()
let faceLandmark68TinyNet: FaceLandmark68TinyNet = new FaceLandmark68TinyNet()
let faceRecognitionNet: FaceRecognitionNet = new FaceRecognitionNet()
let mtcnn: Mtcnn = new Mtcnn()
let tinyYolov2: TinyYolov2 = new TinyYolov2(options.withTinyYolov2 && options.withTinyYolov2.withSeparableConv)
let allFacesSsdMobilenetv1 = allFacesSsdMobilenetv1Factory(faceDetectionNet, faceLandmarkNet, faceRecognitionNet)
let allFacesTinyYolov2 = allFacesTinyYolov2Factory(tinyYolov2, faceLandmarkNet, faceRecognitionNet)
let allFacesSsdMobilenetv1 = allFacesSsdMobilenetv1Factory(faceDetectionNet, faceLandmark68Net, faceRecognitionNet)
let allFacesTinyYolov2 = allFacesTinyYolov2Factory(tinyYolov2, faceLandmark68Net, faceRecognitionNet)
let allFacesMtcnn = allFacesMtcnnFactory(mtcnn, faceRecognitionNet)
beforeAll(async () => {
......@@ -162,7 +158,8 @@ export function describeWithNets(
withAllFacesTinyYolov2,
withAllFacesMtcnn,
withFaceDetectionNet,
withFaceLandmarkNet,
withFaceLandmark68Net,
withFaceLandmark68TinyNet,
withFaceRecognitionNet,
withMtcnn,
withTinyYolov2
......@@ -175,10 +172,17 @@ export function describeWithNets(
)
}
if (withFaceLandmarkNet || withAllFacesSsdMobilenetv1 || withAllFacesTinyYolov2) {
await initNet<FaceLandmarkNet>(
faceLandmarkNet,
!!withFaceLandmarkNet && !withFaceLandmarkNet.quantized && 'face_landmark_68_model.weights'
if (withFaceLandmark68Net || withAllFacesSsdMobilenetv1 || withAllFacesTinyYolov2) {
await initNet<FaceLandmark68Net>(
faceLandmark68Net,
!!withFaceLandmark68Net && !withFaceLandmark68Net.quantized && 'face_landmark_68_model.weights'
)
}
if (withFaceLandmark68TinyNet) {
await initNet<FaceLandmark68TinyNet>(
faceLandmark68TinyNet,
!!withFaceLandmark68TinyNet && !withFaceLandmark68TinyNet.quantized && 'face_landmark_68_tiny_model.weights'
)
}
......@@ -208,7 +212,7 @@ export function describeWithNets(
afterAll(() => {
faceDetectionNet && faceDetectionNet.dispose()
faceLandmarkNet && faceLandmarkNet.dispose()
faceLandmark68Net && faceLandmark68Net.dispose()
faceRecognitionNet && faceRecognitionNet.dispose()
mtcnn && mtcnn.dispose(),
tinyYolov2 && tinyYolov2.dispose()
......@@ -219,7 +223,8 @@ export function describeWithNets(
allFacesTinyYolov2,
allFacesMtcnn,
faceDetectionNet,
faceLandmarkNet,
faceLandmark68Net,
faceLandmark68TinyNet,
faceRecognitionNet,
mtcnn,
tinyYolov2
......
......@@ -2,7 +2,6 @@
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="FileSaver.js"></script>
<script src="quantization.js"></script>
</head>
......@@ -10,14 +9,20 @@
<script>
tf = faceapi.tf
const modelName = 'tiny_yolov2_separable_conv'
const uncompressedWeightsUri = `tiny_yolov2_separable_conv_model_v1.weights`
const net = new faceapi.TinyYolov2(true)
const uncompressedWeightsUri = `face_landmark_68_model.weights`
const net = new faceapi.FaceLandmark68LargeNet()
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
async function load() {
await net.load(new Float32Array(await (await fetch(uncompressedWeightsUri)).arrayBuffer()))
console.log('net loaded')
}
function getNamedTensors() {
return net.getParamList().map(({ path, tensor }) => ({ name: path, tensor }))
}
const modelName = 'face_landmark_68'
function makeShards(weightArray) {
const maxLength = 4096 * 1024
......@@ -41,25 +46,34 @@
async function quantizeAndSave() {
await net.load(await loadNetWeights(uncompressedWeightsUri))
const quantizedTensorArrays = []
const weightEntries = []
net.getParamList().forEach(({ path, tensor }) => {
const { scale, min, qdata } = quantizeWeights(tensor)
getNamedTensors().forEach(({ name, tensor, isSkipQuantization }) => {
const weightEntry = {
name : path,
name,
shape: tensor.shape,
dtype: tensor.dtype,
quantization: { dtype: 'uint8', scale, min }
dtype: tensor.dtype
}
console.log({ scale, min })
if (isSkipQuantization) {
quantizedTensorArrays.push(tensor.dataSync())
weightEntries.push(weightEntry)
return
}
const { scale, min, qdata } = quantizeWeights(tensor)
console.log(name, { scale, min })
const quantizedWeightEntry = {
...weightEntry,
quantization: { dtype: 'uint8', scale, min }
}
quantizedTensorArrays.push(qdata)
weightEntries.push(weightEntry)
weightEntries.push(quantizedWeightEntry)
})
const quantizedWeights = quantizedTensorArrays
......@@ -83,6 +97,9 @@
saveAs(new Blob([JSON.stringify(weightManifest)]), `${modelName}_model-weights_manifest.json`)
}
load()
</script>
</body>
</html>
\ No newline at end of file
async function trainStep(batchCreators) {
await promiseSequential(batchCreators.map((batchCreator, dataIdx) => async () => {
const { batchInput, landmarksBatchTensor } = await batchCreator()
let ts = Date.now()
const cost = optimizer.minimize(() => {
const out = window.trainNet.forwardInput(batchInput.managed())
const loss = lossFunction(
landmarksBatchTensor,
out
)
return tf.sum(out)
}, true)
ts = Date.now() - ts
console.log(`loss[${dataIdx}]: ${await cost.data()}, ${ts} ms (${ts / batchInput.batchSize} ms / batch element)`)
landmarksBatchTensor.dispose()
cost.dispose()
await tf.nextFrame()
console.log(tf.memory())
}))
}
function createBatchCreators(data, batchSize) {
if (batchSize < 1) {
throw new Error('invalid batch size: ' + batchSize)
}
const batches = []
const pushToBatch = (remaining) => {
if (remaining.length) {
batches.push(remaining.slice(0, batchSize))
pushToBatch(remaining.slice(batchSize))
}
return batches
}
pushToBatch(data)
const batchCreators = batches.map(dataForBatch => async () => {
const imgs = dataForBatch.map(d => d.img)
const allLandmarks = dataForBatch.map(d => landmarkPositionsToArray(d.landmarks))
const batchInput = await faceapi.toNetInput(imgs)
const landmarksBatchTensor = tf.tidy(() => {
const landmarkTensors = allLandmarks.map(arr => tf.tensor2d(arr, [1, 136]))
return tf.stack(landmarkTensors, 0).as2D(-1, 136)
})
return {
batchInput,
landmarksBatchTensor
}
})
return batchCreators
}
function landmarkPositionsToArray(landmarks) {
return landmarks.getRelativePositions().map(pt => [pt.x, pt.y])
.reduce((flat, arr) => flat.concat(arr))
}
function toFaceLandmarks(landmarks, { naturalWidth, naturalHeight }) {
return new faceapi.FaceLandmarks68(
landmarks.map(l => new faceapi.Point(l.x / naturalWidth, l.y / naturalHeight)),
{ width: naturalWidth, height: naturalHeight }
)
}
async function loadImagesInBatch(allLandmarks, offset = 0) {
return Promise.all(allLandmarks.map(async (landmarks, i) => {
const imgUri = `/train_images/${i + offset}.png`
const img = await faceapi.bufferToImage(await fetchImage(imgUri))
return {
imgUri,
img,
landmarks: toFaceLandmarks(landmarks, img)
}
}))
}
async function getTrainData() {
const landmarksJson = (await (await fetch('/train_landmarks.json')).json())
const numLandmarks = Object.keys(landmarksJson).length
const allLandmarks = Array.from(
new Array(numLandmarks),
(_, i) => landmarksJson[i]
)
return await loadImagesInBatch(allLandmarks.slice(0, 100))
/**
const batch1 = await loadImagesInBatch(allLandmarks.slice(0, 4000))
const batch2 = await loadImagesInBatch(allLandmarks.slice(4000), 4000)
return batch1.concat(batch2)
*/
}
\ No newline at end of file
function onKeyDown(e) {
e.target.value = (
parseInt(e.target.value) + (e.keyCode === 38 ? 1 : (e.keyCode === 40 ? -1 : 0))
) || e.target.value || 0
const uri = '/train_images/' + e.target.value + '.png'
console.log(uri)
onSelectionChanged(uri)
}
function onChangeDrawLines(e) {
window.drawLines = $(e.target).prop('checked')
redraw()
}
async function onSelectionChanged(uri) {
const imgBuf = await fetchImage(uri)
window.currentImg = await faceapi.bufferToImage(imgBuf)
window.landmarks = await Promise.all(window.nets.map(
async net => net.detectLandmarks(window.currentImg)
))
redraw(uri)
}
function drawLandmarkCanvas(img, landmarks, drawOpts) {
const canvas = faceapi.createCanvasFromMedia(img)
$('#faceContainer').append(canvas)
faceapi.drawLandmarks(
canvas,
landmarks,
drawOpts
)
}
function redraw(uri) {
$('#faceContainer').empty()
const drawOpts = { lineWidth: window.drawLines ? 2 : 4, drawLines: window.drawLines }
window.landmarks.forEach(landmarks => {
drawLandmarkCanvas(window.currentImg, landmarks, drawOpts)
})
const trainImgAndLandmarks = window.trainData.find(t => t.imgUri === uri)
if (trainImgAndLandmarks) {
drawLandmarkCanvas(trainImgAndLandmarks.img, trainImgAndLandmarks.landmarks, drawOpts)
}
}
async function loadNet(file) {
const res = await fetch(file)
const weights = new Float32Array(await res.arrayBuffer())
return faceapi.faceLandmarkNet(weights)
}
async function init() {
//await faceapi.loadFaceLandmarkModel('/')
//window.nets.push(faceapi.landmarkNet)
//window.nets.push(await loadNet('retrained/landmarks_v0.weights'))
//window.nets.push(await loadNet('retrained/landmarks_v2.weights'))
window.trainNet = await loadNet('/tmp/retrained/landmarks_v9.weights')
window.nets.push(trainNet)
$('#loader').hide()
await run()
await onSelectionChanged($('#selectList select').val())
}
$(document).ready(function() {
$('#imgByNr').keydown(onKeyDown);
renderFaceImageSelectList(
'#selectList',
onSelectionChanged,
{ className: 'sheldon', imageIdx: 1 }
)
init()
})
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.12.0"> </script>
<script src="../node_modules/file-saver/FileSaver.js"></script>
</head>
<body>
<div class="row side-by-side">
<button
class="waves-effect waves-light btn"
onclick="save()"
>
Save
</button>
<button
class="waves-effect waves-light btn"
onclick="save(true)"
>
Save Tiny
</button>
</div>
<script>
function toDataArray(tensor) {
return Array.from(tensor.dataSync())
}
function flatten(arrs) {
return arrs.reduce((flat, arr) => flat.concat(arr))
}
function initWeights(initializer, isTiny) {
function initConvWeights(inChannels, outChannels) {
return flatten(
[
// filter
initializer.apply([3, 3, inChannels, outChannels]),
// bias
tf.zeros([outChannels])
]
.map(toDataArray)
)
}
function initSeparableConvWeights(inChannels, outChannels) {
return flatten(
[
// depthwise filter
initializer.apply([3, 3, inChannels, 1]),
// pointwise filter
initializer.apply([1, 1, inChannels, outChannels]),
// bias
tf.zeros([outChannels])
]
.map(toDataArray)
)
}
const separableConvWeights = isTiny
? flatten([
initConvWeights(3, 32),
initSeparableConvWeights(32, 32),
initSeparableConvWeights(32, 32),
initSeparableConvWeights(32, 64),
initSeparableConvWeights(64, 64),
initSeparableConvWeights(64, 64),
initSeparableConvWeights(64, 128),
initSeparableConvWeights(128, 128),
initSeparableConvWeights(128, 128)
])
: flatten([
initConvWeights(3, 32),
initSeparableConvWeights(32, 32),
initSeparableConvWeights(32, 32),
initSeparableConvWeights(32, 32),
initSeparableConvWeights(32, 64),
initSeparableConvWeights(64, 64),
initSeparableConvWeights(64, 64),
initSeparableConvWeights(64, 64),
initSeparableConvWeights(64, 128),
initSeparableConvWeights(128, 128),
initSeparableConvWeights(128, 128),
initSeparableConvWeights(128, 128),
initSeparableConvWeights(128, 256),
initSeparableConvWeights(256, 256),
initSeparableConvWeights(256, 256),
initSeparableConvWeights(256, 256)
])
const fc = flatten(
[
initializer.apply([1, 1, isTiny ? 128 : 256, 136]),
// bias
tf.zeros([136])
]
.map(toDataArray)
)
return new Float32Array(separableConvWeights.concat(fc))
}
function save(isTiny = false) {
const initialWeights = initWeights(
tf.initializers.glorotNormal(),
isTiny
)
saveAs(new Blob([initialWeights]), `initial_glorot.weights`)
}
</script>
</body>
</html>
\ No newline at end of file
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB
let jsonsDb = null
let jpgsDb = null
const openJsons = indexedDB.open('jsons', 1)
const openJpgs = indexedDB.open('jpgs', 1)
openJsons.onupgradeneeded = function() {
jsonsDb = openJsons.result
jsonsDb.createObjectStore('jsons', { keyPath: 'id' })
}
openJpgs.onupgradeneeded = function() {
jpgsDb = openJpgs.result
jpgsDb.createObjectStore('jpgs', { keyPath: 'id' })
}
openJsons.onsuccess = function() {
console.log('connected to jsons')
jsonsDb = openJsons.result
}
openJpgs.onsuccess = function() {
console.log('connected to jpgs')
jpgsDb = openJpgs.result
}
function putReq(store, obj) {
return new Promise((res, rej) => {
const req = store.put(obj)
req.onsuccess = res
req.onerror = rej
})
}
function getReq(store, id, throwIfNoResult = true) {
return new Promise((res, rej) => {
const req = store.get(id)
req.onsuccess = () => {
if (!req.result && throwIfNoResult) {
return rej(`no result for id: ${id}`)
}
res(req.result)
}
req.onerror = rej
})
}
function existsReq(store, id) {
return getReq(store, id, false)
}
async function getNotFound(store, ids) {
return (await Promise.all(ids.map(async id => ({ id, exists: await existsReq(store, id) }))))
.filter(({ exists }) => !exists)
.map(({ id }) => id)
}
async function getNotFoundPts(ids) {
const store = jsonsDb.transaction('jsons', 'readonly').objectStore('jsons')
return getNotFound(store, ids)
}
async function getNotFoundJpegs(ids) {
const store = jpgsDb.transaction('jpgs', 'readonly').objectStore('jpgs')
return getNotFound(store, ids)
}
async function persistPts(ptsById, overwrite = false) {
const store = jsonsDb.transaction('jsons', 'readwrite').objectStore('jsons')
for (let i = 0; i < ptsById.length; i++) {
const { id, pts } = ptsById[i]
if (!await existsReq(store, id)) {
console.log('persistPts - inserting %s', id)
await putReq(store, { id, pts })
}
}
}
function getPts(ids) {
const store = jsonsDb.transaction('jsons', 'readonly').objectStore('jsons')
return Promise.all(ids.map(id => getReq(store, id)))
}
async function persistJpgs(jpgsById, overwrite = false) {
const store = jpgsDb.transaction('jpgs', 'readwrite').objectStore('jpgs')
for (let i = 0; i < jpgsById.length; i++) {
const { id, blob } = jpgsById[i]
if (!await existsReq(store, id)) {
console.log('persistJpgs - inserting %s', id)
await putReq(store, { id, blob })
}
}
}
function getJpgs(ids) {
const store = jpgsDb.transaction('jpgs', 'readonly').objectStore('jpgs')
return Promise.all(ids.map(id => getReq(store, id)))
}
// https://gist.github.com/tralves/9e5de2bd9f582007a52708d7d4209865
var getTableSize = function(db, dbName){
return new Promise((resolve,reject) => {
if (db == null) {
return reject();
}
var size = 0;
db = event.target.result;
var transaction = db.transaction([dbName])
.objectStore(dbName)
.openCursor();
transaction.onsuccess = function(event){
var cursor = event.target.result;
if(cursor){
var storedObject = cursor.value;
var json = JSON.stringify(storedObject);
size += json.length;
cursor.continue();
}
else{
resolve(size);
}
}.bind(this);
transaction.onerror = function(err){
reject("error in " + dbName + ": " + err);
}
});
};
var getDatabaseSize = function (dbName) {
var request = indexedDB.open(dbName);
var db;
var dbSize = 0;
request.onerror = function(event) {
alert("Why didn't you allow my web app to use IndexedDB?!");
};
request.onsuccess = function(event) {
db = event.target.result;
var tableNames = [ ...db.objectStoreNames ];
(function(tableNames, db) {
var tableSizeGetters = tableNames
.reduce( (acc, tableName) => {
acc.push( getTableSize(db, tableName) );
return acc;
}, []);
Promise.all(tableSizeGetters)
.then(sizes => {
console.log('--------- ' + db.name + ' -------------');
tableNames.forEach( (tableName,i) => {
console.log(" - " + tableName + "\t: " + humanReadableSize(sizes[i]));
});
var total = sizes.reduce(function(acc, val) {
return acc + val;
}, 0);
console.log("TOTAL: " + humanReadableSize(total))
});
})(tableNames, db);
};
};
var humanReadableSize = function (bytes) {
var thresh = 1024;
if(Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = ['KB','MB','GB','TB','PB','EB','ZB','YB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1)+' '+units[u];
}
getDatabaseSize('jsons')
getDatabaseSize('jpgs')
\ No newline at end of file
function randomCrop(img, pts) {
const xCoords = pts.map(pt => pt.x)
const yCoords = pts.map(pt => pt.y)
const minX = xCoords.reduce((x, min) => x < min ? x : min, img.width)
const minY = yCoords.reduce((y, min) => y < min ? y : min, img.height)
const maxX = xCoords.reduce((x, max) => x < max ? max : x, 0)
const maxY = yCoords.reduce((y, max) => y < max ? max : y, 0)
const x0 = Math.random() * minX
const y0 = Math.random() * minY
const x1 = (Math.random() * Math.abs(img.width - maxX)) + maxX
const y1 = (Math.random() * Math.abs(img.height - maxY)) + maxY
const targetCanvas = faceapi.createCanvas({ width: x1 - x0, height: y1 - y0 })
const inputCanvas = img instanceof HTMLCanvasElement ? img : faceapi.createCanvasFromMedia(img)
const region = faceapi.getContext2dOrThrow(inputCanvas)
.getImageData(x0, y0, targetCanvas.width, targetCanvas.height)
faceapi.getContext2dOrThrow(targetCanvas).putImageData(region, 0, 0)
return {
croppedImage: targetCanvas,
shiftedPts: pts.map(pt => new faceapi.Point(pt.x - x0, pt.y - y0))
}
}
\ No newline at end of file
async function fillPtsDb(ids) {
const numFetchPerIteration = 4000
for (let i = 0; i < Math.floor(ids.length / numFetchPerIteration) + 1; i++) {
const ptsById = await Promise.all(
ids.slice(i * numFetchPerIteration, (i + 1) * numFetchPerIteration)
.map(async id => ({
id,
pts: await (await fetch(`${id}.json`)).json()
}))
)
console.log('persistPts')
console.time('persistPts')
await persistPts(ptsById)
console.timeEnd('persistPts')
}
}
async function fillJpgsDb(ids) {
const numFetchPerIteration = 4000
for (let i = 0; i < Math.floor(ids.length / numFetchPerIteration) + 1; i++) {
const jpgsById = await Promise.all(
ids.slice(i * numFetchPerIteration, (i + 1) * numFetchPerIteration)
.map(async id => ({
id,
blob: await fetchImage(`${id}.jpg`)
}))
)
console.log('persistJpgs')
console.time('persistJpgs')
await persistJpgs(jpgsById)
console.timeEnd('persistJpgs')
}
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="js/randomCrop.js"></script>
<script src="js/indexedDb.js"></script>
<script src="js/synchronizeDb.js"></script>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
<div id="result" style="position: relative" class="margin">
<img id="inputImg" src="" style="max-width: 400px;" />
<canvas id="overlay" />
</div>
<script>
const net = new faceapi.FaceLandmark68LargeNet()
const modelCheckpoint = 'tmp/densenet4/checkpoints/landmarks_epoch46_lr00001_12_lr000001_18.weights'
const crops = 4
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function load() {
//await net.load('./')
const weights = await loadNetWeights(modelCheckpoint)
await net.load(weights)
console.log('model loaded')
}
load()
function pointsToFlatArray(pts) {
return pts.map(pt => [pt.x, pt.y]).reduce((flat, arr) => flat.concat(arr))
}
async function showResults(landmarks) {
const inputImgEl = $('#inputImg').get(0)
const { width, height } = inputImgEl
const canvas = $('#overlay').get(0)
canvas.width = width
canvas.height = height
faceapi.drawLandmarks('overlay', landmarks, { drawLines: true })
}
async function testAll() {
window.testIds = (await fetch('/face_landmarks_test_ids').then(res => res.json()))
test()
}
async function testSidewaysX(useTrainSet = false) {
window.testIds = (await fetch(useTrainSet ? '/sideways_x_train' : '/sideways_x_test').then(res => res.json()))
test()
}
async function testStrongSidewaysX(useTrainSet = false) {
window.testIds = (await fetch(useTrainSet ? '/strong_sideways_x_train' : '/strong_sideways_x_test').then(res => res.json()))
test()
}
async function testStrongSidewaysY(useTrainSet = false) {
window.testIds = (await fetch(useTrainSet ? '/strong_sideways_y_train' : '/strong_sideways_y_test').then(res => res.json()))
test()
}
async function test() {
let totalError = 0
for (let i = 0; i < window.testIds.length; i++) {
const dataId = window.testIds[i]
let originalImage = (await getJpgs([dataId]))[0]
originalImage = await faceapi.bufferToImage(originalImage.blob)
const groundTruthPts = (await getPts([dataId])).map(res => res.pts)[0]
for (let c = 0; c < (crops || 1); c++) {
console.log(i, c, i / window.testIds.length)
const { croppedImage, shiftedPts } = crops > 0
? randomCrop(originalImage, groundTruthPts)
: { croppedImage: originalImage, shiftedPts: groundTruthPts }
const squaredImg = faceapi.imageToSquare(croppedImage, 112, true)
const landmarks = (await net.detectLandmarks(squaredImg)).forSize(croppedImage.width, croppedImage.height)
const dist = faceapi.euclideanDistance(
pointsToFlatArray(landmarks.getPositions()),
pointsToFlatArray(shiftedPts)
)
const distByCoordinate = dist / 136
totalError += distByCoordinate
if (window.debug) {
const inputImgEl = $('#inputImg').get(0)
inputImgEl.src = croppedImage instanceof HTMLImageElement ? croppedImage.src : croppedImage.toDataURL()
inputImgEl.onload = () => showResults(landmarks)
for (let i = 0; i < 30; i++)
await faceapi.tf.nextFrame()
}
}
}
console.log('done...')
console.log('test set size:', window.testIds.length)
console.log('total error:', totalError / (crops || 1))
console.log('average error:', totalError / ((crops || 1) * window.testIds.length))
}
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="faceLandmarksUi.js"></script>
<script src="faceLandmarksTrain.js"></script>
<script src="js/randomCrop.js"></script>
<script src="js/indexedDb.js"></script>
<script src="js/synchronizeDb.js"></script>
<script src="FileSaver.js"></script>
<script src="trainUtils.js"></script>
</head>
<body>
<div id="navbar"></div>
<div class="center-content page-container">
<div>
<div class="progress" id="loader">
<div class="indeterminate"></div>
</div>
<div class="row side-by-side">
<div class="center-content">
<div id="faceContainer"></div>
<div id="selectList"></div>
</div>
</div>
<p>
<input type="checkbox" id="drawLinesCheckbox" checked="checked" onchange="onChangeDrawLines(event)" />
<label for="drawLinesCheckbox">Draw Lines</label>
</p>
<div class="row">
<label for="imgByNr">Enter image NR: </label>
<input id="imgByNr" type="text" class="bold">
</div>
</div>
</div>
<div id="container"></div>
<script>
tf = faceapi.tf
window.saveEveryNthIteration = 2
window.drawLines = true
window.nets = []
window.landmarks = []
window.trainSteps = 100
window.learningRate = 0.01
//window.optimizer = tf.train.sgd(learningRate)
window.optimizer = tf.train.adam(0.001, 0.9, 0.999, 1e-8)
function lossFunction(labels, out) {
return tf.losses.meanSquaredError(labels, out)
// uri to weights file of last checkpoint
window.net = new faceapi.FaceLandmark68TinyNet()
const modelCheckpoint = 'tmp/face_landmark_68_tiny_model_lr00001_19.weights'
const startEpoch = 20
const learningRate = 0.0001 // 0.001
window.optimizer = tf.train.adam(learningRate, 0.9, 0.999, 1e-8)
window.saveEveryNthSample = Infinity
window.withRandomCrop = true
window.batchSize = 12
//window.batchSize = 32
window.lossValues = []
window.iterDelay = 0
window.withLogging = true
const log = (str, ...args) => console.log(`[${[(new Date()).toTimeString().substr(0, 8)]}] ${str || ''}`, ...args)
function saveWeights(net, filename = 'train_tmp') {
saveAs(new Blob([net.serializeParams()]), filename)
}
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function delay(ms) {
return new Promise(res => setTimeout(res, ms))
}
async function run() {
window.trainData = await getTrainData()
window.trainNet.variable()
async function fetchTrainDataIds() {
return fetch('/face_landmarks_train_ids').then(res => res.json())
}
async function load() {
window.trainIds = (await fetchTrainDataIds())
const weights = await loadNetWeights(modelCheckpoint)
await window.net.load(weights)
window.net.variable()
}
async function onEpochDone(epoch) {
saveWeights(window.net, `face_landmark_68_tiny_model_lr00001_${epoch}.weights`)
return
const loss = window.lossValues[epoch]
saveAs(new Blob([JSON.stringify({ loss, avgLoss: loss / window.trainIds.length })]), `face_landmark_68_tiny_model_lr00001_${epoch}.json`)
await train()
}
async function train(batchSize = 1) {
for (let i = 0; i < trainSteps; i++) {
console.log('step', i)
const batchCreators = createBatchCreators(shuffle(window.trainData), batchSize)
let ts = Date.now()
await trainStep(batchCreators)
ts = Date.now() - ts
console.log('step %s done (%s ms)', i, ts)
if (((i + 1) % saveEveryNthIteration) === 0) {
//saveWeights(window.trainNet, 'landmark_trained_weights_' + idx + '.weights')
async function train() {
await load()
for (let epoch = startEpoch; epoch < Infinity; epoch++) {
if (epoch !== startEpoch) {
// ugly hack to wait for loss datas for that epoch to be resolved
setTimeout(() => onEpochDone(epoch - 1), 10000)
}
window.lossValues[epoch] = 0
const shuffledInputs = faceapi.shuffleArray(window.trainIds)
for (let dataIdx = 0; dataIdx < shuffledInputs.length; dataIdx += window.batchSize) {
const tsIter = Date.now()
const ids = shuffledInputs.slice(dataIdx, dataIdx + window.batchSize)
// fetch image and ground truth bounding boxes
let tsFetch = Date.now()
let tsFetchPts = Date.now()
const allPts = (await getPts(ids)).map(res => res.pts)
tsFetchPts = Date.now() - tsFetchPts
let tsFetchJpgs = Date.now()
const allJpgBlobs = (await getJpgs(ids)).map(res => res.blob)
tsFetchJpgs = Date.now() - tsFetchJpgs
let tsBufferToImage = Date.now()
const allJpgs = await Promise.all(
allJpgBlobs.map(blob => faceapi.bufferToImage(blob))
)
tsBufferToImage = Date.now() - tsBufferToImage
tsFetch = Date.now() - tsFetch
const bImages = []
const bPts = []
for (let batchIdx = 0; batchIdx < ids.length; batchIdx += 1) {
const pts = allPts[batchIdx]
const img = allJpgs[batchIdx]
const { croppedImage, shiftedPts } = window.withRandomCrop
? randomCrop(img, pts)
: { croppedImage: img, shiftedPts: pts }
const squaredImg = faceapi.imageToSquare(croppedImage, 112, true)
const reshapedDimensions = faceapi.computeReshapedDimensions(croppedImage, 112)
const pad = Math.abs(reshapedDimensions.width - reshapedDimensions.height) / (2 * 112)
const padX = reshapedDimensions.width < reshapedDimensions.height ? pad : 0
const padY = reshapedDimensions.height < reshapedDimensions.width ? pad : 0
const groundTruthLandmarks = shiftedPts.map(pt => new faceapi.Point(
padX + (pt.x / croppedImage.width) * (reshapedDimensions.width / 112),
padY + (pt.y / croppedImage.height)* (reshapedDimensions.height / 112)
))
bImages.push(squaredImg)
bPts.push(groundTruthLandmarks)
}
const netInput = await faceapi.toNetInput(bImages)
let tsBackward = Date.now()
let tsForward = Date.now()
const loss = optimizer.minimize(() => {
const out = window.net.runNet(netInput)
tsForward = Date.now() - tsForward
tsBackward = Date.now()
const landmarksTensor = tf.tensor2d(
bPts
.reduce((flat, arr) => flat.concat(arr))
.map(pt => [pt.x, pt.y])
.reduce((flat, arr) => flat.concat(arr)),
[ids.length, 136]
)
const loss = tf.losses.meanSquaredError(
landmarksTensor,
out,
tf.Reduction.MEAN
)
return loss
}, true)
tsBackward = Date.now() - tsBackward
// start next iteration without waiting for loss data
loss.data().then(data => {
const lossValue = data[0]
window.lossValues[epoch] += lossValue
window.withLogging && log(`epoch ${epoch}, dataIdx ${dataIdx} - loss: ${lossValue}, ( ${window.lossValues[epoch]})`)
loss.dispose()
})
window.withLogging && log(`epoch ${epoch}, dataIdx ${dataIdx} - forward: ${tsForward} ms, backprop: ${tsBackward} ms, iter: ${Date.now() - tsIter} ms`)
if (window.logsilly) {
log(`fetch: ${tsFetch} ms, pts: ${tsFetchPts} ms, jpgs: ${tsFetchJpgs} ms, bufferToImage: ${tsBufferToImage} ms`)
}
if (window.iterDelay) {
await delay(window.iterDelay)
} else {
await tf.nextFrame()
}
}
}
}
</script>
</body>
</html>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="js/randomCrop.js"></script>
<script src="js/indexedDb.js"></script>
<script src="FileSaver.js"></script>
</head>
<body>
<div id="container"></div>
<script>
tf = faceapi.tf
// uri to weights file of last checkpoint
window.net = new faceapi.FaceLandmark68LargeNet()
const modelCheckpoint = '/tmp/initial_glorot.weights'
const startEpoch = 0
const learningRate = 0.001 // 0.001
window.optimizer = tf.train.adam(learningRate, 0.9, 0.999, 1e-8)
window.saveEveryNthSample = 50000000
window.withRandomCrop = false
window.batchSize = 12
window.lossValue = 0
window.drawResults = true
const log = (str, ...args) => console.log(`[${[(new Date()).toTimeString().substr(0, 8)]}] ${str || ''}`, ...args)
function saveWeights(net, filename = 'train_tmp') {
saveAs(new Blob([net.serializeParams()]), filename)
}
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function fetchTrainDataFilenames() {
return fetch('/face_landmarks_train_ids').then(res => res.json())
}
async function load() {
window.ptsFiles = await fetchTrainDataFilenames()
window.ptsFiles = window.ptsFiles.slice(1000, 1000 + window.batchSize)
//window.ptsFiles = window.ptsFiles.slice(7, 8)
const weights = await loadNetWeights(modelCheckpoint)
await window.net.load(weights)
window.net.variable()
}
function randomCrop(img, pts) {
const xCoords = pts.map(pt => pt.x)
const yCoords = pts.map(pt => pt.y)
const minX = xCoords.reduce((x, min) => x < min ? x : min, img.width)
const minY = yCoords.reduce((y, min) => y < min ? y : min, img.height)
const maxX = xCoords.reduce((x, max) => x < max ? max : x, 0)
const maxY = yCoords.reduce((y, max) => y < max ? max : y, 0)
const x0 = Math.random() * minX
const y0 = Math.random() * minY
const x1 = (Math.random() * Math.abs(img.width - maxX)) + maxX
const y1 = (Math.random() * Math.abs(img.height - maxY)) + maxY
const targetCanvas = faceapi.createCanvas({ width: x1 - x0, height: y1 - y0 })
const inputCanvas = img instanceof HTMLCanvasElement ? img : faceapi.createCanvasFromMedia(img)
const region = faceapi.getContext2dOrThrow(inputCanvas)
.getImageData(x0, y0, targetCanvas.width, targetCanvas.height)
faceapi.getContext2dOrThrow(targetCanvas).putImageData(region, 0, 0)
return {
croppedImage: targetCanvas,
shiftedPts: pts.map(pt => new faceapi.Point(pt.x - x0, pt.y - y0))
}
}
async function train() {
await load()
for (let epoch = startEpoch; epoch < Infinity; epoch++) {
if (epoch !== startEpoch) {
//saveWeights(window.net, `landmarks_epoch${epoch - 1}.weights`)
//saveAs(new Blob([JSON.stringify({ loss: window.lossValue, avgLoss: window.lossValue / 20000 })]), `landmarks_epoch${epoch - 1}.json`)
window.lossValue = 0
}
const shuffledInputs = window.drawResults ? window.ptsFiles : faceapi.shuffleArray(window.ptsFiles)
for (let dataIdx = 0; dataIdx < shuffledInputs.length; dataIdx += window.batchSize) {
const ids = shuffledInputs.slice(dataIdx, dataIdx + window.batchSize)
// fetch image and ground truth bounding boxes
console.time('getPts')
const allPts = (await getPts(ids)).map(res => res.pts)
console.timeEnd('getPts')
console.time('getJpgs')
const allJpgs = await Promise.all(
(await getJpgs(ids)).map(res => res.blob).map(blob => faceapi.bufferToImage(blob))
)
console.timeEnd('getJpgs')
const bSquaredImages = []
const bCroppedImages = []
const bImages = []
const bPts = []
for (let batchIdx = 0; batchIdx < ids.length; batchIdx += 1) {
const pts = allPts[batchIdx]
const img = allJpgs[batchIdx]
const { croppedImage, shiftedPts } = window.withRandomCrop
? randomCrop(img, pts)
: { croppedImage: img, shiftedPts: pts }
const squaredImg = faceapi.imageToSquare(croppedImage, 112, true)
const reshapedDimensions = faceapi.computeReshapedDimensions(croppedImage, 112)
const pad = Math.abs(reshapedDimensions.width - reshapedDimensions.height) / (2 * 112)
const padX = reshapedDimensions.width < reshapedDimensions.height ? pad : 0
const padY = reshapedDimensions.height < reshapedDimensions.width ? pad : 0
const groundTruthLandmarks = shiftedPts.map(pt => new faceapi.Point(
padX + (pt.x / croppedImage.width) * (reshapedDimensions.width / 112),
padY + (pt.y / croppedImage.height)* (reshapedDimensions.height / 112)
))
bSquaredImages.push(squaredImg)
bCroppedImages.push(croppedImage)
bImages.push(squaredImg)
bPts.push(groundTruthLandmarks)
}
const netInput = await faceapi.toNetInput(bImages)
const ts = Date.now()
const loss = optimizer.minimize(() => {
console.time('runNet')
const out = window.net.runNet(netInput)
console.timeEnd('runNet')
if (window.drawResults) {
// debug
const landmarkTensors = tf.unstack(out)
landmarkTensors.forEach((t, i) => {
const croppedImage = bSquaredImages[i]
const landmarksArray = Array.from(t.dataSync())
const xCoords = landmarksArray.filter((_, i) => faceapi.isEven(i))
const yCoords = landmarksArray.filter((_, i) => !faceapi.isEven(i))
/*
const landmarksArray = bPts[i]
const xCoords = landmarksArray.map(pt => pt.x)
const yCoords = landmarksArray.map(pt => pt.y)
*/
const marks = new faceapi.FaceLandmarks68(
Array(68).fill(0).map((_, i) => new faceapi.Point(xCoords[i], yCoords[i])),
croppedImage
)
const canvas = croppedImage instanceof HTMLCanvasElement ? croppedImage : faceapi.createCanvasFromMedia(croppedImage)
faceapi.drawLandmarks(canvas, marks, { drawLines: true })
const container = document.getElementById('container')
if (container.children[i]) {
const oldChild = container.children[i]
container.insertBefore(canvas, oldChild)
container.removeChild(oldChild)
} else {
container.appendChild(canvas)
}
})
// debug
}
const landmarksTensor = tf.tensor2d(
bPts
.reduce((flat, arr) => flat.concat(arr))
.map(pt => [pt.x, pt.y])
.reduce((flat, arr) => flat.concat(arr)),
[window.batchSize, 136]
)
const loss = tf.losses.meanSquaredError(
landmarksTensor,
out,
tf.Reduction.MEAN
)
//return tf.sum(tf.abs(tf.sub(landmarksTensor, out)))
return loss
}, true)
// start next iteration without waiting for loss data
loss.data().then(data => {
const lossValue = data[0]
window.lossValue += lossValue
log(`epoch ${epoch}, dataIdx ${dataIdx} - loss: ${lossValue}`)
loss.dispose()
})
if (window.iterDelay) {
await delay(window.iterDelay)
} else {
await tf.nextFrame()
}
}
}
}
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="js/randomCrop.js"></script>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
<div id="navbar"></div>
<div class="center-content page-container">
<div class="progress" id="loader">
<div class="indeterminate"></div>
</div>
<div id="results" class="side-by-side">
<div id="result0" style="position: relative" class="margin">
<img id="inputImg0" src="" style="max-width: 400px;" />
<canvas class="overlay" id="overlay0" />
</div>
<div id="result1" style="position: relative" class="margin">
<img id="inputImg1" src="" style="max-width: 400px;" />
<canvas class="overlay" id="overlay1" />
</div>
<div id="result2" style="position: relative" class="margin">
<img id="inputImg2" src="" style="max-width: 400px;" />
<canvas class="overlay" id="overlay2" />
</div>
</div>
<div class="row">
<label for="imgByNr">Enter image NR: </label>
<input id="imgByNr" type="text" class="bold" value="0"/>
</div>
</div>
<script>
const modelCheckpoints = [
'tmp/face_landmark_68_tiny_model_lr00001_0.weights',
'tmp/face_landmark_68_tiny_model_lr00001_13.weights',
'tmp/face_landmark_68_tiny_model_lr00001_24.weights'
]
let originalImage = null
function cloneImage(img) {
var image = document.createElement("img")
image.src = img.src
return image
}
function getInputValue(input) {
input = input || $('#imgByNr').get(0)
return input.value || 0
}
function onKeyDown(e) {
e.target.value = (
parseInt(e.target.value) + (e.keyCode === 38 ? 1 : (e.keyCode === 40 ? -1 : 0))
) || getInputValue(e.target)
const imgUri = `${window.testIds[e.target.value]}.jpg`
console.log(imgUri)
onSelectionChanged(imgUri)
}
async function updateResults(_landmarks) {
const inputImgEl = $('#inputImg0').get(0)
const { width, height } = inputImgEl
for (let i = 0; i < window.nets.length; i++) {
const net = window.nets[i]
const canvas = $(`#overlay${i}`).get(0)
canvas.width = width
canvas.height = height
console.time('detectLandmarks')
const landmarks = await net.detectLandmarks(inputImgEl)
console.timeEnd('detectLandmarks')
faceapi.drawLandmarks(`overlay${i}`, landmarks.forSize(width, height), { drawLines: true })
}
}
async function onSelectionChanged(imgUri) {
const img = await faceapi.bufferToImage(await fetchImage(imgUri))
window.nets.forEach(async (_, i) => {
$(`#inputImg${i}`).get(0).src = img.src
})
originalImage = cloneImage(img)
updateResults()
}
async function applyRandomCrop() {
const dataId = window.testIds[getInputValue()]
const pts = await (await fetch(`${dataId}.json`)).json()
const img = cloneImage(originalImage)
const { croppedImage, shiftedPts } = randomCrop(img, pts)
img.src = croppedImage.toDataURL()
$(`#result${0}`).get(0).removeChild($(`#inputImg${0}`).get(0))
$(`#result${0}`).get(0).appendChild(img)
window.nets.slice(1).forEach((_, i) => {
const im = cloneImage(img)
im.id = `#inputImg${i + 1}`
$(`#result${i + 1}`).get(0).removeChild($(`#inputImg${i + 1}`).get(0))
$(`#result${i + 1}`).get(0).appendChild(im)
})
const { width, height } = croppedImage
img.onload = () => updateResults(
new faceapi.FaceLandmarks68(
shiftedPts.map(pt =>
new faceapi.Point(
pt.x / width,
pt.y / height
)
),
{ width, height }
)
)
}
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function run() {
$('#imgByNr').keydown(onKeyDown)
//faceapi.loadFaceLandmarkModel('./')
//window.trainIds = (await fetch('/face_landmarks_train_ids').then(res => res.json()))
//window.testIds = (await fetch('/face_landmarks_test_ids').then(res => res.json()))
window.testIds = (await fetch('/strong_sideways_test').then(res => res.json()))
window.nets = []
//window.nets.push(faceapi)
//const net = new faceapi.FaceLandmark68TinyNet()
//await net.load('./')
//window.nets.push(net)
modelCheckpoints.forEach(async checkpoint => {
const net = new faceapi.FaceLandmark68TinyNet()
const weights = await loadNetWeights(checkpoint)
await net.load(weights)
window.nets.push(net)
})
$('#loader').hide()
}
$(document).ready(() => run())
</script>
</body>
</html>
\ No newline at end of file
......@@ -2,6 +2,7 @@ require('./faceLandmarks/.env')
const express = require('express')
const path = require('path')
const fs = require('fs')
const app = express()
......@@ -14,9 +15,72 @@ app.use(express.static(path.join(__dirname, '../../weights')))
app.use(express.static(path.join(__dirname, '../../dist')))
const trainDataPath = path.resolve(process.env.TRAIN_DATA_PATH)
app.use(express.static(trainDataPath))
app.get('/', (req, res) => res.redirect('/face_landmarks'))
app.get('/face_landmarks', (req, res) => res.sendFile(path.join(publicDir, 'train.html')))
const pngPath = path.join(trainDataPath, 'png')
const jpgPath = path.join(trainDataPath, 'jpg')
const groundTruthPath = path.join(trainDataPath, 'pts')
app.listen(3000, () => console.log('Listening on port 3000!'))
\ No newline at end of file
app.use(express.static(jpgPath))
app.use(express.static(groundTruthPath))
const trainIds = JSON.parse(fs.readFileSync(path.join(publicDir, './trainData.json')))
const trainIdsSet = new Set(trainIds)
const testFileIds = fs.readdirSync(groundTruthPath)
.map(file => file.replace('.json', ''))
.filter(file => !trainIdsSet.has(file))
app.get('/face_landmarks_train_ids', (req, res) => res.status(202).send(trainIds))
app.get('/face_landmarks_test_ids', (req, res) => res.status(202).send(testFileIds))
const poseAnchors = JSON.parse(fs.readFileSync(path.join(trainDataPath, './pose-anchors.json')))
const sidewaysXTrain = []
const strongSidewaysXTrain = []
const strongSidewaysYTrain = []
const strongSidewaysTrain = []
const sidewaysXTest = []
const strongSidewaysXTest = []
const strongSidewaysYTest = []
const strongSidewaysTest = []
poseAnchors.forEach((pt, idx) => {
const id = `${idx}`
if (Math.abs(0.5 - pt.x) > 0.15) {
(trainIdsSet.has(id) ? sidewaysXTrain : sidewaysXTest).push(id)
}
if (Math.abs(0.5 - pt.x) > 0.2) {
(trainIdsSet.has(id) ? strongSidewaysXTrain : strongSidewaysXTest).push(id)
}
if (Math.abs(0.44 - pt.y) > 0.1) {
(trainIdsSet.has(id) ? strongSidewaysYTrain : strongSidewaysYTest).push(id)
}
if (Math.abs(0.5 - pt.x) > 0.2 || Math.abs(0.44 - pt.y) > 0.1) {
(trainIdsSet.has(id) ? strongSidewaysTrain : strongSidewaysTest).push(id)
}
})
console.log(sidewaysXTrain.length)
console.log(strongSidewaysXTrain.length)
console.log(strongSidewaysYTrain.length)
console.log(strongSidewaysTrain.length)
console.log(sidewaysXTest.length)
console.log(strongSidewaysXTest.length)
console.log(strongSidewaysYTest.length)
console.log(strongSidewaysTrain.length)
app.get('/sideways_x_train', (req, res) => res.status(202).send(sidewaysXTrain))
app.get('/strong_sideways_x_train', (req, res) => res.status(202).send(strongSidewaysXTrain))
app.get('/strong_sideways_y_train', (req, res) => res.status(202).send(strongSidewaysYTrain))
app.get('/strong_sideways_train', (req, res) => res.status(202).send(strongSidewaysTrain))
app.get('/sideways_x_test', (req, res) => res.status(202).send(sidewaysXTest))
app.get('/strong_sideways_x_test', (req, res) => res.status(202).send(strongSidewaysXTest))
app.get('/strong_sideways_y_test', (req, res) => res.status(202).send(strongSidewaysYTest))
app.get('/strong_sideways_test', (req, res) => res.status(202).send(strongSidewaysTest))
app.get('/', (req, res) => res.redirect('/train'))
app.get('/train', (req, res) => res.sendFile(path.join(publicDir, 'train.html')))
app.get('/verify', (req, res) => res.sendFile(path.join(publicDir, 'verify.html')))
app.get('/test', (req, res) => res.sendFile(path.join(publicDir, 'test.html')))
app.listen(8000, () => console.log('Listening on port 8000!'))
\ No newline at end of file
require('./tinyYolov2/.env')
const express = require('express')
const path = require('path')
const fs = require('fs')
const app = express()
const publicDir = path.join(__dirname, './tinyYolov2')
app.use(express.static(publicDir))
app.use(express.static(path.join(__dirname, './shared')))
app.use(express.static(path.join(__dirname, './node_modules/file-saver')))
app.use(express.static(path.join(__dirname, '../../examples/public')))
app.use(express.static(path.join(__dirname, '../../weights')))
app.use(express.static(path.join(__dirname, '../../dist')))
const trainDataPath = path.resolve(process.env.TRAIN_DATA_PATH)
const testDataPath = path.resolve(process.env.TEST_DATA_PATH)
const imagesPath = path.join(trainDataPath, './final_images')
const detectionsPath = path.join(trainDataPath, './final_detections')
app.use(express.static(imagesPath))
app.use(express.static(detectionsPath))
app.use(express.static(testDataPath))
const detectionFilenames = fs.readdirSync(detectionsPath)
const detectionFilenamesMultibox = JSON.parse(fs.readFileSync(path.join(__dirname, './tinyYolov2/multibox.json')))
app.use(express.static(trainDataPath))
app.get('/detection_filenames', (req, res) => res.status(202).send(detectionFilenames))
app.get('/detection_filenames_multibox', (req, res) => res.status(202).send(detectionFilenamesMultibox))
app.get('/', (req, res) => res.sendFile(path.join(publicDir, 'train.html')))
app.get('/verify', (req, res) => res.sendFile(path.join(publicDir, 'verify.html')))
app.get('/test', (req, res) => res.sendFile(path.join(publicDir, 'test.html')))
app.listen(3000, () => console.log('Listening on port 3000!'))
\ No newline at end of file
const log = (str, ...args) => console.log(`[${[(new Date()).toTimeString().substr(0, 8)]}] ${str || ''}`, ...args)
async function promiseSequential(promises) {
const curr = promises[0]
if (!curr) {
return
}
await curr()
return promiseSequential(promises.slice(1))
}
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
function shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
function saveWeights(net, filename = 'train_tmp') {
const binaryWeights = new Float32Array(
net.getParamList()
.map(({ tensor }) => Array.from(tensor.dataSync()))
.reduce((flat, arr) => flat.concat(arr))
)
saveAs(new Blob([binaryWeights]), filename)
}
function imageToSquare(img) {
const scale = 608 / Math.max(img.height, img.width)
const width = scale * img.width
const height = scale * img.height
const canvas1 = faceapi.createCanvasFromMedia(img)
const targetCanvas = faceapi.createCanvas({ width: 608, height: 608 })
targetCanvas.getContext('2d').putImageData(canvas1.getContext('2d').getImageData(0, 0, width, height), 0, 0)
return targetCanvas
}
function getPaddingsAndReshapedSize(img, inputSize) {
const [h, w] = [img.height, img.width]
const maxDim = Math.max(h, w)
const f = inputSize / maxDim
const reshapedImgDims = {
height: Math.floor(h * f),
width: Math.floor(w * f)
}
const paddings = new faceapi.Point(
maxDim / img.width,
maxDim / img.height
)
return { paddings, reshapedImgDims }
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.12.0"> </script>
<script src="FileSaver.js"></script>
</head>
<body>
<script>
const glorotNormal = tf.initializers.glorotNormal()
saveWeights()
function initSeparableConvWeights(inChannels, outChannels) {
return {
depthwiseFilter: glorotNormal.apply([3, 3, inChannels, 1]),
pointwiseFilter: glorotNormal.apply([1, 1, inChannels, outChannels]),
bias: tf.zeros([outChannels])
}
}
function initTinyYolov2SeparableWeights() {
const conv0 = initSeparableConvWeights(3, 16)
const conv1 = initSeparableConvWeights(16, 32)
const conv2 = initSeparableConvWeights(32, 64)
const conv3 = initSeparableConvWeights(64, 128)
const conv4 = initSeparableConvWeights(128, 256)
const conv5 = initSeparableConvWeights(256, 512)
const conv6 = initSeparableConvWeights(512, 1024)
const conv7 = initSeparableConvWeights(1024, 1024)
const conv8 = {
filters: glorotNormal.apply([1, 1, 1024, 25]),
bias: tf.zeros([25])
}
return {
conv0,
conv1,
conv2,
conv3,
conv4,
conv5,
conv6,
conv7,
conv8
}
}
function saveWeights() {
const w = initTinyYolov2SeparableWeights()
const binaryWeights = new Float32Array(
Array(8).fill(0)
.map((_, i) => w[`conv${i}`])
.map(ps => [ps.depthwiseFilter, ps.pointwiseFilter, ps.bias])
.reduce((flat, arr) => flat.concat(arr))
.concat([w.conv8.filters, w.conv8.bias])
.map(tensor => console.log(tensor) || Array.from(tensor.dataSync()))
.reduce((flat, arr) => flat.concat(arr))
)
saveAs(new Blob([binaryWeights]), 'foo.weights')
}
</script>
</body>
</html>
\ No newline at end of file
const CELL_SIZE = 32
const getNumCells = inputSize => inputSize / CELL_SIZE
const inverseSigmoid = x => Math.log(x / (1 - x))
function getAnchors() {
return window.net.anchors
}
function squaredSumOverMask(lossTensors, mask) {
return tf.tidy(() => tf.sum(tf.square(tf.mul(mask, lossTensors))))
}
function assignBoxesToAnchors(groundTruthBoxes, reshapedImgDims) {
const inputSize = Math.max(reshapedImgDims.width, reshapedImgDims.height)
const numCells = getNumCells(inputSize)
return groundTruthBoxes.map(box => {
const { left, top, width, height } = box.rescale(reshapedImgDims)
const ctX = left + (width / 2)
const ctY = top + (height / 2)
const col = Math.floor((ctX / inputSize) * numCells)
const row = Math.floor((ctY / inputSize) * numCells)
const anchorsByIou = getAnchors().map((anchor, idx) => ({
idx,
iou: faceapi.iou(
new faceapi.BoundingBox(0, 0, anchor.x * CELL_SIZE, anchor.y * CELL_SIZE),
new faceapi.BoundingBox(0, 0, width, height)
)
})).sort((a1, a2) => a2.iou - a1.iou)
const anchor = anchorsByIou[0].idx
return { row, col, anchor, box }
})
}
function getGroundTruthMask(groundTruthBoxes, inputSize) {
const numCells = getNumCells(inputSize)
const mask = tf.zeros([numCells, numCells, 25])
const buf = mask.buffer()
groundTruthBoxes.forEach(({ row, col, anchor }) => {
const anchorOffset = anchor * 5
for (let i = 0; i < 5; i++) {
buf.set(1, row, col, anchorOffset + i)
}
})
return mask
}
function getCoordAndScoreMasks(inputSize) {
const numCells = getNumCells(inputSize)
const coordMask = tf.zeros([numCells, numCells, 25])
const scoreMask = tf.zeros([numCells, numCells, 25])
const coordBuf = coordMask.buffer()
const scoreBuf = scoreMask.buffer()
for (let row = 0; row < numCells; row++) {
for (let col = 0; col < numCells; col++) {
for (let anchor = 0; anchor < 5; anchor++) {
const anchorOffset = 5 * anchor
for (let i = 0; i < 4; i++) {
coordBuf.set(1, row, col, anchorOffset + i)
}
scoreBuf.set(1, row, col, anchorOffset + 4)
}
}
}
return { coordMask, scoreMask }
}
function computeBoxAdjustments(groundTruthBoxes, reshapedImgDims) {
const inputSize = Math.max(reshapedImgDims.width, reshapedImgDims.height)
const numCells = getNumCells(inputSize)
const adjustments = tf.zeros([numCells, numCells, 25])
const buf = adjustments.buffer()
groundTruthBoxes.forEach(({ row, col, anchor, box }) => {
const { left, top, right, bottom, width, height } = box.rescale(reshapedImgDims)
const centerX = (left + right) / 2
const centerY = (top + bottom) / 2
//const dCenterX = centerX - (col * CELL_SIZE + (CELL_SIZE / 2))
//const dCenterY = centerY - (row * CELL_SIZE + (CELL_SIZE / 2))
const dCenterX = centerX - (col * CELL_SIZE)
const dCenterY = centerY - (row * CELL_SIZE)
const dx = inverseSigmoid(dCenterX / CELL_SIZE)
const dy = inverseSigmoid(dCenterY / CELL_SIZE)
//const dx = dCenterX / CELL_SIZE
//const dy = dCenterY / CELL_SIZE
const dw = Math.log((width / CELL_SIZE) / getAnchors()[anchor].x)
const dh = Math.log((height / CELL_SIZE) / getAnchors()[anchor].y)
const anchorOffset = anchor * 5
buf.set(dx, row, col, anchorOffset + 0)
buf.set(dy, row, col, anchorOffset + 1)
buf.set(dw, row, col, anchorOffset + 2)
buf.set(dh, row, col, anchorOffset + 3)
})
return adjustments
}
function computeIous(predBoxes, groundTruthBoxes, reshapedImgDims) {
const inputSize = Math.max(reshapedImgDims.width, reshapedImgDims.height)
const numCells = getNumCells(inputSize)
const isSameAnchor = p1 => p2 =>
p1.row === p2.row
&& p1.col === p2.col
&& p1.anchor === p2.anchor
const ious = tf.zeros([numCells, numCells, 25])
const buf = ious.buffer()
groundTruthBoxes.forEach(({ row, col, anchor, box }) => {
const predBox = predBoxes.find(isSameAnchor({ row, col, anchor }))
if (!predBox) {
console.log(groundTruthBoxes)
console.log(predBoxes)
throw new Error(`no output box found for: row ${row}, col ${col}, anchor ${anchor}`)
}
const iou = faceapi.iou(
box.rescale(reshapedImgDims),
predBox.box.rescale(reshapedImgDims)
)
if (window.debug) {
console.log('ground thruth box:', box.rescale(reshapedImgDims).toRect())
console.log('predicted box:', predBox.box.rescale(reshapedImgDims).toRect())
console.log('predicted score:', predBox.score)
console.log('iou:', iou)
}
const anchorOffset = anchor * 5
buf.set(iou, row, col, anchorOffset + 4)
})
return ious
}
window.computeNoObjectLoss = function(outTensor, mask) {
return tf.tidy(() => {
const lossTensor = tf.sigmoid(outTensor)
return squaredSumOverMask(lossTensor, mask)
})
}
function computeObjectLoss(outTensor, groundTruthBoxes, reshapedImgDims, paddings, mask) {
return tf.tidy(() => {
const predBoxes = window.net.postProcess(
outTensor,
{ paddings }
)
const ious = computeIous(
predBoxes,
groundTruthBoxes,
reshapedImgDims
)
const lossTensor = tf.sub(ious, tf.sigmoid(outTensor))
return squaredSumOverMask(lossTensor, mask)
})
}
function computeCoordLoss(groundTruthBoxes, outTensor, reshapedImgDims, mask, paddings) {
return tf.tidy(() => {
const boxAdjustments = computeBoxAdjustments(
groundTruthBoxes,
reshapedImgDims
)
if (window.debug) {
const indToPos = []
const numCells = outTensor.shape[1]
for (let row = 0; row < numCells; row++) {
for (let col = 0; col < numCells; col++) {
for (let anchor = 0; anchor < 25; anchor++) {
indToPos.push({ row, col, anchor: parseInt(anchor / 5) })
}
}
}
const indices = Array.from(mask.dataSync()).map((val, ind) => ({ val, ind })).filter(v => v.val !== 0).map(v => v.ind)
const gt = Array.from(boxAdjustments.dataSync())
const out = Array.from(outTensor.dataSync())
const comp = indices.map(i => (
{
pos: indToPos[i],
gt: gt[i],
out: out[i]
}
))
console.log(comp.map(c => `gt: ${c.gt}, out: ${c.out}`))
const getBbox = (which) => {
const { row, col, anchor } = comp[0].pos
const ctX = ((col + faceapi.sigmoid(comp[0][which])) / numCells) * paddings.x
const ctY = ((row + faceapi.sigmoid(comp[1][which])) / numCells) * paddings.y
const width = ((Math.exp(comp[2][which]) * getAnchors()[anchor].x) / numCells) * paddings.x
const height = ((Math.exp(comp[3][which]) * getAnchors()[anchor].y) / numCells) * paddings.y
const x = (ctX - (width / 2))
const y = (ctY - (height / 2))
return new faceapi.BoundingBox(x, y, x + width, y + height)
}
const outRect = getBbox('out').rescale(reshapedImgDims).toRect()
const gtRect = getBbox('gt').rescale(reshapedImgDims).toRect()
console.log('out', outRect)
console.log('gtRect', gtRect)
}
const lossTensor = tf.sub(boxAdjustments, outTensor)
return squaredSumOverMask(lossTensor, mask)
})
}
function computeLoss(outTensor, groundTruth, reshapedImgDims, paddings) {
const inputSize = Math.max(reshapedImgDims.width, reshapedImgDims.height)
if (!inputSize) {
throw new Error(`invalid inputSize: ${inputSize}`)
}
let groundTruthBoxes = assignBoxesToAnchors(
groundTruth
.map(({ x, y, width, height }) => new faceapi.Rect(x, y, width, height))
.map(rect => rect.toBoundingBox()),
reshapedImgDims
)
const groundTruthMask = getGroundTruthMask(groundTruthBoxes, inputSize)
const { coordMask, scoreMask } = getCoordAndScoreMasks(inputSize)
const noObjectLossMask = tf.tidy(() => tf.mul(scoreMask, tf.sub(tf.scalar(1), groundTruthMask)))
const objectLossMask = tf.tidy(() => tf.mul(scoreMask, groundTruthMask))
const coordLossMask = tf.tidy(() => tf.mul(coordMask, groundTruthMask))
const noObjectLoss = tf.tidy(() =>
tf.mul(
tf.scalar(noObjectScale),
computeNoObjectLoss(outTensor, noObjectLossMask)
)
)
const objectLoss = tf.tidy(() =>
tf.mul(
tf.scalar(objectScale),
computeObjectLoss(outTensor, groundTruthBoxes, reshapedImgDims, paddings, objectLossMask)
)
)
const coordLoss = tf.tidy(() =>
tf.mul(
tf.scalar(coordScale),
computeCoordLoss(groundTruthBoxes, outTensor, reshapedImgDims, coordLossMask, paddings)
)
)
const totalLoss = tf.tidy(() => noObjectLoss.add(objectLoss).add(coordLoss))
return {
noObjectLoss,
objectLoss,
coordLoss,
totalLoss
}
}
\ No newline at end of file
import * as _tf from '@tensorflow/tfjs-core';
const faceapi = require('../../../dist/face-api.js')
const tf: typeof _tf = faceapi.tf
require('./loss')
window['faceapi'] = faceapi
window['tf'] = tf
const anchors = [
new faceapi.Point(1.603231, 2.094468),
new faceapi.Point(6.041143, 7.080126),
new faceapi.Point(2.882459, 3.518061),
new faceapi.Point(4.266906, 5.178857),
new faceapi.Point(9.041765, 10.66308)
]
window['net'] = {
getAnchors() {
return anchors
}
}
describe('loss', () => {
describe('computeNoObjectLoss', () => {
const computeNoObjectLoss = window['computeNoObjectLoss']
it('should only compute loss over scores, 1x1 grid', () => tf.tidy(() => {
const outTensor = tf.zeros([1, 1, 1, 25])
const loss = tf.sum(computeNoObjectLoss(outTensor)).dataSync()[0]
expect(loss).toEqual(0.5 * 0.5 * 5)
}))
it('should only compute loss over scores, 13x13 grid', () => tf.tidy(() => {
const outTensor = tf.zeros([1, 13, 13, 25])
const loss = tf.sum(computeNoObjectLoss(outTensor)).dataSync()[0]
expect(loss).toEqual(0.5 * 0.5 * 5 * 13 * 13)
}))
it('should only compute loss over scores, 13x13 grid, batchSize: 10', () => tf.tidy(() => {
const outTensor = tf.zeros([10, 13, 13, 25])
const loss = tf.sum(computeNoObjectLoss(outTensor)).dataSync()[0]
expect(loss).toEqual(0.5 * 0.5 * 5 * 13 * 13 * 10)
}))
})
})
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="trainUtils.js"></script>
<script src="loss.js"></script>
<script src="FileSaver.js"></script>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
<script>
window.tf = faceapi.tf
// hyper parameters
window.objectScale = 5
window.noObjectScale = 1
window.coordScale = 1
const weightsUrl = `/tmp/tmp__224_35060__320_41188__416_31050__608_16520.weights`
//const inputSizes = [160, 224, 320, 416]
const inputSizes = [512, 608]
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function fetchFddbJson() {
return fetch('/fddb-detections.json').then(res => res.json())
}
async function run() {
window.fddbJson = await fetchFddbJson()
const weights = await loadNetWeights(weightsUrl)
window.net = new faceapi.TinyYolov2(true)
await window.net.load(weights)
await promiseSequential(inputSizes.map(inputSize => async () => {
await promiseSequential(window.fddbJson.map(({ filePath, rects }) => () => {
return test(filePath, rects, inputSize)
}))
const losses = Object.keys(window.lossMap[inputSize]).map(k => window.lossMap[inputSize][k])
const totalLoss = losses
.map(l => l.totalLoss)
.reduce((sum, l) => sum + l)
const avgLoss = totalLoss / losses.length
log(`totalLoss (${inputSize}): ${totalLoss}`)
log(`avgLoss (${inputSize}): ${avgLoss}`)
window.losses = window.losses || {}
window.losses[inputSize] = { totalLoss, avgLoss }
}))
console.log(window.losses)
}
async function test(fileUri, rects, inputSize) {
const img = await faceapi.bufferToImage(await fetchImage(fileUri))
const groundTruthBoxes = rects
.map(({ x, y, width, height }) => new faceapi.Rect(x, y, width, height))
.map(rect => rect.clipAtImageBorders(img.width, img.height))
.map(({ x, y, width, height }) => ({
x: x / img.width,
y: y / img.height,
width: width / img.width,
height: height / img.height,
}))
const { reshapedImgDims, paddings } = getPaddingsAndReshapedSize(img, inputSize)
const squareImg = imageToSquare(img)
const netInput = (await faceapi.toNetInput(squareImg)).managed()
const losses = tf.tidy(() => {
const outTensor = window.net.forwardInput(netInput, inputSize)
const {
noObjectLoss,
objectLoss,
coordLoss,
totalLoss
} = computeLoss(
outTensor,
groundTruthBoxes,
reshapedImgDims,
paddings
)
const losses = {
totalLoss: totalLoss.dataSync()[0],
noObjectLoss: noObjectLoss.dataSync()[0],
objectLoss: objectLoss.dataSync()[0],
coordLoss: coordLoss.dataSync()[0]
}
return losses
})
log(`${fileUri}:`)
log(`ground truth boxes: ${groundTruthBoxes.length}`)
log(`noObjectLoss: ${losses.noObjectLoss}`)
log(`objectLoss: ${losses.objectLoss}`)
log(`coordLoss: ${losses.coordLoss}`)
log(`totalLoss: ${losses.totalLoss}`)
if (Object.keys(losses).map(k => losses[k]).some(loss => isNaN(loss) || loss === Infinity)) {
console.log(groundTruthBoxes)
console.log(img)
console.log(losses)
throw new Error('corrupted loss value')
}
window.lossMap = window.lossMap || {}
window.lossMap[inputSize] = window.lossMap[inputSize] || {}
window.lossMap[inputSize][fileUri] = losses
}
$(document).ready(function() {
run()
})
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<script src="FileSaver.js"></script>
<script src="trainUtils.js"></script>
<script src="train.js"></script>
<script src="loss.js"></script>
</head>
<body>
<script>
tf = faceapi.tf
const startIdx160 = 26600
const startIdx224 = 61660
const startIdx320 = 67788
const startIdx416 = 57650
const startIdx608 = 16520
//const weightsUrl = `/tmp/tmp__160_${startIdx160}__224_${startIdx224}__320_${startIdx320}__416_${startIdx416}__608_${startIdx608}.weights`
const weightsUrl = `/tmp/tmp_multiscale_count_8700.weights`
const fromEpoch = 0
const trainOnlyMultibox = false
const trainSizes = [416, 512, 608]
//const trainSizes = [608]
window.debug = false
window.logTrainSteps = true
window.count = 0
// hyper parameters
window.objectScale = 5
window.noObjectScale = 1
window.coordScale = 1
const rescaleEveryNthBatch = 100
window.saveEveryNthDataIdx = trainSizes.length * rescaleEveryNthBatch
window.trainSteps = 4000
//window.optimizer = tf.train.sgd(0.001)
window.optimizer = tf.train.adam(0.001, 0.9, 0.999, 1e-8)
// all samples
//const dataStartIdx = 8000
const dataStartIdx = 0
const numTrainSamples = Infinity
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function fetchDetectionFilenames() {
return fetch('/detection_filenames').then(res => res.json())
}
async function fetchDetectionFilenamesMultibox() {
return fetch('/detection_filenames_multibox').then(res => res.json())
}
async function run() {
const weights = await loadNetWeights(weightsUrl)
window.net = new faceapi.TinyYolov2(true)
window.net.load(weights)
window.net.variable()
const fetchDetectionsFn = trainOnlyMultibox
? fetchDetectionFilenamesMultibox
: fetchDetectionFilenames
window.detectionFilenames = (await fetchDetectionsFn()).slice(dataStartIdx, dataStartIdx + numTrainSamples)
window.lossMap = {}
console.log('ready')
}
function logLossChange(lossType) {
const { currentLoss, prevLoss, detectionFilenames } = window
log(`${lossType} : ${faceapi.round(currentLoss[lossType])} (avg: ${faceapi.round(currentLoss[lossType] / detectionFilenames.length)}) (delta: ${currentLoss[lossType] - prevLoss[lossType]})`)
}
function onBatchProcessed(dataIdx, inputSize) {
window.count++
const idx = (dataIdx + 1) + (window.epoch * window.detectionFilenames.length)
console.log('dataIdx', dataIdx)
if ((window.count % saveEveryNthDataIdx) === 0) {
saveWeights(window.net, `tmp_multiscale_count_${window.count}.weights`)
}
}
function _onBatchProcessed(dataIdx, inputSize) {
const idx = (dataIdx + 1) + (window.epoch * window.detectionFilenames.length)
console.log('idx', idx)
if ((idx % saveEveryNthDataIdx) === 0) {
saveWeights(window.net, `tmp__224_${startIdx224 + (inputSize === 224 ? idx : 0)}__320_${startIdx320 + (inputSize === 320 ? idx : 0)}__416_${startIdx416 + (inputSize === 416 ? idx : 0)}__608_${startIdx608 + (inputSize === 608 ? idx : 0)}.weights`)
}
}
async function train() {
const batchSize = 1
for (let i = fromEpoch; i < trainSteps; i++) {
window.epoch = i
log('step', i)
let ts2 = Date.now()
const batchCreators = createBatchCreators(shuffle(window.detectionFilenames), batchSize)
await trainStep(batchCreators, trainSizes, rescaleEveryNthBatch, onBatchProcessed)
ts2 = Date.now() - ts2
}
ts = Date.now() - ts
log()
log('--------------------')
log()
log('step %s done (%s ms)', i, ts)
window.prevLoss = window.currentLoss
window.currentLoss = Object.keys(lossMap)
.map(filename => lossMap[filename])
.reduce((accumulatedLosses, losses) =>
Object.keys(losses)
.map(key => ({
[key]: (accumulatedLosses[key] || 0) + losses[key]
}))
.reduce((map, curr) => ({ ...map, ...curr }), {}),
{}
)
if (window.prevLoss) {
logLossChange('noObjectLoss')
logLossChange('objectLoss')
logLossChange('coordLoss')
logLossChange('totalLoss')
}
log()
log('--------------------')
log()
}
run()
</script>
</body>
</html>
\ No newline at end of file
const batchIdx = 0
function minimize(groundTruthBoxes, batchInput, inputSize, batch, { reshapedImgDims, paddings }) {
const filename = batch.filenames[batchIdx]
const { dataIdx } = batch
return optimizer.minimize(() => {
const outTensor = window.net.forwardInput(batchInput, inputSize)
const {
noObjectLoss,
objectLoss,
coordLoss,
totalLoss
} = computeLoss(
outTensor,
groundTruthBoxes,
reshapedImgDims,
paddings
)
const losses = {
totalLoss: totalLoss.dataSync()[0],
noObjectLoss: noObjectLoss.dataSync()[0],
objectLoss: objectLoss.dataSync()[0],
coordLoss: coordLoss.dataSync()[0]
}
const lossKey = `${filename}_${inputSize}`
if (window.logTrainSteps) {
log(`ground truth boxes: ${groundTruthBoxes.length}`)
log(`noObjectLoss[${dataIdx}]: ${losses.noObjectLoss}`)
log(`objectLoss[${dataIdx}]: ${losses.objectLoss}`)
log(`coordLoss[${dataIdx}]: ${losses.coordLoss}`)
log(`totalLoss[${dataIdx}]: ${losses.totalLoss}`)
if (window.lossMap[lossKey]) {
log(`loss change: ${losses.totalLoss - window.lossMap[lossKey].totalLoss}`)
}
}
window.lossMap[lossKey] = losses
return totalLoss
}, true)
}
async function trainStep(batchCreators, inputSizes, rescaleEveryNthBatch, onBatchProcessed = () => {}) {
async function step(currentBatchCreators) {
if (!currentBatchCreators.batchCreators.length) {
return
}
await promiseSequential(inputSizes.map(inputSize => async () => {
await promiseSequential(currentBatchCreators.batchCreators.map(batchCreator => async () => {
const batch = await batchCreator()
const { imgs, groundTruthBoxes, filenames, dataIdx } = batch
const img = imgs[0]
const { reshapedImgDims, paddings } = getPaddingsAndReshapedSize(img, inputSize)
const squareImg = imageToSquare(img)
const batchInput = await faceapi.toNetInput(squareImg)
const [imgHeight, imgWidth] = batchInput.inputs[batchIdx].shape
// skip groundTruthBoxes, which are too tiny
const scaleFactor = inputSize / Math.max(imgHeight, imgWidth)
const filteredGroundTruthBoxes = groundTruthBoxes[batchIdx].filter(({ x, y, width, height }) => {
const box = (new faceapi.Rect(x, y, width, height))
.toBoundingBox()
.rescale({ height: imgHeight, width: imgWidth })
.rescale(scaleFactor)
const isTooTiny = box.width < 40 || box.height < 40
if (isTooTiny && window.debug) {
log(`skipping box for input size ${inputSize}: (${Math.floor(box.width)} x ${Math.floor(box.height)})`)
}
return !isTooTiny
})
if (!filteredGroundTruthBoxes.length) {
if (window.debug) {
log(`no boxes for input size ${inputSize}, ${groundTruthBoxes[batchIdx].length} boxes were too small`)
}
batchInput.dispose()
onBatchProcessed(dataIdx, inputSize)
return
}
let ts = Date.now()
const loss = minimize(filteredGroundTruthBoxes, batchInput, inputSize, batch, { reshapedImgDims, paddings })
ts = Date.now() - ts
if (window.logTrainSteps) {
log(`trainStep time for dataIdx ${dataIdx} (${inputSize}): ${ts} ms`)
}
loss.dispose()
batchInput.dispose()
onBatchProcessed(dataIdx, inputSize)
await tf.nextFrame()
}))
}))
await step(currentBatchCreators.next(rescaleEveryNthBatch))
}
await step(batchCreators.next(rescaleEveryNthBatch))
}
async function fetchGroundTruthBoxesForFile(file) {
const boxes = await fetch(file).then(res => res.json())
return {
file,
boxes
}
}
function createBatchCreators(detectionFilenames, batchSize) {
if (batchSize < 1) {
throw new Error('invalid batch size: ' + batchSize)
}
const batches = []
const pushToBatch = (remaining) => {
if (remaining.length) {
batches.push(remaining.slice(0, batchSize))
pushToBatch(remaining.slice(batchSize))
}
return batches
}
pushToBatch(detectionFilenames)
const batchCreators = batches.map((filenamesForBatch, dataIdx) => async () => {
const groundTruthBoxes = (await Promise.all(filenamesForBatch.map(fetchGroundTruthBoxesForFile)))
.map(({ boxes }) => boxes)
const imgs = await Promise.all(filenamesForBatch.map(
async file => await faceapi.bufferToImage(await fetchImage(file.replace('.json', '')))
))
return {
imgs,
groundTruthBoxes,
filenames: filenamesForBatch,
dataIdx
}
})
let idx = 0
function next(n) {
const nextBatchCreators = batchCreators.slice(idx, idx + n)
idx += n
return {
batchCreators: nextBatchCreators,
next
}
}
return {
data: batchCreators,
next
}
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
<div id="navbar"></div>
<div class="center-content page-container">
<div class="progress" id="loader">
<div class="indeterminate"></div>
</div>
<div style="position: relative" class="margin">
<img id="inputImg" src="" style="max-width: 800px;" />
<canvas id="overlay" />
</div>
<div class="row side-by-side">
<div id="selectList"></div>
<div class="row">
<label for="imgUrlInput">Get image from URL:</label>
<input id="imgUrlInput" type="text" class="bold">
</div>
<button
class="waves-effect waves-light btn"
onclick="loadImageFromUrl()"
>
Ok
</button>
</div>
<div class="row side-by-side">
<div class="row input-field" style="margin-right: 20px;">
<select id="sizeType">
<option value="128">128 x 128</option>
<option value="160">160 x 160</option>
<option value="224">224 x 224</option>
<option value="320">320 x 320</option>
<option value="416">416 x 416</option>
<option value="608">608 x 608</option>
</select>
<label>Input Size</label>
</div>
<div class="row">
<label for="scoreThreshold">Score Threshold:</label>
<input disabled value="0.5" id="scoreThreshold" type="text" class="bold">
</div>
<button
class="waves-effect waves-light btn"
onclick="onDecreaseThreshold()"
>
<i class="material-icons left">-</i>
</button>
<button
class="waves-effect waves-light btn"
onclick="onIncreaseThreshold()"
>
<i class="material-icons left">+</i>
</button>
</div>
<div class="row">
<label for="imgByNr">Enter image NR: </label>
<input id="imgByNr" type="text" class="bold">
</div>
</div>
<script>
let scoreThreshold = 0.5
let sizeType = 608
function onKeyDown(e) {
e.target.value = (
parseInt(e.target.value) + (e.keyCode === 38 ? 1 : (e.keyCode === 40 ? -1 : 0))
) || e.target.value || 0
const imgUri = window.imgs[e.target.value]
console.log(imgUri)
onSelectionChanged(imgUri)
}
function onIncreaseThreshold() {
scoreThreshold = Math.min(faceapi.round(scoreThreshold + 0.1), 1.0)
$('#scoreThreshold').val(scoreThreshold)
updateResults()
}
function onDecreaseThreshold() {
scoreThreshold = Math.max(faceapi.round(scoreThreshold - 0.1), 0.1)
$('#scoreThreshold').val(scoreThreshold)
updateResults()
}
function onSizeTypeChanged(e, c) {
sizeType = e.target.value
$('#sizeType').val(sizeType)
updateResults()
}
async function loadImageFromUrl(url) {
const img = await requestExternalImage($('#imgUrlInput').val())
$('#inputImg').get(0).src = img.src
updateResults()
}
async function updateResults() {
const inputImgEl = $('#inputImg').get(0)
const { width, height } = inputImgEl
const canvas = $('#overlay').get(0)
canvas.width = width
canvas.height = height
const forwardParams = {
inputSize: parseInt(sizeType),
scoreThreshold
}
const detections = await window.net.locateFaces(inputImgEl, forwardParams)
faceapi.drawDetection('overlay', detections.map(det => det.forSize(width, height)))
}
async function onSelectionChanged(uri) {
const imgBuf = await fetchImage(uri)
$(`#inputImg`).get(0).src = (await faceapi.bufferToImage(imgBuf)).src
updateResults()
}
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
async function fetchDetectionFilenames() {
return fetch('/detection_filenames').then(res => res.json())
}
async function run() {
$('#imgByNr').keydown(onKeyDown)
const startIdx224 = 35060
const startIdx320 = 41188
const startIdx416 = 31050
const startIdx608 = 16520
//const weightsUrl = `/tmp/tmp__224_${startIdx224}__320_${startIdx320}__416_${startIdx416}__608_${startIdx608}.weights`
const weightsUrl = `/tmp/tmp_multiscale_count_4200.weights`
const weights = await loadNetWeights(weightsUrl)
window.net = new faceapi.TinyYolov2(true)
await window.net.load(weights)
window.imgs = (await fetchDetectionFilenames()).map(f => f.replace('.json', ''))
$('#loader').hide()
onSelectionChanged($('#selectList select').val())
}
$(document).ready(function() {
renderNavBar('#navbar', 'tiny_yolov2_face_detection')
renderImageSelectList(
'#selectList',
async (uri) => {
await onSelectionChanged(uri)
},
'bbt1.jpg'
)
const sizeTypeSelect = $('#sizeType')
sizeTypeSelect.val(sizeType)
sizeTypeSelect.on('change', onSizeTypeChanged)
sizeTypeSelect.material_select()
run()
})
</script>
</body>
</html>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
[{"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"]}]
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment