ラベル Wifiカメラ の投稿を表示しています。 すべての投稿を表示
ラベル Wifiカメラ の投稿を表示しています。 すべての投稿を表示

2013年11月2日土曜日

Raspberry Pi用カメラモジュールおよび赤外線カメラPi NoIRの映像をandroidで表示してみた

0. はじめに

Raspberry Pi用の赤外線カメラPi NoIRが発売されました。
赤外線カメラは使ったことがないので興味があり、早速注文してみました。

既存のカメラと同じくraspistillコマンドなどで使えるのですが、
今回、その映像をmjpg-streamerで配信し、androidで見てみることにします。
なお、本ページの内容は、Pi NoIRだけでなく通常のRaspberry Pi用カメラでも全く同様に成り立ちます。

なお、2016年4月、Raspberry Piのカメラモジュールのバージョン2が発売になりました。こちらでも本ページのプログラムは動作するのですが、NOOBS1.9.0までのRaspbianでは映像の向きが上下逆転してしまいます。次のRaspbianが出るまで待つことをお勧めします。上級者の方には「sudo rpi-update」でカーネルを最新にすれば正常動作することをお知らせしておきます。


1. 新しい方法


ここから解説するのは、下記のサイトで紹介されている方法です。
解像度640x480、30fpsで高画質で動作するのでおすすめの方法です。

1.1 カメラモジュール対応のmjpg-streamerのインストール

まず、あらかじめraspi-config でカメラを有効にしておいてください。
そして、下記の要領で、カメラモジュール対応のmjpg-streamerをインストールします。


sudo apt-get update
sudo apt-get install libjpeg8-dev cmake
git clone https://github.com/jacksonliam/mjpg-streamer.git mjpg-streamer
cd mjpg-streamer/mjpg-streamer-experimental
make
cd
sudo mv mjpg-streamer/mjpg-streamer-experimental /opt/mjpg-streamer

インストールが完了したら、下記の内容をユーザーpiのホームディレクトリ/home/piにstart_steam.shという名前で保存します。なお、このスクリプトでは解像度640x480、フレームレート15fpsでmjpg-streamerを起動します。お好みで設定を変更してください(解像度640x480、30fpsまでは安定動作を確認しました)

#!/bin/bash
 
if pgrep mjpg_streamer > /dev/null
then
  echo "mjpg_streamer already running"
else
  LD_LIBRARY_PATH=/opt/mjpg-streamer/ /opt/mjpg-streamer/mjpg_streamer -i "input_raspicam.so -fps 15 -q 50 -x 640 -y 480" -o "output_http.so -p 9000 -w /opt/mjpg-streamer/www" > /dev/null 2>&1&
  echo "mjpg_streamer started"
fi

次に、このスクリプトを/etc/rc.localに記述することで、自動起動します。そのために、エディタで/etc/rc.localを編集するのですが、その際、管理者モードでないと保存できませんので注意してください。viで編集する場合は下記のようにsudoをつけて起動します。

sudo vi /etc/rc.local

もともと、「exit 0」という行が最後に記されているのですが、その手前に下記のように1行追記します。

(中略)

sh /home/pi/start_steam.sh

exit 0  # ←この行はもともとある行

以上でRaspberry Piを再起動すると、mjpg-streamerが自動で起動します。


1.2 android側アプリで閲覧

