В этой статье мы расскажем, как обучить модель машинного обучения (ML) с помощью Teachable Machine, интегрировать модель с веб-приложениями и мобильными приложениями и, наконец, создать простую камень-ножницы-бумагу. игра для компьютера. В частности, мы будем строить модель классификации изображений, в которой мы показываем свою руку камере, и модель будет предсказывать, является ли рука «камнем», «бумагой» или «ножницами».

Что такое обучаемая машина?

Teachable Machine — это веб-инструмент, который позволяет нам легко обучать модели машинного обучения без особых предварительных знаний с помощью машинного обучения. Инструмент упрощает процесс добавления обучающих данных, обучения модели и экспорта модели. Вы определяете, какие «Классы» вы хотите тренировать, затем добавляете свой вклад с веб-камеры и нажимаете «Обучить». Затем модель быстро запоминает различия между созданными вами классами. Это довольно легко!

Создание проекта

Во-первых, давайте перейдем к https://teachablemachine.withgoogle.com/ и нажмите Начать. На момент написания этой статьи Teachable Machine поддерживает 3 типа классификации — изображения, аудио и позы.

В этой статье мы рассмотрим только первую часть — классификацию изображений. Итак, нажмите «Проект изображения». Во всплывающем диалоговом окне выберите «Стандартная модель изображения». Это модель, которую мы можем использовать для работы в браузере или на вашем мобильном телефоне с удовлетворительной производительностью. Если вы хотите работать на меньшем устройстве с ограниченными возможностями, вы можете попробовать выбрать «Модель встроенного образа».

Классы

Далее давайте создадим классы или категории, которые нам нужно смоделировать для классификации. Поскольку мы создаем модель ML для классификации игры «Камень-ножницы-бумага», мы создадим 4 класса следующим образом.

  • Ничего: для обнаружения отсутствия жестов рук.
  • Рок: чтобы определить, когда мы показываем знак «рок»
  • Бумага: чтобы определить, когда мы показываем «бумажный» знак
  • Ножницы: чтобы определить, когда мы показываем знак «ножницы».

Добавление обучающих данных

Теперь пришло время собрать тренировочные данные. Вы можете начать с нажатия кнопки «Веб-камера», а затем нажать кнопку «Удерживать для записи», чтобы записать различные изображения, которые вы показываете на веб-камеру.

Ниже я добавил несколько примеров изображений для каждого класса.

То, что вы видели выше, едва ли является примером каждого класса. Что вам нужно сделать, так это добавить больше разных изображений, чтобы модель могла изучить как можно больше разных изображений. Вот несколько идей

  • Класс «Ничего»: добавьте больше изображений, на которых вы представляете себя в нем, ничего не делая или демонстрируя жесты рук, кроме «Камень», «Бумага» или «Ножницы».
  • Класс «Камень»: добавление большего количества изображений с левой и правой руки, разное расстояние от камеры, разный фон и т. д.
  • Вы можете сделать то же самое для классов «Бумага» и «Ножницы». Помните, опять же, цель состоит в том, чтобы представить компьютеру как можно больше разных изображений, чтобы он научился их обобщать и хорошо справлялся, когда мы заставляем их работать.

Попробуйте добавить изображения не менее 100 изображений в класс. В большинстве случаев, чем больше, тем лучше. Обратитесь к этому официальному учебнику для более подробной информации.

Тренироваться

Как только вы добавите достаточно изображений, нажмите «Обучить модель», чтобы обучить вашу модель. Обучение занимает несколько минут в зависимости от объема ваших данных.

Тестовая модель

После того, как это будет сделано, рядом с ним появится новая панель «предварительного просмотра». Затем вы можете показать свою руку веб-камере, и ваша обученная модель постарается классифицировать ее. Вы также можете добавить больше изображений, если считаете, что модель еще недостаточно хороша.

Экспорт модели

Как только он заработает должным образом, вы должны сохранить проект на свой диск Google или загрузить его на свой локальный компьютер для последующего использования, если вы хотите добавить больше изображений или классов в будущем, чтобы вам не пришлось переделывать это. все сначала. Вы можете щелкнуть значок рядом с Teachable Machine в левом верхнем углу и выбрать «Сохранить проект на диск» или «Загрузить проект как файл».

