Объединение WebRTC и TensorFlow.js для реализации «обнаружения касания лица»

TensorFlow RTC

Китайская версия этой статьи взята изКитайская сеть WebRTCСтартер. авторChad Hart.

Не трогайте лицо руками! В целях предотвращения распространения новой эпидемии коронавируса медицинские учреждения советуют не прикасаться к лицу немытыми руками. Но это трудно сделать, если вы часами сидите перед компьютером, не двигаясь. Я не могу не задаться вопросом, можно ли это решить с помощью браузера?

С этой целью мы провели множество экспериментов с компьютерным зрением и WebRTC. Я давно планировал поэкспериментировать с запуском компьютерного зрения в локальном браузере с помощью TensorFlow.js, и теперь мне кажется, что это хорошая возможность. Быстрым поиском я обнаружил, что кто-то думал об этом 2 недели назад. Но модель, используемая этим сайтом, требует некоторой подготовки пользователя, чтобы понять ее, что интересно, но также может быть рискованно. И это не программное обеспечение с открытым исходным кодом, которое могут расширять другие. Итак, на выходных я сделал некоторую обработку с изоляцией кода, чтобы посмотреть, что получилось.

ты сможешьfacetouchmonitor.comПосмотреть программу на . Также читайте ниже, чтобы увидеть, как это работает. Все коды можно найти вВзлом GitHub.com/Web RTC…найти на. Я поделюсь некоторыми моментами и альтернативами в этой статье.

TensorFlow.js

TensorFlow.js — это версия Tensorflow для JavaScript. Возможно, вы слышали о Tensorflow, потому что это самый популярный инструмент машинного обучения в мире.

TensorFlow.js использует все преимущества машинного обучения и будет применяться к node.js и браузерам, поддерживающим JavaScript. Более того, TensorFlow.js включает готовые модели для компьютерного зрения из нескольких основных библиотек моделей TensorFlow. В этой статье будут представлены две из этих моделей.

TensorFlow.js + WebRTC

Tensorflow очень дружелюбен к WebRTC. Его базовая модель подходит для статических изображений, но TensorFlow.js включает вспомогательные функции для автоматического извлечения изображений из видеопотоков. Некоторые функции, такие как tf.data.webcam(webcamElement), даже вызывают для вас getUserMedia.

Перенос обучения с классификатором KNN

donottouchyourface.comОн был выпущен несколько недель назад и также получил несколько положительных отзывов. Для первого применения необходимо ознакомиться с контурами лица, около 5 секунд. Затем нужно коснуться лица и повторить процесс несколько раз. Программа воспроизводит аудиофайл и отправляет уведомления браузеру. Если после этого ваша позиция будет такой же, как и во время процесса идентификации, программа запустится успешно. Но если вы измените положение во время распознавания или не предоставите достаточное количество распознаваемых элементов, программа может работать с ошибками. Пример выглядит следующим образом:

Вместе с бесчисленными 2IX28 небольшими 10GI обучаемыми 34 GV-WP engine.net DNA-ratings.com/I’m Боюсь-Контент/…

Принцип работы

Даже если результаты не такие точные, все равно здорово делать это в браузере. Так как же это делают разработчики? Из своего JavaScript они используют библиотеки obilenet и knnClassifier:

import * as mobilenet from '@tensorflow-models/mobilenet'; 
import * as knnClassifier from '@tensorflow-models/knn-classifier';

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

На самом деле, Google создала кодовую лабораторию классификатора изображений TensorFlow.js, которая делает то же самое. Я настроил кодовую лабораторию с двумя строками кода, чтобы воспроизвести то, что делает donottoouchyourface.com в JSFiddle ниже:

const webcamElement = document.getElementById('webcam');
const classifier = knnClassifier.create();
let net;
async function app() {
  console.log('Loading mobilenet..');
  // Load the model.
  net = await mobilenet.load();
  console.log('Successfully loaded model');
  // Create an object from Tensorflow.js data API which could capture image
  // from the web camera as Tensor.
  const webcam = await tf.data.webcam(webcamElement);
  // Reads an image from the webcam and associates it with a specific class
  // index.
  const addExample = async classId => {
    for (let x = 50; x > 0; x--) {
      // Capture an image from the web camera.
      const img = await webcam.capture();
      // Get the intermediate activation of MobileNet 'conv_preds' and pass that
      // to the KNN classifier.
      const activation = net.infer(img, 'conv_preds');
      // Pass the intermediate activation to the classifier.
      classifier.addExample(activation, classId);
      // Dispose the tensor to release the memory.
      img.dispose();
      // Add some time between images so there is more variance
      setTimeout(() => {
        console.log("Added image")
      }, 100)
    }
  };
  // When clicking a button, add an example for that class.
  document.getElementById('class-a').addEventListener('click', () => addExample(0));
  document.getElementById('class-b').addEventListener('click', () => addExample(1));
  while (true) {
    if (classifier.getNumClasses() > 0) {
      const img = await webcam.capture();
      // Get the activation from mobilenet from the webcam.
      const activation = net.infer(img, 'conv_preds');
      // Get the most likely class and confidence from the classifier module.
      const result = await classifier.predictClass(activation);
      const classes = ['notouch', 'touch'];
      document.getElementById('console').innerText = `
        prediction: ${classes[result.label]}\n
        probability: ${result.confidences[result.label]}
      `;
      // Dispose the tensor to release the memory.
      img.dispose();
    }
    await tf.nextFrame();
  }
}
app();
<html>
  <head>
    <!-- Load the latest version of TensorFlow.js -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/knn-classifier"></script>
  </head>
  <body>
    <div id="console">Remember to allow your camera first</div>
    <!-- Add an image that we will use to test -->
    <video autoplay playsinline muted id="webcam" width="224" height="224"></video>
    <button id="class-a">No touch</button>
    <button id="class-b">Touch Face</button>
    <span id="message"></span>
    <!-- Load index.js after the content of the page -->
    <script src="index.js"></script>
  </body>
