アンドロイドでOpenCVを使ってみる。

まずは、お約束の「顔検出」です。



目次

EclipseにOpenCV 2.3.1をインポート

サンプルを使って顔検出をやってみる

顔検出のサンプルコードにデータ保存用コードを追加

顔検出のマーカーを変更してみる

PC版はこちら

Android 2.3.1 for Windows

他のOpenCV関連ページNew

TOP


EclipseにOpenCVをインポート

OpenCV 2.3.1をライブラリとして使います。

環境は、Eclipse(INDIGO) + Android SDK + ADT

因みにWisteriaHillの環境はこんな感じ。





ターゲットはAndroid 2.3.3(API10)

1:OpenCV 2.3.1をダウンロードして解凍

Sourceforge(tarball)

OpenCV-2.3.1-android-bin.zip

ダウンロードしたものを解凍して、適当なドライブに移動。

2:Eclipseを起動して、インポート





[Existing Projects into Workspace]を選ぶ。





OpenCV 2.3.1のフォルダーを選んでOK。








OpenCV 2.3.1を右クリックして、Propertiesを設定。

Androidを選んで、Targetを2.3.3としてLibraryに登録。





以上です。

TOP






サンプルを使って顔検出をやってみる

解凍したOpenCV 2.3.1にsamplesフォルダーがあります。

再度、File->importでface_detectionフォルダーを選んでインポートします。





エラーが出る場合、OpenCV 2.3.1がライブラリとして認識されていない場合があります。





右クリックしてPropertiesを設定。





Android 2.3.3をターゲットにして、OpenCV 2.3.1をライブラリとして選んで、OK。





Sample-face-detectionがプロジェクト名です、実機にインストールして動作確認。





お顔が、緑の矩形で検出されます。

メニュには、Face size が20,30,40,50%の4種類あります。

多分画面に対するお顔のサイズの比率...かな。20%ならこのサイズも検出されます。





実行ファイルはここ(Sample - face-detection.apk)

6  11.7MBくらいの、結構デカいファイルですが、これはOpenCV 2.3.1がARM用の2種類のコードを持っているためです。

実機にデプロイして使う分には、armeabi-v7a(OpenCVのlibsフォルダー)だけでいいです。これだけなら、8.7MBくらいのサイズになります。


次は、この結果を使った、「お顔の認識」です。サーバーサイドと連携しましょう。
ところで、この「サーバーサイド」という言い方は良くないのかなあ、一般的じゃないのかも。
「クラウド」と、今後、言い換えましょうかね(実質、同じなんだけど....) 。

Under construction


以上。

TOP






顔検出のサンプルコードにデータ保存用コードを追加

2012/05/09 訂正

画面をタップして、
画像やデータの取得に、画面をタップするやり方を変更しました。
メニュに入れました、こんな感じ。





カメラ画像と、検出した際に表示される緑色の矩形の座標値をローカル(SDカード)に保存します。

ややこしくならないように、お顔の検出数は1つに制限しています。

//addや//modifyとついているところが追加部分。


新規プロジェクトから作成する場合

新規パッケージに内の以下のファイルを作成

Public Class -> FdView.java
Default Class -> FpsMeter.java
Public Abstract Class -> SampleCvViewBase.java
メインアクティビティのクラスにFdActivity.javaのコードを移植
AndroidManifest.xmlにカメラと外部ストレージを使えるようにパーミッション追加
res/rawフォルダーにカスケードファイルを置いておくこと


以下、ファイルのパスは適当に読み替えてください。
ニコちゃんマークを出すようになってますが、緑の矩形に戻す場合は、Core.rectangleを戻して、 int mwidthからCore.lineまでをコメントアウトしてください。

-----------------------------------------------------
【FdView.java】

package org.opencv.samples.fd;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.objdetect.CascadeClassifier;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.SurfaceHolder;

import android.util.Log;

//add
import android.view.MotionEvent;
import android.os.Environment;
import android.graphics.Bitmap.CompressFormat;
import org.opencv.core.Point;



public class FdView extends SampleCvViewBase {
    private static final String TAG = "Sample::FdView";
    private Mat                 mRgba;
    private Mat                 mGray;

    private CascadeClassifier   mCascade;
    
    //add
    private String RectCoordinate = "";
    //add
    private int tl_x = 0;
    private int tl_y = 0;
    private int br_x = 0;
    private int br_y = 0;
    
    private int prev_tl_x = 0;
    private int prev_tl_y = 0;
    private int prev_br_x = 0;
    private int prev_br_y = 0;
    