PCのブラウザで、Raspberry Piが動いているIPアドレスの9000ポートを見ると(http://xx.xx.xx.xx:9000/)、
映像が配信されていることがわかります。このポート番号は、上のスクリプトstart_stream.shの内部で決定されています。

この映像をandroidで受け取るには、手前味噌ですが簡単MJPEGビューアを使うと簡単です。
このアプリのソースはこちらにあります。

アプリを起動したら、メニューから「設定」を選択し、下図のように、解像度を640x480に、「ポート番号」を9000に(キーボードで記述)、コマンドを「?action=stream」に設定してください。


設定を保存すると下記のように、Raspberry Piのカメラから配信された映像がandroid上に表示されます。
なお、この映像は暗い部屋で人形にテレビのリモコンの赤外線を当てて撮影しました。


以上で目的は達成です。

冒頭で紹介した「Raspberry Piで学ぶ電子工作」ではこのRaspberry Piとカメラモジュールの組み合わせをキャタピラ式模型に搭載し、iPadなどのタブレットのウェブブラウザから映像を見ながら操作する、という演習を取り入れています。下記の内容紹介動画のおよそ1:38からその様子が示されています。



若干映像が詰まるところが見られますが(およそ1:57)、これはRaspberry Piと用いたWifiドングルの相性の問題と考えています。

「Raspberry Pi 2 + kernel 4.1 / 4.4 系列で5GHz対応WifiドングルGW-450Dを動かした」 を参考にGW-450Dを用いるとこの「映像の詰まり」は解消されるのですが、kernelの再構築が必要なので、書籍では紹介できませんでした。

以上です。


なお、以下に記すのは古い内容なので読み飛ばして頂いて構いません。


2. 古い方法

ここから先は、このエントリを執筆した2013年11月当時の古い方法です。今この方法を用いる必要性はないのですが、記録として残しておきます。

2.1Raspbian側の準備

まず、あらかじめraspi-config でカメラを有効にしておいてください。

次に、OSを最新にします。

sudo apt-get update
sudo apt-get upgrade

さらに、ファームウェアを最新にします。 これを実行しておかないと後でmjpg-streamerを起動するときに「ERROR opening V4L interface: Interrupted system call」というエラーがでます。
(2014.6.5追記:なお、2014.5.6のRaspbianを用いたところ、このプロセスは不要になっていました)

sudo rpi-update

なお、アップデートを実行すると、Raspberry Piの起動時の「Mounting local filesystems」の際にmmcblk0に関するエラーが出て起動しなくなる場合があります。その場合、こちらに関連情報がありますが、SDカードを変更すれば状況が改善する場合があります。

私の場合、Transcend SDHC 8GB(class 10)で起動しなくなったので、古いTranscend SDHC 8GB(class 2)に変更したところ、問題が解決しました。

2.2 Video4Linuxの有効化

次に、Rapsberry PiのカメラをVideo4Linuxで用いるために、こちらを参考にVideo4Linuxを有効にします。まず、以下を実行。

wget http://www.linux-projects.org/listing/uv4l_repo/lrkey.asc && sudo apt-key add ./lrkey.asc

次に、/etc/apt/sources.listの末尾に下記の行を加えます。

deb http://www.linux-projects.org/listing/uv4l_repo/raspbian/ wheezy main

そして、以下を実行します。

sudo apt-get update
sudo apt-get install uv4l uv4l-raspicam

2.3 mjpg-streamerのインストール

こちらを参考に、mjpg-streamerをインストールします。

まず、インストールに必要なライブラリやヘッダなどをインストールします。

sudo apt-get install libjpeg8-dev libv4l-dev
sudo apt-get install imagemagick

ヘッダにリンクを貼ります。

sudo ln -s /usr/include/linux/videodev2.h /usr/include/linux/videodev.h

次に、mjpg-streamerの最新のソースをダウンロードするため、subversionをインストールします。

sudo apt-get install subversion

そしてmjpg-streamerのソースをダウンロードします。

svn co https://svn.code.sf.net/p/mjpg-streamer/code/mjpg-streamer mjpg-streamer

そして、以下のようにmakeします。

cd mjpg-streamer
make

エラーが出ずにビルドが完了すれば次に進みます。

2.4 mjpg-streamerの起動

mjpg-streamerをビルドしたディレクトリで作業します。

まず、Video4Linuxを有効にします(このコマンドはどのディレクトリでもOK)。なお、無効にするには pkill uv4lを実行します。この例は解像度320x240、フレームレート10fpsです。

uv4l --driver raspicam --auto-video_nr --width 320 --height 240 --framerate 10

そして、mjpg-streamerを起動します。やはり解像度320x240、フレームレート10fpsを指定しています。

export LD_LIBRARY_PATH="$(pwd)"
LD_PRELOAD=/usr/lib/uv4l/uv4lext/armv6l/libuv4lext.so ./mjpg_streamer -i "input_uvc.so -d /dev/video0 -r 320x240 -f 10 -no_dynctrl" -o "output_http.so -w ./www"

色々試してみたところ、フレームレートを大きくすると不安定になるようで、mjpg-streamerが落ちます。私の環境では解像度320x240、フレームレート10fpsでは安定して動きますので、どこまでいけるか色々試してみてください。

2.5 android側アプリで閲覧

PCのブラウザで、Raspberry Piが動いているIPアドレスの8080ポートを見ると(http://xx.xx.xx.xx:8080/)、
映像が配信されていることがわかります。

この映像をandroidで受け取るには、手前味噌ですが簡単MJPEGビューアを使うと簡単です。
このアプリのソースはこちらにあります。

アプリを起動したら、メニューから「設定」を選択し、下図のように、解像度を320x240に、ポート番号を8080に設定します。

設定を保存すると下記のように、Raspberry Piのカメラから配信された映像がandroid上に表示されます。
なお、この映像は暗い部屋で人形にテレビのリモコンの赤外線を当てて撮影しました。




2.6  mjpg-streamerの自動起動

さて、動作が確認できたところで、必要な方はRaspberry Piが起動するときにmjpg-streamerが自動で起動するようにしておきましょう。以下、Raspberry Piでの操作です。

mjpg-streamerのディレクトリが/home/pi/mjpg-streamerであるという前提で話を進めます。

cd ~/mjpg-streamer

このディレクトリに、start-mjpg-streamer.shというファイルを作成し、下記の内容を記述します。エディタはviでもnanoでも、お好みのもので構いません。

#!/bin/sh

uv4l --driver raspicam --auto-video_nr --width 320 --height 240 --framerate 10

cd /home/pi/mjpg-streamer

export LD_LIBRARY_PATH="$(pwd)"

LD_PRELOAD=/usr/lib/uv4l/uv4lext/armv6l/libuv4lext.so ./mjpg_streamer -i "input_uvc.so -d /dev/video0 -r 320x240 -f 10 -no_dynctrl" -o "output_http.so -w ./www"

次に、このスクリプトを/etc/rc.localに記述することで、自動起動します。そのために、エディタで/etc/rc.localを編集するのですが、その際、管理者モードでないと保存できませんので注意してください。viで編集する場合は下記のようにsudoをつけて起動します。

sudo vi /etc/rc.local

もともと、「exit 0」という行が最後に記されているのですが、その手前に下記のように1行追記します。

(中略)

sh /home/pi/mjpg-streamer/start-mjpg-streamer.sh &

exit 0  # ←この行はもともとある行

保存したらRaspberry Piを再起動してみてください。mjpg-streamerが自動で起動しているはずです。お疲れ様でした。




「ラズパイ4対応 カラー図解 最新 Raspberry Piで学ぶ電子工作」、「実例で学ぶRaspberry Pi電子工作」、「Raspberry Piではじめる機械学習」を執筆しました。

2013年10月10日木曜日

enchantMOONで三輪オムニロボットを操作してみた

はじめに

enchantMOONで三輪オムニロボットを操作してみました。
動画はこちら。




経緯など

以前、「Firefox OSとRaspberry Piで三輪オムニホイールロボットを操作してみた」というエントリで、JavaScriptの勉強のために、Firefox OSで三輪ロボットを操作するという実験を行いました。

三輪ロボット側のRaspberry Piでnode.jsを動かし、websocket.ioを用いてFirefox OSと通信を行う、というものでした。

Firefox OS+JavaScriptという組み合わせで操作できるなら、enchantMOON+JavaScriptでもいけるのでは?というのが今回の着想です。

しかし、それほど簡単ではなかったので、以下に注意点をまとめます。

注意点

公式のsocket.io.jsでは通信ができませんでした。また、サーバーであるロボット側は前回はwebsocket.ioを用いましたが、enchantMOONに合わせてサーバー側もsocket.io.jsを用いるように変更しました(こちらは公式のものでOK)。
  • enchantMOONのバージョンはver.2.6.0 (r1880)以上を用いる
これより前のバージョンでは、つながったりつながらなかったりと、通信が不安定でしたが、ver.2.6.0では安定しています。その理由は「enchantMOONのXHRでresponseTextの末尾にゴミデータがつく」問題が、version 2.6.0で改善されたからだそうです。esmasuiさん、ありがとうございます。

ソースの構成など

enchantMOON側のソースの構成は下記のようにしました。

hack.js
main.js
lib/MOON.js
lib/socket.io.js  : esmasuiさんのもの
lib/enchant.js

タッチイベントを使いたかったので、parachesさんの『enchantMOONで「簡易版とことんぷよぷよ」を遊べるようにしてみた』を参考にenchant.jsを使ってみました。

hack.jsの内容
importJS(['lib/MOON.js', 'lib/enchant.js', "lib/socket.io.js"], function() {

    var sticker = Sticker.create();
    sticker.ontap = function() {
        var script=document.createElement('script');
        script.src="main.js";
        script.type='text/javascript';
        script.language='javascript';
        document.body.appendChild(script);
    };
    sticker.register();
});

main.jsの内容の一部
enchant();

var URL = 'http://192.168.1.5:8888'; // サーバーのアドレス
var width = 240
var height = 320
var socket;
var game = new Game(width,height);

game.onload = function () {
    socket = io.connect(URL, {'transports': ["xhr-polling"]});

    var touchPanel = new Sprite(width,height);
    touchPanel.x = 0;
    touchPanel.y = 0;
    touchPanel.addEventListener('touchstart', function(e) {
        moonTouched(e.x, e.y);
    });
    touchPanel.addEventListener('touchmove', function(e) {
        moonTouched(e.x, e.y);
    });
    touchPanel.addEventListener('touchend', function(e) {
        stopMotors();
    });

    game.rootScene.addChild(touchPanel);
}
game.start();

function moonTouched(keyx, keyy) {
    //ここにタッチエリアに応じた処理を書く。
    //上の動画では、4つのモーターにデータを送りたいので、
    //コンマ区切りの数値を送っている。

    //例えば以下のように
    //socket.send('90,0,0,0');
}
function stopMotors() {
    //例えば以下のようにモーターを停止させている
    //socket.send('90,0,0,0');
}

コントロール画面はこんな感じです。やっつけで書きました。絵心がある人だったら、面白いコントロール画面を描けると思います。


2013年8月27日火曜日

Firefox OSとRaspberry Piで三輪オムニホイールロボットを操作してみた

Firefox OSを載せたXperia arcと、node.jsが動作するRaspberry Piで三輪オムニホイールロボットを操作してみました。動画はこちら。



以下、動画について解説していきます。

はじめに

最近、Google Glass、Firefox OS、Windows ストアアプリ、enchantMOONのようにJavaScriptを用いて開発する環境が増えています。

私のWeb関係の知識ははHTML3.2時代で止まっていて、このままではとてもこれらの流れについていけそうにないので、少し知識をアップデートすることにしました。

また、別ジャンルで名刺サイズの小型PCであるRaspberry Piが去年から流行っていますが、この流行にも乗り遅れた感があるので、合わせて触ってみようと思い立ちました。

というわけで、これらを満たす目標として
  • Firefox OSでnode.jsが動作するRaspberry PiとWebSocket通信し、ロボットを動かす
ことを目指してみます。

動かすロボットは、
で用いた三輪オムニホイールロボットとします。こちらはandroidアプリでbluetooth通信してロボットを動かしていましたが、こちらと同じ動作を目指します。

なお、androidでは画像処理による対象物追跡を行いましたが、さすがにJavaScriptで画像処理はキツイ気がするので今回は割愛しました。

システム

こんな感じのシステムとしました。Firefox OSを載せた端末としては、「Xperia arcとXperia ray向けにFirefox OSをビルドしてみた」で紹介したXperia arcを用います。

基本的に、Android上のChromeやFirefoxでも動作するように作成しているので、自前ビルド端末を使ったことによる影響は多分ないと思います。


node.jsを動かしてモーターを動かすなら、こちらでやられているようにmbedを用いた方がスマートな気がしますが、mbedは未体験ということもあり今回は見送りました。

方針が決まれば、あとは様々な方の成果を活用させて頂くだけ、ということで、以下でそれらへのポインタを示したいと思います。


Raspberry Pi上のnode.js

Raspberry Piにnode.jsをインストールし、Firefox OS(というよりブラウザ)からの指令を受け取れるようにする方法はInterface誌2013年9月号の記事「ARMコンピュータRaspberryPi×HTML5でスマートI/O」に書いてあります。

この例では、指令に基づいてRaspberry Piのピンに接続したLEDを点灯させていましたが、その部分をシリアル通信でのaruduinoへの指令に変更します。

Raspberry Piからのシリアル通信は、こちらのページのC言語のプログラムを用いました。リンク先の例ではRaspberry Pi上のピンでシリアル通信しているので/dev/ttyAMA0を用いていますが、今回私はUSBシリアルアダプタを用いたので/dev/ttyUSB0を指定します。(3.3V←→5Vのレベル変換が面倒だったので)

また、送る文字列ですが、コマンドラインの引数で与えた文字列をそのままaruduinoに送りたかったので、以下のようにしました。

main関数の引数部
int main(int argc, char *argv[]){

送信文字列の定義部
  p_tx_buffer = &tx_buffer[0];
  char *num_buffer = argv[1];

  while(*num_buffer != '\0')
  {
     *p_tx_buffer++ = *num_buffer++;
  }
  *p_tx_buffer++ = '\0';


なお、このシリアル通信プログラムを起動する部分のJavaScriptは以下のような感じです。「[value]」の部分がブラウザから送られる文字列で、serialtestという名前をつけた上のシリアル通信用プログラムの引数となります。

server.on('connection', function(socket) {
        socket.on('message', function(data){
                console.log('Receive: ' + data);
                send_to_arduino(data);
        });
});

var send_to_arduino = function (value){
        var spawn = require('child_process').spawn;
        var sendexec = spawn('./serialtest', [value]);
        sendexec.stdout.on('data', function(data){});
}

なお、「Raspberry PiとNode.jsで作る独立稼働モバイルサーバ (Think IT)」によると、node-serialportを用いるとnode.jsを通してRaspberry Pi/arduino間でJavaScriptで通信できるそうなので、そちらでもよさそうですね。

Firefox OSアプリ

Firefox OSアプリは実質Webアプリなので、HTML/CSS/JavaScriptで記述します。
  • Raspberry PiにSSHでログインしてエディタでJavaScriptを編集
  • Android上のChromeとFirefoxで動作確認
という手順で開発しました。ブラウザのリロードでアプリのアップデートが完了するのは、当たり前といえ新鮮な体験です。

AndroidのFirefoxで動作確認するとFirefox OSでもほぼ同じ動作をするので、あまりFirefox OSの出番はなく開発を進められました(Firefox OSの存在意義は…)。

今回は小さなアプリなのでそれほど苦労する部分はありませんでしたが、端末の画面の回転の検出にChromeとFirefoxで違いがあったので注意が必要でした。

  var mqOrientation = window.matchMedia("(orientation: portrait)");

  mqOrientation.addListener(function() {
    // 画面回転検出時の処理
  });
によってChromeとFirefox両方で画面回転を検出できます。

また、IP Webcamからの画像取得ですが、今回は簡易的に済まそうということで、IP Webcamのスナップショット画像を得る機能をひたすら呼び出してcanvasに描画し続けるという方法を取っています。


Firefox OSアプリのpackaged app化

Firefox OSでは、通常ブラウザ上で動作するWeb アプリを、zipで固めたpackaged appとして端末にインストールすることができます。

packaged appを作成するためのテンプレートとして今回はkassy-kzさんのFirefoxOS_Templateを用いました。

packaged appをインストールする方法としては
  1. マーケットからインストール
  2. Firefox OSシミュレーターからインストール
  3. ローカルネットワーク上のサーバーからのインストール
などがあります。私は公式端末であるKeonやPeakを購入しておらず、Nexus SやGalaxy S2などといった端末に自分でビルドしたFirefox OSをインストールするという方法でFirefox OSを試しています。

この自前ビルドの端末ですが、上記の2の方法でのインストールがうまくいきませんでした。Remote Debuggingを有効にしてはいるのですが。

仕方ないので、3の方法でインストールしました。リンク先の解説の通りですが、package.manifest は、アプリのmanifest.webappをコピーして、package_pathの行を追加するだけで良さそうでした。インストール後、「XXXX downloading...」という通知が、端末を再起動するまで消えないのですが、気にしないことにします。



感想など

Androidアプリで行っていたbluetoothでのロボット操作を、Firefox OSでも実現できました。ただし、いくつか気になる点もありました。

まず、モーターへ指令が到達するまでのステップ数がかなり増えているので若干遅延が発生することがあります。Wifiを利用しているので、Wifiの干渉が大きい場所ではこの遅延がより大きくなることが予想されます。

また、タッチイベントを全て送ると処理が追いつかなくなり、やはり遅延が大きくなるので、指令を送信する回数を減らすなどの工夫が必要な場面がありました。

一方、良い面ですが、Firefox OS用に作成したアプリはWebアプリなので、androidでもiOSデバイスでも動作する(はず)というのはなかなか魅力的に思えました。

ただ、どうしても機能が最大公約数的になりますし、結局各端末でデバッグしなければいけないということも起こりがちなので良し悪しかなあと思います。なんだかこの辺はJavaが出てきたときに言われたことの繰り返しですね。

あと個人的には、ビルドだけして放っておいたFirefox OS端末の使いみちができてよかったな、と。

そんな感じで今回は以上です。

こちらもどうぞ




「ラズパイ4対応 カラー図解 最新 Raspberry Piで学ぶ電子工作」、「実例で学ぶRaspberry Pi電子工作」、「Raspberry Piではじめる機械学習」を執筆しました。

2013年5月14日火曜日

スマートウォッチi'm watchのマーケットi'marketでアプリを公開してみた

スマートウォッチi'm watchを使ってこれまで
してきましたが、せっかくなのでマーケットであるi'marketにアプリを出してみることにしました。

モノはこの前GooglePlayに出しSimpleMjpegViewのi'm watch版で名前をMjpegViewとしました(名前の長さに制限があります)。

i'm watchはスマートフォンと連携して使うのですが、そのスマートフォンからネットワーク的にアクセスできるネットワークカメラからのMJPEGストリームを表示するアプリです。

スクリーンショットはこんな感じ。
ネットワークの設定画面はこんな感じです。
そして使用イメージはこんな感じ。
なお、i'm watchはbluetoothで母艦のスマートフォンと接続するため、データ量を極力小さくする必要があります。そのため、解像度やフレームレートなどを細かく設定可能なIP Webcamというアプリをandroid上で動かしてネットワークカメラとするのが現実的です。

IP Webcamの設定は以下の程度とするのが良いと思います。
  • Resolution: 320x240
  • Quality: 20-30
  • Orientation: Landscape
  • FPS Limit: 5
スマートウォッチi'm WatchでMJPEGストリームを表示してみた」や「スマートウォッチi'm WatchでタミヤのRCモデルを操作してみた」でやったように、ものを動かすコントローラーとしてi'm watchを使う場合、一緒にカメラ映像も表示してあげると、程よい未来感が出てカッコいいかなあ、なんて思います。

ソースはGitHubで公開してます。

2013年4月30日火曜日

簡単MJPEGビューア(SimpleMjpegView)をGoogle Playに公開してみた

Trek Ai-BallIP Webcam (Google Play)のようにMJPEGストリーミングを行うネットワークカメラをandroidで表示するためのサンプルアプリのソースを以前から公開していました(詳細)。

何人かの方が使ってくださっているようなのですが、カメラのIPアドレスがソースに直書きされているので使いにくいという意見がありました。

サンプルだからシンプルな方が良いだろう、と思ってIPアドレス変更用のインターフェイスをつけていなかったのですが、せっかくなので必要な機能を追加した上でGoogle Playに公開してみました。ソースもこれまで通り公開しています。

以下、リンクなど

<実行ファイル>
簡単MJPEGビューア(Google Play)

<ソース>
SimpleMjpegView (bitbucket)

<解説>
AndroidでWifiカメラからのMJPEGストリームを表示する

<画像処理の方法>
AndroidでWifiカメラから受け取った動画を画像処理する(1)ピクセルを直接操作する方法
AndroidでWifiカメラから受け取った動画を画像処理する(2)OpenCVを用いる方法

以前との違いは下記の通りです。

  • カメラのIPアドレスを設定するインターフェイスをつけた(デフォルトは http://192.168.2.1:80/?action=stream になっており、Trek Ai-Ballにつながる)
  • 解像度を可変にした(デフォルトは640x480だが、任意の解像度を指定可能)
ロボットなどに搭載して遊ぶのに良いと思います。

注意点としては、映像を受信する端末によって映像の滑らかさに差が出ることが挙げられます。これは以前の解説ページに書いた通りですが、Nexus 4 は特に遅い印象がありました。快適なのは以前も書いた通りXperia Arcなどです。




2013年4月22日月曜日

スマートウォッチi'm WatchでタミヤのRCモデルを操作してみた

スマートウォッチI'm WatchでタミヤのRCモデルを操作してみました。
動画はこちら


経緯


卒業生がTAMIYAのRCモデル「初音ミク Studie GLAD BMW Z4」を遺産として残してくれました。塗装もデカール貼りも気合いが入っています。


androidからbluetooth経由で操作できるよう改造されているのですが、スマートフォンでのラジコン操作は2010年の時点で既にいわたんさんによるすごい動画があるのでどう使おうか迷いどころでした。

しかしこのまま眠らせるのも惜しいと思い、手持ちのスマートウォッチi'm Watchで操作してみることにしました。ものを動かすためにi'm watchを利用するのは下記のプラレール操作に引き続き2つ目ということになります。
i'm Watchは血迷って2つ購入してしまったので、無理にでも積極的に活用しないと損をした気になってしまうのです。



i'm Watchと回路を連携するためには、母艦となるスマートフォンに中継してもらう方法を用いており、これは上記のプラレールと本質的には変わりません。

ただし、そのままではつまづくポイントがあったので、以下ではそれらをメモします。

技術的なこと


実現したいことは以下の通りです。

  • i'm WatchでRCモデルを操作できること
  • RCモデルにカメラを搭載し、i'm Watchから閲覧できること
プラレールの時に用いた方法を用いるなら、こんな感じになるでしょう。



なお、Xperia RayでBluetoothテザリングを行うには、カスタムロムをインストールする必要があります(ここではCM10-basedなJCROM)。が、実際に試してみると、i'm Watchのbluetoothは2、3m程度しか飛ばないことがわかり、RCモデルの操作には適していないことがわかります(プラレールの場合はi'm Watchを常にプラレールの近くに置いて操作していたので問題なかったのですが)。

そこで、実際に採用したのは以下の第2案です。


…なんというか、必要以上に複雑なシステムになってしまいました。実用性よりも「i'm Watchを無理にでも使うこと」が目的になってしまっている感がありますが、とにかくこうなっています(という自分用のメモ)。なお、Xperia ArcでBluetoothテザリングを行ってi'm watchの母艦とするために、カスタムロムをインストールして用いています(ここではCM10-basedなJCROM)。このXperia Arcは動画ではシャツの胸ポケットに入っています。

なお、RCモデル上のカメラを省略すれば、Xperia Rayは不要になって若干シンプルになります。

図では省略していますが、RCモデルの操作はオリジナルの操作系をそのまま活用するため、プロポの基板+ESCをそのまま車体内に搭載し、プロポ基板に対してPIC24F+DA変換チップ(MAX525)からアナログ電圧を与えることで操作しています。

Xperia ArcとPIC24FとのBluetooth通信には、いつも通りhrdakinoriさんのBT_DROIDを活用させて頂いております。

おしまい


i'm WatchでRCモデルを操作してみましたが、スマートウォッチとしての本来の目的(メール、スケジュール管理、SNS連携など)はどうかというと、何をやるにしてもスマホの使い勝手には敵わないというのが現実です。そのあたりの事情はWiredの「Smartwatchは本当に革新的なのだろうか?」という記事が参考になります。

そうはいっても、腕時計で通信をしたりものを動かしたりというのは「古き良き未来」を感じさせてワクワクさせるものがあります。個人的にはウルトラセブンのモロボシダンがキリシマ隊長とビデオシーバーを使ってテレビ電話をしていたのを良く覚えています。また、この作業をしているときにたまたま読んでいた「ファイブスター物語リブート1」でザ・ナイト・オブ・ゴールドの外部からの操作にやはりこの手のスマートウォッチ風のデバイスを用いていたのもイイ感じでした(1986年頃の作品です)。

今後数年でスマートウォッチがより進化して、我々の生活に浸透してくると面白いですね。

2013年1月24日木曜日

スマートウォッチi'm WatchでMJPEGストリームを表示してみた

前回のエントリでi'm Watchを用いてプラレールを操作する方法を紹介しました。今回は、MJPEGをストリーミングするタイプのWifiカメラの映像をi'm Watchで受け取って表示してみます。

いつものプラレールですが、使用例の動画が下記になります。


今回のポイントは、プラレール上のXperia Rayからの映像がMJPEGストリームとして送信され、i'm Watchがそれをデコードして表示している点です。以下で解説するように、動画はBluetoothテザリングで送られてきますので高速にはできず、毎秒5フレーム程度です。

なお、同時にプラレールの操作もi'm Watchで行っていますが、これは前回のエントリに基づいています。

仕組みとサンプルソース

システムの大まかな流れは下の図のようになります。

動画を送信するXperia Rayでは、Google PlayからインストールできるIP Webcamというソフトウェアを導入して実行しています。IPWebcamはサービスとしてバックグラウンドで実行できますので、他のアプリ(ここではBluetoothでPICマイコンに命令を送信するもの)を同時に実行できます。IP Webcamの設定の注意点は以下の通りです。上図で分かるようにデータはBluetoothで送られますので、データ量を減らすことが重要です。
  • Resolution: 320x240に。データ量削減のため
  • Quality: 30~50程度。低いほど画質が下がるが、データ量は減るので有効
  • Orientation: Landscape
  • FPS limit: 5以下に。一秒間に送られる画像の枚数
i'm Watch用の受信アプリですが、MJPEGのデコードに特化したサンプルのソースを
に置きました。このアプリをi'm Watchにインストールして利用します。注意点は以下の通りです。
  • i'm Watchを母艦(上の図ではXperia Ray)と接続し、Xperia Ray側でBluetoothテザリングを有効にします。なお、今回はXperia Rayにはカスタムロムをインストールしています(CM10-based JCROM)。
  • i'm Watch側のアプリから母艦のIPアドレスを指定する必要があります。GUIによる設定画面を用意しましたので、そちらで設定を行います(追記2013.4.30)。なお、androidでBluetoothテザリングを行うと母艦側は192.168.44.1になるようなので、その値を初期値としてあります。
このサンプルを使用しているイメージは下図のようになります。いい感じです。この図では母艦はCM10-based JCROMをインストールしたGalaxy S3です。


おわりに

届いた当初は何に使ったらよいのか途方に暮れたi'm Watchでしたが、adbが入ったファームウェアが公開され、自作アプリを試せるようになると俄然愛着がわきますね。時計としてはまあちょっと残念ですが(毎日充電しなきゃいけないし)。

expansysには色によって在庫があるようなので、気になる人は試してみては?

こちらもどうぞ


2013年1月21日月曜日

スマートウォッチi'm Watchでプラレールを操作してみた

android 1.6ベースのスマートウォッチi'm Watchでプラレールを操作してみました。早速ですが動画はこちら。



操作の流れは下図のようになります。

i'm Watchは母艦であるGalaxy S3とBluetoothテザリングによりネットワークを共有しているので、i'm WatchからGalaxy S3へソケット接続し、命令を送信します。Galaxy S3は受け取った命令をもとに、プラレール上のPIC24FマイコンへBluetooth命令を送信し、モーターを動かしています。なお、今回母艦であるGalaxy S3にはカスタムロム(CM10-based JCROM)をインストールしています。

i'm WatchとPICは直接通信できないの?

当初はi'm WatchとPICを直接通信できないかと思っていました。i'm Watchはandroid 1.6ベースなので、自作アプリからBluetoothを利用するときは(例えば)backport-android-bluetoothなどを使わねばならないのですが、実際にやってみると

のようにbackport-android-bluetoothが必要とするandroid.bluetooth.IBluetoothDeviceがないと言われたので今回は断念しました。

ソースとか

通信のサンプルソースは以下の通りです。
このサンプルの利用イメージは下図のようになります。

母艦からPICへの送信アプリは、hrdakinoriさんのDroidControlにサーバーソケット機能を追加して作成しています。hrdakinoriさんに感謝します。

なお、i'm Watch用アプリであるDroidControlImWatchでは、母艦のIPアドレスを指定する必要があります。設定用のGUIを用意しましたので、そちらで設定を行ってください(追記2013.4.30)。なお、androidでBluetoothテザリングを行うと母艦側は192.168.44.1になるようなので、その値を初期値としてあります。ポートは両方のアプリで指定する必要がありますが、今回は8081にしています(特に根拠はないです)。

参考にしたサイト

2012年12月29日土曜日

AndroidでWifiカメラから受け取った動画を画像処理する(2)OpenCVを用いる方法

前回、Wifiカメラから受け取った映像をピクセル操作によって画像処理してみました。しかし、簡単な処理でも記述量が多くなるという問題がありました。

そこで、今回はOpenCVと呼ばれるライブラリを用いてお手軽に画像処理をしてみます(といっても、OpenCVを用いるのは私も今回が初めてなのですが)。

準備として、前回の前半に書いてあるように、SimpleMjpegViewがandroid上で動作するようにしておいてください。前回の続きでも良いですし、新規に始めても構いません。新規の場合は図のように映像が表示されます。

OpenCVのインストール

まずはeclipseがインストールされたマシンにandroid用のOpenCVをインストールします。こちらからopencv-android→(最新の数字)とたどり、OpenCV-x.x.x-android-sdk.zipをダウンロードします。私が試したときの最新版はOpenCV-2.4.7.1-android-sdk.zipでした。解凍して現れるファイルを、例えばeclipseのワークスペースにコピーします。

次に、OpenCVのフォルダに含まれるsdk/javaフォルダをeclipseにインポートします。通常通り、「ファイル→インポート→既存プロジェクトをワークスペースへ」で行います。

次に、SimpleMjpegViewからOpenCVを参照する設定を行います。eclipse上のプロジェクトエクスプローラー上でSimpleMjpegViewを右クリック→プロパティーとたどり、項目の中のandroidをクリックすると、下半分にライブラリーという項目があるので、追加ボタンをクリックして、OpenCVライブラリを追加します。

最後に、アプリをインストールするandroid端末に、Google PlayからOpenCV Managerをインストールしておきます。

以上の準備が済んだら、ファイルの編集を行います。

ファイルの編集

まず、初期化処理のためにSimpleMjpegViewのsrcフォルダ以下にあるMjpegActivity.javaを編集します。冒頭のimport文に、下記の 3項目を追加します。
import org.opencv.android.OpenCVLoader;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
次に、MjpegViewのインスタンスmvの宣言の直後に下記の初期化処理を記述します(※ソース更新に合わせて、若干の変更 2013.1.30/2013.4.29)。
private MjpegView mv = null;
String URL;

// mv と URL の宣言の直後に下記を記述

private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) {
  @Override
  public void onManagerConnected(int status) {
    switch (status) {
      case LoaderCallbackInterface.SUCCESS:
      {
        Log.i(TAG, "OpenCV loaded successfully");
        setContentView(R.layout.main);
        mv = (MjpegView) findViewById(R.id.mv);
        if(mv != null){
          mv.setResolution(width, height);
        }
        new DoRead().execute(URL); 
      } break;
      default:
      {
         super.onManagerConnected(status);
      } break;
    }
  }
};
そして、同じファイル内で、MjpegViewをnewしている部分をみつけ、下記のようにコメントアウトした上で初期化処理の追記を行います(※ソース更新に合わせて、若干の変更 2013.1.30/2013.4.29)。
// 下記の6行を見つけてコメントアウトし、代わりに下記を追記

// setContentView(R.layout.main);
// mv = (MjpegView) findViewById(R.id.mv);
// if(mv != null){
//   mv.setResolution(width, height);
// }
// new DoRead().execute(URL);

if (!OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mOpenCVCallBack))
{
  Log.e(TAG, "Cannot connect to OpenCV Manager");
}
次に、画像処理の本体を記述します。SimpleMjpegViewのsrcフォルダ以下にあるMjpegView.javaを編集します。まず冒頭のimport文の中に、下記の四項目を追加します。
import org.opencv.android.Utils;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
次に、同じくMjpegView.java内で下記のように readMjpegViewを呼び出している部分を見つけます。前回の続きとして実行する場合は、imageprocessing関数が記述されていると思いますが、その場合はその行をコメントアウトしてください。

そしてその後ろに5行のOpenCV命令を記述します。これは画像のグレースケール化を行う処理なのですが、詳細は末尾に記すTechBoosterさんのサイトを参考にしてください。
if(bmp==null){
  bmp = Bitmap.createBitmap(IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888);
}
int ret = mIn.readMjpegFrame(bmp);
          
if(ret == -1)
{
  ((MjpegActivity)saved_context).setImageError();
  return;
}        
// 前回の続きとして実行している場合は下記1行をコメントアウトしておく
//imageprocessing(bmp);

//OpenCVによる画像処理
Mat bmp_mat = new Mat(IMG_HEIGHT, IMG_WIDTH, CvType.CV_8UC4);
Utils.bitmapToMat(bmp, bmp_mat);
Imgproc.cvtColor(bmp_mat, bmp_mat, Imgproc.COLOR_RGB2GRAY);
Imgproc.cvtColor(bmp_mat, bmp_mat, Imgproc.COLOR_GRAY2RGBA, 4);
Utils.matToBitmap(bmp_mat, bmp);
以上の記述ができたらandroidにアプリをインストールします。
成功すると、図のようにグレースケール画像が得られます。

このように、OpenCVを用いると少ない記述量で様々な処理を行うことができます。OpenCVが流行っている理由がわかる気がします。

計算時間の比較

前回の「(JNIによる)ピクセル直接編集」と今回のOpenCVの計算にかかる時間はどの程度か調べてみます。対象は今回取り扱ったグレースケール処理の部分のみで、かかった計算時間を100回以上平均をとって計測しました。結果は以下の通りです。

端末ピクセル直接編集OpenCV
Galaxy S3
cm10-based JCROM
7.7ms14.8ms (*)
Xperia Arc
cm10-based JCROM
13ms23ms

みてわかるように、2つの端末の両方でピクセル直接編集の方が高速であることがわかりました。といっても、私はOpenCVを今回初めて使ったので、もっとOpenCVを効率的に用いる方法があるのかもしれませんが。なお、参考までに、表中で (*) のついた 14.8ms について、OpenCVで用いた5行の画像処理命令の内訳を記すと下記のようになります。

Mat bmp_mat = new Mat(IMG_HEIGHT, IMG_WIDTH, CvType.CV_8UC4); 0.2ms
Utils.bitmapToMat(bmp, bmp_mat); 3.1ms
Imgproc.cvtColor(bmp_mat, bmp_mat, Imgproc.COLOR_RGB2GRAY); 6.5ms
Imgproc.cvtColor(bmp_mat, bmp_mat, Imgproc.COLOR_GRAY2RGBA, 4); 4.4ms
Utils.matToBitmap(bmp_mat, bmp); 0.6ms

RGB2GRAYがグレースケール化の実体だと思いますが、その前後のbitmapToMatとGRAY2RGBAに地味に時間がかかるのが痛いように思います。

おしまい

今回私も初めてのOpenCV体験でしたので、下記の薄い本とサイトを参考にさせて頂きました。ありがとうございました。

こちらもどうぞ

2012年12月28日金曜日

AndroidでWifiカメラから受け取った動画を画像処理する(1)ピクセルを直接操作する方法

前回はWifiカメラからのMJPEGストリームをandroidで受け取って表示する方法を紹介しましたが、今回は受け取った画像を画像処理してみます。取り扱うのは簡単なフィルタリングですが、この手法を発展させると、画像から対象物を見つけるというような処理も可能になります。

今回はandroidが持っているBitmap画像のピクセル値を直接処理する手法を取扱います。NDKを用いてCygwin上でC言語のモジュールをビルドする必要がありますので注意してください。

画像処理ライブラリであるOpenCVを用いる方法は次回紹介します。

準備として、前回紹介したSimpleMjpegViewをダウンロードし、eclipseからandroidにインストールできるようにしておいてください。ダウンロード法はいろいろありますが、「Downloads→Branches→masterのzip」と辿るのが簡単です。展開して得られたフォルダをお好みの場所(例えばeclipseのworkspace)に移動します。そのままではフォルダ名が長いので、短い名前に変更した方がNDKによるビルド時に楽です。

そのフォルダをeclipseに取り込むには「ファイル→インポート→一般→既存プロジェクトをワークスペースへ」とたどり、ルートディレクトリを設定して完了します。私はeclipseをpleiadesによって日本語化しているのでこのような表記になりますが、英語版を使っている方は適宜読みかえてください。




このアプリケーションをandroidにインストールして動作を確認しておきます。Wifiカメラは前回紹介した方法のどれでも構いません。Ai-Ballの場合は変更なしでそのまま動作します。スマホ+IP WebCamの場合、メニューからIP Webcamを動かすスマートフォンのURLをあらかじめ指定しておきます。どのようなURLを指定すべきかは、Wifiカメラ側のスクリーンに表示されていますので参照してください(URLの末尾には「videofeed」をつけます)。

もちろん、映像を見るandroidとWifiカメラは同一のネットワークに属している必要がありますので注意してください。

横画面で表示した結果は図の通りです。画質を落としたjpegが送られてきますので、静止画でみるとかなり粗いですね。

MjpegView.javaの編集

動作を確認したら、ここから画像処理に入っていきます。ここではグレースケール化を試してみます。src以下のMjpegView.javaの130行目に下記のような記述があるのを見つけます。
if(bmp==null){
  bmp = Bitmap.createBitmap(IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888);
}
int ret = mIn.readMjpegFrame(bmp);

if(ret == -1)
{
  ((MjpegActivity)saved_context).setImageError();
  return;
}
ここではBitmapクラスのインスタンスbmpのメモリを必要に応じて確保し、readMjpegFrameという関数によってWifiカメラからの映像をbmpに格納しています。なお、readMjpegFrameはC言語で書かれた関数であり、JNI経由で呼び出しています。

この処理が終わった時点でbmpにはWifiカメラから得た画像が格納されており、これがそのまま画面に表示されますので、これを加工すればそれがそのまま画面に現れます。やってみましょう。

ここでは、引数にBitmapをとるimageprocessingを関数を作ることを考えましょう。つまり、先ほどのreadMjpegFrameの後に下記の内容を追加します。
if(bmp==null){
  bmp = Bitmap.createBitmap(IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888);
}
int ret = mIn.readMjpegFrame(bmp);

if(ret == -1)
{
  ((MjpegActivity)saved_context).setImageError();
  return;
}               
// 下記のJNIによる画像処理を追加
imageprocessing(bmp);
次にこのimageprocessingをどのように実装するかですが、画像処理は非常に計算量が多く時間がかかる処理であるため、Javaでは書かずにJNIを利用してC言語で記述することにします。

そのために、同じくMjpegView.javaでIMG_WIDTHとIMG_HEIGHTを設定している部分を見つけ、その後ろに下記の4行を追加します。
public int IMG_WIDTH=640;
public int IMG_HEIGHT=480;

// 下記の4行を追加 
static {
  System.loadLibrary("ImageProc");
}
public native void imageprocessing(Bitmap bmp); 
これは、libImageProc.soという自作ライブラリを呼び出す設定と、その中の(これから作成する)imageprocessing関数の宣言を記述しています。

ImageProc.hとImageProc.cの編集

次に、imageprocessing関数をC言語のモジュールとして記述していきます。このアプリケーションでは、jpegのデコードで既にJNIによるC言語モジュールを利用していますので、そこに追記していく形式をとります。まず、jni/ImageProcフォルダにあるヘッダファイルImageProc.hを開き、末尾に下記の2行を追加します。
void Java_com_camera_simplemjpeg_MjpegView_imageprocessing(JNIEnv* env,jobject thiz, jobject bmp);
int getIndex(int i, int j);
一つ目がこれから作成するimageprocessing関数ですが、Javaにおけるパッケージ名とクラス名をアンダーバーでつないだ形になっていることに注意してください。これはJNIの規約です。また、2行目は画像の座標を配列のインデックスに変換する関数です。これはC言語の内部からしか用いないので、パッケージ名やクラス名は入っていません。

次にjni/ImageProcフォルダにあるImageProc.cを開き、末尾に下記の内容を追加します。
void Java_com_camera_simplemjpeg_MjpegView_imageprocessing(JNIEnv* env,jobject thiz, jobject bmp){

  AndroidBitmapInfo  info;
  int*              pixels;
  int                ret;
  int i,j;

  if(bmp==NULL) return;

  if ((ret = AndroidBitmap_getInfo(env, bmp, &info)) < 0) {
    LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
    return;
  }

  if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
    LOGE("Bitmap format is not RGBA_8888 !");
    return;
  }

  // Java側にあるBitmap画像のピクセルの配列をpixelとして利用
  if ((ret = AndroidBitmap_lockPixels(env, bmp, (void *)&pixels)) < 0) {
    LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    return;
  }

  //グレースケール化
  for(j=0 ; j<IMG_HEIGHT ; j++){
    for(i=0 ; i<IMG_WIDTH ; i++){
      int blue =(0xff0000 & pixels[getIndex(i, j)])>>16;
      int green =(0xff00 & pixels[getIndex(i, j)])>>8;
      int red =0xff & pixels[getIndex(i, j)];

      // 浮動小数演算を行うとパフォーマンスが悪いので整数演算で
      //int Y = (int)(0.299f*red + 0.587f*green + 0.114f*blue);
      int Y = (299*red + 587*green + 114*blue)/1000;
      blue = green = red = Y;

      pixels[getIndex(i, j)] = 0xff000000 | blue<<16 | green<<8 | red ;
    }
  }

  // ※ (この位置、後で使います)

  // 配列pixelのロックを解除して終了
  AndroidBitmap_unlockPixels(env, bmp);
}