</html>

Метод БодиПикс

Как вы можете видеть на изображении выше, нам трудно идентифицировать модель за секунды. В идеале мы могли бы попросить сотни людей поделиться фотографиями, на которых они касаются и не касаются своего лица в разных средах и местах. Затем мы можем построить новую модель на основе этого. Я не знаю, есть ли такой размеченный набор данных, но у TensorFlow.js есть похожий инструмент —BodyPix. BodyPix может идентифицировать людей и сегментировать части их тела (руки, ноги, лицо и т. д.). Новая версия 2.0 BodyPix может даже определять позы, как и то, что предлагает PoseNet.

Гипотеза: мы можем использовать BodyPix для обнаружения рук и лиц. Когда рука перекрывает лицо, мы можем определить, что есть контакт лица.

Достаточно ли точен BodyPix?

Первое, на что стоит обратить внимание, это качество модели. Если он не может надежно обнаружить руки и лица, он будет объявлен устаревшим. Репозиторий включает демо-страницу, так что легко проверить, работает ли она:

2.0 отлично справляется со своей задачей, особенно по сравнению с моделью 1.0. Это впечатлило меня.

Использование API БодиПикс

API имеет следующие параметры:

  • PersonSegmentation: отделяет людей от фона изображения, все в озере носят маски,

  • segmentMultiPerson: отделить человека от фона экрана и дать каждому человеку маску;

  • segmentPersonParts: сегментация одной части тела одного или нескольких людей в единый набор данных.

  • segmentMultiPersonParts: задает части тела одного или нескольких людей как отдельный набор данных.

Мне нужна функция "personParts". Но файл readme.md предупреждает, что segmentMultiPersonParts работает медленнее и его структура данных сложнее. Поэтому я выбрал сегментPersonParts. Если вы хотите увидеть все параметры в действии, перейдите к документации в репозитории BodyPix.

При выполнении операции возвращается объект, подобный следующему:

allPoses: Array(1)
  0:
   keypoints: Array(17)
   0: {score: 0.9987769722938538, part: "nose", position: {…}}
   1: {score: 0.9987848401069641, part: "leftEye", position: {…}}
   2: {score: 0.9993035793304443, part: "rightEye", position: {…}}
   3: {score: 0.4915933609008789, part: "leftEar", position: {…}}
   4: {score: 0.9960852861404419, part: "rightEar", position: {…}}
   5: {score: 0.7297815680503845, part: "leftShoulder", position: {…}}
   6: {score: 0.8029483556747437, part: "rightShoulder", position: {…}}
   7: {score: 0.010065940208733082, part: "leftElbow", position: {…}}
   8: {score: 0.01781448908150196, part: "rightElbow", position: {…}}
   9: {score: 0.0034013951662927866, part: "leftWrist", position: {…}}
   10: {score: 0.005708293989300728, part: "rightWrist", position: {…}}
...
score: 0.3586419759016922
length: 1
data: Int32Array(307200) [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, …]
height: 480
width: 640

Основы включают в себя размеры изображения, оценку каждой из 24 частей тела с определенной точкой позы и массив целых чисел, соответствующих каждому пикселю или -1 для части тела. Точки положения — это, по сути, менее точная версия того, что доступно в другой библиотеке TensorFlow, PoseNet.

Алгоритмы повсюду

Хотя это заняло у меня некоторое время, я пришел к следующему алгоритму:

  1. Если существует высокая вероятность того, что на изображении нет носа и хотя бы одного глаза, игнорируйте изображение;
  2. Проверить, была ли рука когда-либо на части лица в предыдущем кадре — каждое перекрытие засчитывается в 1 балл;
  3. Проверить, касается ли рука лица – каждое касание оценивается в 1 балл;
  4. Когда сумма вышеуказанных элементов превышает пороговое значение, срабатывает предупреждение.