Затем нажмите «Экспорт модели» на панели «Предварительный просмотр» → Выберите вкладку «Tensorflow.js» → Выберите «Загрузить» → и нажмите «Загрузить мою модель», чтобы загрузить обученную модель на свой компьютер для использования позже в приложении.

Давайте напишем наше приложение

Теперь, когда у нас есть модель, давайте научимся использовать ее в приложении и создадим игру камень-ножницы-бумага. Мы будем использовать Vue3 и Framework7 для создания этого приложения. Вы можете клонировать минимальный шаблон из этого Github и начать строить блок за блоком из этой статьи или клонировать это готовое к производству приложение из этого Github. Я рекомендую последний. Вы можете просто лежать, а я объясню основную часть программы.

Прежде чем мы перейдем к кодированию, давайте сначала опишем макет приложения и функциональные возможности, которые мы собираемся создать. На скриншоте выше четыре основные части. Первая часть — это видеопоток в реальном времени с камеры, и он будет классифицировать изображение, которое мы показываем на камеру. Далее у нас есть знак «Рука» как «Игрок» и знак «Ножницы» как компьютер. Знак «Рука» — это то, что наша модель классифицирует по тому, что она видит в камере, а «Ножницы» — это случайный знак, выбранный компьютером. После двух кнопок первая кнопка — это кнопка-переключатель, которая может начать воспроизведение или приостановить игру, а вторая кнопка — это кнопка-переключатель, отображающая камеру или нет. Наконец, последняя часть — это раздел результатов сравнения между выбранной игроком и компьютером рукой.

А вот и код — «src/pages/home.vue»

<template>
<f7-page name="home">
<!-- Top Navbar -->
<f7-navbar>
<f7-nav-title>Rock Paper Scissors</f7-nav-title>
</f7-navbar>
<div v-show="!loaded" class="text-align-center">
<div class="preloader"></div>
</div>
<f7-block v-show="loaded">
<f7-row class="justify-content-center text-align-center camera-container">
<f-card>
<div class="camera" v-show="!isMobile() && showCamera">
<video ref="videoRef" playsinline autoplay></video>
</div>
<div class="camera" v-show="isMobile() && showCamera">
<img ref="imageRef" src="static/icons/Nothing.svg">
</div>
</f-card>
</f7-row>
<f7-row class="justify-content-center">
<f7-col class="hand-container">
<div>
<h1>Player</h1>
<img class="hand" :src="playerImage">
</div>
</f7-col>
<f7-col class="hand-container">
<div>
<h1>Computer</h1>
<img class="hand" :src="computerImage">
</div>
</f7-col>
</f7-row>
<f7-row class="justify-content-center">
<f7-col>
<f7-button fill raised @click="play">
<f7-icon :f7="playIcon" :size="36"></f7-icon>
</f7-button>
</f7-col>
<f7-col>
<f7-button fill raised @click="toggleCamera">
<f7-icon :f7="toggleCameraIcon" :size="36"></f7-icon>
</f7-button>
</f7-col>
</f7-row>
</f7-block>
<f7-block class="result-block">
<h2 class="vs">{{ vs }}</h2>
<h3 class="result">{{ result }}</h3>
</f7-block>
</f7-page>
</template>
view raw home.vue hosted with ❤ by GitHub

Создаем 2 элемента из строк 13–18. Элемент видео используется для отображения живого видео с веб-камеры ПК. а элемент изображения используется для проецирования изображений с камеры мобильного телефона. Остальной код должен быть очень простым.

Далее давайте рассмотрим тег «script».