// 座標を配列のインデックスに変換
// インライン関数にしないとパフォーマンスが悪い
inline int getIndex(int i, int j){
  return i + j*IMG_WIDTH;
}
imageprocessing関数の方は、「Javaから渡されたbitmapオブジェクトからピクセルの配列を取得→画像処理→ピクセルの解放」という流れで処理が進んでいます。この手法はandroid 2.2およびNDKバージョン4bにて導入された手法を用いていますので、それ以前のandroidでは動作しません。

得られたピクセルは、上位ビットから順に8ビットずつ不透明度、blue、green、redの順で並んでいます。これはJava側でBitmapを構築する際にBitmap.Config.ARGB_8888を指定したためです。不透明度はここでは使わずに0xffで固定しています。

グレースケールの輝度値(Y)を得るための演算は実数演算として定義されますが、floatで浮動小数点演算を行うとパフォーマンスが落ちますので、ここでは整数演算として実行します。浮動小数点演算でどれくらいパフォーマンスが落ちるかは後で試してみても良いでしょう。

また、画像の座標を配列に変換する関数getIndexはインライン関数として実装します。C言語のマクロ機能を用いても良いのですが、(後で)ビルドに用いるコンパイラのgccはC言語でもinline関数をサポートしていますので利用してみました。「inline」を削除して通常の関数とするとパフォーマンスが落ちますので、後で試してみても良いでしょう。

