- PVSM.RU - https://www.pvsm.ru -

Ардуино и видео? Легко

Ардуино и видео? Легко - 1
Известно, что слабый микроконтроллер ардуино не способен пропустить через себя видеопоток.
А если подойти к этой задаче с другого бока?

… и растянуть процесс съёмки во времени.

Хе, многие уже и сами догадались, эта техника съёмки называется timelapse (замедленная покадровая съёмка). То есть это никакая не видеосъёмка, а фотосъёмка, в результате которой создаётся видео.

Признаюсь, изначально я не планировал делать замедленную съёмку. Просто хотел сделать для своей ардуины регистрацию событий, в том числе и с фотографиями. Ну а потом пошло-поехало. Если мой читатель не занимается разработкой, то может просто посмотреть полученный результат [1] (зайти из под Chrome).

Из чего состоит моя система:

  • плата Arduino Mega;
  • модуль JPEG камеры;
  • служебная программа и база данных MongoDB;
  • WEB сервер для размещения HTML файлов;

Когда ардуина посылает свои данные на сервер, то к каждому параметру прикрепляется своя метка времени.
Отправили параметр — добавилась запись в хранилище, ещё два раза отправили — сохранились ещё две записи.

Вся работа с хранилищем ведётся через служебную программу (далее посредник), которая запускается на стационарном компьютере. При этом, сам WEB сервер отдаёт только статичный контент. То есть, все клиенты ведут информационный обмен через служебную программу посредника, аналогично популярному протоколу MQTT. Основное отличие от MQTT будет в том, что этот посредник напрямую работает с хранилищем данных, обеспечивая работу с историческими данными. Этот факт упрощает схему взаимодействия, и не требует дополнительного сетевого трафика для сохранение данных.

Для удобства разработки своих веб приложений, я создал javascript библиотеку с таким API:

Так создаётся клиент для работы с сетевым хранилищем:

var client = new MgtClient("localhost", "login", "password", onDebugLog);

Аргументы функции:

  1. сетевой адрес, на котором запущена программа посредника, можно просто указать IP, например так — «127.0.0.1»;
  2. логин пользователя;
  3. пароль пользователя;
  4. функция обратного вызова, для отладочных строковых сообщений;

Функция обратного вызова для отладочных сообщений может выглядеть так:

function onDebugLog(aStr) {
  // метка времени и сообщение будут выводиться в отладочную консоль браузера
  console.log((new Date()).getTimeString() + ' ' + aStr + 'n'); 
}

Пока не сложно? Дальше будет потрудней.

Структура запроса к хранилищу:

var request = {
  name: "параметр 1", // имя запрашиваемого параметра
  placeId: 1, // идентификатор вашего объекта                      
  beginTime: 1458108472000, // начальное время в миллисекундах от 1 Января 1970 года
  endTime: 1458194872000, // конечное время в миллисекундах от 1 Января 1970 года (не включительно)
  limit: 10000 // максимальное количество записей, не обязательный параметр (по умолчанию без ограничения)
};

Ещё не запутались?

Тогда вот структура ответа на запрос:

var result = {
  times: [], // массив временных меток полученных записей (время в миллисекундах от 1 Января 1970 года)
  values: [], // массив значений полученных записей
  position: 20, // порядковый номер указывающий на новые данные в массиве (данные могут поступать порциями)
  status: "progress", // состояние запущенного процесса ("progress", "abort", "done", "fail")
  progress: 91 // индикатор выполнения (в процентах)   
};

Ага, уже сложней?

Состояние поля «status»:

  • «done» — получили всё что запросили (либо получены данные за весь временной диапазон, либо сработало ограничение на количество записей);
  • «progress» — указывает, что это не последняя порция данных, процесс скачивания ещё не окончен;
  • «abort» — прервалось скачивания данных (сработало ограничение на общий объём выкачиваемых данных), можете тут же сформировать новый запрос на получение недостающих данных;
  • «fail» — что то пошло не так (может тока нет в розетке?)

