Добавление AR-фильтров в 100-минутный видеозвонок — часть 1

Как было бы здорово, если бы вы могли создать свое собственное приложение для видеозвонков с фильтрами, подобными Snapchat!

Этот материал был первоначально опубликован — ЗДЕСЬ

Икр! Вот что я подумал, когда наткнулся на Jeeliz. В прошлом я работал с библиотеками на основе tensorflow.js, но они, как правило, требовали много процессорных ресурсов для работы с живым видео. Jeeliz выглядела многообещающе, поскольку была разработана для этого случая использования. Поэтому я подумал, почему бы не опробовать его, добавив несколько 3d AR-фильтров к нашим видеозвонкам. Что ж! Именно это мы и собираемся сделать.

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

Мы будем использовать React и 100ms’ React SDK для видеозвонков в нашем приложении. 100ms, вкратце, создает ориентированные на разработчиков живые SDK, которые абстрагируются от низкоуровневых сложностей. Недавно была выпущена поддержка видеоплагинов, что облегчает эксперименты с AR-фильтрами после создания базового приложения. И вот я отправился в путешествие. В этом блоге я буду говорить в основном о деталях реализации, связанных с самими фильтрами, а не о создании приложения для видеозвонков с нуля. Вы можете ознакомиться с руководством по быстрому старту, чтобы получить краткий обзор SDK и его работы, или вы можете просто форкнуть его (это также первый шаг 😀) и следовать за моим исследованием.

Начните с 100ms React SDK и получайте 10 000 бесплатных минут каждый месяц

Я разделил блог на части, чтобы он не был перегружен. В этой части мы попытаемся понять интерфейс плагина, предоставляемый SDK, узнаем немного об элементах HTML Canvas и реализуем базовый фильтр. Более подробно об AR, WebGL и реализации плагина фильтра AR мы поговорим в следующих частях.

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


Выглядит круто? Вы можете посмотреть демо-версию готовой вещи здесь. Давайте начнем с кодовой части.

Форк быстрого запуска

Этот шаг можно пропустить, если вы интегрируете фильтры в существующее веб-приложение, уже использующее 100ms SDK. Если это не так, то давайте начнем с форка codesandbox, связанного в документе, в репозиторий GitHub. Сейчас я уже сделал это, поэтому форк моего репозитория на GitHub будет намного быстрее. Исходный код находится в ветке с именем original.

Вы также можете проверить эту ветку, чтобы следовать локально —

git clone -b original https://github.com/triptu/100ms-face-filters.git
Вход в полноэкранный режим Выйти из полноэкранного режима

Запустите приложение локально

Теперь мы можем клонировать репозиторий и запустить его локально. Не стесняйтесь обновить SDK до последних версий здесь, а затем запустить с помощью yarn install, а затем yarn start. Мы увидим вот такой экран, если все прошло нормально.

Если вам интересно, что это за токен аутентификации, мы можем представить его как идентификатор встречи, который говорит 100ms, в какую комнату вас поселить. Получение такого токена довольно просто (не требует ничего технического или кодового) и более подробно описано здесь. Получив токен, проверьте, все ли работает нормально. Вы можете попробовать присоединиться с нескольких вкладок или поделиться ссылкой с друзьями (после раскрытия с помощью ngrok конечно). Вы также можете присоединиться к той же комнате по ссылке, доступной на приборной панели (откуда был скопирован токен).

Фильтр градаций серого


Допустим, нам нужно преобразовать цветное изображение в градации серого, и мы задаемся вопросом, что для этого потребуется. Чтобы ответить на этот вопрос, давайте попробуем разбить изображение на части. Изображение — это матрица из множества пикселей, где один пиксель может быть описан с помощью трех чисел от 0 до 255 — значений интенсивности красного, зеленого и синего цветов. Для полутонового изображения каждый пиксель может быть описан только одним числом в диапазоне 0-255, где 0 — черный (наименьшая интенсивность), а 255 — белый (наибольшая интенсивность).
Если мы хотим преобразовать цветной пиксель с RGB-значениями в градации серого, нам понадобится некое отображение между ними. Довольно простым способом преобразования является усреднение трех интенсивностей.

intensity = (red + blue + green)/3
Войти в полноэкранный режим Выйти из полноэкранного режима

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

// Luma
intensity = red * 0.299 + green * 0.587 + blue * 0.114 
Вход в полноэкранный режим Выход из полноэкранного режима

Изучение документации по плагину

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

Суть его в том, что нам нужно написать класс, реализующий метод processVideoFrame(inputCanvas, outputCanvas), в котором нам передается изображение на входном холсте, и мы должны поместить результат на выходной холст. Это значительно облегчает нам работу, поскольку нам не нужно беспокоиться о видео, а только об одном изображении за раз. Итак, если мы сможем найти способ получать значения RGB с входного холста и помещать значения градаций серого на выходной холст, мы сможем реализовать рассмотренный алгоритм, и все будет в порядке.

