AndroidでOpenCVを使ってみよう
AndroidでOpenCVを使ってみよう
第1回 開発ツールの用意
OpenCVはオープンソースの画像処理ライブラリです。
静止画の処理(イメージングと呼ばれることが多い)だけではなく、OpenCVのCVはComputer Visionの略である通り、画像解析などの処理も豊富で、その応用範囲は多岐にわたっています。 画像処理のコードを一から作成するより、まずはOpenCVに使いたい処理が用意されている場合は、そちらを使った方が効率的でしょう。 ただし、OpenCVはC/C++で記述されているので、利用にはC/C++のプログラミング知識が必要です。
ところで、iPhoneやAndroidのアプリ開発も注目されていますね。
Androidは搭載デバイス的にも面白いハードウエアだと思います。
Androidの開発環境はJavaがベースです。OpenCVを利用するにはNDKと言うC++ネイティブ用のSDKが必要でしたし、その敷居は高いものでした。
最近になり、OpenCV for Androidと言うJavaから利用できるOpenCVが登場し、敷居が敷居が下がりました。
ここではそのOpenCV for Androidを使ってビデオキャプチャ画像に簡単な処理を行うプログラミングを扱います。
必要な開発ツール
JDK6.0
Eclipse
Android SDK
Android Development Tool (ADT)
OpenCV packege for Android
JDK6.0 / Eclipse
上記をそれぞれインストールしても良いですが、Eclipseの日本語化プラグインPleiadesをインストールするのがお勧めです。
Pleiadesのダウンロード先
「Eclipse 3.7 Indigo Pleiades All in One」 or 「Eclipse 3.6 Helios Pleiades All in One」が良いでしょう。
All in OneにはJDK6.0も含まれています。
またグレードですが「Java」でもOKなのですが、OpenCVを使っていくと将来NDKなどが必要になってくることもあるので、 最初からC/C++(CDT)が含まれているUltimateを選んでおくと良いかもしれません。(HDに余裕がある場合)
Full All in One (JRE あり)版とStandard All in One (JRE なし) 版がありますが、「お勧め」となっているFull All in One (JRE あり)の方が無難でしょう。
インストールはダウンロードしたZIPを解凍し、作成されたフォルダをお好きな場所に移動するだけです。
eclipseフォルダの下にeclipse.exeがあるので、それをダブルクリックするとEclipseが立ち上がります。
ショートカットを作成しておくと便利でしょう。
Android SDK
ここからダウンロードします。
Windowsの場合はインストーラータイプの方が便利です。
インストールが終わったら、Eclipseを立ち上げます。
「ウィンドウ」メニューの「設定」をクリックして、設定ダイアログの左の一覧から「Android」を選択します。
「SDKロケーション」にAndroid SDKをインストールしたパスを指定します。
インストーラータイプでインストールした場合は、
C:¥ProgramFiles¥Android¥android-sdk
のようになっているかと思います。(OSが64bitの場合はProgram Files (x86)の下に入ります)
次に「ウィンドウ」メニューの「Android SDK Manager」を選択します。
ダイアログ上のリストから、基本的にはすべてを選択してインストールすれば良いですが、 OpenCVを扱うには、最低でも Android SDK Tools, revision12 より新しいもの SDK Platform Android 2.2, API 8,revision 2 が必要になります。
続けて「ウィンドウ」メニューの「AVD Manager」を選択します。
ダイアログ上の「新規」ボタンを押し、PC上で動作するエミュレータを作成します。 OpenCVではAndroid 2.2以降である必要あります。以下の様な例を一つ作っておくと良いでしょう。
名前: sdk2.2
ターゲット:Android 2.2 - API Level 8
SD Card サイズ: 64 MiB
Skin ビルトイン: WVGA800
ADT
Eclipseの「ヘルプ」メニューの「新規ソフトウエアのインストール...」を選択します。
インストールダイアログ上の「追加(A)...」ボタンを押します。
リポジトリーの追加ダイアログが表示されますので、
名前: ADT Plugin
ロケーション: https://dl-ssl.google.com/android/eclipse/
を入力し、「OK」ボタンを押します。
インストールダイアログのリストから「Developer Tools」をチェックして「次へ」ボタンを押します。
次の画面も「次へ」ボタンを押します。
ライセンスの画面で同意し、「完了」ボタンを押すとプラグインのインストールが始まります。
再起動の確認が出たら、Eclipseを再起動してください。
OpenCV for Android
OpenCV for Androidはここから入手します。
ページの中程に「download」があると思います。
「アンドロイド上でOpenCVアプリの開発をするには2つの基本的な方法がある」と書かれています。 ここでは「お勧め」となっている、プレビルドのパッケージの方をダウンロードしましょう。
「prebuilt OpenCV package」のリンクをクリックするとSourceForgeに飛びます。
OpenCV-2.3.1-android-bin.tar.bz2をダウンロードしてください。
tar.bz2と言うあまり使わない(?)ようなアーカイブファイルになっていますが、Lhaplusなどで解凍できると思います。 解凍してできたフォルダをお好きなところに移動してください。
次に、Eclipsを立ち上げます。
ワークスペースは皆さんがお使いのワークスペースでOKです。
「ファイル」メニューの「インポート」を選択します。
「インポート」ダイアログで「一般」「既存プロジェクトをワークスペースへ」を選択し「次へ」ボタンを押します。
ルートディレクトリの選択のラジオボタンを選び、「参照」ボタンを押し、先ほどのOpenCVのフォルダの下にある「OpenCV-2.3.1」フォルダを選択します。
プレビルド版を選んでいるので、インストールはこれで完了です。
次回の記事でカメラキャプチャーのプログラムを作成します。
////
第2回 カメラキャプチャの作成
まずは、Eclipseを立ち上げます。
「ファイル」メニューの「新規」「Androidプロジェクト」を選択します。
プロジェクト名に「OpenCV_Capture」と入力し「次へ」ボタンを押します。
ビルドターゲットは「Android 2.2」にチェックを入れ「次へ」ボタンを押します。
パッケージ名に「com.pronowa.android.OpenCV_Capture」と入力し「完了」ボタンを押します。
パッケージ・エクスプローラーに「OpenCV_Capture」プロジェクトが追加されます。
Androidプロジェクトは初期の状態ですと内蔵カメラが利用できなくなっています。 利用できる様にするには、AndroidManifest.xmlに記述を追加する必要があります。
パッケージ・エクスプローラーの「AndroidManifest.xml」をダブルクリックするとAndroid Manifestの編集ができる様になります。 xmlファイルを直接編集しますので、「AndroidManifest.xml」タブを選択してください。 初期の状態は以下になっていると思います。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pronowa.android.OpenCV_Capture"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".OpenCV_CaptureActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name=
"android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
これを以下の様にしてください。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pronowa.android.OpenCV_Canny"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".OpenCV_CannyActivity"
android:screenOrientation="landscape" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name=
"android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
</manifest>
android:screenOrientation="landscape" はキャプチャー画面を横長(Androidを横向けに持った状態)に指定します。
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
の3行でカメラを利用できる様に指定します。
次に「OpenCV_Capture」プロジェクトを右クリックして「プロパティ」を選択します。
「Android」を選択し「ライブラリー」のところにある「追加」ボタンをおします。
「プロジェクトの選択」ダイアログから「OpenCV-2.3.1」を選択し、「OK」ボタンを押します。 これでOpenCVのライブラリが利用できる様になります。
プロジェクトの下にあるsrcを展開し、com.pronowa.android.OpenCV_Captureを右クリックします。
「新規」「クラス」を選択します。
ダイアログで、名前に「OpenCV_CaptureView」と入力します。
スーパークラスの「参照」ボタンを押して、「型を選択してください」の下のエディットボックスに「SurfaceView」と入力します。
一致する項目に「SurfaceView」と表示されたら、それを選択状態にして「OK」ボタンを押します。
インターフェイス「追加」ボタンを押して、「インターフェイスを追加してください」の下のエディットボックスに「Callback」と入力します。
リストから「android.vew.SurfaceHolder:Callback」を選択して「OK」ボタンを押します。
もう一度「追加」ボタンを押して、今度は「Runnable」と入力します。
リストから「java.lang.Runnable」を選択して「OK」ボタンを押します。
パッケージ・エクスプローラーのOpenCV_CaptureActivity.javaをダブルクリックしエディタを開き、以下のコードを入力します。
package com.pronowa.android.OpenCV_Capture;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class OpenCV_CaptureActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new OpenCV_CaptureView(this));
}
}
Eclipseが自動でコード生成をしているので、setContenViewを書き換えるだけでOKです。
次にOpenCV_CaptureView.javaをダブルクリックして、エディタを開き、各メソッドの中身を記述します。
package com.pronowa.android.OpenCV_Capture;
import java.util.List;
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.core.Size;
class OpenCV_CaptureView
extends SurfaceView implements SurfaceHolder.Callback, Runnable
{
private SurfaceHolder m_surfaceHolder;
private VideoCapture m_videoCapture;
private Mat m_rgbaMat;
public OpenCV_CaptureView(Context context)
{
super(context);
m_surfaceHolder = getHolder();
m_surfaceHolder.addCallback(this);
}
public void surfaceChanged(
SurfaceHolder holder, int format, int width, int height)
{
synchronized (this)
{
if (m_videoCapture != null && m_videoCapture.isOpened())
{
List<Size> sizes =
m_videoCapture.getSupportedPreviewSizes();
int frameWidth = width;
int frameHeight = height;
double minDifference = Double.MAX_VALUE;
for (Size size : sizes)
{
if (Math.abs(size.height - height) < minDifference)
{
frameWidth = (int)size.width;
frameHeight = (int)size.height;
minDifference = Math.abs(size.height - height);
}
}
m_videoCapture.set(
Highgui.CV_CAP_PROP_FRAME_WIDTH, frameWidth);
m_videoCapture.set(
Highgui.CV_CAP_PROP_FRAME_HEIGHT, frameHeight);
}
m_rgbaMat = new Mat();
}
}
public void surfaceCreated(SurfaceHolder holder)
{
m_videoCapture = new VideoCapture(Highgui.CV_CAP_ANDROID);
if (m_videoCapture.isOpened())
{
(new Thread(this)).start();
}
else
{
m_videoCapture.release();
m_videoCapture = null;
}
}
public void surfaceDestroyed(SurfaceHolder holder)
{
if (m_videoCapture != null)
{
synchronized (this)
{
m_videoCapture.release();
m_videoCapture = null;
}
}
}
public void run()
{
while (true)
{
Bitmap bmp = null;
synchronized (this)
{
if (m_videoCapture == null)
break;
if (!m_videoCapture.grab()) {
break;
}
m_videoCapture.retrieve( m_rgbaMat,
Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
bmp = Bitmap.createBitmap(
m_rgbaMat.cols(), m_rgbaMat.rows(),
Bitmap.Config.ARGB_8888);
if (Utils.matToBitmap(m_rgbaMat, bmp) == false)
{
bmp.recycle();
bmp = null;
}
}
if (bmp != null)
{
Canvas canvas = m_surfaceHolder.lockCanvas();
if (canvas != null)
{
canvas.drawBitmap(bmp,
(canvas.getWidth() - bmp.getWidth()) / 2,
(canvas.getHeight() - bmp.getHeight()) / 2,
null);
m_surfaceHolder.unlockCanvasAndPost(canvas);
}
bmp.recycle();
}
}
synchronized (this)
{
if (m_rgbaMat != null)
{
m_rgbaMat.release();
}
m_rgbaMat = null;
}
}
}
「プロジェクト」メニューの「自動的にビルド」がチェックされていれば、ソースが変更されているときにも同時にビルドしてくれます。
入力ミスがあった場合は、エディタの左端とパッケージ・エクスプローラーのjavaファイルの横に×が表示されますので、ソースを修正してください。 ただし、まだ定義されていないメソッドの呼び出しを先に記述した場合も×になってしまいますが、その場合は、後でメソッドを記述すると消えるので気にしないでください。
Android端末上で作成したアプリを実行するには、プロジェクト名を右クリックして「実行」「実行の構成」を選択します。 名前に「OpenCV_Capture」と入力し、プロジェクト欄の「参照」ボタンを押し「OpenCV_Capture」プロジェクトを選択し「OK」ボタンを押し、戻ります。
「ターゲット」タブを選択し、「手操作」ラジオボタンをチェックします。 Android端末をUSBでPCとつないでおく必要がありますが、PCとの接続方法は端末ごとにことなるので、ここでは割愛させてください。
(端末の「アプリケーション」の設定を「提供元不明のアプリ」をチェックし、「USBデバッグ」モードにする方法も割愛しますm(_ _)m)
あと、OpenCVが動作するのはAndroid 2.2以降を搭載した端末だけですので、ご了承ください。
「実行」ボタンを押し、Android Device Chooserで、接続されたデバイスを選択し、「OK」ボタンを押すと、端末にインストールされたのち、アプリが実行されます。 画面上にカメラのキャプチャー(静止画では無く、動画で表示されます)が表示されたら成功です。 うまく行かない場合は、開発環境の設定からもう一度見直してください。
/////
第3回 カメラキャプチャの解説
OpenCV_CaptureActivity.javaの解説
自動生成されたソースからの変更点は以下の1行です。
setContentView(new OpenCV_CaptureView(this));
でビューをOpenCV_CaptureViewに設定しています。
OpenCV_CaptureView.javaの解説
まずは、android関連のインポートを行います。
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.graphics.Bitmap;
import android.graphics.Canvas;
次に、OpenCV関連のインポートです。
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.core.Size;
OpenCV_CaptureViewクラスの宣言は以下になっています。
class OpenCV_CaptureView
extends SurfaceView implements SurfaceHolder.Callback, Runnable
OpenCV_CaptureViewクラスは、SurefaceViewクラスを継承し、SurfaceHolder.CallbackインタフェースとRunnableインターフェイスを実装しています。 カメラのキャプチャー画像など表示するにはSurfaceViewを継承する必要があります。
キャプチャー画面の表示はビデオ再生のようになるので、頻繁に画面を描画することになります。SurfaceViewはメインのスレッドとは別のスレッドで描画を行うので、今回のようなケースに使うためのクラスです。
SurfaceHolder.Callbackインターフェイスでは、以下のメソッドを実装する必要があります。 surfaceCreatedメソッドはSurfaceViewの画面が作られたときに呼ばれます。
surfaceChangedメソッドはSurfaceViewの画面が変化したときに呼ばれ、surfaceDestroyedは削除されるときに呼ばれます。
Runnableインターフェイスはスレッドに用いられ、runメソッドの中にスレッドで実行する処理を実装します。
OpenCV_CaptureViewクラスのメンバ変数の説明です。
SurfaceHolder m_surfaceHolder;
SurfaceViewの設定にはSurfacHolderクラスのオブジェクトを介して行います。
VideoCapture m_videoCapture;
VieeoCaptureクラスはOpenCVに用意されたカメラから動画をキャプチャーするクラスです。
Mat m_rgbaMat;
MatクラスもOpenCVのクラスで画像データを扱うためのものです。(厳密にはマトリックス用のクラスですが、画像も2次元のマトリックスと言うことでMatクラスが担当しているようです)
OpenCV_CaptureViewクラスのコンストラクタの説明です。
m_surfaceHolder = getHolder();
SurfaceViewクラスのgetHolderメソッドの返値は、そのビューのホルダーになります。
m_surfaceHolder.addCallback(this);
SurfaceHolder.Callbackインタフェースのコールバックメソッドはどのクラスのメソッドであるかを設定します。thisを渡していますので、OpenCV_CaptureViewクラスに実装されたメソッドを呼び出すことになります。
まずは、surfaceChangedメソッドです。 前述のように、SurfaceViewが変更になったときに呼び出されます。
synchronized (this)
synchronizedはスレッドの排他制御で、thisであるOpenCV_CaptureViewではrunメソッドがスレッドとして走っています。 このrunメソッドの中で変数などが書き換えられないようにするために、一時的に排他にします。
if (m_videoCapture != null && m_videoCapture.isOpened())
isOpenedメソッドは、VideoCaptureオブジェクトが初期化済みである場合にtrueを返します。
List<Size> sizes = m_videoCapture.getSupportedPreviewSizes();
getSupportedPreviewSizesメソッドは、カメラがサポートしているプレビューサイズのリストを返します。SizeクラスはOpenCVのクラスでheight,widthをメンバ変数に持ちます。
int frameWidth = width;
int frameHeight = height;
surfaceChangedメソッドの引数 width, heightは、変更されたSurfaceViewのサイズを保持しています。これをフレームの幅、高さを表す変数に代入します。
double minDifference = Double.MAX_VALUE;
minDifferenceは、SurefaceViewとVideoCaptureのプレビューサイズの差分を保持する変数です。最初はdouble型の最大値を入れます。
for (Size size : sizes)
sizesリストのSizeを一つ一つ取り出します。
if (Math.abs(size.height - height) < minDifference)
カメラのプレビューサイズとSurefaceViewとの差が前より小さい場合、以下の入替をします。
frameWidth = (int)size.width;
frameHeight = (int)size.height;
minDifference = Math.abs(size.height - height);
フレームサイズの変数と差分を保持する変数を入れ替えることで、for分を抜けたときは、差分が一番小さいサイズが保持されていることになります。
m_videoCapture.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, frameWidth);
m_videoCapture.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, frameHeight);
で、VideoCaptureオブジェクトにフレームの幅と高さを設定しています。
setメソッドはVideoCaptreのプロパティをセットするもので、Highgui.CV_CAP_PROP_FRAME_WIDTHを指定すると幅を、Highgui.CV_CAP_PROP_FRAME_HEIGHTを指定する高さを設定することになります。
m_rgbaMat = new Mat();
キャプチャ画像を取得するMatクラスのオブジェクトを生成しています。 カラー画像(RGBAで表現)を取得するのでm_rgbaMatと言うオブジェクト変数名にします。
次にsurfaceCreatedメソッドです。 これはSurfaceViewが生成されたときに呼び出されます。
m_videoCapture = new VideoCapture(Highgui.CV_CAP_ANDROID);
VideoCaptureオブジェクトを生成する際に、カメラデバイスをHighgui.CV_CAP_ANDROIDを設定し、androidのビデオキャプチャーを指定しています。
if (m_videoCapture.isOpened())
VideoCaptureの初期化が成功していたら、
(new Thread(this)).start();
で、スレッドを開始します。これでrunメソッドが別スレッドで実行されます。 もし、失敗したら
m_videoCapture.release();
m_videoCapture = null;
VideoCaptureオブジェクトを解放しています。 surfaceDestroyedメソッドは、アプリが終了してSurefaceViewが削除されるときに呼び出されます。
synchronized (this)
別スレッドでrunメソッドの処理も走っているので、排他処理にします。 そして、VideoCaptureオブジェクトの解放をしています。初期化失敗の時と同じですね。
スレッドを実行するrunメソッドの説明です。
while (true)
runメソッドから抜けるとスレッドは終了してしまうので、無限ループにしています。
Bitmap bmp = null;
BitmapクラスはAndorid APIに用意されているもので、画像データを扱うクラスです。 生成前なのでnullを入れておきます。
synchronized (this)
他のスレッドからアクセスされないように排他処理にします。
if (m_videoCapture == null)
{
break;
}
もし、surfaceDestroyedメソッドなどでVideoCaptureオブジェクトが解放されたら、無限ループを抜けます。
if (!m_videoCapture.grab())
{
break;
}
grabメソッドは、次のフレームを取得します。失敗したらループを抜けます。
m_videoCapture.retrieve(
m_rgbaMat, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
retrieveメソッドは、取得したフレームを画像データにデコードしm_rgbaMatに格納します。 デコードの種類はHighgui.CV_CAP_ANDROID_COLOR_FRAME_RGBAを指定しており、RGBAのカラーフォーマットになります。
m_rgbaMatはOpenCVに用意されたMatクラスのオブジェクトですので、そのままではAndroidが直接表示することはできません。 Bitmapのオブジェクトに変換する必要があります。
bmp = Bitmap.createBitmap(
m_rgbaMat.cols(), m_rgbaMat.rows(), Bitmap.Config.ARGB_8888);
まずは、Bitmapクラスのオブジェクトを生成します。 引数は幅、高さ、フォーマットの順で、それぞれ m_rgbaMat.cols() Matの列数、m_rgbaMat.rows() Matの行数、Bitmap.Config.ARGB_8888 ARGB各8ビットの32bitカラー に指定します。
if (Utils.matToBitmap(m_rgbaMat, bmp) == false)
Utils.matToBitmapはMatからBitmapに変換するOpenCVのユーティリティメソッドです。
もし、変換に失敗したら
bmp.recycle();
bmp = null;
Bitmapオブジェクトは一端解放して、nullを代入します。(コマ飛びになります)
if (bmp != null)
nullでない=変換に成功ですので、取得した画像の描画処理に進みます。
Canvas canvas = m_surfaceHolder.lockCanvas();
CanvasクラスはAndroidの描画用のクラスです。 Canvasクラスのオブジェクトに画像を描画して (まだ画面には表示されない。メモリの中での話)、 描画が終了したら画面に表示すると言うダブルバッファの仕組みを使います。 lockCanvasメソッドはダブルバッファの仕組みを使うためのメソッドです。
if (canvas != null)
描画がうまくいったら
canvas.drawBitmap(bmp,
(canvas.getWidth() - bmp.getWidth()) / 2,
(canvas.getHeight() - bmp.getHeight()) / 2, null);
drawBitmapメソッドで、CanvasクラスのオブジェクトにBitmapオブジェクト描画します。 画像がCanvasの中心に表示されるようにtop, leftを計算しています。 最後の引数をnullにするとビットマップ描画になります。
m_surfaceHolder.unlockCanvasAndPost(canvas);
unlockCanvasAndPostメソッドにCanvasのオブジェクトを渡すと、このタイミングで、ダブルバッファの画像データが画面に表示されます。
bmp.recycle();
フレーム画像のCanvasへの転送がすんだので、Bitmapオブジェクトの画像は不要になり、一端解放します。 これをアプリの終了まで繰り返します。
whileループから抜けてきた場合は、
synchronized (this)
排他処理にして
if (m_rgbaMat != null)
m_rbgaMatがnullでないかを調べ、
m_rgbaMat.release();
m_rgbaMat = null;
m_rbgaMatオブジェクトを解放し、runメソッドから抜けます。
OpenCVによるビデオキャプチャの説明でした。
/////
第4回 グレースケール
手順は前回と同じです。
プロジェクト作成
Eclipseを立ち上げます。
「ファイル」メニューの「新規」「Androidプロジェクト」を選択します。
プロジェクト名に「OpenCV_Grayscale」と入力し「次へ」ボタンを押します。
ビルドターゲットは「Android 2.2」にチェックを入れ「次へ」ボタンを押します。
パッケージ名に「com.pronowa.android.OpenCV_Grayscale」と入力し「完了」ボタンを押します。
パッケージ・エクスプローラーに「OpenCV_Grayscale」プロジェクトが追加されます。
AndroidManifest.xml編集
カメラを利用できる様に、AndroidManifest.xmlに記述を追加します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pronowa.android.OpenCV_Grayscale"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".OpenCV_GrayscaleActivity"
android:screenOrientation="landscape" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name=
"android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
</manifest>
OpenCVライブラリの追加
次に「OpenCV_Capture」プロジェクトを右クリックして「プロパティ」を選択します。
「Android」を選択し「ライブラリー」のところにある「追加」ボタンをおします。
リストからOpenCV-2.3.1を選択し、「OK」ボタンを押します。
これでOpenCVのライブラリが利用できる様になります。
Viewクラスの追加
プロジェクトの下にあるsrcを展開し、com.pronowa.android.OpenCV_Grayscaleを右クリックします。
「新規」「クラス」を選択します。
ダイアログで、名前に「OpenCV_GrayscaleView」と入力します。,br> スーパークラスの「参照」ボタンを押して、 「型を選択してください」の下のエディットボックスに「SurfaceView」と入力します。 一致する項目に「SurfaceView」と表示されたら、それを選択状態にして「OK」ボタンを押します。
インターフェイス「追加」ボタンを押して、「インターフェイスを追加してください」の下のエディットボックスに「Callback」と入力します。 リストから「android.vew.SurfaceHolder:Callback」を選択して「OK」ボタンを押します。
もう一度「追加」ボタンを押して、今度は「Runnable」と入力します。 リストから「java.lang.Runnable」を選択して「OK」ボタンを押します。
Activityクラスの修正
パッケージ・エクスプローラーのOpenCV_GrayscaleActivity.javaをダブルクリックしエディタを開き、以下のコードを入力します。
自動生成されているコードの内、setContentViewメソッドを書き換えるだけです。
package com.pronowa.android.OpenCV_Grayscale;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class OpenCV_GrayscaleActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new OpenCV_GrayscaleView(this));
}
}
Viewクラスの記述
次にOpenCV_GrayscaleView.javaをダブルクリックして、エディタを開き、各メソッドの中身を記述します。
package com.pronowa.android.OpenCV_Grayscale;
import java.util.List;
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.core.Size;
class OpenCV_GrayscaleView
extends SurfaceView implements SurfaceHolder.Callback, Runnable
{
private SurfaceHolder m_surfaceHolder;
private VideoCapture m_videoCapture;
private Mat m_rgbaMat;
private Mat m_grayMat;
public OpenCV_GrayscaleView(Context context)
{
super(context);
m_surfaceHolder = getHolder();
m_surfaceHolder.addCallback(this);
}
public void surfaceChanged(
SurfaceHolder _holder, int format, int width, int height)
{
synchronized (this)
{
if (m_videoCapture != null && m_videoCapture.isOpened())
{
List<Size> sizes =
m_videoCapture.getSupportedPreviewSizes();
int mFrameWidth = width;
int mFrameHeight = height;
double minDifference = Double.MAX_VALUE;
for (Size size : sizes)
{
if (Math.abs(size.height - height) <
minDifference)
{
mFrameWidth = (int) size.width;
mFrameHeight = (int) size.height;
minDifference =
Math.abs(size.height - height);
}
}
m_videoCapture.set(
Highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth);
m_videoCapture.set(
Highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight);
}
m_grayMat = new Mat();
m_rgbaMat = new Mat();
}
}
public void surfaceCreated(SurfaceHolder holder)
{
m_videoCapture = new VideoCapture(Highgui.CV_CAP_ANDROID);
if (m_videoCapture.isOpened())
{
(new Thread(this)).start();
}
else
{
m_videoCapture.release();
m_videoCapture = null;
}
}
public void surfaceDestroyed(SurfaceHolder holder)
{
if (m_videoCapture != null)
{
synchronized (this)
{
m_videoCapture.release();
m_videoCapture = null;
}
}
}
public void run()
{
while (true)
{
Bitmap bmp = null;
synchronized (this)
{
if (m_videoCapture == null)
{
break;
}
if (!m_videoCapture.grab())
{
break;
}
m_videoCapture.retrieve(
m_grayMat, Highgui.CV_CAP_ANDROID_GREY_FRAME);
Imgproc.cvtColor(
m_grayMat, m_rgbaMat, Imgproc.COLOR_GRAY2RGBA, 4);
bmp = Bitmap.createBitmap(
m_rgbaMat.cols(), m_rgbaMat.rows(),
Bitmap.Config.ARGB_8888);
if (Utils.matToBitmap(m_rgbaMat, bmp) == false)
{
bmp.recycle();
bmp = null;
}
}
if (bmp != null)
{
Canvas canvas = m_surfaceHolder.lockCanvas();
if (canvas != null)
{
canvas.drawBitmap(bmp,
(canvas.getWidth() - bmp.getWidth()) / 2,
(canvas.getHeight() - bmp.getHeight()) / 2, null);
m_surfaceHolder.unlockCanvasAndPost(canvas);
}
bmp.recycle();
}
}
synchronized (this)
{
if (m_rgbaMat != null)
{
m_rgbaMat.release();
}
if (m_grayMat != null)
{
m_grayMat.release();
}
m_rgbaMat = null;
m_grayMat = null;
}
}
}
Androidでの起動方法等は、第2回「カメラキャプチャ」や他の文献を参考にしてください。
ソースの説明
前回と異なる箇所を中心に説明をします。
グレースケール用のMatクラスのオブジェクト変数を追加しています。
private Mat m_grayMat;
グレースケールにしているのは、runメソッドの中です。
m_videoCapture.retrieve(m_grayMat, Highgui.CV_CAP_ANDROID_GREY_FRAME);
retrieveメソッドはキャプチャしたフレームをMatオブジェクトに変換して返します。
retrieveメソッドの引数は
retrieve( Mat image, int channel )
です。
channelにHighguiクラスにstaticで定義されているCV_CAP_ANDROID_GREY_FRAMEを渡すとグレイスケールに変換されたものがMatオブジェクトに入ります。
Bitmapオブジェクトはフルカラー様に作成しているので、m_grayMatからはそのまま変換できません。
そこで、
Imgproc.cvtColor(m_grayMat, m_rgbaMat, Imgproc.COLOR_GRAY2RGBA, 4);
にて、フルカラーのフォーマットに変換します。
cvtColorメソッドの引数は、
cvtColor(Mat src, Mat dst, int code, int dstCn)
codeにImgproc.COLOR_GRAY2RGBAを渡すと、グレースケールからRGBA(αチャンネルを持った32bitフルカラー)に変換します。
dstCnは変換後のチャンネル数です。 RGBAなので、4chに指定します。
dstに指定したm_rgbaMatに変換後のデータが入ることになります。
このm_rgbaMatは前回「カメラキャプチャ」の時と同じフォーマットなので後の処理は一緒です。
/////
第6回 エッジ抽出
写っているものの輪郭(エッジ)を抽出する処理です。 Canny法という処理を使います。
プロジェクト作成
Eclipseを立ち上げます。
「ファイル」メニューの「新規」「Androidプロジェクト」を選択します。
プロジェクト名に「OpenCV_Canny」と入力し「次へ」ボタンを押します。
ビルドターゲットは「Android 2.2」にチェックを入れ「次へ」ボタンを押します。
パッケージ名に「com.pronowa.android.OpenCV_Canny」と入力し「完了」ボタンを押します。
パッケージ・エクスプローラーに「OpenCV_Canny」プロジェクトが追加されます。
AndroidManifest.xml編集
前回と同じ要領で、カメラを利用できる記述を追加してください。
OpenCVライブラリの追加
次に「OpenCV_Canny」プロジェクトを右クリックして「プロパティ」を選択します。
「Android」を選択し「ライブラリー」のところにある「追加」ボタンをおします。
リストからOpenCV-2.3.1を選択し、「OK」ボタンを押します。
これでOpenCVのライブラリが利用できる様になります。
Viewクラスの追加
プロジェクトの下にあるsrcを展開し、com.pronowa.android.OpenCV_Cannyを右クリックします。
「新規」「クラス」を選択します。
ダイアログで、名前に「OpenCV_CannyView」と入力します。
スーパークラスの「参照」ボタンを押して、「型を選択してください」の下のエディットボックスに「SurfaceView」と入力します。
一致する項目に「SurfaceView」と表示されたら、それを選択状態にして「OK」ボタンを押します。
インターフェイス「追加」ボタンを押して、「インターフェイスを追加してください」の下のエディットボックスに「Callback」と入力します。
リストから「android.vew.SurfaceHolder:Callback」を選択して「OK」ボタンを押します。
もう一度「追加」ボタンを押して、今度は「Runnable」と入力します。
リストから「java.lang.Runnable」を選択して「OK」ボタンを押します。
Activityクラスの修正
パッケージ・エクスプローラーのOpenCV_CannyActivity.javaをダブルクリックしエディタを開き、setContentViewメソッドを以下に書き換えます。
setContentView(new OpenCV_CannyView(this));
Viewクラスの記述
次にOpenCV_CannyView.javaをダブルクリックして、エディタを開き、各メソッドの中身を記述します。
package com.pronowa.android.OpenCV_Canny;
import java.util.List;
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.core.Size;
public class OpenCV_CannyView
extends SurfaceView implements SurfaceHolder.Callback, Runnable
{
private SurfaceHolder m_surfaceHolder;
private VideoCapture m_videoCapture;
private Mat m_cannyMat;
private Mat m_grayMat;
private Mat m_tempMat;
public OpenCV_CannyView(Context context)
{
super(context);
m_surfaceHolder = getHolder();
m_surfaceHolder.addCallback(this);
}
public void surfaceChanged(
SurfaceHolder _holder, int format, int width, int height)
{
synchronized (this)
{
if (m_videoCapture != null && m_videoCapture.isOpened())
{
List<Size> sizes =
m_videoCapture.getSupportedPreviewSizes();
int mFrameWidth = width;
int mFrameHeight = height;
double minDiff = Double.MAX_VALUE;
for (Size size : sizes)
{
if (Math.abs(size.height - height) < minDiff)
{
mFrameWidth = (int) size.width;
mFrameHeight = (int) size.height;
minDiff = Math.abs(size.height - height);
}
}
m_videoCapture.set(
Highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth);
m_videoCapture.set(
Highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight);
}
m_cannyMat = new Mat();
m_grayMat = new Mat();
m_tempMat = new Mat();
}
}
public void surfaceCreated(SurfaceHolder holder)
{
m_videoCapture = new VideoCapture(Highgui.CV_CAP_ANDROID);
if (m_videoCapture.isOpened())
{
(new Thread(this)).start();
}
else
{
m_videoCapture.release();
m_videoCapture = null;
}
}
public void surfaceDestroyed(SurfaceHolder holder)
{
if (m_videoCapture != null)
{
synchronized (this)
{
m_videoCapture.release();
m_videoCapture = null;
}
}
}
public void run()
{
while (true)
{
Bitmap bmp = null;
synchronized (this)
{
if (m_videoCapture == null)
{
break;
}
if (!m_videoCapture.grab())
{
break;
}
m_videoCapture.retrieve(m_grayMat,
Highgui.CV_CAP_ANDROID_GREY_FRAME);
Imgproc.Canny(m_grayMat, m_tempMat, 80, 100);
Imgproc.cvtColor(m_tempMat, m_cannyMat,
Imgproc.COLOR_GRAY2BGRA, 4);
bmp = Bitmap.createBitmap(
m_cannyMat.cols(), m_cannyMat.rows(),
Bitmap.Config.ARGB_8888);
if (Utils.matToBitmap(m_cannyMat, bmp) == false)
{
bmp.recycle();
bmp = null;
}
}
if (bmp != null)
{
Canvas canvas = m_surfaceHolder.lockCanvas();
if (canvas != null)
{
canvas.drawBitmap(bmp,
(canvas.getWidth() - bmp.getWidth()) / 2,
(canvas.getHeight() - bmp.getHeight()) / 2, null);
m_surfaceHolder.unlockCanvasAndPost(canvas);
}
bmp.recycle();
}
}
synchronized (this)
{
if (m_cannyMat != null)
{
m_cannyMat.release();
}
if (m_grayMat != null)
{
m_grayMat.release();
}
if (m_tempMat != null)
{
m_tempMat.release();
}
m_cannyMat = null;
m_grayMat = null;
m_tempMat = null;
}
}
}
Androidでの起動方法等は、第2回「カメラキャプチャ」や他の文献を参考にしてください。
ソースの説明
Matクラスのオブジェクト変数は以下の3つを用意します。
private Mat m_cannyMat;
private Mat m_grayMat;
private Mat m_tempMat;
m_cannyMatはBitmapに変換する前の画像用です。
m_grayMatはキャプチャ画像用です。
m_tempMatはm_grayMatからエッジを抽出した画像用です。
処理はrunメソッドの中です。
まずは、retrieveメソッドでキャプチャ画像をMatオブジェクトに変換しますが、今回は
m_videoCapture.retrieve(m_grayMat, Highgui.CV_CAP_ANDROID_GREY_FRAME);
と、channelにCV_CAP_ANDROID_GREY_FRAMEを指定し、グレースケール画像で取得します。 これはCanny法でエッジ抽出をするCannyメソッドは入力にグレースケール画像を渡す必要があるためです。
Imgproc.Canny(m_grayMat, m_tempMat, 80, 100);
OpenCVのImgproc名前空間のCannyメソッドの引数は以下になります。
Canny(Mat image, Mat edges, double threshold1, double threshold2)
imageはエッジ抽出する元の画像でグレースケール(1chの8bit画像)の必要があります。
edgesは抽出後の画像で、imageと同じサイズとフォーマット(グレースケール)になります。
threshold1とthreshold2はエッジ抽出処理に使用するしきい値で、2つのうち大きいしきい値を用い抽出し、 次に小さいしきい値で抽出し、先ほどの大きい方の抽出と隣接している場合は抽出したエッジに加えると言う処理になります。
m_tempMatはグレースケールなので、このままではフルカラーのBitmapに変換できません。
Imgproc.cvtColor(m_tempMat, m_cannyMat, Imgproc.COLOR_GRAY2BGRA, 4);
を用いて、34bitフルカラーフォーマットに変換します。(画像自体はRGB各チャンネルとも同じ値のグレースケールのまま)
/////