мониторинг в реальном времени

Очень полезно иметь возможность видеть работу классификатора в режиме реального времени. BodyPix включает в себя несколько API-интерфейсов с поддержкой моделей для рисования точек поз и данных сегментов. Если вы хотите увидеть все части сегмента, просто передайте ему объект сегмента, возвращаемый segmentPersonParts:

const coloredPartImage = bodyPix.toColoredPartMask(targetSegmentation);
const opacity = 0.7;
const maskBlurAmount = 0;
 
bodyPix.drawMask(
   drawCanvas, sourceVideo, coloredPartImage, opacity, maskBlurAmount,
   flipHorizontal);

Я хотел сосредоточиться на руках и лицах, поэтому я изменил данные сегментации, чтобы включить только те части, которые указаны выше в targetSegmentation.

Кроме того, я скопировал код для оценки точек после построения из массива ключевых точек.

оптимизация

Заставить модель работать на различных устройствах — самая трудоемкая часть работы. Мы еще не совсем решили эту проблему. Но до тех пор, пока вы знаете, как правильно его открыть, вы сможете запустить его идеально и гладко.

Чувствительность и ложные срабатывания

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

СегментПерсон используетобъект конфигурации. Есть несколько параметров, влияющих на чувствительность. Я использую два параметра: segmentationThreshold и scoreThreshold.

Но он не очень хорошо распознает многопользовательские сцены. В этом случае более целесообразно использовать segmentMultiPersonParts. В параметрах конфигурации я установил для maxDetections значение 1. Это сообщает оценщику позы, сколько людей нужно искать (но не влияет на обнаружение частей).

Наконец, с этой моделью вы можете сделать несколько вариантов, влияющих на производительность, о которых я расскажу в следующем разделе.

использование процессора

BodyPix имеет высокую загрузку процессора. У меня на MacBook установлен 6-ядерный процессор Intel Core i9 с тактовой частотой 2,9 ГГц, поэтому для меня это не имеет большого значения, но проблема более заметна на более медленных машинах. TensorFlow.js также максимально использует графический процессор, поэтому, если у вас нет подходящего графического процессора, он будет работать медленнее. Это видно по частоте кадров в секунду (FPS).

Настройка параметров модели

Из иллюстрации мы знаем: у BodyPix есть много параметров модели, которые жертвуют точностью ради скорости. Чтобы помочь проверить эти параметры, я добавил 4 переключателя, которые будут загружать модель со следующими настройками.

Более точные настройки определенно уменьшат количество ложных срабатываний.

Что мы обнаружили: обычная версия потребляла все ядро ​​​​виртуального процессора на моем новом MacBook, работая со скоростью около 15 кадров в секунду.

Не только БодиПикс. То же самое верно и для примера классификатора MobileNet + KNN, с которым я работал ранее, видео меньше, но не так хорошо.

У меня есть Windows Surface Go с более медленным двухъядерным процессором Pentium 1,6 ГГц, работающим на обычной модели со скоростью 5 кадров в секунду. Загрузка модели на этот компьютер занимает больше времени.

Изменение настроек улучшит CPU и CPS, но только на несколько процентных пунктов по CPU и 1 или 2 FPS. Это намного ниже моих ожиданий.

Можно ли улучшить

TensorFlow.js на самом деле имеетWeb Assemblyбиблиотека, которая работает значительно быстрее. Я надеюсь, что это поможет решить проблему с потреблением ресурсов ЦП, но я столкнулся с двумя проблемами:

Серверная часть wasm подходит только для BodyPix 1.0, но точность обнаружения 1.0 намного меньше, чем у 2.0;

Я вообще не могу загрузить его с помощью BodyPix.

Я могу исправить вторую проблему, но это не имеет особого смысла, если она все еще работает не очень хорошо. Я полагаю, что версия tfjs-backend-wasm, поддерживающая BodyPix 2.0, будет официально запущена в ближайшем будущем.

проблема с дросселированием

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

Поддержка браузера

Без сомнения, запуск модели в Chrome работает лучше всего, потому что TensorFlow.js — это проект Google. Также отлично работает в Firefox и Edge (Edgium). Но мне не удалось загрузить его в Safari.

эксперимент

Весь код доступен на GitHub по адресу https://github.com/webrtchacks/facetouchmonitor.

В экспериментах за последние несколько дней модель работала хорошо.

Не трогай свое лицо!

На самом деле я мог бы сделать больше настроек, но в большинстве тестов, которые я и другие проводили, модель работала просто отлично. Мы также призываем вас вносить дополнительные изменения и предложения по оптимизации.

Я надеюсь, что это поможет вам оставаться здоровым, призывая вас не касаться лица слишком часто. Если вы действительно хотите прикоснуться к своему лицу, подумайте о том, чтобы надеть шлем!