19 - Webcam Fun 俗話說的好,一天一蘋果,醫生遠離我
一天一 JS,What the f*ck JavaScript?
small steps every day - 記錄著新手村日記
完成目標
使用Webcam進行以下動作
功能
拍照功能
拍照檔案儲存.png檔
拉動上面的range會改變濾鏡參數
畫面
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 > <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 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); }); } 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 ; 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) { 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) { 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