新手村19 - Webcam Fun

19 - Webcam Fun

俗話說的好,一天一蘋果,醫生遠離我

一天一 JS,What the f*ck JavaScript?

small steps every day - 記錄著新手村日記

完成目標

  • 使用Webcam進行以下動作

    • 功能

      • 拍照功能
      • 拍照檔案儲存.png檔
      • 拉動上面的range會改變濾鏡參數
    • 畫面

      • 右上角出現webcam的畫面

      • 中間的大圖是canvas輸出結果

      • 出現照片縮圖列表

      • 加上濾鏡(redEffect、rgbSplit、globalAlpha、Green screen)

index_START.html

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Get User Media Code Along!</title>
<link rel="stylesheet" href="style.css">
</head>
<body>

<div class="photobooth">
<div class="controls">
<button onClick="takePhoto()">Take Photo</button>
<!-- <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> -->
</div>

<canvas class="photo"></canvas>
<video class="player"></video>
<div class="strip"></div>
</div>

<audio class="snap" src="http://wesbos.com/demos/photobooth/snap.mp3" hidden></audio>

<script src="scripts.js"></script>

</body>
</html>

Script.js

1
2
3
4
5
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');

JS - step by step

首先,我們需要使用者先允許瀏覽器可以使用現在裝置的相機,否則這個範例就在此結束,我們就跟大家下一篇見面了…允許之後,使用者能在瀏覽器上看見自己的螢幕呈現於畫面上,至於應該怎麼抓呢?可以在後面設定要開啟的只有影片還是聲音,以及影片的大小寬高等等。此外,如果沒有開啟攝像頭成功應該在 console 中告訴使用者這件事情,詳細的範例連結可以直接在這個地方了解:https://tinyurl.com/y22tls2h

Navigator:https://tinyurl.com/y2s2bozg
MediaDevices.getUserMedia():https://tinyurl.com/ojzyz72

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// script.js
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');

// 舊語法 navigator.getUserMedia();
function getVideo() {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(localMediaStream => {
console.log(localMediaStream);

video.srcObject = localMediaStream;
video.play();
})
.catch(err => {
console.error(`OH NO!!!`, err);
});
}

getVideo();

能開啟攝像頭之後,我們來將攝像頭的螢幕透過 Canvas 繪圖畫在螢幕上

window.setInterval:https://tinyurl.com/y6ae337q

CanvasRenderingContext2D.drawImage():https://tinyurl.com/y4wzme56

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function paintToCanvas() {
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;

return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);
let pixels = ctx.getImageData(0, 0, width, height);
ctx.putImageData(pixels, 0, 0);
}, 16);
}

video.addEventListener('canplay', paintToCanvas);

接下來,我們要來處理在RGBA情況下,我們要的顏色是什麼,其中這邊會跑一個FOR迴圈。而為什麼會是 +=4 就很明顯了,我們抓到的 pixels.data 會有四個值,如果你在前段有把螢幕轉換成 Canvas 的點的時候,有把點印出來就會發現,轉出來的點會是原來的值的四倍,也就是這邊的RGBA四個點。因此,如果是第一個也就是紅色、第二個是綠色…

迴圈內的數字也就是分別調整紅、綠、藍要加上多少的顏色了!並透過三個色版分開並做位移

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function redEffect(pixels) {
for (let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i + 0] = pixels.data[i + 0] + 200; // RED
pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREEN
pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Blue
}
return pixels;
}

function rgbSplit(pixels) {
for (let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i - 150] = pixels.data[i + 0]; // RED
pixels.data[i + 500] = pixels.data[i + 1]; // GREEN
pixels.data[i - 550] = pixels.data[i + 2]; // Blue
}
return pixels;
}

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;
}

最後,再來拍張照片吧!如果 canvas 的高度或是寬度為 0,,將會回傳字串 “data:,”,如果要求的圖像類型並非 image/png,但是回傳的類型卻是 data:image/png,表示要求的圖像類型並不支援!

HTMLCanvasElement.toDataURL():https://tinyurl.com/y2swfgtb

人臉辨識opencv.js:https://tinyurl.com/yyg498jc

1
2
3
4
5
6
7
8
9
10
11
function takePhoto() {
snap.currentTime = 0;
snap.play();

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);
}

就大功告成啦!

JS - final

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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 => {
console.log(localMediaStream);

video.srcObject = localMediaStream;
video.play();
})
.catch(err => {
console.error(`OH NO!!!`, err);
});
}

function paintToCanvas() {
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;

return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);
let pixels = ctx.getImageData(0, 0, width, height);
pixels = rgbSplit(pixels);
ctx.putImageData(pixels, 0, 0);
}, 16);
}

function redEffect(pixels) {
for (let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i + 0] = pixels.data[i + 0] + 200;
pixels.data[i + 1] = pixels.data[i + 1] - 50;
pixels.data[i + 2] = pixels.data[i + 2] * 0.5;
}
return pixels;
}

function rgbSplit(pixels) {
for (let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i - 150] = pixels.data[i + 0];
pixels.data[i + 500] = pixels.data[i + 1];
pixels.data[i - 550] = pixels.data[i + 2];
}
return pixels;
}

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;
}

function takePhoto() {
snap.currentTime = 0;
snap.play();
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);
}

getVideo();
video.addEventListener('canplay', paintToCanvas);

本刊同步於個人網站:http://chestertang.site/

本次範例程式碼原作者來源:https://tinyurl.com/yavm5f5n