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

2014年11月2日日曜日

Raspberry PiとArduinoで16x32 RGB LEDパネルにカメラモジュールの映像を表示してみた

はじめに

5月頃に秋月で「RGBフルカラードットマトリクスLEDパネル 16x32ドット」を購入し、長いこと放置していたのですが、少し時間ができたので試してみることにしました。

最近ずっと仕事で使っていたRaspberry Piで試すことにします。ただし、Raspberry Piに直接接続するわけではなく、

RaspberryPi―(シリアル通信)→Arduino→RGB LEDパネル

という構成にしました。そうすることで、Adafruitで公開されているArduino用のライブラリを流用することができます。

なお、この構成で始めた後に、やはりAdafruitでRaspberry Pi用のチュートリアルが出てることを知りました。そちらのチュートリアルでは画像ファイルをスクロールする例を取り扱っていますが、本ページでは、それとは異なる構成でRaspberry Piのカメラモジュールの映像をRGB LEDパネルに表示するところまでを試してみます(下図)。


準備

まず、AdafruitのArduino用のチュートリアルを元に、Arduinoで動作することを確かめてください。ArduinoのIDEでFile→Examples→RGBmatrixPanelとたどった時に「16x32」が含まれているサンプルは動作します。

用いるArduinoはチュートリアルにあるようにArduino Unoで良いですが、私は「Arduino Pro Mini 328 5V 16MHz」+「FT232RL搭載小型USB-シリアルアダプタ 5V」の組み合わせを用いました。ソースを少し変更するだけでArduino Megaでもいけるようですが、こちらでは試していません。

C++ & OpenCVの場合

それでは、Raspberry Piでの準備に入っていきます。Raspberry Piのカメラモジュールの映像を取得し、それをRGB LEDパネルに表示することを目指しますが、そのためにC++とOpenCVの組み合わせを用いることにします。

最初はPythonと画像処理ライブラリのPillowの組み合わせで試していたのですが、思った速度が出なかったので、C++とOpenCVの組み合わせに切り替えました。Python+Pillow版についてはページ末尾にメモを残しますので、興味のある方はどうぞ。

さて、Raspberry Pi、Arduino、RGB LEDパネルを下記のように接続しておきます。この場合、Arduinoの電源はRaspberry PiのUSBから供給されます。



さらに、Raspberry Piではカメラモジュールを使えるようにしておきましょう。「sudo raspi-config」で設定を起動し、「5. Enable Camera」→「Enable」とたどれば良いのでした。

次に、Raspberry PiでOpenCV、および後に必要なcmakeをインストールします。

sudo apt-get update
sudo apt-get install libopencv-dev cmake

次に、このRaspberry Piのカメラモジュールに対してOpenCVを用いることができるよう、「RaspiCam : C++ API for using Raspberry camera with/without OpenCv」をインストールします。まず、SourceForgeからraspicam-0.1.1.zip (129.5 kB)をダウンロードし、下記のコマンドでインストールします。

unzip raspicam-0.1.1.zip
cd raspicam-0.1.1
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig

このRaspiCamを用いると、Raspberry Piのカメラモジュールで取得した画像に対してOpenCVを利用できるので良さそうです。私は普段OpenCVを使っているわけではなく、RaspiCamも今回初めて触ったので、「良さそう」という程度しか言えないのですが。

次に、RGB LEDパネル用に私が用意したファイルを準備します。

cd
git clone https://github.com/neuralassembly/raspi_16x32RGBLED
cd raspi_16x32RGBLED
make

makeを実行した後、しばらく待つとビルドが完了します。画像ファイル表示用のimage2LEDmatrix、カメラモジュール表示用のcamera2LEDmatrixの2つの実行ファイルができています。 

さらに、Arduino用のserial2LEDmatrix.inoというC言語ファイルが存在します。このserial2LEDmatrix.inoをWindowsなどであらかじめArduinoに書き込んでおきましょう。 

このスケッチは、下図のように16x32x3バイトの階調(各階調は0~Fで表示)をシリアル通信で受け取り、「.」(ピリオド)を受け取ったタイミングでLEDパネルの内容を更新するように記述されています。



以下、image2LEDmatrixとcamera2LEDmatrixの使用法を解説してきます。

画像ファイルの表示 

image2LEDmatrixはSDカード上の画像をRGB LEDパネルに表示するプログラムです。練習兼動作検証用に書きました。