このように、androidでは通常のWindowsなどでのプログラミングの感覚で記述するとパフォーマンスが落ちることがあります。

以上を記述して保存したら、ビルドしてみます。

ビルド

上記のコードはC言語のコードを含んでいますので、ビルドにはAndroid NDK(Android Native Development Kit)を用いる必要があります。

NDKのセットアップはネットでは
などが詳しいです。また、書籍では出村成和著「Android NDKネイティブプログラミング」などがあります。

NDKのセットアップが済んだらビルドをCygwin上でコマンドラインで行います。eclipseのプラグインであるADTを最新にするとeclipse上でビルドできるはずなのですが、今回それを試したところ「ビルドは通るがエディタでエラーが消えない」状態になったので、面倒でもコマンドラインで実行してください。

ビルドの流れは
  1. Cygwinを起動し、「cd  (プロジェクトがある場所)」でプロジェクトのフォルダに移動
  2. 「ndk-build」を実行し、ビルドを行う
となります。

1.のフォルダの移動ですが、WindowsのCドライブのトップは「/cygdrive/c/」となることに注意してください。Cygwin解説サイトなどでフォルダの移動に慣れてからの方が良いかもしれません。

2.のビルドではwarningがいくつかでますが、最後に以下が表示されていればビルドに成功しています。