    public FdView(Context context) {
        super(context);
        
        
        
        try {
            InputStream is = context.getResources().openRawResource(R.raw.lbpcascade_frontalface);
            File cascadeDir = context.getDir("cascade", Context.MODE_PRIVATE);
            File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
            FileOutputStream os = new FileOutputStream(cascadeFile);

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();

            mCascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
            if (mCascade.empty()) {
                Log.e(TAG, "Failed to load cascade classifier");
                mCascade = null;
            } else
                Log.i(TAG, "Loaded cascade classifier from " + cascadeFile.getAbsolutePath());

            cascadeFile.delete();
            cascadeDir.delete();

        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) {
        super.surfaceChanged(_holder, format, width, height);

        synchronized (this) {
            // initialize Mats before usage
            mGray = new Mat();
            mRgba = new Mat();
        }
    }

    @Override
    protected Bitmap processFrame(VideoCapture capture) {
        capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
        capture.retrieve(mGray, Highgui.CV_CAP_ANDROID_GREY_FRAME);

        if (mCascade != null) {
            int height = mGray.rows();
            int faceSize = Math.round(height * FdActivity.minFaceSize);
            List<Rect> faces = new LinkedList<Rect>();
            mCascade.detectMultiScale(mGray, faces, 1.1, 2, 2 // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                    , new Size(faceSize, faceSize));
            
            int index = 0;//add
            for (Rect r : faces){
                //add
                if (index == 0) {
                    /*
                    Core.rectangle(mRgba, r.tl(), r.br(), new Scalar(0, 255, 0, 255), 3);
                    */
                    //add
                    int mwidth = (int)(r.br().x - r.tl().x);
                    int mheight = (int)(r.br().y - r.tl().y);
                    int c_x = (int)(r.tl().x + r.br().x)/2;
                    int c_y = (int)(r.tl().y + r.br().y)/2;
                    int radius = (int)(r.br().x - r.tl().x)/2;
                    Core.circle(mRgba, new Point(c_x,c_y), radius, new Scalar(200, 200, 200, 100), -1);
                    
                    int ls_radius = (int)(mwidth * 0.3);
                    
                    Core.ellipse(mRgba, new Point(c_x,c_y), new Size(ls_radius,ls_radius), 0, 0, 180, new Scalar(0, 0, 0, 255), 3);
                    
                    int pt1_x = c_x - radius + 2;
                    int pt1_y = c_y + 5;
                    int pt2_x = c_x + radius - 2;
                    int pt2_y = c_y + 5;
                    
                    Core.line(mRgba, new Point(pt1_x,pt1_y), new Point(pt2_x,pt2_y), new Scalar(255, 255, 255, 255),2);
                
                    
                    
                    tl_x = (int)r.tl().x;
                    tl_y = (int)r.tl().y;
                    br_x = (int)r.br().x;
                    br_y = (int)r.br().y;
                    
                    if (prev_tl_x != 0) {
                        tl_x = (int)(tl_x * 0.1 + prev_tl_x * 0.9);
                        tl_y = (int)(tl_y * 0.1 + prev_tl_y * 0.9);
                        br_x = (int)(br_x * 0.1 + prev_br_x * 0.9);
                        br_y = (int)(br_y * 0.1 + prev_br_y * 0.9);
                    }
                    
                    RectCoordinate = tl_x + "," + tl_y + "/" + br_x + "," + br_y;
                    prev_tl_x = tl_x;
                    prev_tl_y = tl_y;
                    prev_br_x = br_x;
                    prev_br_y = br_y;
                    
                    
                }
            }
        }

        Bitmap bmp = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.ARGB_8888);

        if (Utils.matToBitmap(mRgba, bmp))
            return bmp;

        bmp.recycle();
        return null;
    }
    
    //add
    //---------------------------------------
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //get_cvPicture();
            
        }
        return true;
    }
    
    
    public void get_cvPicture() {
        Bitmap bmp = null;
        
        synchronized (this) {
            if (mCamera.grab()) {
                bmp = processFrame(mCamera);
            }
        }

        if (bmp == null) {
            return;
        } else {
            //String filePath = Environment.getExternalStorageDirectory() + "/DCIM/Camera/" + System.currentTimeMillis() + ".jpg";
            String filePath4Picture = Environment.getExternalStorageDirectory() + "/DCIM/Camera/example.jpg";
            String filePath4Data = Environment.getExternalStorageDirectory() + "/DCIM/Camera/example.txt";
            
            try {

                write_cvPicture(filePath4Picture, bmp);
                
                write_cvData(filePath4Data, RectCoordinate);
                
                //webView.loadUrl("javascript:show_alert('" + RectCoordinate + "')");
                
                
            } catch (IOException e) {
                //e.printStackTrace();
                Log.e("err","Error:" + e.toString());
            }
            bmp.recycle();
        }
    }
    
    private void write_cvPicture(String filePath, Bitmap bmp) throws IOException {

        FileOutputStream out = null;
        
        try {
            
            out = new FileOutputStream(filePath);
            
            bmp.compress(CompressFormat.JPEG, 100, out);
            
            
            
            //
        } finally {
            if (out != null) {
                out.close();
            }
            
        }
    }
    private void write_cvData(String filePath, String data) throws IOException {
        FileOutputStream out = null;
        
        try {
            
            out = new FileOutputStream(filePath);
            out.write(data.getBytes());
            //
            
            //
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
    //---------------------------------------
    
    @Override
    public void run() {
        super.run();

        synchronized (this) {
            // Explicitly deallocate Mats
            if (mRgba != null)
                mRgba.release();
            if (mGray != null)
                mGray.release();

            mRgba = null;
            mGray = null;
        }
    }
}
-----------------------------------------------------
【SampleCvViewBase.java】

package org.opencv.samples.fd;

import java.util.List;

import org.opencv.core.Size;
import org.opencv.highgui.VideoCapture;
import org.opencv.highgui.Highgui;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;



public abstract class SampleCvViewBase extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private static final String TAG = "Sample::SurfaceView";

    private SurfaceHolder       mHolder;
    public VideoCapture        mCamera;//modify
    private FpsMeter            mFps;
    
    
    public SampleCvViewBase(Context context) {
        super(context);
        mHolder = getHolder();
        mHolder.addCallback(this);
        mFps = new FpsMeter();
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

    public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) {
        Log.i(TAG, "surfaceCreated");
        synchronized (this) {
            if (mCamera != null && mCamera.isOpened()) {
                Log.i(TAG, "before mCamera.getSupportedPreviewSizes()");
                List<Size> sizes = mCamera.getSupportedPreviewSizes();
                Log.i(TAG, "after mCamera.getSupportedPreviewSizes()");
                int mFrameWidth = width;
                int mFrameHeight = height;

                // selecting optimal camera preview size
                {
                    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);
                        }
                    }
                }

                mCamera.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth);
                mCamera.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight);
            }
        }
    }

    public void surfaceCreated(SurfaceHolder holder) {
        Log.i(TAG, "surfaceCreated");
        mCamera = new VideoCapture(Highgui.CV_CAP_ANDROID);
        if (mCamera.isOpened()) {
            (new Thread(this)).start();
        } else {
            mCamera.release();
            mCamera = null;
            Log.e(TAG, "Failed to open native camera");
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i(TAG, "surfaceDestroyed");
        if (mCamera != null) {
            synchronized (this) {
                mCamera.release();
                mCamera = null;
            }
        }
    }

    protected abstract Bitmap processFrame(VideoCapture capture);

    public void run() {
        Log.i(TAG, "Starting processing thread");
        mFps.init();

        while (true) {
            Bitmap bmp = null;

            synchronized (this) {
                if (mCamera == null)
                    break;

                if (!mCamera.grab()) {
                    Log.e(TAG, "mCamera.grab() failed");
                    break;
                }

                bmp = processFrame(mCamera);

                mFps.measure();
            }

            if (bmp != null) {
                Canvas canvas = mHolder.lockCanvas();
                if (canvas != null) {
                    canvas.drawBitmap(bmp, (canvas.getWidth() - bmp.getWidth()) / 2, (canvas.getHeight() - bmp.getHeight()) / 2, null);
                    mFps.draw(canvas, (canvas.getWidth() - bmp.getWidth()) / 2, 0);
                    mHolder.unlockCanvasAndPost(canvas);
                }
                bmp.recycle();
            }
        }

        Log.i(TAG, "Finishing processing thread");
    }
}
-----------------------------------------------------
【FdActivity.java】

