Интеграция с WebRTC и MediaStream для инференса на видеопотоке

ONNX Runtime Web (ORT Web) предоставляет возможность выполнять инференс моделей машинного обучения прямо в браузере на клиентской стороне. Для работы с видеопотоком используется объект MediaStream, который может быть получен с помощью API getUserMedia или через подключение к WebRTC-потоку. Важным аспектом является правильное преобразование кадров видеопотока в формат, совместимый с моделью ONNX, а также организация эффективного цикла инференса, чтобы не перегружать поток и не снижать производительность интерфейса.

const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const videoElement = document.createElement('video');
videoElement.srcObject = stream;
videoElement.play();

Подготовка модели и среды исполнения

Для работы с ONNX Runtime Web необходимо загрузить модель и создать сессию исполнения. ORT Web поддерживает несколько движков исполнения: WebAssembly (WASM) и WebGL, где WebGL позволяет использовать ускорение на GPU для повышения производительности инференса.

import * as ort from 'onnxruntime-web';

const session = await ort.InferenceSession.create('model.onnx', {
  executionProviders: ['webgl'], // использование GPU через WebGL
  graphOptimizationLevel: 'all'  // включение оптимизации графа модели
});

Ключевой момент: выбор executionProviders напрямую влияет на скорость инференса. Для высокочастотного видеопотока рекомендуется использовать webgl.

Захват и преобразование кадров видеопотока

Кадры видеопотока необходимо преобразовать в тензоры, поддерживаемые ONNX. Наиболее часто модели ожидают данные в виде массивов с плавающей точкой (Float32Array) и с нормализацией значений пикселей.

function videoFrameToTensor(video, width, height) {
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(video, 0, 0, width, height);
  const imageData = ctx.getImageData(0, 0, width, height);
  const data = imageData.data;

  const tensorData = new Float32Array(width * height * 3);
  for (let i = 0; i < width * height; i++) {
    tensorData[i * 3 + 0] = data[i * 4 + 0] / 255.0; // R
    tensorData[i * 3 + 1] = data[i * 4 + 1] / 255.0; // G
    tensorData[i * 3 + 2] = data[i * 4 + 2] / 255.0; // B
  }

  return new ort.Tensor('float32', tensorData, [1, 3, height, width]);
}

Особенности:

  • Использование canvas позволяет извлечь текущий кадр из видео без прямого доступа к пикселям видеоэлемента.
  • Формат [1, 3, height, width] является стандартом для многих моделей компьютерного зрения.

Организация цикла инференса на видеопотоке

Для поддержания плавного воспроизведения видео и высокой частоты инференса необходимо использовать requestAnimationFrame или таймер с ограниченной частотой. Прямой вызов инференса на каждом кадре может вызвать задержки и «зависания» интерфейса.

async function inferLoop() {
  const inputTensor = videoFrameToTensor(videoElement, 224, 224);
  const feeds = { input: inputTensor };

  const results = await session.run(feeds);
  const output = results.output.data;

  // Обработка результатов (например, детекция объектов)
  processOutput(output);

  requestAnimationFrame(inferLoop);
}

videoElement.onloadedd ata = () => {
  inferLoop();
};

Рекомендации по производительности:

  • Масштабировать кадры до размера, соответствующего модели, чтобы минимизировать нагрузку на CPU/GPU.
  • Для WebGL рекомендуется использовать texture-оптимизированные тензоры при больших потоках данных.
  • Возможна обработка кадров через Web Worker для разгрузки основного потока интерфейса.

Интеграция с WebRTC

При работе с удалёнными видеопотоками через WebRTC используется объект MediaStream аналогично локальной камере. Разница заключается в том, что необходимо управлять подключением к удалённой сессии и возможной динамической сменой потоков.

const pc = new RTCPeerConnection();

pc.ontr ack = (event) => {
  const remoteVideo = document.createElement('video');
  remoteVideo.srcObject = event.streams[0];
  remoteVideo.play();

  remoteVideo.onloadedd ata = () => {
    inferLoopRemote(remoteVideo);
  };
};

async function inferLoopRemote(video) {
  const inputTensor = videoFrameToTensor(video, 224, 224);
  const feeds = { input: inputTensor };

  const results = await session.run(feeds);
  handleRemoteOutput(results.output.data);

  requestAnimationFrame(() => inferLoopRemote(video));
}

Особенности WebRTC-интеграции:

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

Оптимизация памяти и задержек

  • Переиспользование тензоров вместо их постоянного создания снижает нагрузку на сборщик мусора.
  • Включение графовой оптимизации (graphOptimizationLevel) сокращает время выполнения инференса.
  • Для WebGL можно использовать OrtWebGLBuffer для хранения текстур и сокращения копирования данных между CPU и GPU.

Хотя основной цикл инференса выглядит просто, производительность при работе с реальным видеопотоком зависит от грамотного управления размером кадров, частотой инференса и оптимизацией сессии ONNX Runtime Web. Это обеспечивает плавный пользовательский опыт даже при сложных моделях глубокого обучения.