[x86] SharedLibrary  : libImageProc.so
[x86] Install        : libImageProc.so => libs/x86/libImageProc.so
ndk-buildによるC言語のモジュールのビルドが終わったら、androidにアプリをインストールします。ndk-buildを実行したのにandroidアプリに変更が反映されなかった場合、eclipseで「プロジェクト→クリーン」を実行して、apkを作り直せばよいでしょう。

実行すると、図のようなグレースケール画像が得られます。

エッジ画像風にしてみる

最後にもう一つ、画像処理らしさを出すために、エッジ画像風の画面表示にしてみます。ここでは数学的な厳密さよりも、記述のシンプルさと見た目のわかりやすさを重視して、「水平方向微分と垂直方向微分の絶対値和」を画像として表示してみます。

上で記述したグレースケールのコード中に「※」を記した位置があるのですが、そこに下記のコードを挿入してください。
  // ※部に下記を挿入

  // |水平微分| + |垂直微分| の整数倍を配列pixelsに格納
  for(j=0 ; j<IMG_HEIGHT-1 ; j++){
    for(i=0 ; i<IMG_WIDTH-1 ; i++){

      int Y1 =0xff & pixels[getIndex(i, j)];
      int Y2 =0xff & pixels[getIndex(i+1, j)];
      int Y3 =0xff & pixels[getIndex(i, j+1)];
      int Y4 =0xff & pixels[getIndex(i+1, j+1)];

      int x_diff = (Y1-Y2+Y3-Y4)/2; // 水平方向微分
      int y_diff = (Y1+Y2-Y3-Y4)/2; // 垂直方向微分

      //それぞれ絶対値をとる
      if(x_diff<0){
        x_diff = -x_diff;
      }
      if(y_diff<0){
        y_diff = -y_diff;
      }
      
      int diff = x_diff + y_diff;

      diff *= 5; // 画面表示用に明るくするため、任意数倍

      if(diff > 255){
        diff = 255; // 255以下に丸める
      }

      //緑色のエッジ風に表示
      pixels[getIndex(i, j)] = 0xff000000 |  diff<<8  ;
    }
  }
  //右端を黒に
  for(j=0 ; j<IMG_HEIGHT ; j++){
    pixels[getIndex(IMG_WIDTH-1, j)] = 0xff000000;
  }
  //下端を黒に
  for(i=0 ; i<IMG_WIDTH ; i++){
    pixels[getIndex(i, IMG_HEIGHT-1)] = 0xff000000;
  }