package org.opencv.samples.fd;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;

public class FdActivity extends Activity {
    private static final String TAG         = "Sample::Activity";

    private MenuItem            mItemFace50;
    private MenuItem            mItemFace40;
    private MenuItem            mItemFace30;
    private MenuItem            mItemFace20;
  //add
    private MenuItem            mItemPicture;
    
    public static float         minFaceSize = 0.5f;
    //add
    private FdView fdv;
    
    public FdActivity() {
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //modify
        fdv = new FdView(this);
        setContentView(fdv);
        //setContentView(new FdView(this));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Log.i(TAG, "onCreateOptionsMenu");
        mItemFace50 = menu.add("Face size 50%");
        mItemFace40 = menu.add("Face size 40%");
        mItemFace30 = menu.add("Face size 30%");
        mItemFace20 = menu.add("Face size 20%");
        //add
        mItemPicture = menu.add("Picture");
        
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Log.i(TAG, "Menu Item selected " + item);
        if (item == mItemFace50)
            minFaceSize = 0.5f;
        else if (item == mItemFace40)
            minFaceSize = 0.4f;
        else if (item == mItemFace30)
            minFaceSize = 0.3f;
        else if (item == mItemFace20)
            minFaceSize = 0.2f;
        else if (item == mItemPicture)//add
            take_photo();
        return true;
    }
    
