Работа с несколькими камерами

Подключение и инициализация видеопотоков

В ml5.js работа с видеопотоком осуществляется через объект p5.Video, который интегрируется с библиотекой p5.js. Для подключения нескольких камер необходимо создать отдельные объекты createCapture() для каждого устройства. По умолчанию createCapture() использует первую доступную камеру, но можно указать конкретное устройство через объект с параметром video: { deviceId: "ID_камеры" }.

Пример создания двух видеопотоков:

let video1, video2;

function setup() {
  createCanvas(640, 480);
  
  navigator.mediaDevices.enumerateDevices().then(devices => {
    const videoDevices = devices.filter(device => device.kind === 'videoinput');
    
    video1 = createCapture({ video: { deviceId: videoDevices[0].deviceId } });
    video1.size(320, 240);
    video1.hide();

    video2 = createCapture({ video: { deviceId: videoDevices[1].deviceId } });
    video2.size(320, 240);
    video2.hide();
  });
}

Ключевой момент: использование navigator.mediaDevices.enumerateDevices() позволяет динамически получать список доступных камер и управлять их подключением.

Интеграция с моделями ml5.js

После инициализации видеопотоков их можно подключать к различным моделям ml5.js, таким как poseNet, objectDetector или faceApi. Каждое видео может быть связано с отдельной моделью или с одним экземпляром модели, которая обрабатывает несколько потоков последовательно.

Пример интеграции с PoseNet:

let poseNet1, poseNet2, poses1 = [], poses2 = [];

function setup() {
  createCanvas(640, 480);
  
  // video1 и video2 создаются как показано выше
  poseNet1 = ml5.poseNet(video1, modelReady1);
  poseNet1.on('pose', results => poses1 = results);

  poseNet2 = ml5.poseNet(video2, modelReady2);
  poseNet2.on('pose', results => poses2 = results);
}

function modelReady1() {
  console.log('PoseNet для первой камеры загружен');
}

function modelReady2() {
  console.log('PoseNet для второй камеры загружен');
}

Важно: каждая модель работает асинхронно, поэтому события on('pose', ...) позволяют обрабатывать результаты по мере их поступления без блокировки основного потока.

Отображение нескольких видеопотоков

Для визуализации нескольких камер необходимо правильно размещать их на холсте с использованием функции image() p5.js. Можно комбинировать оба потока на одном холсте или создавать отдельные канвасы для каждого потока.

Пример совместного отображения двух видеопотоков:

function draw() {
  background(220);

  image(video1, 0, 0, 320, 240);
  drawPoses(poses1);

  image(video2, 320, 0, 320, 240);
  drawPoses(poses2);
}

function drawPoses(poses) {
  for (let i = 0; i < poses.length; i++) {
    let pose = poses[i].pose;
    for (let j = 0; j < pose.keypoints.length; j++) {
      let keypoint = pose.keypoints[j];
      fill(0, 255, 0);
      noStroke();
      ellipse(keypoint.position.x, keypoint.position.y, 10, 10);
    }
  }
}

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

Обработка нескольких камер в реальном времени

Для реального времени важно минимизировать задержку при обработке нескольких видеопотоков. Для этого рекомендуется:

  • Ограничивать разрешение видео для уменьшения нагрузки на процессор.
  • Асинхронно обрабатывать модели, не вызывая блокирующих операций в draw().
  • Использовать video.hide(), чтобы рендеринг происходил только через image(), что ускоряет отрисовку.

Пример оптимизации:

video1.size(160, 120);
video2.size(160, 120);

function draw() {
  background(220);

  if (poses1.length > 0) drawPosesScaled(video1, poses1, 0, 0, 320, 240);
  if (poses2.length > 0) drawPosesScaled(video2, poses2, 320, 0, 320, 240);
}

function drawPosesScaled(video, poses, xOffset, yOffset, w, h) {
  image(video, xOffset, yOffset, w, h);
  let scaleX = w / video.width;
  let scaleY = h / video.height;
  
  for (let i = 0; i < poses.length; i++) {
    let pose = poses[i].pose;
    for (let j = 0; j < pose.keypoints.length; j++) {
      let keypoint = pose.keypoints[j];
      fill(255, 0, 0);
      ellipse(keypoint.position.x * scaleX + xOffset, keypoint.position.y * scaleY + yOffset, 8, 8);
    }
  }
}

Примечание: масштабирование координат ключевых точек необходимо при изменении размера видео для корректного отображения на холсте.

Использование нескольких камер для обучения моделей

ml5.js позволяет обучать простые модели прямо в браузере с использованием видеопотока. Наличие нескольких камер предоставляет возможность собрать более разнообразные данные для обучения:

  • Можно использовать разные ракурсы объекта для улучшения точности модели.
  • Потоки могут служить как отдельные входные данные для классификации или детекции.
  • Все видеопотоки можно объединять в один массив данных для batch-обучения модели.

Пример использования нескольких видеопотоков для классификации:

let featureExtractor, classifier;

function setup() {
  createCanvas(640, 480);
  featureExtractor = ml5.featureExtractor('MobileNet');
  classifier = featureExtractor.classification(video1);

  // Добавление данных с второй камеры
  classifier.addImage(video2, 'label2');
}

Важный аспект: при работе с несколькими камерами важно поддерживать синхронизацию данных, чтобы метки соответствовали правильным кадрам.

Совместное использование нескольких моделей

В сценариях с несколькими камерами возможно использование нескольких моделей одновременно:

  • Разные модели для разных камер (например, PoseNet для одной, BodyPix для другой).
  • Один экземпляр модели для нескольких потоков с последовательной обработкой.

Для высокой производительности рекомендуется:

  • Ограничивать частоту обновления каждого потока с помощью frameRate().
  • Обрабатывать только изменившиеся кадры при необходимости.
  • Использовать async/await или callback-функции для последовательной обработки, чтобы избежать блокировки основного потока.