これをndk-buildによりビルドしてインストールすると下記のような画像が得られます。

なかなかいい感じです。


おしまい

以上、Wifiカメラから得られた画像をピクセルを直接操作することで画像処理してみました。

前回の紹介で「古い端末でも映像の取得が高速な場合がある」という趣旨の記述をしました。それはWifi経由での画像取得の速度についての記述だったのですが、今回の画像処理は、純粋に計算能力で速度が決まりますので、ほとんどの場合、新しい端末の方が高速に動作します。

ちなみに、最後に行った「エッジ画像風表示」をAi-Ballで行った場合、Galaxy S3では1秒以内に処理が完了しますが、Xperia Arcでは数秒の計算遅延が起こります。

実際の応用では無理に全画面を処理の対象とせず、画像の一部だけにするなど、何らかの工夫が必要になる場合もあります。そういう場合でもこのピクセルを直接いじる方法は柔軟に対応できます。

こちらもどうぞ

2012年12月17日月曜日

AndroidでWifiカメラからのMJPEGストリームを表示する

最近ひとりぶろぐさんのエントリ
でも紹介されていましたが、Ai-BallのようにMJPEGストリームを送信できるカメラが楽しいです。

ここではその映像をandroidで受信する方法について紹介します。利用形態は左図のようなイメージです。

