sudo rm -rf /

(株)アジャストのエンジニアリング?ブログです

笑い男ビデオチャットを作りたい! その3 node.jsでコードを書いてみる


普通自動車免許の学科試験に落ちました。アジャストの渕上です。

前回は、笑い男ビデオチャットを実装するための方法を検討しました。
今回は、その方法に沿ってコードを書いてみようと思います。

前提条件

node.jsとsocket.ioを利用して実装する

今回の目標

今回は下記を、ブラウザの更新なしで実現したいと思います。

  1. Webカメラで撮影しているデータをWebサーバに渡す
  2. Webサーバ上でOpenCVによる加工を行う
  3. 加工したデータをブラウザに返す

実践

1. Webサーバに送る準備、ローカルで動画をキャプチャする

まずはWebカメラで撮影している動画を、videoタグで再生して画像としてキャプチャします。

コード

下記のHTML+Javascriptで実現することができました
1. PC内蔵カメラで撮影した動画をvideo要素で再生
2. timeupdateイベント発生ごとにcanvasに転写
3. canvasの内容をdataURIに変換し、img要素のsrcに設定
しています

<html>
<head>
<script>
var  video,
     canvas,
     localCapture;

function initialize(){
  video  = document.createElement('video');
  video.setAttribute('width', 640);
  video.setAttribute('height', 480);
  canvas = document.createElement('canvas');
  canvas.setAttribute('width', 640);
  canvas.setAttribute('height', 480);
  localCapture  = document.getElementById('local-capture');

  navigator.getUserMedia = ( navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);

  if (navigator.getUserMedia) {
    navigator.getUserMedia (
      { video: true, audio: true },
      function(localMediaStream) {
        // カメラの動画をvideoタグで再生(src属性にセット)
        video.src  = window.URL.createObjectURL(localMediaStream);
        // timeupdateイベントにリスナーを追加
        video.addEventListener('timeupdate', webCameraTimeUpdate);
        video.play();
      },
      function(err) {
        console.log("The following error occured: " + err);
      }
    );
  }
  else {
     console.log("getUserMedia not supported");
  }

}

function webCameraTimeUpdate(){
  // videoタグのキャプチャをcanvasに転写
  var ctx    = canvas.getContext('2d');
  ctx.drawImage(video,0,0);
  // キャンバスの内容をdataURLに変換して、img要素にセット
  var data = canvas.toDataURL();
  localCapture.setAttribute('src', data);
}

window.addEventListener('load', initialize);

</script>
</head>
<body>
<img id="local-capture">
</body>
</html>

実行結果

上記のコードをブラウザで実行した結果のキャプチャです。
わかりずらいのですが、img要素のsrcを切り替えつづけた結果、動画のように見えています。
(きたないおっさんの顔でスミマセン・・・)

20150909_01

2. キャプチャした画像をWebSocket(socket.io)で送信(クライアント側処理)

ここからはコードが長くなってきますので、部分的に紹介し、最後に一式を添付します。
下記は添付ファイルの ./template/index.htmlの一部です。

先ほど定義したwebCameraTimeUpdateに処理を追加します。

コード

function webCameraTimeUpdate(){
  // videoタグのキャプチャをcanvasに転写
  var ctx    = canvas.getContext('2d');
  ctx.drawImage(video,0,0);
  // キャンバスの内容をdataURLに変換して、img要素にセット
  var data = canvas.toDataURL();
  localCapture.setAttribute('src', data);
  // socket.io を使って、dataURLをサーバに送信
  socket.emit('video-update', data);
}

3. WebSocketで受け取った画像をOpenCVで加工して、クライアントに送信(サーバ側処理)

下記は添付ファイルの ./index.jsの一部です

コード

io.sockets.on('connection', function(socket){
  // クライアントからvideo-updateを受け取る
  socket.on('video-update', function(dataURL){
     // dataURLのプリフィックスを削除してデータ部分だけをBase64デコードし、Bufferに変換する
     var prefix = 'data:image/png;base64,';
     var bitmap = dataURL.replace(prefix,'');
     var buf    = new Buffer(dataURL.replace(prefix,''), 'base64');

     // openCVでバッファを読み込む
     opencv.readImage(buf, function(err, im){
       // 
       im.detectObject('./node_modules/opencv/data/haarcascade_frontalface_alt.xml', {}, function(err, faces) {
         // 読み込んだ画像の顔部分を判別し、枠で囲う
         for (var i = 0, max=faces.length; i < max; i++) {
           var face = faces[i];
           im.ellipse(face.x + face.width / 2, face.y + face.height / 2, face.width / 2, face.height / 2);
         }

         // 加工した画像を再度dataURL化し、クライアントに送信する
         io.emit('capture-send', { dataURL: prefix+im.toBuffer().toString('base64')});
       });
     });
  });
});

4. 加工された画像を受け取ってimg要素にセット(クライアント側処理)

コード

socket.ioで受け取ったdataURLを、img要素にセットします
下記は添付ファイルの ./template/index.htmlの一部です。

  socket.on('capture-send', function(data){
     remoteCapture.setAttribute('src', data.dataURL);
  });

実行結果

今回のコードの実行結果です。
向かって左が、サーバに送る前のカメラのキャプチャ画像
向かって右が、サーバで加工した画像になっています。
右側は、顔の部分が赤丸で囲われています。

20150909_02

今回作成したファイル一式

今回作成したファイル一式を添付します。
node.jsとOpenCVがインストールされている環境で実行できるかと思います。
ソースコード一式

次回予定

このテーマもずいぶん引っ張ってしまっているので、
次回でなんとか積み残し課題を解決したいと思います。

  1. 1対1での双方向送受信をできるようにする
  2. nodeのOpenCVモジュールでも笑い男画像でマスクできるようにする

です。2はOpenCVだけでは骨が折れるので、ほかのモジュールの利用も視野に入れておきます。


3 Comments

  1. こちらのサイトを参考にさせて頂いています
    シンプルで分かりやすいです
    ありがとうございます

    [その3 node.js] ですが、  最終的なソース一式に index.html や index.js が入っていないようです できればこちらをUPしていただけないでしょうか

    私は サーバーに設置した WEBカメラの画像を node.js + socket.io
    でブロードキャストしたいと思っているのですが なかなかうまくいきません
    多分このサイトが一番近いのかと思ってます

    どうぞよろしくお願いいたします
    阪本

    • adjust

      2016年3月22日 at 9:58 PM

      コメントいただきありがとうございます。この記事を書いたものです。

      > 最終的なソース一式に index.html や index.js が入っていないようです できればこちらをUPしていただけないでしょうか

      クライアント側のソース一式という理解でよろしいでしょうか。
      templates/index.html がそれになっています。

      サーバの”/path/to/test”にファイル一式をアップしたとすると、

      $ vim /path/to/test/config.js
      

      でIPアドレスをご自身のサーバのIPに合わせた後、

      $ cd /path/to/test
      $ node ./index.js
      

      でnodeを立ち上げ、”http://サーバのIP:8080/”にアクセスしてみていただけるでしょうか。

      あと、この記事を書いたとき、勝手がわからずnode_moduleまでzipに含めていました。
      無駄にファイル容量が大きくなっていますので、後日アップロードしなおしてみようと思います。

  2. すごい!やりたかったことが全部網羅されています!
    ありがとうございます。

コメントを残す

Your email address will not be published.

*

© 2017 sudo rm -rf /

Theme by Anders NorenUp ↑