emoji-net
Version:
EmojiNet is an image to emoji recognizer based on MobileNet / Google Emoji Scavenger Hunt
146 lines (124 loc) • 4 kB
text/typescript
/**
* @license
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
*/
import 'isomorphic-fetch'
import * as tfc from '@tensorflow/tfjs-core'
import {
loadFrozenModel,
FrozenModel,
} from '@tensorflow/tfjs-converter'
import {
EmojiId,
EmojiName,
EMOJI_CLASSES,
} from './emoji-classes'
import {
createCanvas,
loadImage,
resizeImage,
} from './canvas-utils'
type TensorMap = {[name: string]: tfc.Tensor}
const MOBILENET_SIZE = 224
const MODEL_FILE_URL = 'https://emojiscavengerhunt.withgoogle.com/model/tensorflowjs_model.pb'
const WEIGHT_MANIFEST_FILE_URL = 'https://emojiscavengerhunt.withgoogle.com/model/weights_manifest.json'
// const MODEL_FILE_URL = 'file:///model/tensorflowjs_model.pb'
// const WEIGHT_MANIFEST_FILE_URL = 'file:///model/weights_manifest.json'
const INPUT_NODE_NAME = 'input'
const OUTPUT_NODE_NAME = 'final_result'
const PREPROCESS_DIVISOR = tfc.scalar(255 / 2)
interface PredictItem {
id : EmojiId,
name : EmojiName,
probobility : number,
}
class EmojiNet {
model?: FrozenModel
async load () {
this.model = await loadFrozenModel(
MODEL_FILE_URL,
WEIGHT_MANIFEST_FILE_URL
)
this.predict(tfc.zeros([
MOBILENET_SIZE,
MOBILENET_SIZE,
3,
]))
}
dispose () {
if (this.model) {
this.model.dispose()
}
}
async recognize (src: string | Buffer): Promise<PredictItem[]> {
const image = await loadImage(src)
const resizedImage = await resizeImage(image, MOBILENET_SIZE, MOBILENET_SIZE)
const canvas = await createCanvas(MOBILENET_SIZE, MOBILENET_SIZE)
const ctx = canvas.getContext('2d')
if (!ctx) { throw new Error('no ctx') }
ctx.putImageData(resizedImage, 0, 0)
const pixels = tfc.fromPixels(canvas)
const predictions = this.predict(pixels)
const top3 = this.getTopKClasses(predictions, 3)
return top3
}
/**
* Infer through MobileNet, assumes variables have been loaded. This does
* standard ImageNet pre-processing before inferring through the model. This
* method returns named activations as well as softmax logits.
*
* @param input un-preprocessed input Array.
* @return The softmax logits.
*/
protected predict (input: tfc.Tensor): tfc.Tensor1D {
const preprocessedInput = tfc.div(
tfc.sub(input.asType('float32'), PREPROCESS_DIVISOR),
PREPROCESS_DIVISOR)
const reshapedInput = preprocessedInput.reshape([
1,
...preprocessedInput.shape,
])
const dict: TensorMap = {}
dict[INPUT_NODE_NAME] = reshapedInput
return this.model!.execute(dict, OUTPUT_NODE_NAME) as tfc.Tensor1D
}
protected getTopKClasses (
predictions: tfc.Tensor1D,
topK: number,
): PredictItem[] {
const values = predictions.dataSync()
predictions.dispose()
let predictionList = []
for (let i = 0; i < values.length; i++) {
predictionList.push({
index: i,
probobility: values[i],
})
}
predictionList = predictionList.sort((a, b) => {
return b.probobility - a.probobility
}).slice(0, topK)
return predictionList.map(x => ({
id: x.index as EmojiId,
name: EMOJI_CLASSES[x.index as EmojiId],
probobility: x.probobility,
}))
}
}
export {
MOBILENET_SIZE,
EmojiNet,
}