サンプルコードはstack overflowの「Android ICS and MJPEG using AsyncTask」で既に紹介されています。しかし、このコードを用いると
  • メモリ使用量が多くGarbage Collectionが一秒間に多数回実行される
  • 端末によっては映像の遅れが1秒以上起こる
となり、少し性能が物足りないです。

MJPEGを表示するアプリケーションでGoogle Playでダウンロードできるものとしては「MJpeg Viewer」がありますが、これも同様の問題を抱えています。

この問題を解決するため、その高速版として「SimpleMjpegView」を作成して公開しました。
(ソースだけではなく、ビルド済みアプリもGoogle Playからダウンロードできるようにしました。2013.4.29)


メモリ使用量を削減し、jpegのデコードにJNI経由でlibjpegを利用しているのが主な変更点です。上記の問題に対しては下記のように解決されています。
  • Garbage Collection は1秒に1回以下(端末依存、後述)
  • 映像の遅れは1秒より大幅に短い(<< 1秒)
このコードをベースに作成したのが下記の、WifiカメラAi-Ballを搭載したプラレールです。


このプラレールを2012年10月20日(土)に仙台にて行われたICT ERA + ABC 2012 東北や2012年12月1日(土)、2日(日)に日本未来科学館で行われたMaker Faire Tokyo 2012にて展示させて頂きました。

ここでは、そこでの体験なども含め、MJPEGストリームをAndroidで表示する方法についてまとめてみたいと思います。

利用形態

(1)Wifiカメラ+スマホ

Trek Ai-Ballが用いている方式で、カメラ自体がアクセスポイントになります。メリットはとにかく小型であることです。

Planexからも多数のネットワークカメラが発売されていますが、それらで動作するかは試していません。

URLはデフォルトでは
http://192.168.2.1:80/?action=stream
となります。


(2)スマホ+Wifiルーター+スマホ

カメラデータ送信用スマートフォンにIP WebcamというフリーソフトをインストールしてWifiカメラ化し、そこからの映像をWifiルーター経由で受信するという方式です。(1)よりも無線が安定するのではないかと導入してみました。

URLはデフォルトでは
http://192.168.x.y:8080/videofeed
となります。

(3)スマホ(テザリング)+スマホ

(2)と同じですが、Wifiルーターの代わりにスマートフォンのテザリング機能を用いるものです。

部品点数が減るのがメリットですが、送信側のスマートフォンの電池持ちが悪くなるのがデメリットです。


(4)PC+Wifiルーター+スマホ

LinuxなどのUnix系PCにmjpeg streamerというソフトウェアを導入して、UVC対応のUSBカメラの映像をルーター経由で送信します。

様々なカメラを用いることができるのがメリットですが、PCとカメラを有線接続する必要があるため、規模が大きくなるのがデメリットです。

こちらの方法を(ノートPCなどでなく)Raspberry Piで使っている方々がいらっしゃいます。

