前回、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ライブラリを追加します。Managerをインストールしておきます。以上の準備が済んだら、ファイルの編集を行います。ファイルの編集まず、初期化処理のためにSimpleMjpegViewのsrcフォルダ以下にあるMjpegActivity.javaを編集します。2013.1.30/2013.4.29)。2013.1.30/2013.4.29)。Manager");}次に、画像処理の本体を記述します。SimpleMjpegViewのsrcフォルダ以下にあるMjpegView.javaを編集します。冒頭のimport文の中に、下記の四項目を追加します。readMjpegViewを呼び出している部分を見つけます。前回の続きとして実行する場合は、imageprocessing関数が記述されていると思いますが、その場合はその行をコメントアウトしてください。その後ろに5行のOpenCV命令を記述します。これは画像のグレースケール化を行う処理なのですが、詳細は末尾に記すTechBoosterさんのサイトを参考にしてください。bmp);以上の記述ができたらandroidにアプリをインストールします。成功すると、図のようにグレースケール画像が得られます。このように、OpenCVを用いると少ない記述量で様々な処理を行うことができます。OpenCVが流行っている理由がわかる気がします。前回の「(JNIによる)ピクセル直接編集」と今回のOpenCVの計算にかかる時間はどの程度か調べてみます。対象は今回取り扱ったグレースケール処理の部分のみで、かかった計算時間を100回以上平均をとって計測しました。結果は以下の通りです。23msみてわかるように、2つの端末の両方でピクセル直接編集の方が高速であることがわかりました。いっても、私はOpenCVを今回初めて使ったので、もっとOpenCVを効率的に用いる方法があるのかもしれませんが。ついて、OpenCVで用いた5行の画像処理命令の内訳を記すと下記のようになります。0.6msRGB2GRAYがグレースケール化の実体だと思いますが、その前後のbitmapToMatとGRAY2RGBAに地味に時間がかかるのが痛いように思います。おしまい今回私も初めてのOpenCV体験でしたので、下記の薄い本とサイトを参考にさせて頂きました。ありがとうございました。前回は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の場合は変更なしでそのまま動作します。Webcamを動かすスマートフォンのURLをあらかじめ指定しておきます。どのようなURLを指定すべきかは、Wifiカメラ側のスクリーンに表示されていますので参照してください(URLの末尾には「videofeed」をつけます)。映像を見るandroidとWifiカメラは同一のネットワークに属している必要がありますので注意してください。横画面で表示した結果は図の通りです。画質を落としたjpegが送られてきますので、静止画でみるとかなり粗いですね。動作を確認したら、ここから画像処理に入っていきます。ここではグレースケール化を試してみます。src以下のMjpegView.javaの130行目に下記のような記述があるのを見つけます。return;}ここではBitmapクラスのインスタンスbmpのメモリを必要に応じて確保し、readMjpegFrameという関数によってWifiカメラからの映像をbmpに格納しています。readMjpegFrameはC言語で書かれた関数であり、JNI経由で呼び出しています。この処理が終わった時点でbmpにはWifiカメラから得た画像が格納されており、これがそのまま画面に表示されますので、これを加工すればそれがそのまま画面に現れます。ここでは、引数にBitmapをとるimageprocessingを関数を作ることを考えましょう。先ほどのreadMjpegFrameの後に下記の内容を追加します。下記のJNIによる画像処理を追加imageprocessing(bmp);次にこのimageprocessingをどのように実装するかですが、画像処理は非常に計算量が多く時間がかかる処理であるため、Javaでは書かずにJNIを利用してC言語で記述することにします。そのために、同じくMjpegView.javaでIMG_WIDTHとIMG_HEIGHTを設定している部分を見つけ、その後ろに下記の4行を追加します。これは、libImageProc.soという自作ライブラリを呼び出す設定と、その中の(これから作成する)imageprocessing関数の宣言を記述しています。次に、imageprocessing関数をC言語のモジュールとして記述していきます。このアプリケーションでは、jpegのデコードで既にJNIによるC言語モジュールを利用していますので、そこに追記していく形式をとります。jni/ImageProcフォルダにあるヘッダファイルImageProc.hを開き、末尾に下記の2行を追加します。j);一つ目がこれから作成するimageprocessing関数ですが、Javaにおけるパッケージ名とクラス名をアンダーバーでつないだ形になっていることに注意してください。これはJNIの規約です。2行目は画像の座標を配列のインデックスに変換する関数です。これはC言語の内部からしか用いないので、パッケージ名やクラス名は入っていません。次にjni/ImageProcフォルダにあるImageProc.cを開き、末尾に下記の内容を追加します。j*IMG_WIDTH;}imageprocessing関数の方は、「Javaから渡されたbitmapオブジェクトからピクセルの配列を取得→画像処理→ピクセルの解放」という流れで処理が進んでいます。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などでのプログラミングの感覚で記述するとパフォーマンスが落ちることがあります。以上を記述して保存したら、ビルドしてみます。Kit)を用いる必要があります。NDKのインストールと設定などが詳しいです。NDKネイティブプログラミング」などがあります。NDKのセットアップが済んだらビルドをCygwin上でコマンドラインで行います。eclipseのプラグインであるADTを最新にするとeclipse上でビルドできるはずなのですが、今回それを試したところ「ビルドは通るがエディタでエラーが消えない」状態になったので、面倒でもコマンドラインで実行してください。「ndk-build」を実行し、ビルドを行うとなります。1.のフォルダの移動ですが、WindowsのCドライブのトップは「/cygdrive/c/」となることに注意してください。Cygwin解説サイトなどでフォルダの移動に慣れてからの方が良いかもしれません。2.のビルドではwarningがいくつかでますが、最後に以下が表示されていればビルドに成功しています。libs/x86/libImageProc.sondk-buildによるC言語のモジュールのビルドが終わったら、androidにアプリをインストールします。ndk-buildを実行したのにandroidアプリに変更が反映されなかった場合、eclipseで「プロジェクト→クリーン」を実行して、apkを作り直せばよいでしょう。実行すると、図のようなグレースケール画像が得られます。エッジ画像風にしてみる最後にもう一つ、画像処理らしさを出すために、エッジ画像風の画面表示にしてみます。ここでは数学的な厳密さよりも、記述のシンプルさと見た目のわかりやすさを重視して、「水平方向微分と垂直方向微分の絶対値和」を画像として表示してみます。上で記述したグレースケールのコード中に「※」を記した位置があるのですが、そこに下記のコードを挿入してください。}これをndk-buildによりビルドしてインストールすると下記のような画像が得られます。おしまい以上、Wifiカメラから得られた画像をピクセルを直接操作することで画像処理してみました。前回の紹介で「古い端末でも映像の取得が高速な場合がある」という趣旨の記述をしました。それはWifi経由での画像取得の速度についての記述だったのですが、今回の画像処理は、純粋に計算能力で速度が決まりますので、ほとんどの場合、新しい端末の方が高速に動作します。Arcでは数秒の計算遅延が起こります。実際の応用では無理に全画面を処理の対象とせず、画像の一部だけにするなど、何らかの工夫が必要になる場合もあります。いう場合でもこのピクセルを直接いじる方法は柔軟に対応できます。Ai-Ballをハックする超小型無線LAN内蔵カメラAi-BallでiPhoneから自宅を監視可能にするでも紹介されていましたが、Ai-BallのようにMJPEGストリームを送信できるカメラが楽しいです。ここではその映像をandroidで受信する方法について紹介します。利用形態は左図のようなイメージです。AsyncTask」で既に紹介されています。Collectionが一秒間に多数回実行される端末によっては映像の遅れが1秒以上起こるとなり、少し性能が物足りないです。Viewer」がありますが、これも同様の問題を抱えています。この問題を解決するため、その高速版として「SimpleMjpegView」を作成して公開しました。Playからダウンロードできるようにしました。Play)メモリ使用量を削減し、jpegのデコードにJNI経由でlibjpegを利用しているのが主な変更点です。上記の問題に対しては下記のように解決されています。1秒)このコードをベースに作成したのが下記の、WifiカメラAi-Ballを搭載したプラレールです。2012にて展示させて頂きました。ここでは、そこでの体験なども含め、MJPEGストリームをAndroidで表示する方法についてまとめてみたいと思います。Ai-Ballが用いている方式で、カメラ自体がアクセスポイントになります。メリットはとにかく小型であることです。Planexからも多数のネットワークカメラが発売されていますが、それらで動作するかは試していません。URLはデフォルトではhttp://192.168.2.1:80/?action=streamとなります。WebcamというフリーソフトをインストールしてWifiカメラ化し、そこからの映像をWifiルーター経由で受信するという方式です。(1)よりも無線が安定するのではないかと導入してみました。URLはデフォルトではhttp://192.168.x.y:8080/videofeedとなります。(3)スマホ(テザリング)+スマホ(2)と同じですが、Wifiルーターの代わりにスマートフォンのテザリング機能を用いるものです。部品点数が減るのがメリットですが、送信側のスマートフォンの電池持ちが悪くなるのがデメリットです。streamerというソフトウェアを導入して、UVC対応のUSBカメラの映像をルーター経由で送信します。様々なカメラを用いることができるのがメリットですが、PCとカメラを有線接続する必要があるため、規模が大きくなるのがデメリットです。Piで使っている方々がいらっしゃいます。Pi専用のカメラモジュールを用いる方法です。画質も良いのでお勧めの方法です。下記で解説しています。overflowで公開されているサンプルと、性能改善したSimpleMjpegViewとで性能を比較したのが下記の表です。Ai-Ballを用いました。解像度640x480、30fpsの映像が送られてきます。手元にあった端末が全てカスタムOSが載ったものだったのですが、結果は公式OSでも変わりません。OSのバージョンにもあまり依存しないようです。オリジナルのサンプルから。1ここで見るべきポイントは以下の通りです。(GC)が実行されるフレーム数はそれほど悪くないが、1秒以上の映像の時間遅れがある端末がある新しい端末の方が性能が良いとは限らない。Arcの性能も良い一方、性能改善したアプリでは下記のようになります。1見るべきポイントは下記の通りです。上の表で見た通り、映像表示の性能には端末による差があり、なおかつ新しい端末の方が性能が良いとは限らない、という現象が見られます。性能改善したSimpleMjpegViewを用いる場合、映像表示するだけならそれほど差は見られませんが、上のプラレールの動画のように、画像処理に基づいて制御を行う場合、微妙な性能差が結果に影響を及ぼすことがあるので注意が必要です。下記の端末で特に性能が悪かったのが印象的です。Arcを用いているのは、古い端末で安価に入手できるにも関わらず映像表示の性能が良いためです。S3の価格が下がって入手しやすくなればそちらに乗り換えるかもしれません。この端末による性能差の原因はわかっていませんが、ハードウェアの違いやkernelのドライバに起因する問題ではないかと現在のところは考えています。Wifiの干渉についての体験記このようなWifiカメラの利用法の一つとして、自作のロボットなどに搭載して遊ぶ、というものが考えられます。個人で遊ぶぶんには何の問題もないのですが、大勢の人が集まる展示会でデモしようとすると、Wifiの干渉により映像が遅れるなどして、期待の動作をしないということがあります。この現象は個人で再現することがなかなか難しく、有効な対策ができない場合が多いのですが、ここでは私が展示会で体験した経験をいくつか紹介したいと思います。結論から先に述べると、「利用形態」の(3)で紹介した「スマホ(テザリング)+スマホ」が、Wifiの干渉に最も強い組み合わせでした。Arcで受信しました。この場合、映像が3~5秒くらい遅れて届いたため、画像処理に基づく制御はあきらめ、Bluetooth経由での手動制御を中心に紹介しました。Ai-BallはWifiのチャンネルを手動で変更する機能がついているのですが、それを行っても焼け石に水という感じでした。Ai-Ballでの展示はあきらめ、「利用形態」(2)で紹介した「スマホ+ルーター+スマホ」の組み合わせで行いました。Webcamを入れてWifiカメラ化し、プラレールに搭載します(左図)。結果はボチボチ、といったところでした。カメラの映像は基本的にはスムーズに届き、画像処理に基づく制御もうまく機能しました。不安定で、1~2分おきに映像が届かなくなる現象が起こりました。そんなときはルーターとスマホの位置関係を変えると突然映像が復帰することが多かったので、そのまま展示を続けることができました。不安定ではありましたが、少なくとも昼から17時までの展示はなんとか乗り切ることができました。2012の2日目です。前日と同様に「スマホ+ルーター+スマホ」の組み合わせで行こうとしたのですが、前日と異なり映像が頻繁に途切れてうまく流れません。想像ですが、恐らく会場では私と同様に2.4GHz帯の無線を使って何かデモをしようとした方が大勢いらっしゃったと思います。初日に無線の干渉の問題でデモに失敗した方が、2日目にリベンジとして別の無線機器を持ち込んだ、ということがあったかもしれません。いずれにせよ前日の組み合わせではうまくいかなかったので、「利用形態」の(3)で紹介した「スマホ(テザリング)+スマホ」の組み合わせを試してみたところ、(何故か)非常に安定して映像が送受信でき、終日それで乗り切りました。1日目のルーターを噛ませる場合に比べて安定性はかなり上でした。Webcamも動作させました。テザリングはdocomoの公式OSでもカスタムOSでも問題なくできます(SIMは刺さずにデモしました)。電池の持ちは悪いので予備バッテリーは必須です。10:00~17:00の展示で、Rayの電池は2回交換したと思います。Wifi干渉についてのまとめ以上、展示会3日分の体験を紹介しました。少なくともこの3日間の体験から判断するなら、安定性は「スマホ(テザリング)+スマホ」で決まり、なのですが、もう少し検証をしたい気もします。展示会ほど無線が飛び交う状況をなかなか作れないので、新たに展示会に出展することでしか検証は難しいのですが。あと、この3日間で用いた無線の規格は802.11gか11nのどちらかです。802.11aを用いたらどうなるだろう、という興味もありますが、この場合、映像の送信側も受信側も802.11aに対応している必要があります。SXが小型で11aに対応しているので送信側に適してそうな気がしています。おしまい以上、私自身の体験も交えてWifiカメラからのMJPEGストリームをandroidで表示する方法についてまとめてみました。気が向いたら、画像処理の方法(ピクセルを直接いじる方法、OpenCVを用いる方法)や複数個のカメラからの映像を一つの端末で受信する方法などについても書いてみたいと思います。