Реализация плагина Grayscale

Ознакомьтесь с полным коммитом здесь.

Итак, как мы выяснили из документации, мы будем иметь дело с HTML Canvas. Теперь у canvas есть то, что называется контекстом, который раскрывает прямые методы как для получения значений RGB с холста (getImageData), так и для их применения (putImageData). С этой информацией мы можем приступить к написанию нашего плагина GrayScale Plugin. Я добавил дополнительные комментарии в код ниже. Обратите внимание, что некоторые другие методы также присутствуют, поскольку они требуются SDK.

class GrayscalePlugin {
   /**
   * @param input {HTMLCanvasElement}
   * @param output {HTMLCanvasElement}
   */
  processVideoFrame(input, output) {
    // we don't want to change the dimensions so set the same width, height
    const width = input.width;
    const height = input.height;
    output.width = width;
    output.height = height;
    const inputCtx = input.getContext("2d");
    const outputCtx = output.getContext("2d");
    const imgData = inputCtx.getImageData(0, 0, width, height);
    const pixels = imgData.data; 
    // pixels is an array of all the pixels with their RGBA values, the A stands for alpha
    // we will not actually be using alpha for this plugin, but we still need to skip it(hence the i+= 4)
    for (let i = 0; i < pixels.length; i += 4) {
      const red = pixels[i];
      const green = pixels[i + 1];
      const blue = pixels[i + 2];
      // the luma algorithm as we discussed above, floor because intensity is a number
      const lightness = Math.floor(red * 0.299 + green * 0.587 + blue * 0.114);
      // all of RGB is set to the calculated intensity value for grayscale
      pixels[i] = pixels[i + 1] = pixels[i + 2] = lightness;
    }
    // and finally now that we have the updated values for grayscale we put it on output
    outputCtx.putImageData(imgData, 0, 0);
  }

  getName() {
    return "grayscale-plugin";
  }

  isSupported() {
    // we're not doing anything complicated, it's supported on all browsers
    return true;
  }

  async init() {} // placeholder, nothing to init

  getPluginType() {
    return HMSVideoPluginType.TRANSFORM; // because we transform the image
  }

  stop() {} // placeholder, nothing to stop
}
Вход в полноэкранный режим Выход из полноэкранного режима

Добавление компонента кнопки для добавления плагина

Ознакомьтесь с полным коммитом здесь.

Теперь давайте напишем компонент кнопки переключения, который будет включать/выключать фильтр. Компонент будет принимать плагин и название кнопки для отображения.

// also intialise the grayscale plugin for use by the Button's caller
export const grayScalePlugin = new GrayscalePlugin();

export function PluginButton({ plugin, name }) {
  const isPluginAdded = false;
  const togglePluginState = async () => {};

  return (
    <button className="btn" onClick={togglePluginState}>
      {`${isPluginAdded ? "Remove" : "Add"} ${name}`}
    </button>
  );
}
Вход в полноэкранный режим Выйти из полноэкранного режима

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

<PluginButton plugin={grayScalePlugin} name={"Grayscale"} />
Вход в полноэкранный режим Выйти из полноэкранного режима

Однако щелчок по кнопке пока не работает, потому что мы не добавляем плагин в видеодорожку. Давайте посмотрим, как это сделать в следующем разделе.

Создание функциональной кнопки

Ознакомьтесь с полным коммитом здесь.

С помощью документации мы можем сделать наш компонент кнопки функциональным, используя крючки, предоставляемые SDK. Есть два хука из SDK, которые мы должны использовать для реализации нашей функции переключения —

import {
  selectIsLocalVideoPluginPresent,
  useHMSActions,
  useHMSStore,
} from "@100mslive/react-sdk";

export function PluginButton({ plugin, name }) {
  const isPluginAdded = useHMSStore(
    selectIsLocalVideoPluginPresent(plugin.getName())
  );
  const hmsActions = useHMSActions();

  const togglePluginState = async () => {
    if (!isPluginAdded) {
      await hmsActions.addPluginToVideoTrack(plugin);
    } else {
      await hmsActions.removePluginFromVideoTrack(plugin);
    }
  };

  return (
    <button className="btn" onClick={togglePluginState}>
      {`${isPluginAdded ? "Remove" : "Add"} ${name}`}
    </button>
  );
}
Войти в полноэкранный режим Выход из полноэкранного режима

Вуаля!

Вот и все, теперь наша кнопка функционирует. Все работает и выглядит потрясающе. Итак, мы смогли с нуля написать фильтр серого цвета, который преображает наше видео для всех присутствующих в комнате.

Отсюда вы можете продолжить работу над другими фильтрами (например, сепия, насыщенность, контраст) или поэкспериментировать с другими алгоритмами обработки изображений, чтобы изучить возможности. Мы поговорим о создании AR-фильтра в следующих частях, которые будут основываться на основах, изученных в этой части.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *