Image classification using Tensorflow.js

This is an image training and classificator using Tensorflow.js for the Machine Learning, and Vue.js for the interface.

by katth

HTML

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/@tensorflow/tfjs"></script>
<script src="https://unpkg.com/@tensorflow-models/mobilenet"></script>
<script src="https://unpkg.com/@tensorflow-models/knn-classifier"></script>
<div id="app">
    <h1>Model status: {{ statusMessage }}</h1>

    <!-- Computer webcam -->
    <video id="player" controls autoplay muted
        width="224" height="224"></video>

    <form v-on:submit.prevent
        action="">
        <!-- New class Registrator -->
        <label for="newClassInput"
            >Register an unexistent class: </label>
        <input type="text"
            placeholder="Example: 'apple'" id="newClassInput">
            
            <!-- registerButton -->
        <button type="button" id="btnAddClass"
            @click="registerNewClass()">Register new class</button>

        <!-- Selector of class that will be trained -->
        <label for="classSelector"
            >Choose the class that will be trained:</label>
        <select name="classSelector" id="classSelector">
            <option v-for="(c, index) in foodClasses" :value="c.name"
                v-bind:key="c.id" v-bind:id="'option' + index">{{ c.name }}</option>
        </select>

        <button type="button" @click="addExample()">Train class</button>
    </form>

    <hr>

    <div class="classificationContainer">
        <div class="info">
            <h2>Classification</h2>
            <p>Labels: {{ label }}</p>
            <p>Confidence: {{ confidence }}</p>
        </div>
    </div>
</div>

CSS

* {
    box-sizing: border-box;
}

html, body {
    margin: 0;
}

#app {
    width: 100%;
    padding-right: 16px;
    padding-left: 16px;
}

button, input {
    display: block;
}

.classificationContainer {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    grid-column-gap: 16px;
    grid-row-gap: 16px;
}

.previewContainer img {
    width: 60px;
    height: 60px;
}

#image {
    max-width: 100%;
    height: auto;
    margin-top: 16px;
    margin-bottom: 16px;
}

@media screen and (min-width: 768px) {
    .classificationContainer {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 1fr;
    }
}

JavaScript

// Vue.js
let app = new Vue({
    el: '#app',
    data: {
        net: null,
        classifier: null,
        previewURLS: [],
        imageDisplayed: "",
        imagesSources: [],
        label: '',
        confidence: '',
        statusMessage: "",
        foodClasses: [],
        cameraOn: false
    },
    methods: {
        /* previewImage(event) {
            // Mostra um preview das imagens que o usuário selecionou para fazer upload
            let fileList = event.srcElement.files;
            const file = fileList[0];
            if (!file.type.startsWith('image/')) {
                throw new Error(`File "${file.name}" is not an image!`);
            }
            this.previewURLS.push(URL.createObjectURL(file));
        }, */
        registerNewClass() {
            let newClass = {};
            const newClassInput = document.getElementById('newClassInput');
            if (newClassInput.value !== '') {
                newClass.name = newClassInput.value,
                newClass.id = this.numberOfClasses;
                this.foodClasses.push(newClass);
                newClassInput.value = '';
            }
        },
        // Add the class to the model
        async addExample() {
            const selector = document.getElementById('classSelector');
            const player = document.getElementById('player');

            // Get the intermediate activation of MobileNet 'conv_preds' and pass that
            // to the KNN classifier.
            const activation = this.net.infer(player, 'conv_preds');

            // Pass the intermediate activation to the classifier
            this.classifier.addExample(activation, selector.selectedIndex);

            console.log("Class '" + selector.value + "' added to the model");

            while (true) {
                if (this.classifier.getNumClasses() > 0) {
                    const activation = this.net.infer(player, 'conv_preds');
            
                    const result = await...