Javascript

Javascript30 - day19 Webcam Fun

joy_lee 2021. 4. 20. 21:01

오늘 만들 site에 필요한 기능

  • site에 webcam을 통해 찍는 영상을 가져온다
  • 영상을 사진으로 찍는다
  • 사진을 다운로드 한다
  • filter를 적용한다(redEffect, rgbSplit, greenScreen)

 

사용한 기능들

  • npm
  • canvas

canvas를 다뤄보지 않아서 영상을 보면서 코딩을 따라해봤다.

구현 순서대로 코드들을 정리해본다.

 

코드를 작성하기 전에

실시간으로 webcam -> video로 보여주기 위해 서버를 구동해야 한다.

node.js를 다운로드 후 폴더에서 git BASH를 실행,

npm start 라는 명령어를 통해 node server.js를 실행한다.

 

index.html

1
2
3
4
5
<canvas class="photo"></canvas>
<video class="player"></video>
<div class="strip"></div>
 
<audio class="snap" src="./snap.mp3" hidden></audio>
cs

script.js



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
const video = document.querySelector('.player');
const canvas = document.querySelector('.photo');
const ctx = canvas.getContext('2d');
const strip = document.querySelector('.strip');
const snap = document.querySelector('.snap');
 
function getVideo() {
    navigator.mediaDevices.getUserMedia({ video: true, audio: false })
    .then(localMediaStream => {
        // video.srcObject = window.URL.createObjectURL(localMediaStream);
        video.srcObject = localMediaStream;
        video.play();
    })
    .catch(err => {
        console.error('OH NO!!!', err);
    });
}
 
getVideo();

cs

index.html에서 class를 통해 필요한 video, canvas, div, audio를 가져온다.

canvas를 제어하기 위한 canvas context(ctx)도 선언해준다.

홈페이지가 열리자마자 getVideo()를 실행한다.

getVideo()에서 만든 영상은 화면 상단 우측에 작게 자리한다.

 

getVideo()에서는 웹캠을 사용하기 위해 navigator.mediaDevices.getUserMedia()를 실행한다.(video만 사용, audio: false)

video가 promise형태로 반환된다. -> then 사용

주석처리한 구문이 제대로 실행되지 않아서 아래의 .srcObject를 사용해 video에 localMediaStream을 넣어준다.

video.play();는 실행하지 않으면 처음 getVideo()를 실행했을 때의 이미지가 멈춘 채 남아있기 때문에 play해준다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function paintToCanvas() {
    const width = video.videoWidth;
    const height = video.videoHeight;
    canvas.width = width;
    canvas.height = height;
 
    return setInterval(() => {
        ctx.drawImage(video, 00, width, height);
        // take the pixels out
        let pixels = ctx.getImageData(00, width, height);
        // mess with them
        // pixels = redEffect(pixels);
        
        // pixels = rgbSpllit(pixels);
        
        pixels = greenScreen(pixels);
        // put them back
        ctx.putImageData(pixels, 00)
    }, 16);
}
 
// video.play(); emit an event 'canplay' -> paintToCanvas()
video.addEventListener('canplay', paintToCanvas);
cs

getVideo()를 통해 video가 play되면, addEventListener에서 'canplay'동작을 감지해 painToCanvas()가 실행된다.

paintToCanvas는 화면 중간에 effect가 적용된 큰 화면을 보여주는 기능을 한다.

canvas의 크기를 실제 비디오의 크기에 맞춰준다.

 

setInterval을 통해 16ms(0.016s)마다 익명함수를 실행해 캔버스 화면이 바뀌도록 한다.

ctx.drawImage로 video를 canvas에 (0.0)부터 (width, height)까지 video를 그려준다.

그리고 getImageData를 통해 ctx에 나온 비디오 화면을 pixels로 추출한다.

pixels를 다양한 필터에 입력해 출력값을 다시 받은 후 보정된 화면을 다시 canvas에 그려준다.

 

video --(drawImage)--> canvas --(getImageData)--> pixels --(filter)--> newPixels --(putImageData)--> canvas

 