<script>
import { onMounted, ref, computed } from 'vue';
import * as tmImage from '@teachablemachine/image';
import { fetch as fetchPolyfill } from 'whatwg-fetch';
export default {
setup() {
// variable and DOM
const videoRef = ref(null);
const imageRef = ref(null);
const showCamera = ref(true);
const usermediaLoaded = ref(false);
const firstTimePredicted = ref(false);
const playIcon = ref('play');
const toggleCameraIcon = ref('nosign');
const player = ref('Nothing');
const computer = ref('Nothing');
const result = ref(null);
const vs = ref(null);
const w = 224; // width for video, image
const h = 224; // height for video, image
const predictionInterval = 1000 * 1; // set it lower if the pc and model could run faster
const gamePlayInterval = 1000 * 1.5; // will play with computer every this interval
let model;
let predictionTimer = null;
let gamePlayTimer = null;
let totalClasses; // classes from model - nothing, rock, paper, scissors
let labels;
onMounted(async () => {
if (isAndroid()) {
// overwrite fetch so that it can load model from file system
window.fetch = fetchPolyfill;
}
// load model
const URL = 'static/model/rock-paper-scissors/';
const modelURL = URL + 'model.json';
const metadataURL = URL + 'metadata.json';
model = await tmImage.load(modelURL, metadataURL);
// run the first prediction since it takes few seconds. the subsequent predictions are almost realtime.
totalClasses = model.getTotalClasses();
labels = model.getClassLabels();
classify(imageRef.value);
// start usermedia stream on web and if browser support it
if (!isMobile() && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
startUserMedia();
}
});
const play = () => {
if (isMobile()) {
predictWithCanvasCamera()
} else {
predictWithUsermedia();
}
}
const toggleCamera = () => {
showCamera.value = !showCamera.value;
if (!showCamera.value) {
toggleCameraIcon.value = 'camera';
} else {
toggleCameraIcon.value = 'nosign';
}
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub

Нам нужно установить еще 2 зависимости — @teachablemachine/image и whatwg-fetch ​​поверх зависимостей для Vue и Framework7. Библиотека Teachablemachine необходима для работы с моделью, обученной с помощью веб-приложения Teachable Machine. Библиотека whatwg-fetch используется для исправления модели загрузки из файловой системы на платформе Android, в которой мы используем строку 31.

В строке 36 мы загружаем модель из этого каталога «static/model/rock-paper-scissors». Поэтому, пожалуйста, разархивируйте загруженную модель в каталог. Есть 3 файла — metadata.json, model.json и weights.bin. metadata.json — это просто метаданные вашей модели. В нем хранится такая информация, как размер изображения, метки, версия и временные метки вашей модели и т. д. Файлы model.json и weights.bin являются важными файлами, представляющими сеть модели. структуру, параметры, веса и смещение, которые вы обучили.

В строке 50 у нас есть функция play. Если мы работаем на мобильном устройстве, мы будем вызывать функцию predictWithCanvasCamera, которая отображает изображение из плагина CanvasCamera и использует веб-API MediaDevices, вызывая функцию predictWithUserMedia при работе в веб-браузере.

Прогнозирование в веб-браузере

<script>
...
export default {
setup() {
...
const startUserMedia = () => {
const constraint = {
audio: false,
video: {
facingMode: 'environment',
width: { min: w, ideal: w, max: w, exact: w },
height: { min: h, ideal: h, max: h, exact: h },
}
};
const handleSuccess = (stream) => {
usermediaLoaded.value = true;
videoRef.value.srcObject = stream;
}
const handleError = (error) => {
alert(error.message);
usermediaLoaded.value = true;
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
navigator.mediaDevices.getUserMedia(constraint).then(handleSuccess).catch(handleError);
}
const predictWithUsermedia = async () => {
if (!model || !videoRef.value) {
return;
}
// toggle camera icon and prediction
if (playIcon.value === 'play') {
playIcon.value = 'pause';
// start prediction
predictionTimer = setInterval(() => {
classify(videoRef.value);
}, predictionInterval);
startGamePlayTimer();
} else {
playIcon.value = 'play';
stopPredictionTimer();
stopGamePlayTimer();
resetGame();
}
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub

В строке 9 мы создаем функцию для подготовки настроек и ограничений для веб-API UserMedia. Мы устанавливаем высоту и ширину точно на 224 пикселя, того же размера, что и размер входного изображения нашей модели. В функции обратного вызова успеха (строка 21) мы назначаем поток с веб-камеры в качестве источника видеоэлемента.

В строке 38 при нажатии кнопки «воспроизведение» мы сначала переключаем значок на «паузу», а затем запускаем два таймера — predictionTimer и gamePlayerTimer. PredictionTimer будет периодически вызывать функцию «классифицировать» с видеоэлементом (videoRef) в качестве входных данных. GamePlayTimer — это интервальный таймер для периодического изменения значения раздачи компьютера. Как только обе руки выбраны, вызывается функция «вычислить» для сравнения и отображения результата. Функция «вычислить» — это просто простая функция для определения того, какая рука выигрывает. В противном случае в строке 45, если нажать кнопку «пауза», мы все сбрасываем.

<script>
...
export default {
setup() {
...
const startGamePlayTimer = () => {
gamePlayTimer = setInterval(() => {
computer.value = labels[getRandomClassIndex()];
calculate();
setTimeout(() => {
// change to loading gif after shown what computer has chosen
computer.value = 'loading';
}, 500);
}, gamePlayInterval);
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub
<script>
...
export default {
setup() {
...
const calculate = () => {
if (!player.value || player.value === 'Nothing') {
vs.value = 'Player has not chosen the hand';
result.value = null;
return;
}
if (!computer.value || computer.value === 'Nothing') {
vs.value = 'Computer has not chosen the hand';
result.value = null;
return;
}
vs.value = player.value + ' vs. ' + computer.value;
let whoWin;
if (player.value === computer.value) {
whoWin = 'Draw';
} else if (player.value === 'Paper') {
if (computer.value === 'Rock') {
whoWin = 'Player Win';
}
} else if (player.value === 'Rock') {
if (computer.value === 'Scissors') {
whoWin = 'Player Win';
}
} else if (play.value === 'Scissors') {
if (computer.value === 'Paper') {
whoWin = 'Player Win';
}
}
if (!whoWin) {
whoWin = 'Computer Win';
}
result.value = whoWin;
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub

А вот и функция «классифицировать». Мы вызываем функцию «предсказать» модели TeachableMachine. Метод принимает аргумент в виде элемента изображения или видео. Результат сохраняется в переменной «prediction» в виде массива. Затем мы зацикливаемся, чтобы найти класс с наибольшей вероятностью (значение достоверности). Наконец, мы назначаем и визуализируем изображение для руки игрока, строка 22.

<script>
...
export default {
setup() {
...
const classify = (image) => {
model.predict(image)
.then(prediction => {
let maxProbab = 0;
let index = 0;
for (let i = 0; i < totalClasses; i++) {
if (prediction[i].probability.toFixed(2) > maxProbab) {
maxProbab = prediction[i].probability.toFixed(2);
index = i;
}
}
if (!firstTimePredicted.value) {
firstTimePredicted.value = true;
} else {
player.value = prediction[index].className;
}
});
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub

Прогнозирование на мобильном устройстве

Веб-API UserMedia не полностью работает на мобильном устройстве (фреймворк Cordova), поэтому вместо этого мы используем плагин Cordova Canvas Camera. Нам также необходимо установить плагин Cordova File для управления отображением изображений из файловой системы мобильного телефона.

<script>
...
export default {
setup() {
...
const startCanvasCamera = () => {
const options = {
canvas: {
width: w,
height: h
},
capture: {
width: w,
height: h
},
use: 'file',
fps: 30,
hasThumbnail: false,
cameraFacing: 'front'
};
window.plugin.CanvasCamera.start(options, async (error) => {
alert('could not start canvas camera');
}, (data) => {
readImageFile(data);
});
}
const readImageFile = (data) => {
// set file protocol
const protocol = 'file://';
let filepath = '';
if (isAndroid()) {
filepath = protocol + data.output.images.fullsize.file;
} else {
filepath = data.output.images.fullsize.file;
}
// read image from local file and assign to image element
window.resolveLocalFileSystemURL(filepath, async (fileEntry) => {
fileEntry.file((file) => {
const reader = new FileReader();
reader.onloadend = async () => {
const blob = new Blob([new Uint8Array(reader.result)], { type: 'image/png' });
imageRef.value.src = window.URL.createObjectURL(blob);
};
reader.readAsArrayBuffer(file);
}, (err) => {
console.log('read', err);
});
}, (error) => {
console.log(error);
})
}
const predictWithCanvasCamera = () => {
if (!model || !window.plugin.CanvasCamera || !imageRef) {
return;
}
// toggle camera icon and prediction
if (playIcon.value === 'play') {
playIcon.value = 'pause';
startCanvasCamera();
// start predicting
predictionTimer = setInterval(() => {
if (!imageRef || !imageRef.value || !imageRef.value.src) return;
classify(imageRef.value);
}, predictionInterval);
startGamePlayTimer();
} else {
playIcon.value = 'play';
stopCanvasCamera();
stopPredictionTimer();
stopGamePlayTimer();
resetGame();
}
}
const stopCanvasCamera = () => {
// stop canvas camera
window.plugin.CanvasCamera.stop((error) => {
console.log('[CanvasCamera stop]', 'error', error);
}, (data) => {
console.log('[CanvasCamera stop]', 'data', data);
});
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub

В строке 27, когда плагин камеры обнаруживает приемлемое изображение, он вызывает функцию «readImageFile» для рендеринга изображения. В последней версии Cordova мы больше не можем отображать изображения из источника файловой системы. Таким образом, функция создана для обработки различных файловых протоколов на ios/android и преобразования изображений в большие двоичные объекты перед их назначением элементу изображения (imageRef).

Функция «predictWithCanvasCamera» в строке 57 очень похожа на функцию «predictWithUserMedia». Единственное основное отличие заключается в том, что мы передаем элемент изображения (imageRef) в функцию «классифицировать» вместо элемента видео (videoRef).

И, наконец, остальные функции — в основном вспомогательные.

<script>
...
export default {
setup() {
...
const loaded = computed(() => {
if (isMobile()) {
return firstTimePredicted.value;
} else {
// on web, the app is ready when both usermedia and model is loaded
return usermediaLoaded.value && firstTimePredicted.value;
}
})
const playerImage = computed(() => {
return getImage(player.value);
})
const computerImage = computed(() => {
return getImage(computer.value);
})
const resetGame = () => {
player.value = 'Nothing';
vs.value = null;
result.value = null;
setTimeout(() => {
// change computer hand to nothing
computer.value = 'Nothing';
}, 1000);
}
const getImage = (icon) => {
let extension = '.svg';
if (icon === 'loading') {
extension = '.gif';
}
return 'static/icons/' + icon + extension;
}
const getRandomClassIndex = () => {
const max = totalClasses - 1;
return Math.floor(Math.random() * max) + 1;
}
const stopPredictionTimer = () => {
clearInterval(predictionTimer);
predictionTimer = null;
}
const stopGamePlayTimer = () => {
clearInterval(gamePlayTimer);
gamePlayTimer = null;
}
const isMobile = () => {
return window.cordova && (isAndroid() || isIos());
}
const isAndroid = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/android/i.test(userAgent)) return true;
return false;
}
const isIos = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/iPad|iPhone|iPod/i.test(userAgent)) return true;
return false;
}
...
}
}
</script>
view raw home.vue hosted with ❤ by GitHub

Запуск на мобильном устройстве

Если вы хотите запустить это приложение на своем телефонном устройстве, зарегистрируйте учетную запись в Monaca и следуйте этому руководству по созданию приложения для Android или iOS.

Что такое Монака?

Кроссплатформенная гибридная платформа разработки мобильных приложений и инструменты в облаке

Если у вас все на месте, приложение должно работать следующим образом

Весь исходный код можно найти здесь — https://github.com/yong-asial/rock-paper-scissors

Заключение

В этой статье мы узнали, как использовать Teachable Machine для обучения пользовательской модели машинного обучения классификации изображений, интеграции их с веб-приложениями с помощью веб-API UserMedia и с помощью мобильное приложение с помощью подключаемого модуля Cordova Canvas Camera. Это длинное руководство, поэтому, если вы обнаружите какую-либо ошибку, сообщите мне об этом в разделе комментариев или откройте для меня вопрос Github.

счастливое кодирование.