iOS
WebRTC
webcam
ios11

[iOS11]WebRTCでカメラアクセス&ピクセル取得する

More than 1 year has passed since last update.

先日、会社でWebRTCとARKitのデモと技術解説をまとめたサイトを公開しました。

・WebRTC Examples http://webrtc.r-u.co.jp/

・ARKit Examples http://arkit.r-u.co.jp/

WebRTC Examplesでは、カメラにアクセスしたあとにどのような処理を行ってサンプルを実現しているかを説明しましたので、本記事ではカメラにアクセスし、ピクセルを取得するまでの基礎的な部分を解説したいと思います。
(ARKitの説明についてはこちらの記事で)

そもそもWebRTCって?

WebRTCは、Web Real-Time Communicationの略で、Webブラウザ上でリアルタイムの音声/ビデオチャットなどのコミュニケーションを行えるようにするためのAPI群です。

※ この記事では、カメラの映像を通信に載せるのではなく、フロントのみの処理で映像を解析/処理することを重きに置いているため、本来のWebRTCの用途とは少し異なります。

HTTPS環境を用意する

すぐコードを書き始めたいところですが、WebRTCを動作させるためにはHTTPS環境が必要です。これは、WebRTCの技術がカメラやマイク、ファイルなど秘匿性の高い情報を通信で扱うことが前提となっているためです。
本記事内のサンプルではカメラの映像をローカルで処理し、通信には乗せていないのですが、その場合でもHTTPS環境が必要となります。

本サンプルでは、GitHub Pagesを用いてHTTPS環境を用意しました。

カメラの映像を表示する

Webカメラの映像を表示するにはVideoタグが必要になります。
Videoタグのソースの映像として、webカメラを指定することで映像を表示することができます。

<!--videoタグ、自動再生するためにautoplayとplaysinlineをtrueにして配置-->
<video id="video" autoplay="true" playsinline="true"></video>
<script>
    //videoタグを取得
    var video = document.getElementById("video");
    //取得するメディア情報を指定
    var medias = { audio:false, video:{}};
    //getUserMediaを用いて、webカメラの映像を取得
    navigator.mediaDevices.getUserMedia(medias).then(
        function(stream) {
            //videoタグのソースにwebカメラの映像を指定
            video.srcObject = stream;
        }
    ).catch(
        function(err) {
            //カメラの許可がされなかった場合にエラー
            window.alert("カメラの使用が許可されませんでした");
        }
    );                
</script>

上記のようにwebカメラの取得には、getUserMediaを用います。
getUserMediaを実行した際には、画面にカメラアクセスの
取得できない場合はエラーが発生するため、コールバックで例外処理を書いてください。

デモページ1

バックカメラを指定する

パソコンのブラウザ上では、基本的にはフロントカメラのみになるためか、iOSなど両面にカメラがある場合でも、デフォルトではフロントカメラにアクセスされるようです。
下記のように指定することで、バックカメラにアクセスすることが可能です。

medias.video.facingMode = {exact:"environment"};

また、明示的にフロントカメラを設定したい場合は、

medias.video.facingMode = {exact:"user"}; 

と指定します。

デモページ2

WebRTC非対応の環境への対応

WebRTC非対応の環境では、上記コードでエラーが出てしまうため分岐処理が必要になります。

navigator.mediaDevicesnavigator.mediaDevices.getUserMediaのAPIがあるかどうかで判定が可能です。

if(!!navigator.mediaDevices&&!!navigator.mediaDevices.getUserMedia) {
    // 対応環境
}else {
    // 非対応環境
}

デモページ3

ブラウザごとの対応環境はこちらをご確認ください。

Canvasに転写する

後に、ピクセルにアクセスできるようにするため、カメラの映像をCanvasに転写しておきます。

<video id="video" autoplay playsinline="true" ></video>
<canvas id="canvas"></canvas>
<script>
    /* 
    ** 前述のため省略 **
    */

    var canvas = document.getElementById("canvas");
    //ビデオのメタデータが読み込まれるまで待つ
    video.addEventListener("loadedmetadata",function(e) {
        //canvasにカメラの映像のサイズを設定
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;

        //getContextで描画先を取得
        var ctx = canvas.getContext("2d");
        //毎フレームの実行処理
        setInterval(function(e) {
            //videoタグの描画をコンテキストに描画
            ctx.drawImage(video,0,0,canvas.width,canvas.height);
        },33);      
    });

</script>

カメラの映像がvideoタグにストリームされるまで待ったり、サイズを取得できるまで待つため、loadedmetadataのタイミングで実行されるようにしています。
デモページ4

ピクセルへのアクセス

各ピクセルの色情報については、ContextからgetImageDataで取得します。

setInterval(function(e) {
    ctx.drawImage(video,0,0,canvas.width,canvas.height);
    // ピクセルへのアクセス
    var imageData = ctx.getImageData(0,0,canvas.width,canvas.height);
},33);  

取得したimageDataのdataプロパティには、配列で各ピクセルのデータがR,G,B,A,R,G,B,A・・・の順で格納されています。

カメラの左右反転

カメラの指定がフロントになっている場合は、映像が鏡状に表示されないと直感的な形にならないため、左右反転して表示させる必要があります。

video.addEventListener("loadedmetadata",function(e) {
    var ctx = canvas.getContext("2d");
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;

    //Contextを反転
    ctx.translate(canvas.width,0);
    ctx.scale(-1,1);

    /* 
    ** 前述のため省略 **
    */
});

デモページ5

効果を加える(グレースケール)

最後に、各ピクセルのカラーから計算し、canvasの画像をグレースケールに変換します。

setInterval(function(e) {
    ctx.drawImage(video,0,0,canvas.width,canvas.height);
    var imagedata = ctx.getImageData(0,0,canvas.width,canvas.height);
    var data = imagedata.data;

    for(var i=0; i<canvas.height; i++) {
        for(var j=0; j<canvas.width; j++) {
            var index = (i*canvas.width+j)*4;
            //元のピクセルカラーを取得
            var r = data[index+0];
            var g = data[index+1];
            var b = data[index+2];

            //RGBをグレースケールに変換
            var color = Math.round(r*0.299+g*0.587+b*0.114);
            data[index+0] = color;
            data[index+1] = color;
            data[index+2] = color;
        }
    }
    ctx.putImageData(imagedata,0,0,0,0,canvas.width,canvas.height);  
},33);  

一つ一つのピクセルカラーに対し、計算を行いグレースケール変換したカラー値を出しています。
グレースケール変換にはさまざまな方法がありますが、今回は人間が色を知覚する際の感覚を考慮した重み付けを用いた近似計算式を使用しました。
デモページ6

最後に

入門記事としては、少し長くなってしまいましたが、ほとんどの処理はどのようなケースでもあまり変わらないため、一度慣れると、様々な実験的な映像処理をかけることができるようになります。(その辺り、WebRTC Examplesに動作デモがありますので是非ご覧ください)

iOS11のWebRTC対応により、カメラアクセスを用いた、体験に直結するようなコンテンツが多く作れるようになります。

アプリをダウンロードすることなくブラウザだけで動作し、ユーザーの環境によって体験の変わるカメラコンテンツは非常に面白いのではと思っています。

この記事で、世の中に実験的なコンテンツが増えたら嬉しいです。