任意サイズの画像を与えることができます。画像の形式は、JPEGやPNGなど、OpenCVが読み込みに対応しているものです。動作モードは下記の2モードあります。

./image2LEDmatrix filename 0

末尾の0は省略することができます。こちらは下図のように画像の全体をスケーリングして表示します。なお、このページの画像は、見やすくするために全てRGB LEDパネルに紙をかぶせて撮影しています。


一方、下記の用に末尾に1をつけて起動すると、下図のように画像の左右または上下を捨て、なるべく画像を大きく表示します。

./image2LEDmatrix filename 1


Raspberry Piのカメラモジュールの映像の表示

カメラモジュールの映像を表示するには、下記のコマンドを実行します。

./camera2LEDmatrix

すると、下図のようにカメラモジュールの映像がRGB LEDパネルに表示されます。真面目に計測していませんが、体感で5fps以上は出ているのではないかと思います。

なお、フレームレートが低いのは、動作の安定化のためにwhileループ内でusleep関数によるウェイトを入れているためです。RaspiCam自体は約30fpsで映像を取得する能力がありますのでご注意ください。


やってみるとわかりますが(いや、やる前にわかるか)、解像度があまりにも小さいことと、LEDが明るすぎることとがあいまって、何が映っているかわからないことが多く、ちょっと厳しいです。

普通に周囲の映像を映すと、ほとんどのLEDがかなりの輝度で輝くので、1.0A以上の電流がパネルに流れてしまいます。

そのため、使いこなすには何らかの画像処理が必要そうです。せっかくOpenCVを使っているので、エッジ抽出、ビット反転、時間差分などを試したのですが、解像度の小ささから、何が起こっているのかわからず、イマイチでした。

何か面白い応用があれば、お知らせ頂ければ幸いです。

(おまけ)Pythonの場合

上で、C++とOpenCVでの例を紹介しましたが、その前にはPython + Pillowで試していました。この方法にはいくつか欠点があったため途中でやめてしまったのですが、メモとして記録を残しておきます。

準備

まず回路ですが、Python用いてArduinoとシリアル通信する場合は、こちらのstackoverflowにかかれているように、ArduinoのRESETとGNDの間に220μF程度のコンデンサを挟む必要があります。ただし、このコンデンサはスケッチ書き込み時は外してください。
(追記:すいません、コンデンサなしでも問題なく動くかもしれません。追試の必要あり)


次に、Raspberry Piでpipとpython-devとlibjpeg8-devをインストールします。

sudo apt-get update
sudo apt-get install python-pip python-dev libjpeg8-dev

次に、Pillow をインストールします。

sudo pip install pillow

以上の準備のもと、次に、RGB LEDパネル用に私が用意したファイルを準備します。

git clone https://github.com/neuralassembly/raspi_16x32RGBLED_Python

raspi_16x32RGBLED_Pythonの中に、画像ファイル表示用のimage2LEDmatrix.pyとカメラモジュールの映像表示用のcamera2LEDmatrix.pyとが入っています。

これらを用いるのは、C++版でも用いたserial2LEDmatrix.inoをArduinoに書き込んでおいてください。これは https://github.com/neuralassembly/raspi_16x32RGBLED に含まれています。

画像ファイルの表示

C++版と同様に、画像全体表示

python image2LEDmatrix.py filename 0

および、画像の切り出し表示

python image2LEDmatrix.py filename 1

に対応しています(末尾の0は省略できます)。画像ファイルのサイズも任意ですが、画像リサイズのアルゴリズム(PIL.Image.NEAREST、PIL.Image.BILINEAR、PIL.Image.BICUBIC、PIL.Image.ANTIALIAS)がイマイチなため、縮小後の画像があまり綺麗に表示されないことがあるのが欠点です(OpenCV版ではINTER_AREAを用いました)。

そのため、Python版ではWindowsのgimpなどで事前に画像を横32x縦16にリサイズしておいた方が良い場合があります。

Raspberry Piのカメラモジュールの映像の表示

カメラモジュールの映像を表示するには、下記のコマンドを用います。この辺りを参考にしました。

python camera2LEDmatrix.py

残念ながら、こちらはでは、2秒に1回程度しか映像が更新されません。そのため、Pythonをあきらめ、C++とOpenCVによる方法に移行したのでした。




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

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体験でしたので、下記の薄い本とサイトを参考にさせて頂きました。ありがとうございました。

こちらもどうぞ