2015年12月1日火曜日

Kinect + Raspberry Pi 2 で監視カメラを作る(4)

Kinect のカメラ画像を Jpeg ファイルに出力してブラウザで見る

freenect の example には camtest.c が入っている。
ソースを読んだ感じでは、RGB/Depth カメラのデータを繰り返し取得しているようなのだが、そのままではメッセージを出力するだけで画像が取得できないのでこれを改造して画像を出力するようにする。

カメラのデータは video_cb という関数の第二引数であるdata に格納されていると思われるが、どのようなフォーマットで格納されているかいまいちドキュメントが見当たらなかった。仕方なしに先頭数バイトを HEX でダンプして心の目で見た感じだとどうやらRGBの生データぽかったので、これを上手いこと整形して JPEG のライブラリに食わせれば画像ファイルに変換できると考えた。

まずは Jpeg ライブラリのインストール
> sudo apt-get install libjpeg-dev

で、video_cb の中身を以下のように変更

#include <jpeglib.h>

char filename[] = "/var/www/html/kinect/output.jpg";

void video_cb(freenect_device* dev, void* data, uint32_t timestamp)
{
        JSAMPROW jpg_row[1];
        struct jpeg_compress_struct jpg_cinfo;
        struct jpeg_error_mgr jerr;
        FILE *fp;
        int i;
        char* buf = data;

        jpg_cinfo.err = jpeg_std_error(&jerr);
        jpeg_create_compress(&jpg_cinfo);
        fp = fopen(filename, "wb");
        jpeg_stdio_dest(&jpg_cinfo, fp);
        jpg_cinfo.image_width = 640;
        jpg_cinfo.image_height  = 480;
        jpg_cinfo.input_components = 3;
        jpg_cinfo.in_color_space = JCS_RGB;
        jpeg_set_defaults(&jpg_cinfo);
        jpeg_set_quality(&jpg_cinfo, 75, TRUE);
        jpeg_start_compress(&jpg_cinfo, TRUE);
        for(i = 0; i < 480; i++) {
                jpg_row[0] = (JSAMPROW)&buf[i * 640 * 3];
                jpeg_write_scanlines(&jpg_cinfo, jpg_row, 1);
        }
        jpeg_finish_compress(&jpg_cinfo);
        jpeg_destroy_compress(&jpg_cinfo);
        fclose(fp);
}

コンパイルは -lfreenect に加えて -ljpeg を追加してコンパイルする。
無事にコンパイルできたら、/var/www/html/kinect ディレクトリを作成してから実行すればここに Kinect カメラで撮影した画像が保存される。
実のところ Jpeg のライブラリを使うのは初めてだったが、思ってた以上にすんなり出来た。なので、細かいパラメータはどっかで拾ったサンプルそのまま。

画像を見るにはPCのブラウザで "http://ラズパイのIP/Kinect/output.jpg" と入れれば撮影した画像が閲覧できる。
ブラウザのリロードボタンをひたすら連打すればアニメーションぽいのも見れるはずである・・・が、
ここで問題が発生した。
一つはそのままだと画像がキャッシュされてしまい更新されない。
これは以下の.httaccesを作成して対処した。

<Files ~ "\.jpg$">
Header set Cache-Control "no-cache"
Header set Pragma "no-cache"
</Files>

もう一つは読み込むタイミングによってはライブラリが書き込んでる途中のデータが読み込まれてしまい、画像の一部が切れてしまうことがあった。

苦肉の策として、一旦テンポラリに吐き出した後で、mv コマンドで移動するようにした。
互換性がだいぶ犠牲になるが、どうせラズパイ専用なので気にしない。

char filename[] = "tmp.jpg";
char postcmd[] = "mv tmp.jpg /var/www/html/kinect/output.jpg";

void video_cb(freenect_device* dev, void* data, uint32_t timestamp)
{
        JSAMPROW jpg_row[1];

   ・・・

        fclose(fp);
        system(postcmd);
}

蛇足だが、Linux/Unix 系のファイルシステムでは読み込み途中のファイルが mv コマンドで変更されても、読み込んでファイルがクローズされるまでは読み込み側ではもとのファイルを参照し続けるので、このように mv で上書きしてしまってもブラウザで表示されるデータが混ざったり崩れたりする心配は無い。
FATやNTFSの場合は知らんが。

手動でリロードするのも何なので、JavaScriptで自動でリロードするようにしてあげれば、Kinectのカメラで撮った画像をぱらぱら漫画風の動画として見ることが出来る。

/var/www/html/kinect/index.html

<html>
<head>
<script language="javascript"><!--
function autoreload() {
    location.reload();
}
//--></script>
</head>
<body onload="setInterval('autoreload()',100)">
<img src="output.jpg">
</body>

このままでも良いのだが、リロードの時のちらつきが気になる。
見栄えの問題なので後回しにしようと思ったが、canvas を使えば意外と簡単に解決できそうなのでやってみた。

<html>
<head>
<script language="javascript"><!--
function loadimage() {
        var canvas = document.getElementById("cv");
        if( ! canvas || ! canvas.getContext ) { return false; }
        var ctx = canvas.getContext('2d');
        var img = new Image();
        img.src = "output.jpg?" + new Date().getTime();
        img.onload = function() {
                ctx.drawImage(img, 0, 0);
                setTimeout(loadimage, 100);
        }
}
//--></script>
</head>
<body onload="loadimage()">
<canvas id="cv" width="640" height="480"></canvas>
</body>

同じページでのリロードの場合は画像のキャッシュを無効にしても意味が無いようなので、結局ファイル名の後に"?"と日時を追加してブラウザに違うファイルとして認識させるようにする必要がある。

実態はパラパラ漫画なのだが、この程度でもLAN 環境なら動画と遜色ない感じで表示できる。
setTimeout はもっと短くても良いかもしれない。

とりあえずはこれでスタート地点と同じ RGB の動画(っぽいの)を表示するところまではたどり着いた。
せっかく頑張って libfreenect を導入したのだから、IR/Depth 画像の表示やチルト操作もできるように今後拡張していく。

0 件のコメント:

コメントを投稿