俺にはおまえの動きが見えるぞ
Webカメラの画像を、JavaScriptで処理して残像をつけてみました。ダンスや機か運動の様子を撮影して、動きを見られるようできたらと考えつくりました。フリーズさせるための「止まれ」ボタンもつけてみました。でも、できがいまいちでした。(-_-)
https://kaihatuiinkai.jp/afterimage/
「動きがブレて見える!」。動きがはっきり見えない。この教材はボツかな?
それでは、作成ステップを説明します。データの流れとプログラム作動を確認してもらうため、ソースコードを全て貼り付けしました。長くなってしまうことをご了承願います。
WebカメラをJavaScriptで画像表示させるための方法はこちらを参照願います。
https://qiita.com/mvm43236/items/398e92761440eb006271
残像表示(画像重ね合わせ)
Webカメラの画像を<canvas>に表示します。前の画像と重ね合わせをして、右の<canvas>に表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera5.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>残像表示(画像重ね合わせ)</title>
<style>
canvas, video{ border: 1px solid gray; }
</style>
</head>
<body>
<h2>残像表示(画像重ね合わせ)</h2>
video canvas1 canvas2(画像重ね合わせ)<br>
<video id="video" width="300" height="200"></video>
<canvas id="canvas1" width="300" height="200"></canvas>
<canvas id="canvas2" width="300" height="200"></canvas>
<script>
var imageDelay = 10; // 表示遅延 1秒
window.onload = () => {
const video = document.querySelector("#video");
const canvas1 = document.querySelector("#canvas1");
const canvas2 = document.querySelector("#canvas2");
// カメラ設定 //
const constraints = {
audio: false,
video: {
width: 300,
height: 200,
facingMode: "user" // フロントカメラを利用する
// facingMode: { exact: "environment" } // リアカメラを利用する場合
}
};
// カメラを<video>と同期 //
navigator.mediaDevices.getUserMedia(constraints)
.then( (stream) => {
video.srcObject = stream;
video.onloadedmetadata = (e) => {
video.play();
};
})
.catch( (err) => {
console.log(err.name + ": " + err.message);
});
// canvas2 の準備
var picture2 = canvas2.getContext("2d");
// canvas2に画像を貼り付ける
picture2.drawImage(video, 0, 0, canvas1.width, canvas1.height);
// RGBの範囲
function set255(value){
if (value > 255) return 255;
if (value < 0) return 0;
return value;
}
// インターバル処理
var intervalID = setInterval(function(){
// canvas1 の準備
var picture1 = canvas1.getContext("2d");
// canvas1に画像を貼り付ける
picture1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
// イメージデータの取得
image1 = picture1.getImageData(0,0,canvas1.width, canvas1.height);
// canvas2 の準備
var picture2 = canvas2.getContext("2d");
// イメージデータの取得
image2 = picture2.getImageData(0,0,canvas1.width, canvas1.height);
var afterimage =picture2.createImageData(canvas2.width, canvas2.height);
// 画像合成
var cnt = 0;
var amount = 128 / 255;
for (var y = 0; y < canvas1.height; y++) {
for (var x = 0;x < canvas1.width; x++) {
afterimage.data[(cnt*4)] = set255(Math.round((1 - amount) * image1.data[(cnt*4)] + (amount * image2.data[(cnt*4)])));
afterimage.data[(cnt*4)+1] = set255(Math.round((1 - amount) * image1.data[(cnt*4)+1] + (amount * image2.data[(cnt*4)+1])));
afterimage.data[(cnt*4)+2] = set255(Math.round((1 - amount) * image1.data[(cnt*4)+2] + (amount * image2.data[(cnt*4)+2])));
afterimage.data[(cnt*4)+3] = 255;
cnt++;
}
}
// エラー回避処理
try {
// canvas2 に変数の画像を貼り付ける
document.getElementById("canvas2").getContext('2d').putImageData(afterimage, 0, 0);
}catch(e){
// エラー時には何もしない
};
}, 300);
};
</script>
</body>
</html>
残像表示(差分抽出表示)
Webカメラの画像を<canvas>に表示します。前の画像と比較して、下の<canvas>に差分を表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera6.html
動いた部分が表示されます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>残像表示(差分抽出表示)</title>
<style>
canvas, video{ border: 1px solid gray; }
</style>
</head>
<body>
<h2>残像表示(差分抽出表示)</h2>
video canvas1 canvas2<br>
<video id="video" width="300" height="200"></video>
<canvas id="canvas1" width="300" height="200"></canvas>
<canvas id="canvas2" width="300" height="200"></canvas>
<br>canvas2-canvas1 差分抽出画像<br>
<canvas id="canvas3" width="300" height="200"></canvas>
<script>
var imageDelay = 10; // 表示遅延 1秒
window.onload = () => {
const video = document.querySelector("#video");
const canvas1 = document.querySelector("#canvas1");
const canvas2 = document.querySelector("#canvas2");
const canvas3 = document.querySelector("#canvas3");
// カメラ設定 //
const constraints = {
audio: false,
video: {
width: 300,
height: 200,
facingMode: "user" // フロントカメラを利用する
// facingMode: { exact: "environment" } // リアカメラを利用する場合
}
};
// カメラを<video>と同期 //
navigator.mediaDevices.getUserMedia(constraints)
.then( (stream) => {
video.srcObject = stream;
video.onloadedmetadata = (e) => {
video.play();
};
})
.catch( (err) => {
console.log(err.name + ": " + err.message);
});
// canvas2 の準備
var picture2 = canvas2.getContext("2d");
// canvas2に画像を貼り付ける
picture2.drawImage(video, 0, 0, canvas1.width, canvas1.height);
// RGBの範囲
function set255(value){
if (value > 255) return 255;
if (value < 0) return 0;
return value;
}
// インターバル処理
var intervalID = setInterval(function(){
// canvas1 の準備
var picture1 = canvas1.getContext("2d");
// canvas1に画像を貼り付ける
picture1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
// イメージデータの取得
image1 = picture1.getImageData(0,0,canvas1.width, canvas1.height);
// canvas2 の準備
var picture2 = canvas2.getContext("2d");
// イメージデータの取得
image2 = picture2.getImageData(0,0,canvas1.width, canvas1.height);
var afterimage =picture2.createImageData(canvas2.width, canvas2.height);
// 画像合成
var cnt = 0;
var difference = 15;
var cValue = 255;
for (var y = 0; y < canvas1.height; y++) {
for (var x = 0;x < canvas1.width; x++) {
if((image1.data[(cnt*4)]-image2.data[(cnt*4)] > difference)||((image1.data[(cnt*4)+1]-image2.data[(cnt*4)+1] > difference))||(image1.data[(cnt*4)+2]-image2.data[(cnt*4)+2] > difference)){
afterimage.data[(cnt*4)] = image1.data[(cnt*4)];
afterimage.data[(cnt*4)+1] = image1.data[(cnt*4)+1];
afterimage.data[(cnt*4)+2] = image1.data[(cnt*4)+2];
}else{
afterimage.data[(cnt*4)] = cValue;
afterimage.data[(cnt*4)+1] = cValue;
afterimage.data[(cnt*4)+2] = cValue;
};
afterimage.data[(cnt*4)+3] = 255;
cnt++;
}
}
// エラー回避処理
try {
// canvas2 に変数の画像を貼り付ける
document.getElementById("canvas2").getContext('2d').putImageData(image1, 0, 0);
// canvas3 の準備
var picture3 = canvas3.getContext("2d");
// canvas3 に変数の画像を貼り付ける
document.getElementById("canvas3").getContext('2d').putImageData(afterimage, 0, 0);
}catch(e){
// エラー時には何もしない
};
}, 300);
};
</script>
</body>
</html>
残像表示(差分抽出、重ね合わせ)
Webカメラの画像を<canvas>に表示します。前の画像と比較して、下の<canvas>に差分を重ね合わせて表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera7.html
動いた部分が影のようについてきます。
・カメラの前で顔を動かすと、シュールな顔になれます。
・仮○ライ○○の変身シーンの動きをすると、かっこよく見えます。
・カメラに向かって、拳を何度も降り出すと、ペガ○○流○拳、北○百○拳が再現できます。
・・・あれ?教材にならない。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>残像表示(差分抽出、重ね合わせ)</title>
<style>
canvas, video{ border: 1px solid gray; }
</style>
</head>
<body>
<h2>残像表示(差分抽出、重ね合わせ)</h2>
video canvas1 現在の画像 canvas2 この画像をもとにして重ね合わせ<br>
<video id="video" width="300" height="200"></video>
<canvas id="canvas1" width="300" height="200"></canvas>
<canvas id="canvas2" width="300" height="200"></canvas>
<br>canvas2-canvas1 差分抽出、重ね合わせ画像<br>
<canvas id="canvas3" width="300" height="200"></canvas>
<script>
var imageMax = 30; // 遅延最大時間 最大3秒
var imageArray = new Array(imageMax); // 画像保存用
var imageNumber = 0; // 現在の画像番号
var imageDisplay = 0; // 表示番号
var imageDelay = 8; // 表示遅延 1秒
// 読み込まれたら 作動します
window.onload = () => {
const video = document.querySelector("#video");
const canvas1 = document.querySelector("#canvas1");
const canvas2 = document.querySelector("#canvas2");
const canvas3 = document.querySelector("#canvas3");
// カメラ設定 //
const constraints = {
audio: false,
video: {
width: 300,
height: 200,
facingMode: "user" // フロントカメラを利用する
// facingMode: { exact: "environment" } // リアカメラを利用する場合
}
};
// カメラを<video>と同期 //
navigator.mediaDevices.getUserMedia(constraints)
.then( (stream) => {
video.srcObject = stream;
video.onloadedmetadata = (e) => {
video.play();
};
})
.catch( (err) => {
console.log(err.name + ": " + err.message);
});
// canvas3 の準備
var picture3 = canvas3.getContext("2d");
// canvas2に画像を貼り付ける
picture3.drawImage(video, 0, 0, canvas3.width, canvas3.height);
// イメージデータの取得
var afterimage =picture3.createImageData(canvas3.width, canvas3.height);
// エラー回避のため、事前に画像を入れる
for(var i= 0; i <= imageMax; i++){
imageArray[i] = picture3.getImageData(0,0,canvas1.width, canvas1.height);
};
// インターバル処理
var intervalID = setInterval(function(){
// 画像記録番号
imageNumber = imageNumber + 1;
if(imageNumber >= imageMax){
imageNumber =0;
}
// 表示番号
imageDisplay = imageNumber - imageDelay;
if(imageDisplay <0 ){
imageDisplay = imageDisplay + imageMax; // マイナスの処理
}
// canvas1 の準備
var picture1 = canvas1.getContext("2d");
// canvas1に画像を貼り付ける
picture1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
// イメージデータの取得
imageArray[imageNumber] = picture1.getImageData(0,0,canvas1.width, canvas1.height);
// canvas2 の準備
var picture2 = canvas2.getContext("2d");
try {
// canvas2 に変数の画像を貼り付ける
document.getElementById("canvas2").getContext('2d').putImageData(imageArray[imageDisplay], 0, 0);
}catch(e){
// エラー時には何もしない
};
// 画像合成
imageDisplay = imageNumber - imageDelay;
if(imageDisplay <0 ){
imageDisplay = imageDisplay + imageMax; // マイナスの処理
}
afterimage = imageArray[imageDisplay];
for(var i= 1; i <= imageDelay; i++){
funcComposition(imageArray[setImageNumber(imageNumber - imageDelay + i)],imageArray[imageDisplay]);
};
// エラー回避処理
try {
// canvas3 の準備
var picture3 = canvas3.getContext("2d");
// canvas3 に変数の画像を貼り付ける
document.getElementById("canvas3").getContext('2d').putImageData(afterimage, 0, 0);
}catch(e){
// エラー時には何もしない
};
}, 50);
// ImageNumberの範囲
function setImageNumber(value){
if (value > imageMax) return (value - imageMax);
if (value < 0) return (value + imageMax);
return value;
}
function funcComposition(image1,image2){ // 画像合成
var cnt = 0;
var difference = 4;
var cValue = 255;
for (var y = 0; y < canvas1.height; y++) {
for (var x = 0;x < canvas1.width; x++) {
if((image1.data[(cnt*4)]-image2.data[(cnt*4)] > difference)||((image1.data[(cnt*4)+1]-image2.data[(cnt*4)+1] > difference))||(image1.data[(cnt*4)+2]-image2.data[(cnt*4)+2] > difference)){
afterimage.data[(cnt*4)] = image1.data[(cnt*4)];
afterimage.data[(cnt*4)+1] = image1.data[(cnt*4)+1];
afterimage.data[(cnt*4)+2] = image1.data[(cnt*4)+2];
};
afterimage.data[(cnt*4)+3] = 255;
cnt++;
}
}
};
};
</script>
</body>
</html>
以前、Windows用に画像重ね合わせソフト「多重露光」を作りました。
http://www1.iwate-ed.jp/tantou/joho/material/tajuurokou/index.html
.NETの機能を使って、Windowsネイティブのソフトで処理していたことが、今は、ブラウザで表示できることに進歩を感じます。ハードとソフトの進化ですね。
1990年代、CPUがV30のPC9801で「気象衛星画像」のビットマップを1ドットごとに処理をして表示する理科の教材を作りました。でも、400×400を終えるのに1分30秒かかり、授業で使うことをあきらめました。→事前に処理して保存しておきました。それを今では、0.05秒で処理してしまう。コンピュータの能力の進化はすばらしいですね。