1
<button onClick="takePhoto()">Take Photo</button>
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
function takePhoto() {
    // played the sound
    snap.currentTime = 0;
    snap.play();
 
    // take the data out of the canvas
    const data = canvas.toDataURL('image/jpeg');
    const link = document.createElement('a');
    link.href = data;
    link.setAttribute('download''handsome');
    link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;
    strip.insertBefore(link, strip.firstChild);
}
cs

HTML의 button과 연결된 takePhoto()를 구현한다.

snap은 찰칵 소리가 나는 audio 이다.

snap.currentTime = 0으로 소리가 다 끝나기 전에 버튼을 눌려도 처음부터 소리가 들리도록 한다.

버튼을 누르면 셔터소리가 나도록 snap.play()를 한다.

 

canvas에 현재 표시되고 있는 화면을 toDataURL로 사진을 나타내는 URL로 바꿔준다.

새로운 link를 만든다.

setAttribute()로 다운로드하고, 다운로드시 파일이름은 'handsome'으로 지정해준다.

link의 innerHTML에 image가 나타나게 해서 다운받고싶은 파일을 골라서 클릭하면 다운로드하도록 한다.

 

 

 
filter들
 
pixels는 수많은 데이터값이 있는데 4개가 한 pixel을 담당한다.
0번 : red
1번 : green
2번 : blue
4번 : alpha(투명도)
를 나타내는 값이다. 그래서 for()으로 i +=4씩 효과를 주도록 한다.
 
redEffect
1
2
3
4
5
6
7
8
function redEffect(pixels) {
    for (let i = 0; i < pixels.data.length; i += 4){
        pixels.data[i + 0= pixels.data[i + 0+ 100;// red
        pixels.data[i + 1= pixels.data[i + 1- 50;// green
        pixels.data[i + 2= pixels.data[i + 2* 0.5;// blue
    }
    return pixels;
}
cs

red는 증가, green과 blue는 감소한 이미지는 전체적으로 붉은 빛을 띈다.

 

 

rgbSplit

1
2
3
4
5
6
7
8
9
function rgbSpllit(pixels) {
    for (let i = 0; i < pixels.data.length; i += 4){
        pixels.data[i - 150= pixels.data[i + 0];// red
        pixels.data[i + 100= pixels.data[i + 1];// green
        pixels.data[i - 150= pixels.data[i + 2];// blue
    }
    return pixels;
}
 
cs

한 화소의 red, green, blue의 위치를 바꿔준다. red와 blue는 왼쪽으로, green은 오른쪽으로 움직이게 한다.

 

 

 

greenScreen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="rgb">
    <label for="rmin">Red Min:</label>
    <input type="range" min=0 max=255 name="rmin">
    <label for="rmax">Red Max:</label>
    <input type="range" min=0 max=255 name="rmax">
 
    <br>
 
    <label for="gmin">Green Min:</label>
    <input type="range" min=0 max=255 name="gmin">
    <label for="gmax">Green Max:</label>
    <input type="range" min=0 max=255 name="gmax">
 
    <br>
 
    <label for="bmin">Blue Min:</label>
    <input type="range" min=0 max=255 name="bmin">
    <label for="bmax">Blue Max:</label>
    <input type="range" min=0 max=255 name="bmax">
</div>
cs

HTML에 range input을 통해 r,g,b의 min, max를 입력받는다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
function greenScreen(pixels) {
    const levels = {};
 
    document.querySelectorAll('.rgb input').forEach((input) => {
      levels[input.name= input.value;
    });
  
    for (i = 0; i < pixels.data.length; i = i + 4) {
      red = pixels.data[i + 0];
      green = pixels.data[i + 1];
      blue = pixels.data[i + 2];
      alpha = pixels.data[i + 3];
  
      if (red >= levels.rmin
        && green >= levels.gmin
        && blue >= levels.bmin
        && red <= levels.rmax
        && green <= levels.gmax
        && blue <= levels.bmax) {
        // take it out!
        pixels.data[i + 3= 0;
      }
    }
    return pixels; 
}
cs

입력받은 r,g,b의 min과 max를 현재 canvas의 pixel들과 비교한다.

각 색깔 중 하나라도 입력받은 min과 max 사이의 값이면 alpha = 0;으로 설정해 흰 색이 나오도록 한다.