Захват изображения с камеры в браузере с помощью getUserMedia (с зеркальным отображением)

Друзья! Близятся те времена, когда почти все, для чего нужен был флеш в браузере, можно будет делать и без него. Не знаю как у вас, а у меня это вызывает кучу положительных эмоций. Одним из шагов на пути вытеснения flash становится реализация в браузерах getUserMedia (Stream) javascript API. На данный момент Stream API для видеопотока реализовано в последних десктопных версиях Chrome и Opera. Firefox на подходе. Аудиопоток “coming soon”. Даже не знаю, ждать ли чего-то от IE .. по идее, он скорее умрет (..а он умрет), чем начнет догонять всех остальных.

Подробнее о поддержке в браузерах можно посмотреть тут – /caniuse.com/stream

Давайте взглянем на пример, а потом посмотрим, как это работает:

getUserMedia

Давайте разберемся, как это работает.

Для начала нам понадобятся такие элементы, как:

  1. video, в котором мы будем воспроизводить поточное видео с камеры пользователя
  2. canvas, в который мы будем помещать кадры для сохранения
  3. кнопка для захвата изображения
  4. подсказка для юзера, который не понял, что вообще от него хотят.

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

JS

navigator.getUserMedia(
  {video:true}, // тип запрашиваемого стрима (может быть audio) 
  function(stream) {/*callback в случае удачи*/},
  function(){/*callback в случае отказа*/})

Как видно, в случае удачи в callback вернется объект stream, на основе которого можно получить url видеопотока. Сделать это можно с помощью window.URL.createObjectURL(stream), которая может быть вам знакома, если вы когда-либо использовали js file API.

JS

var url = window.URL.createObjectURL(stream);

Далее:

  • передаем этот url объекту video
  • при нажатии на кнопку захватываем текущий кадр video в canvas
  • забираем data:url получившегося изображения из canvas
  • и все, готово! Можно делать с ним все, что угодно: отправить на сервер, отфильтровать, передать другу через сокет и тп. Подробнее про base64 и data:url формат можно почитать тут.

Давайте рассмотрим код примера в начале статьи для наглядности

Конечно, лучше и красивее создать все элементы (canvas, video, ..) динамически, но для наглядности и понимания давайте изначально расположим их статически на странице:

HTML

<div id="allow">▲ ▲ ▲ Разрешите использовать камеру ▲ ▲ ▲ <br/> ( Сверху текущей страницы )</div>

<div class="item">
  <span> video </span>
  <video id="video" width="320" height="240" autoplay="autoplay" ></video>
</div>
<div class="item">
  <span> canvas </span>
  <canvas id="canvas" width="320" height="240" ></canvas>
</div>

<input id="button" type="button" value="Жми!" />

JS

<script>
  window.onload = function () {
    var canvas = document.getElementById('canvas');
    var video = document.getElementById('video');
    var button = document.getElementById('button');
    var allow = document.getElementById('allow');
    var context = canvas.getContext('2d');
    var videoStreamUrl = false;

    // функция которая будет выполнена при нажатии на кнопку захвата кадра
    var captureMe = function () {
      if (!videoStreamUrl) alert('То-ли вы не нажали "разрешить" в верху окна, то-ли что-то не так с вашим видео стримом')
      // переворачиваем canvas зеркально по горизонтали (см. описание внизу статьи)
      context.translate(canvas.width, 0);
      context.scale(-1, 1);
      // отрисовываем на канвасе текущий кадр видео
      context.drawImage(video, 0, 0, video.width, video.height);
      // получаем data: url изображения c canvas
      var base64dataUrl = canvas.toDataURL('image/png');
      context.setTransform(1, 0, 0, 1, 0, 0); // убираем все кастомные трансформации canvas
      // на этом этапе можно спокойно отправить  base64dataUrl на сервер и сохранить его там как файл (ну или типа того) 
      // но мы добавим эти тестовые снимки в наш пример:
      var img = new Image();
      img.src = base64dataUrl;
      window.document.body.appendChild(img);
    }

    button.addEventListener('click', captureMe);

    // navigator.getUserMedia  и   window.URL.createObjectURL (смутные времена браузерных противоречий 2012)
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    window.URL.createObjectURL = window.URL.createObjectURL || window.URL.webkitCreateObjectURL || window.URL.mozCreateObjectURL || window.URL.msCreateObjectURL;

    // запрашиваем разрешение на доступ к поточному видео камеры
    navigator.getUserMedia({video: true}, function (stream) {
      // разрешение от пользователя получено
      // скрываем подсказку
      allow.style.display = "none";
      // получаем url поточного видео
      videoStreamUrl = window.URL.createObjectURL(stream);
      // устанавливаем как источник для video 
      video.src = videoStreamUrl;
    }, function () {
      console.log('что-то не так с видеостримом или пользователь запретил его использовать :P');
    });
  };
