browser에서 webcam 이용하기

Dave Lee·2021년 3월 25일
1

웹캠 가지고 놀기

목록 보기
1/1
post-thumbnail

웹캠을 이용한 여러가지 재밌는 것들을 하기 전에, 우선 웹캠의 사용방법과 requestAnimationFrame에 대해서 알아보자.

video

video 태그는 대부분의 모던 브라우저에서 사용이 가능하다. (ie는 9이상이면 됨) 동영상을 플레이하는 용도이기도 한데, 소스를 웹캠으로 연결하면 이를 사용할 수 있게 된다.

// 비디오 캡쳐 시작
const video = document.querySelector("#videoElement");

if (navigator.mediaDevices.getUserMedia) {
	navigator.mediaDevices.getUserMedia({ video: true })
        .then( (stream) => { 
          video.srcObject = stream;
    	})
  		.catch(function (error) {
          console.log("Something went wrong!");
          console.log(error);
          return;
        });
};

이렇게 하면 video의 소스로 웹캠에서 나오는 스트림을 넣은 것이다. 여기까지만 해도 video element 안에 웹캠에서 촬영하는 내용이 보일 것이다.

이 실행 결과를 보면 촬영하는 원본 그대로라, 거울같지 않고 뭔가 어색해 보인다. 화면을 좌우로 뒤집어 제대로 보이는 영상을 바로 옆 캔버스에 옮겨그려보자.

우선 소스의 크기를 알아내보자. video는 소스의 가로 세로 크기를 리턴해줄 수 있는데, 처음부터 할 수 있는 것은 아니고 메타데이터가 준비되어야 가능하다.

video.addEventListener( "loadedmetadata", () => {
	console.log(video.videoWidth);
  	console.log(video.videoHeight);
});

requestAnimationFrame

다음으로 requestAnimationFrame의 사용법에 대해 알아보자. 브라우저에서는 화면을 갱신할 때 마다 이 함수에 우리가 넣어준 콜백함수를 실행시켜준다. 사용법은 다음과 같다.

class App {
  constructor() {
    window.requestAnimationFrame(this.draw.bind(this));
  }
  
  draw(t) {
    // t에는 이 함수가 실행된 시각(timestamp)이 담겨 온다.
    
    // 재귀적으로 다시 requestAnimationFrame을 호출  
    window.requestAnimationFrame(this.draw.bind(this));
    
    // do something
  }

이 requestAnimationFrame은 보통 화면의 주사율에 따라 달라지는데 1초에 30번 혹은 60번 불릴 수 있다.

context와 transform

여기서 video로부터 받아온 장면을 좌우를 뒤집어 canvas에 찍어보자. canvas는 한마디로 우리가 뭔가 그릴 수 있는 도화지다. context 개념이 중요한데, context란 이 canvas를 우리가 어떤 맥락(혹은 시각)으로 바라보고 있는지 정보를 담고 있다.

가령 ctx를 처음에 얻으면 우리는 이 도화지의 좌상단을 (0,0)으로 보고, 오른쪽으로 갈 수록 x증가, 아래로 갈 수록 y증가로 생각한다. 이게 ctx의 기본 시각이라고 생각해도 된다.

이 때, ctx.translate(x1,y1) 을 실행하면 브라우저는 이제 (x1,y1)을 캔버스의 원점으로 생각하게 된다. 이렇게 하고 나서

ctx.fillStyle = 'black';
ctx.fillRect( 0, 0, 1, 1 ); // (0,0) 에 1x1 크기 사각형 그리기

이를 실행하면 canvas에는 실제로 (x1, y1)에 검정 점이 찍히게 된다.

또 ctx.scale(-2,1) 을 실행하고 나면 브라우저는 이제 x좌표가 1 증가할 때마다 캔버스의 원점에서 2칸씩 왼쪽으로, y좌표가 1 증가할 때마다 캔버스의 원점에서 1칸씩 아래로 가면서 뭔가를 그릴 것이다.

따라서 화면을 좌우로 뒤집는 코드는 다음과 같게 된다.

ctx.translate(video.videoWidth, 0);
ctx.scale(-1,1);

code

전체 코드는 아래와 같다.

    const canvas = document.querySelector("#mirrored");
    const video = document.querySelector("#videoElement");
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;

    const ctx = canvas.getContext('2d');
    ctx.translate(video.videoWidth, 0);
    ctx.scale(-1,1);
    ctx.drawImage(video, 0, 0, 
    	video.videoWidth, 
        video.videoHeight);  

video부터 뒤집기까지 전체적으로 테스트할 수 있는 소스는 아래와 같이 정리된다.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Display Webcam Stream</title>
    <link rel="stylesheet" href="./style.css">
    <script src="index.js"></script>
  </head>
  <body>
	  <video autoplay="true" id="videoElement"></video>
      <canvas class="canvas" id="mirrored"></canvas>
  </body>
</html>

style.css

#videoElement, #mirrored {
	width: 500px;
	height: 375px;
	background-color: #666;
  display: inline-block;
}

index.js

document.addEventListener("DOMContentLoaded", () => {
  new App();
})

class App {
  constructor() {

    const video = document.querySelector("#videoElement");

    if (navigator.mediaDevices.getUserMedia) {
      navigator.mediaDevices.getUserMedia({ video: true })
        .then( (stream) => { // function 의 this와 화살표 함수의 this 가 다름
          video.srcObject = stream;
        })
        .catch(function (error) {
          console.log("Something went wrong!");
          console.log(error);
          return;
        });
    }

    video.addEventListener( "loadedmetadata", () => {
      window.requestAnimationFrame(this.draw.bind(this));
    });
  }

  draw(t) {

    window.requestAnimationFrame(this.draw.bind(this));
    
    const canvas = document.querySelector("#mirrored");
    const video = document.querySelector("#videoElement");
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;

    const ctx = canvas.getContext('2d');
    ctx.translate(video.videoWidth, 0);
    ctx.scale(-1,1);
    ctx.drawImage(video, 0, 0, 
    	video.videoWidth, 
        video.videoHeight);  
    
  }
}

profile
developer

0개의 댓글