<html>
<head>
<title>サーマルカメラ</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>
<style>
body {
background-color: #000;
}
body.alert {
background-color: #900;
}
#temp {
position: absolute;
top: 300px;
left: 0;
right: 0;
margin: auto;
font-size: 72px;
color: #EEE;
text-align: center;
line-height: 1;
}
#thermalArea {
display: flex;
justify-content: center;
align-items: center;
}
#thermalArea div {
position: relative;
width: 240px;
height: 240px;
overflow: hidden;
}
#thermalArea img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.8;
}
#canvas {
position: relative;
transform: scale(30);
transform-origin: top left;
z-index: -1;
mix-blend-mode: screen;
}
#camera {
position: absolute;
top: 0;
left: -40px;
z-index: -2;
}
</style>
</head>
<body>
<main>
<button type="button" id="thermalBtn">検温!</button>
<div id="thermalArea">
<div>
<canvas id="canvas" width="8" height="8"></canvas>
<video id="camera" width="320px"></video>
<img src="img/silhouette.png" alt="">
</div>
</div>
<div id="temp"></div>
</main>
<script>
// サーモグラフィー彩度・明度設定
const maxH = 250;
const minH = 0;
const stepH = 7;
const maxL = 50;
const minL = 20;
const stepL = 7;
// 体温判定
const minTemp = 36;
const alertTemp = 37;
const socket = io();
const video = document.getElementById('camera');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const $body = $('body');
const $thermalBtn = $('#thermalBtn');
const $temp = $('#temp');
let temp = 0;
let calibration = 9;
let isTemp = false;
let isNear = false;
let tempList = [];
const init = () => {
setSocket();
setBtn();
};
const setSocket = () => {
let array,
result = [];
socket.on('thermal', (data) => {
drawCanvas(data);
});
};
const setBtn = () => {
let timer;
$thermalBtn.on('click', function(){
const emitThermal = function(){
socket.emit('msg', 'thermal');
};
isTemp = !isTemp;
if(isTemp){
setCamera();
emitThermal();
timer = setInterval(emitThermal, 250);
$thermalBtn.text('計測中…');
}else{
stopCamera();
clearInterval(timer);
$thermalBtn.text('検温!');
}
return false;
});
};
const drawCanvas = (data) => {
let h,
l,
t = 0,
count = 0;
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].length; j++) {
// 最大温度記録
if(data[i][j] > t){
t = data[i][j];
// 体温ヒット数をカウント
if(t + calibration > minTemp){
count++;
// 2画素越えると近すぎ
if(count > 2){
isNear = true;
}else{
isNear = false;
}
}
}
// 色相を設定
h = maxH - data[i][j] * stepH;
if(maxH < h){
h = maxH;
}else if(minH > h){
h = minH;
}
// 明度を設定
l = (data[i][j] - minL) * stepL;
if(maxL < l){
l = maxL;
}
ctx.fillStyle = 'hsl('+ h +', 100%, '+ l +'%)';
ctx.fillRect(j, i, 1, 1);
}
}
tempList.push(t);
if(tempList.length > 10){
tempList.shift();
}
// 体温中央値を取得して判定
temp = getTemp(tempList);
if(isNear){
$temp.text('はなれて!');
}else if(temp < minTemp){
//$temp.text('');
$temp.text(temp.toFixed(1) + '℃');// 低い温度でも表示
}else{
// 体温なら温度表示して平熱判定
$temp.text(temp.toFixed(1) + '℃');
if(temp < alertTemp){
$body.removeClass('alert');
}else{
$body.addClass('alert');
}
}
};
const getTemp = (tempList) => {
// 中央値の温度を返す
let array = tempList.slice(),
val;
array.sort((a, b) => {
return a - b;
});
let half = Math.floor(array.length / 2);
if (array.length % 2) {
val = array[half];
} else {
val = (array[half - 1] + array[half]) / 2;
}
//キャリブレーション
val += calibration;
return val;
};
const setCamera = () => {
// カメラ起動
const constrains = { video: {width: 320, height: 240}, audio: false };
navigator.mediaDevices.getUserMedia(constrains)
.then(function(stream) {
video.srcObject = stream;
video.play();
});
};
const stopCamera = () => {
// カメラ停止
const tracks = video.srcObject.getTracks();
tracks.forEach(track => {
track.stop();
});
video.srcObject = null;
};
init();
</script>
</body>
</html>