LoginSignup
0
0

More than 3 years have passed since last update.

「見える!動きが見える!」JavaScriptで残像カメラを作ってみた

Posted at

俺にはおまえの動きが見えるぞ

 Webカメラの画像を、JavaScriptで処理して残像をつけてみました。ダンスや機か運動の様子を撮影して、動きを見られるようできたらと考えつくりました。フリーズさせるための「止まれ」ボタンもつけてみました。でも、できがいまいちでした。(-_-)
https://kaihatuiinkai.jp/afterimage/

「動きがブレて見える!」。動きがはっきり見えない。この教材はボツかな?
 それでは、作成ステップを説明します。データの流れとプログラム作動を確認してもらうため、ソースコードを全て貼り付けしました。長くなってしまうことをご了承願います。
 WebカメラをJavaScriptで画像表示させるための方法はこちらを参照願います。
https://qiita.com/mvm43236/items/398e92761440eb006271

残像表示(画像重ね合わせ)

Webカメラの画像を<canvas>に表示します。前の画像と重ね合わせをして、右の<canvas>に表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera5.html

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

 動いた部分が表示されます。

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

 動いた部分が影のようについてきます。
・カメラの前で顔を動かすと、シュールな顔になれます。
・仮○ライ○○の変身シーンの動きをすると、かっこよく見えます。
・カメラに向かって、拳を何度も降り出すと、ペガ○○流○拳、北○百○拳が再現できます。
・・・あれ?教材にならない。

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秒で処理してしまう。コンピュータの能力の進化はすばらしいですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0