Вы думаете это всё? К сожалению, нет.

Запрашиваемые параметры могут быть разного типа.

  • Если параметр числовой, то в массиве значений окажутся числа.
  • Если строковый, то в массиве значений будут строки.
  • Если булев, то в массиве значений будут «true» или «false».
  • Если бинарный (например JPEG картинка), то в массиве значений будут массивы байт.
  • Если это событие, то вернуться массивы, сформированные особым образом.

Пример одной записи события:

var event = [
  "сработал датчик движения", // заголовок произошедшего события
  "термометр", // имя параметра 1
  27.5, // значение параметра 1
  "светодиод", // имя параметра 2
  true, // значение параметра 2
  ...
  "фото", // имя последнего параметра
  1458108472000 // сюрприз!!! это всего лишь временная метка сохранённого изображения,
                // нужно сформировать дополнительный запрос для выкачивания этого изображения.
];

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

Уфф, самое сложное уже позади.

А так выглядит сама отправка запроса:

// aRequest - структура запроса
// onReadArchive - обратная функция для получения ответа/ответов
client.readArchive(aRequest, onReadArchive);                                                           

Обратная функция для получения ответов:

// aResult - структура ответа
onReadArchive(aResult) {
  // TODO  вставьте свой код обработки пришедших данных

  // если вернём "false", то продолжим принимать ответы (если процесс приёма не завершён)
  // если вернём "true", то обрываем получение остальных ответов (если процесс приёма не завершён) 
  return false; 
}

Наконец мы подошли к самому монтажу видео.

Для создания видео я использовал javascript библиотеку Whammy, здесь подробнее. [2]

Функция, которая создаёт видео:


<script src="whammy.js"></script>
<canvas id="canvas" style="display:none"></canvas>
<video id="player" controls autoplay loop></video>

function createVideo() {
  var canvas = document.getElementById("canvas");
  var context = canvas.getContext("2d");
  canvas.width = '640'; // это ширина фоток в пикселях
  canvas.height = '480'; // это высота фоток в пикселях
  var framerate = 10; // устанавливаем количество кадров в секунду
  var quality = 0.8; //  устанавливаем качество видео

  var video = new Whammy.Video(framerate, quality); // объект для создания видео в формате WebM
 
  for (var i = 0; i < images.length; i++) { // пройдёмся по всем изображениям
    var image = images[i];
    context.globalAlpha = 1;
    context.drawImage(image, 0, 0, 640, 480); // сначала размещаем изображение на канву
    video.add(context); // добавляем новый кадр
  }

  var output = video.compile(); // создаём видео из кадров
  var url = URL.createObjectURL(output); // конвертируем в нужный формат

  document.getElementById('player').src = url; // подсовываем результат нашему проигрывателю
}

К сожалению, создать видео можно не во всех браузерах. Например, мой любимый Firefox не умеет преобразовывать изображения в формат WebP, на основе которого и происходит конвертация в видео. И хотя я нашёл javascript библиотеку для такого преобразования, но конвертировала она так медленно (а кадров было так много), что я отказался от её применения. Впрочем, во всех браузерах с «хромовским» движком эта штука работать будет.

Здесь вы можете посмотреть что у меня получилось. [1]

Не имея цельной документации, могу предложить мои предыдущие статьи.
Статья 1 [3]
Статья 2 [4]

Ну вот и всё, и сказать то мне больше нечего.

Автор: valeraba

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/umny-j-dom/115419

Ссылки в тексте:

[1] полученный результат: http://samde.ru/ru/history.html

[2] здесь подробнее.: http://techslides.com/convert-images-to-video-with-javascript

[3] Статья 1: https://geektimes.ru/post/255018/

[4] Статья 2: https://geektimes.ru/post/255412/

[5] Источник: https://geektimes.ru/post/272694/