(5)Raspberry Pi+カメラモジュール

(4)と少し似ていますが、こちらはUSBカメラではなく、Raspberry Pi専用のカメラモジュールを用いる方法です。画質も良いのでお勧めの方法です。下記で解説しています。



性能

stack overflowで公開されているサンプルと、性能改善したSimpleMjpegViewとで性能を比較したのが下記の表です。カメラは「利用形態」の(1)で紹介したTrek Ai-Ballを用いました。解像度640x480、30fpsの映像が送られてきます。

なお、たまたま手元にあった端末が全てカスタムOSが載ったものだったのですが、結果は公式OSでも変わりません。また、OSのバージョンにもあまり依存しないようです。

まず、オリジナルのサンプルから。

stack overflow のサンプル
端末 フレーム数 (fps) 1秒あたりのGC回数 時間遅れ (s)
Nexus 7
OS: 4.2.1, JCROM
15 25 << 1
Galaxy S3 (docomo)
OS: 4.1.2, cm10-based JCROM
17 4 << 1
Galaxy Nexus
OS: 4.2.1, JCROM
16 26 > 1
Xperia Arc
OS: 4.1.2, CM10-based JCROM
17 18 << 1
Nexus S
OS: 4.2.1, JCROM
11 30 > 1

ここで見るべきポイントは以下の通りです。
  • どの端末も、1秒間に多数回のGarbage Collection (GC)が実行される
  • フレーム数はそれほど悪くないが、1秒以上の映像の時間遅れがある端末がある
  • 新しい端末の方が性能が良いとは限らない。最新機種のGalaxy S3の性能が良い一方で、古い端末であるXperia Arcの性能も良い
一方、性能改善したアプリでは下記のようになります。

性能改善したSimpleMjpegView
端末 フレーム数 (fps) 1秒あたりのGC回数 時間遅れ (s)
Nexus 7
OS: 4.2.1, JCROM
17 1 << 1
Galaxy S3 (docomo)
OS: 4.1.2, cm10-based JCROM
17 およそ1/10 << 1
Galaxy Nexus
OS: 4.2.1, JCROM
17 1 << 1
Xperia Arc
OS: 4.1.2, CM10-based JCROM
17 1/4 << 1
Nexus S
OS: 4.2.1, JCROM
17 1 << 1

見るべきポイントは下記の通りです。
  • どの端末もGCの回数は秒間1回以下に改善(性能が良いのはやはりGalaxy S3とXperia Arc)
  • 時間遅れはどの端末でも1秒以下に改善された

端末による性能差

上の表で見た通り、映像表示の性能には端末による差があり、なおかつ新しい端末の方が性能が良いとは限らない、という現象が見られます。

性能改善したSimpleMjpegViewを用いる場合、映像表示するだけならそれほど差は見られませんが、上のプラレールの動画のように、画像処理に基づいて制御を行う場合、微妙な性能差が結果に影響を及ぼすことがあるので注意が必要です。

下記の端末で特に性能が悪かったのが印象的です。
  • Galaxy Nexus
  • Nexus S
上記のプラレールの動画でXperia Arcを用いているのは、古い端末で安価に入手できるにも関わらず映像表示の性能が良いためです。今後、Galaxy S3の価格が下がって入手しやすくなればそちらに乗り換えるかもしれません。

なお、この端末による性能差の原因はわかっていませんが、ハードウェアの違いやkernelのドライバに起因する問題ではないかと現在のところは考えています。

Wifiの干渉についての体験記

このようなWifiカメラの利用法の一つとして、自作のロボットなどに搭載して遊ぶ、というものが考えられます。個人で遊ぶぶんには何の問題もないのですが、大勢の人が集まる展示会でデモしようとすると、Wifiの干渉により映像が遅れるなどして、期待の動作をしないということがあります。

この現象は個人で再現することがなかなか難しく、有効な対策ができない場合が多いのですが、ここでは私が展示会で体験した経験をいくつか紹介したいと思います。

結論から先に述べると、「利用形態」の(3)で紹介した「スマホ(テザリング)+スマホ」が、Wifiの干渉に最も強い組み合わせでした。

2012年10月20日(土)ICT ERA + ABC 2012 東北(Trek Ai-Ball + Xperia Arc, 802.11g)

動画で紹介したプラレールを展示した初めての展示会でしたが、「利用形態」(1)で紹介したWifiカメラ(Trek Ai-Ball)で送信した映像をXperia Arcで受信しました。

この場合、映像が3~5秒くらい遅れて届いたため、画像処理に基づく制御はあきらめ、Bluetooth経由での手動制御を中心に紹介しました。なお、Trek Ai-BallはWifiのチャンネルを手動で変更する機能がついているのですが、それを行っても焼け石に水という感じでした。

2012年12月1日(土)Maker Faire Tokyo 2012 (Xperia Ray + Router + Xperia Arc, 802.11n)

ABC2012東北の約1か月後に行われたMaker Faire Tokyo 2012の初日です。Trek Ai-Ballでの展示はあきらめ、「利用形態」(2)で紹介した「スマホ+ルーター+スマホ」の組み合わせで行いました。

カメラはXperia RayにIP Webcamを入れてWifiカメラ化し、プラレールに搭載します(左図)。ルーターはNECのAtermWR8370N、受信はいつも通りXperia Arcです。

結果はボチボチ、といったところでした。カメラの映像は基本的にはスムーズに届き、画像処理に基づく制御もうまく機能しました。ただしやや不安定で、1~2分おきに映像が届かなくなる現象が起こりました。そんなときはルーターとスマホの位置関係を変えると突然映像が復帰することが多かったので、そのまま展示を続けることができました。

不安定ではありましたが、少なくとも昼から17時までの展示はなんとか乗り切ることができました。

2012年12月2日(日)Maker Faire Tokyo 2012 (Xperia Ray + Xperia Arc, 802.11g)

Maker Faire Tokyo 2012の2日目です。前日と同様に「スマホ+ルーター+スマホ」の組み合わせで行こうとしたのですが、前日と異なり映像が頻繁に途切れてうまく流れません。

想像ですが、恐らく会場では私と同様に2.4GHz帯の無線を使って何かデモをしようとした方が大勢いらっしゃったと思います。初日に無線の干渉の問題でデモに失敗した方が、2日目にリベンジとして別の無線機器を持ち込んだ、ということがあったかもしれません。

いずれにせよ前日の組み合わせではうまくいかなかったので、「利用形態」の(3)で紹介した「スマホ(テザリング)+スマホ」の組み合わせを試してみたところ、(何故か)非常に安定して映像が送受信でき、終日それで乗り切りました。1日目のルーターを噛ませる場合に比べて安定性はかなり上でした。

テザリングを行ったスマホはXperia Rayで、この上でIP Webcamも動作させました。テザリングはdocomoの公式OSでもカスタムOSでも問題なくできます(SIMは刺さずにデモしました)。ただし、当然電池の持ちは悪いので予備バッテリーは必須です。10:00~17:00の展示で、Rayの電池は2回交換したと思います。

Wifi干渉についてのまとめ

以上、展示会3日分の体験を紹介しました。少なくともこの3日間の体験から判断するなら、安定性は「スマホ(テザリング)+スマホ」で決まり、なのですが、もう少し検証をしたい気もします。展示会ほど無線が飛び交う状況をなかなか作れないので、新たに展示会に出展することでしか検証は難しいのですが。

あと、この3日間で用いた無線の規格は802.11gか11nのどちらかです。802.11aを用いたらどうなるだろう、という興味もありますが、この場合、映像の送信側も受信側も802.11aに対応している必要があります。個人的にはXperia SXが小型で11aに対応しているので送信側に適してそうな気がしています。

おしまい

以上、私自身の体験も交えてWifiカメラからのMJPEGストリームをandroidで表示する方法についてまとめてみました。

気が向いたら、画像処理の方法(ピクセルを直接いじる方法、OpenCVを用いる方法)や複数個のカメラからの映像を一つの端末で受信する方法などについても書いてみたいと思います。

こちらもどうぞ