    //add
    private void take_photo(){
        fdv.get_cvPicture();
        
    }
    
}
-----------------------------------------------------
【AndroidManifest.xml】

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.opencv.samples.fd"
      android:versionCode="1"
      android:versionName="1.0">

    <supports-screens android:resizeable="true"
                      android:smallScreens="true" 
                      android:normalScreens="true" 
                      android:largeScreens="true" 
                      android:anyDensity="true" />

    <application android:label="@string/app_name" android:icon="@drawable/icon">
        <activity android:name="FdActivity"
                  android:label="@string/app_name"
                  android:screenOrientation="landscape"
                  android:configChanges="keyboardHidden|orientation">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    
    <uses-sdk android:minSdkVersion="8" />

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    //add
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest> 


サンプル





画像サンプル

データサンプル

サンプル内で、緑の矩形とデータが若干違うのは、データはフィルターをかけたものだからです。

画像がぼやけているのは、画面タッチが強すぎて、手ぶれしているからです。

手ぶれ補正などという高級なものは実装していません、ソフトにタッチすればもう少し鮮明に撮れます。


TOP






顔検出のマーカーを変更してみる

ニコニコマーク風にしてみます。

こんな感じ。





FdView.javaの
Core.rectangle(mRgba, r.tl(), r.br(), new Scalar(0, 255, 0, 255), 3);を以下のように変更。


int mwidth = (int)(r.br().x - r.tl().x);
int mheight = (int)(r.br().y - r.tl().y);
int c_x = (int)(r.tl().x + r.br().x)/2;
int c_y = (int)(r.tl().y + r.br().y)/2;
int radius = (int)(r.br().x - r.tl().x)/2;
Core.circle(mRgba, new Point(c_x,c_y), radius, new Scalar(200, 200, 200, 100), -1);

int ls_radius = (int)(mwidth * 0.3);

Core.ellipse(mRgba, new Point(c_x,c_y), new Size(ls_radius,ls_radius), 0, 0, 180, new Scalar(0, 0, 0, 255), 3);

int pt1_x = c_x - radius + 2;
int pt1_y = c_y + 5;
int pt2_x = c_x + radius - 2;
int pt2_y = c_y + 5;

Core.line(mRgba, new Point(pt1_x,pt1_y), new Point(pt2_x,pt2_y), new Scalar(255, 255, 255, 255),2);


ご一同さんおそろいで、ニコちゃんマークの図(ちょっとシュール)。





TOP






image_manipulationをやってみる

メニュは、こんな感じ





Canny(cannyを使ったエッジ検出)





Sepia
あまり効果が無かったので割愛

Sobel(sobelを使ったエッジ検出)





Blur(ぼやけ)
部分的にぼやかしてます。





Zoom(拡大)
赤枠の中を拡大





ちなみに、以下はTutorialにある、Cannyを使ったエッジ検出の例。










TOP






他のOpenCV関連ページ

OpenCV覚書

矩形領域の座標を取得するページ

OpenCV 2.3.1でカスケードを作って、Androidで使ってみる

アンドロイドでOpenCV(色検出)

Android OpenCV 2.3.1で画像認識

アンドロイドでOpenCV(特徴点検出)

AndroidでOpenCV 2.4.6を使ってみる

OpenCV + NyMMDで初音ミクさんにご挨拶してもらいます

Android OpenCV 2.4.6 の顔検出アプリを一から作ってみる