</script>

CSS

video{
          transform: scaleX(-1);
       -o-transform: scaleX(-1);
      -ms-transform: scaleX(-1);
     -moz-transform: scaleX(-1);
  -webkit-transform: scaleX(-1);
}

Зеркальное отображение при съемке

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

Как мы этого достигли:

Во-первых, мы сделали css transform для video

CSS

video{
          transform: scaleX(-1);
       -o-transform: scaleX(-1);
      -ms-transform: scaleX(-1);
     -moz-transform: scaleX(-1);
  -webkit-transform: scaleX(-1);
}

Во-вторых, повернули изображение на canvas таким же образом

JS

// переворачиваем canvas зеркально по горизонтали
context.translate(canvas.width, 0);
context.scale(-1, 1);

Если вам не нужно зеркальное отображение, просто удалите эту часть js и css

Удачи! Жду вопросов и поправок в комментариях.

14 комментариев на “Захват изображения с камеры в браузере с помощью getUserMedia (с зеркальным отображением)
  1. nick пишет:

    как раз вчера клиент просил такую фичу и я думал, что придется оборачивать приложение в phonegap для снабжения его большей свободой :( – А тут очень кстати твой пост! Супер!

  2. Pavel пишет:

    Мы сейчас на проекте обсуждаем альтернативы нативному iOS приложению, которое нам
    пока не удалось сделать достаточно стабильным. Т.к. есть разработчики которые
    делают веб-приложение, то рассматриваем альтернативы типа phonegap или appcelerator.

    Но ни у кого нет опыта работы с ними. Есть понимание, что простые вещи там можно
    сделать, а вот запись видео/фото/аудио, отправка на сервер да так, чтобы это
    работало стабильно… мы не знаем.

    Может есть опыт и знание этих платформ и можете подсказать, возможно ли там такое
    реализовать?

    • admin пишет:

      Так получилось, что последнее время мы делаем мини приложения для http://urturn.com у которого есть phonegap based ios app. Мы используем нативную objective C часть для захвата с камеры, а в webview (web часть) только для интерфейсов и всей пост обработки (canvas, svg, разные стикеры, эффекты и тп). Сразу хочу сказать, работать в ios uiwebview с phonegap – это кошмар. Очень много ограничений, нужно следить за памятью, дебажить очень сложно + сотни непонятных багов сходных по уровню магии с ie6. Если Вы хотите сделать приложеие с хорошим быстрым юзер-интерфейсом, обработкой видео или аудио или даже просто фото и все это для IOS.. лучше сделать его нативым (( Я думаю что apple еще долго будет искуственно тормозить развитие web-based приложений, и это связано в основном с политикой компании. Что касается android, firefoxOS, blackberryOS, TizenOS.. то через несколько лет, все перейдут (если уже не перешли) к web-based приложениям. Но это уже совсем другая история :)

  3. katja пишет:

    ковыряюсь с программаой.
    на ноуте все работает, на смартфоне – нет.
    как разрешить на смартфоне использование камеры? у меня самсунг GT-S6810 с андроидом 4.1.2
    как браузеры стоят хром и мозила 5.0
    эта страничка открвается, но нету диалога(или скорее я не могу его найти) о том, что есть запрос на использование камеры.
    подскажите, где почитать
    спасибо заранее

  4. Алексей пишет:

    Немного не по теме, но близко. Нужен захват “видео” из окна браузера. Пользователь работает с корпоративным сайтом и для саппорта очень бы помогло увидеть то, что видит пользователь. Может быть подскажите в какую сторону копать и возможно ли вообще такое?

  5. Сергей пишет:

    Пробую скопировать код, всё отрабатывает, но при отправке на сервер и base64_decode получаю чёрную картинку… Что может быть не так?

    • admin пишет:

      перед отправкой на сервер, попробуйте вставить base64 url в img.

      <img src="ваш-base64-тут"/>
      

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

  6. Вадим пишет:

    Замечательно! Как раз искал варианты для вызова камеры со смартфона из HTML+JS

  7. Вадим пишет:

    Подскажите, пожалуйста! При сохранении переданного изображения BASE64 на сервере сохраняется неверный формат, который не отображается. Клиент:

    savePhoto(base64dataUrl); 
    function savePhoto(image){ 
      $.post("savebase64.php", { img: image, name: uniqid(), dataType: 'xml' }); 
    }; 
    
  8. Anton пишет:

    Спасибо за статью. Тестировал Вашу программу на firefox и хроме под android – всё работает. Скажите подалуйста, как получить управление фокусировкой и другими настройками камеры?

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

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

*

* Copy This Password *

* Type Or Paste Password Here *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Узнавай о новых статьях первым!

Последние записи