ブラウザで動くWebカメラアプリを作ります。撮影した写真をNode.jsでローカルサーバーに保存していきます。
開発環境
OS:raspbian 10.7(WindowsやMacでも問題ないと思います)
Node.js 10.21.0
Node.js モジュール
fs
Express
Socket.IO
撮影から保存までの流れ
Webカメラで撮影
↓
カメラ映像をcanvasに転写
↓
保存ボタンが押されたら、canvas画像をbase64にエンコード
↓
base64(画像をテキスト化したもの)をWebSocketでNode.jsサーバーに送信
↓
Node.jsサーバーで受信したbase64データをデコード
↓
デコードした画像を任意の場所に保存
まずはWebカメラ設置
最初にWebカメラの設置します。(もちろん端末内臓カメラでもOKです)
画質やフォーカス性能は、Webカメラの性能次第になります。
今回はモルモットのケージにカメラを取り付けました。
Node.jsのコード
Node.jsでExpressを使い、Webサーバーにしています。
Socket.IO(WebSocket)で受信したbase64データをデコードし、「record」ディレクトリに画像の保存します。
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 |
const express = require('express'); const app = express(); const http = require('http').createServer(app); const io = require('socket.io')(http); const PORT = 3000; const fs = require('fs'); const init = () => { app.use(express.static('htdocs')); io.on('connection', (socket) => { socket.on('recImg', (data) => { saveImg(data); }); }); http.listen(PORT, () => { console.log(`接続ポート ${ PORT }`); }); }; const saveImg = (data) => { const base64 = data.img.split(",")[1]; const decode = new Buffer.from(base64,'base64'); fs.writeFile(`record/${data.date}.jpg`, decode, (err) => { if(err){ console.log(err); io.emit('msg', '保存失敗'); }else{ console.log(`${data.date}.jpgで保存`); io.emit('msg', '保存完了'); } }); }; init(); |
「htdocs」ディレクトリ内にindex.htmlを置くと、http://localhost:3000/でアクセスできます。
このindex.htmlにカメラアプリのフロント側コードを書きます。
フロント側のHTMLコード
Webカメラ映像をcanvasに転写します。そしてページ上の保存ボタンが押されたら、canvas画像をbase64にエンコードします。
次に、base64画像をWebSocketでNode.jsサーバーに送信しています。その時あわせて撮影日時を「年-月-日T時分秒」で送信します。
撮影日時をファイル名に使えば、保存した画像の上書きは防げます。(1秒以内に連写しなければ)
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 |
<html> <head> <title>Node.jsで画像を保存</title> <script src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.0/socket.io.js"></script> </head> <body> <main> <canvas id="canvas" width="1280px" height="720px"></canvas> <video id="camera" width="1280px" height="720px" style="display:none;"></video> <!--低解像度--> <!-- <canvas id="canvas" width="640px" height="480px" style="display:none;"></canvas> <video id="camera" width="640px" height="480px"></video> --> <button type="button" id="btn">保存</button> </main> <script> const socket = io(); const $btn = $('#btn'); const camera = document.getElementById('camera'); const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const init = () => { setCamera(); setSocket(); setBtn(); }; const setCamera = () => { // カメラ起動 const constrains = { video: {width: 1280, height: 720}, audio: false }; navigator.mediaDevices.getUserMedia(constrains) .then((stream) => { camera.srcObject = stream; camera.play(); drawCanvas(); }); }; const drawCanvas = () => { const draw = function(){ ctx.drawImage(camera, 0, 0); } setInterval(draw, 100); }; const setSocket = () => { socket.on('msg', (val) => { $btn.text(val); }); }; const setBtn = () => { const getNow = () => { let dt = new Date(), y = dt.getFullYear(), m = ('00' + (dt.getMonth()+1)).slice(-2), d = ('00' + dt.getDate()).slice(-2), h = ('00' + dt.getHours()).slice(-2), min = ('00' + dt.getMinutes()).slice(-2), s = ('00' + dt.getSeconds()).slice(-2); return `${y}-${m}-${d}T${h}${min}${s}`; }; $btn.on('click', function(){ $btn.text('保存中…'); let data = { "date": getNow(), "img": canvas.toDataURL("image/jpeg") }; socket.emit('recImg', data); }); }; init(); </script> </body> </html> |
Webカメラの解像度は、端末スペックにより調整してください。
ラズパイ3で720p画像だと、処理が追い付かないのか、たまに荒れます。
あまり動きのないものを撮影する場合は、フレームレート(canvas転写頻度)を落としても良いでしょう。
setInterval(draw, 100);の数字を変更すると、撮影間隔が変わります。
実際に保存された写真
「2021-02-22T085213.jpg」という名前で保存されました。
余談ですが、モルモットが寝ているところを初めてみました。近づくと目を開けるため、いつ寝ているか分かりませんでした。
タイムラプスや防犯カメラ、機械学習に
今回、写真撮影のトリガーは「保存」ボタンでした。
ラズパイを常時稼働させている場合、トリガーを変えれば、さまざまな使い方ができるでしょう。
例えば植物の成長記録。タイマーをトリガーにして毎時間経過を撮影し、写真をつなげてタイムラプスにもできます。
人感センサーと組み合わせれば、防犯カメラにもなるでしょう。
タイマーやセンサーと組み合わせ、機械学習用の画像を蓄積していくのにも向いていると思います。
私の場合はラズパイのサーマルカメラで体温を毎朝測るので、その際顔の写真を保存して、健康状態(目の下のクマの様子など)を検証しようと思います。