Google OS実験室 ~Moonlight 明日香~

GoogleのAndroidで遊び始めて, すでに6年以上が経った. Androidは思った以上の発展を遂げている. この技術を使って, 新しいことにチャレンジだ!!

OpenCV

Android6.0でOpenCVが起動しない

Android 4.4では問題なく動作していたOpenCV 2.4.11を使ったアプリが, Android 6.0で動作しなくなった.

[エラー発生環境]
Nexus 7(2013) / Android 6.0.1
OpenCVバージョン2.4.11

エラーメッセージを確認したところ, OpenCVLoader#initAsyncメソッドでエラーが発生していた.

[エラーメッセージ]
01-09 21:56:28.665 14620-14620/com.moonlight_aska.android.vision.flironedemo I/LOG_TAG: onResume, init ImageProc!
01-09 21:56:28.666 14620-14620/com.moonlight_aska.android.vision.flironedemo D/AndroidRuntime: Shutting down VM
01-09 21:56:28.669 14620-14620/com.moonlight_aska.android.vision.flironedemo E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.moonlight_aska.android.vision.flironedemo, PID: 14620
        java.lang.RuntimeException: Unable to resume activity {com.moonlight_aska.android.vision.flironedemo/com.moonlight_aska.android.vision.flironedemo.PreviewActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=org.opencv.engine.BIND }
                at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3103)
                at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
                at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
                at android.app.ActivityThread.-wrap11(ActivityThread.java)
                at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                at android.os.Handler.dispatchMessage(Handler.java:102)
                at android.os.Looper.loop(Looper.java:148)
                at android.app.ActivityThread.main(ActivityThread.java:5417)
                at java.lang.reflect.Method.invoke(Native Method)
                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                        Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=org.opencv.engine.BIND }
                at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1209)
                at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1308)
                at android.app.ContextImpl.bindService(ContextImpl.java:1286)
                at android.content.ContextWrapper.bindService(ContextWrapper.java:604)
                at org.opencv.android.AsyncServiceHelper.initOpenCV(AsyncServiceHelper.java:25)
                at org.opencv.android.OpenCVLoader.initAsync(OpenCVLoader.java:89)
                at com.moonlight_aska.android.vision.flironedemo.ImageProc.init(ImageProc.java:41)
                at com.moonlight_aska.android.vision.flironedemo.PreviewActivity.onResume(PreviewActivity.java:218)
          :
 
対応策について少し調べてみたところ, OpenCVのコードを以下のように修正すればよいようである. [1]

[対象ファイル]
org\opencv\Android\AsyncServiceHelper.java

[修正箇所]
public static boolean initOpenCV(String Version, final Context AppContext,
        final LoaderCallbackInterface Callback)
{
    AsyncServiceHelper helper = new AsyncServiceHelper(Version, AppContext, Callback);  
  /* 修正前 2016.1.9
  if (AppContext.bindService(new Intent("org.opencv.engine.BIND"),
         helper.mServiceConnection, Context.BIND_AUTO_CREATE))
  */
  Intent intent = new Intent("org.opencv.engine.BIND");
  intent.setPackage("org.opencv.engine");
  if (AppContext.bindService(intent,
      helper.mServiceConnection, Context.BIND_AUTO_CREATE))
    {
        return true;
    }
    else
    {
        AppContext.unbindService(helper.mServiceConnection);
        InstallService(AppContext, Callback);
        return false;
    }
}

一応, これで問題なくOpenCVが使用できることを確認した.
どうもAndroid 5.0以降で同じ問題が発生するようだ...

----
参照URL:
[1] Issue with OpenCV for Android on Android 5.0 (lollipop)


OpenCV 2.3.1からOpenCV 2.4.5にアップデート

Nexus7 / 10で, OpenCV 2.3.1を使ったカメラアプリが起動に失敗する.
画像処理のみのアプリは問題ないのだが....

1. 症状
例えば, OpenCV 2.3.1のサンプルである"Android Camera"を起動すると, 以下のようなエラーメッセージが表示される.

opencv24_02

どこでエラーが発生しているか確認してみると, SampleViewBase.java内のsurfaceCreated()でNullPointerExceptionが発生しているようだ.

opencv24_03

エラー解析はひとまず置いておき, まずはOpenCVを最新バージョンにしてみることにした.

2. OpenCV 2.4.5インストール[1]
最新のOpenCV 2.4.5をダウンロードして, インストールした.

そして, OpenCV 2.4.5のサンプルである"Camera Preview"を起動すると, 以下のようなメッセージが表示された.

opencv24_01

OpenCV Managerなるものが必要なようで, インストールを要求された.

opencv24_04

OpenCV Managerのインストールが完了すると, サンプルの"Camera Preview"が正常に動作した.

opencv24_05

結局, OpenCV 2.3.1の問題だったのか!?

----
参照URL:
 [1] OpenCV 2.3.1 for Androidをインストールする


[OpenCV] 目を検出する

前回に続き, 画像認識の基礎について説明する.

3.19 目を検出する[1][2][3]
まず, 画像から顔を検出し, その検出矩形の中から目を検出する.

[手順]
 (1) 顔検出については, 「顔を検出する」を参照.
 (2) 目の分類器を読み込む.
 (3) 検出した顔の領域の画像を取得する.
 (4) 目を探索する.

[コード]
package com.moonlight_aska.android.opencv.image19;

import java.io.File;
import java.io.FileNotFoundException;
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.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;

public class Image19Activity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        // cv::Mat img = cv::imread("../../iamge/lenna.png", 1);
        Mat srcMat = Highgui.imread("/sdcard/OpenCV/sample/lena.jpg", 1);
        if (!srcMat.empty()) {
            double scale = 2.0;
            // cv::Mat gray, smallImg(cv::saturate_cast<int>(img.rows/scale),
            //        cv::saturate_cast<int>(img.cols/scale), CV_8UC1);
            Mat grayMat = new Mat();
            Size smallSize = new Size(srcMat.rows()/scale, srcMat.cols()/scale);
            Mat smallMat = new Mat(smallSize, CvType.CV_8UC1);
            // グレースケール画像に変換
            // cv::cvtColor(img, gray, CV_RGB2GRAY);
            Imgproc.cvtColor(srcMat, grayMat, Imgproc.COLOR_RGB2GRAY);
            // 処理時間短縮のために画像を縮小
            // cv::resize(gray, smallImg, smallImg.size(), 0, 0, cv::INTER_LENEAR;
            Imgproc.resize(grayMat, smallMat, smallMat.size(), 0, 0, Imgproc.INTER_LINEAR);
            // cv::equalizeHist(smallImg, smallImg);
            Imgproc.equalizeHist(smallMat, smallMat);
    
            try {
                // 分類器の読み込み
                // std::string cascadeName = "./haarcascade_frontalface_alt.xml";   // Haar-like
                InputStream inStream = getResources().openRawResource(R.raw.haarcascade_frontalface_alt);
                File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                File cascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt.xml");       
                // // std::string cascadeName = "./lbpcascade_frontablface.xml"; // LBP
                // InputStream inStream = getResources().openRawResource(R.raw.lbpcascade_frontalface);
                // File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                // File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                FileOutputStream outStream;
                outStream = new FileOutputStream(cascadeFile);
                byte[] buf = new byte[2048];
                int rdBytes;
                while ((rdBytes = inStream.read(buf)) != -1) {
                    outStream.write(buf, 0, rdBytes);
                }
                outStream.close();
                inStream.close();
                // cv::CascadeClassifier cascade;
                // if(!cascade.load(cascadeName))
                //     return -1;
                CascadeClassifier cascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
                if (cascade.empty()) {
                    cascade = null;
                    return;
                }
                else {
                    cascadeDir.delete();
                    cascadeFile.delete();  
                }
                // std::vector<cv::Rect> faces;
                List<Rect> faces = new LinkedList<Rect>();
                /// マルチスケール(顔)探索
                // 画像, 出力矩形, 縮小スケール, 最低矩形数, (フラグ), 最小矩形
                // cascade.detectMultiScale(smallImg, faces,
                //       1.1, 2
                //       CV_HAAR_SCALE_IMAGE
                //       ,
                //       cv::Size(30,30);
                cascade.detectMultiScale(smallMat, faces,
                        1.1, 2, 2 // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                        , new Size(30, 30));
         
                // 分類器の読み込み
                // std::string cascadeName = "./haarcascade_eye.xml";   // Haar-like

                inStream = getResources().openRawResource(R.raw.haarcascade_eye);
                cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                cascadeFile = new File(cascadeDir, "haarcascade_eye.xml");
       
                // // std::string nested_cascadeName = "./haarcascade_eye_tree_eyeglasses.xml";
                // InputStream inStream = getResources().openRawResource(R.raw.haarcascade_eye_tree_eyeglasses);
                // File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                // File cascadeFile = new File(cascadeDir, "haarcascade_eye_tree_eyeglasses.xml");

                outStream = new FileOutputStream(cascadeFile);
                while ((rdBytes = inStream.read(buf)) != -1) {
                    outStream.write(buf, 0, rdBytes);
                } 
                outStream.close();
                inStream.close();

                // cv::CascadeClassifier nested_cascade;
                // if(!nested_cascade.load(nested_cascadeName))
                //     return -1;

                CascadeClassifier nested_cascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
                if (cascade.empty()) {
                    cascade = null;
                    return;
                }
                else {
                    cascadeDir.delete();
                    cascadeFile.delete();  
                }
                // std::vector<cv::Rect>::const_iterator r = faces.begin();
                // for(; r != faces.end(); ++r ) {
                for(Rect r : faces) {
                    // 検出結果(顔)の描画
                    // cv::Point face_center;
                    // int radius;
                    // face_center.x = cv::saturate_cast<int>((r->x + r->width*0.5)*scale);
                    // face_center.y = cv::saturate_cast<int>((r->y + r->height*0.5)*scale);
                    // face_radius = cv::saturate_cast<int>((r->width + r->height)*0.25*scale);
                    // cv::sircle(img, face_center, face_radius, cv::Scalar(80,80,255), 3, 8, 0);
                    Point face_center = new Point();
                    int face_radius;
                    face_center.x = (int)((r.x + r.width*0.5)*scale);
                    face_center.y = (int)((r.y + r.height*0.5)*scale);
                    face_radius = (int)((r.width + r.height)*0.25*scale);
                    Core.circle(srcMat, face_center, face_radius, new Scalar(80,80,255), 3, 8, 0);
                 
                    // cv::Mat smallImgROI = smallImg(*r);
                    // std::vector<cv::Rect> nestedObjects;

                    Mat smallMatROI = new Mat(smallMat, r);
                    List<Rect> nestedObjects = new LinkedList<Rect>();

                    /// マルチスケール(目)探索
                    // 画像, 出力矩形, 縮小スケール, 最低矩形数, (フラグ), 最小矩形
                    // nested_cascade.detectMultiScale(smallImgROI, nestedObjects,
                    //       1.1, 3
                    //       CV_HAAR_SCALE_IMAGE
                    //       ,
                    //       cv::Size(10,10);
                    nested_cascade.detectMultiScale(smallMatROI, nestedObjects,
                            1.1, 3, 2 // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                            , new Size(10, 10));

                    // 検出結果(目)の描画
                    // std::vector<cv::Rect>::const_iterator nr = nestedObjects.begin();
                    // for(; nr != nestedObjects.end(); ++nr ) {
                    //  cv::Point center;
                    //  int radius;
                    //  center.x = cv::saturate_cast<int>((r->x + nr->x + nr->width*0.5)*scale);
                    //   center.y = cv::saturate_cast<int>((r->y + nr->y + nr->height*0.5)*scale);
                    //  radius = cv::saturate_cast<int>((nr->width + nr->height)*0.25*scale);
                    //  cv::sircle(img, center, radious, cv::Scalar(80,255,80), 3, 8, 0);
                    // }
                    for (Rect nr : nestedObjects) {
                        Point center = new Point();
                        int radius;
                        center.x = (int)((r.x + nr.x + nr.width*0.5)*scale);
                        center.y = (int)((r.y + nr.y + nr.height*0.5)*scale);
                        radius = (int)((nr.width + nr.height)*0.25*scale);
                        Core.circle(srcMat, center, radius, new Scalar(80,255,80), 3, 8, 0);
                    }
                }
                Bitmap faceImg = convMatToBitmap(srcMat);
                ImageView faceView = (ImageView)findViewById(R.id.face_view);
                faceView.setImageBitmap(faceImg);
            } catch (FileNotFoundException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
   
    // MatからBitmapに変換
    Bitmap convMatToBitmap(Mat src) {
        Mat dst = new Mat();
        // BGR→RGBAに変換
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA, 4);
        Bitmap img = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888);
        // MatからBitmapに変換
        Utils.matToBitmap(dst, img);
        return img;
    }
}


[入力画像]
image02-1

[実行結果]
Image19

他の学習結果ファイルを使うことで, メガネをかけた人の目や, 鼻, 口なども検出できるので, 試してみてください.

----
参照URL:
 [1] OpenCV 2 プログラミングブック OpenCV2.2/2.3対応
 [2] OpenCV逆引きリファレンス―OpenCV-CookBook
 [3] OpenCVで学ぶ画像認識

[OpenCV] 顔を検出する


今回は, 画像認識の基本を説明する.

3. 18 顔を検出する[1][2][3]
OpenCVには, Haar-Like特徴やLBP(Local Binary Pattern)特徴を用いて, オブジェクト(顔, 目等)検出をするための仕組みが準備されている.
各特徴で, いくつかのオブジェクトを学習した学習結果ファイルが以下のディレクトリにあるので, 検出するオブジェクトの学習ファイルをres/rawの下にコピーする.
OpenCV-2.X.X-android\OpenCV-2.X.X\share\OpenCV\{haarcascades, lbpcascades}
 (1) Haar-Like特徴
   - haarcascade_eye.xml
   - haarcascade_eye_tree_eyeglasses.xml
   - haarcascade_frontalface_alt.xml
   - haarcascade_frontalface_alt2.xml
   - haarcascade_frontalface_alt_tree.xml
   - haarcascade_frontalface_default.xml
   - haarcascade_fullbody.xml
   - haarcascade_lefteye_2splits.xml
   - haarcascade_lowerbody.xml
   - haarcascade_mcs_eyepair_big.xml
   - haarcascade_mcs_eyepair_small.xml
   - haarcascade_mcs_leftear.xml
   - haarcascade_mcs_lefteye.xml
   - haarcascade_mcs_mouth.xml
   - haarcascade_mcs_nose.xml
   - haarcascade_mcs_rightear.xml
   - haarcascade_mcs_righteye.xml
   - haarcascade_mcs_upperbody.xml
   - haarcascade_profileface.xml
   - haarcascade_righteye_2splits.xml
   - haarcascade_upperbody.xml
 (2) LBP特徴
   - lbpcascade_frontalface.xml

[手順]
 (1) 画像データを読み込む.
 (2) グレースケール画像に変換し, 画像データを縮小する.
 (3) 顔の分類器を読み込む.
 (4) 顔を探索する.

[コード]
package com.moonlight_aska.android.opencv.image18;

import java.io.File;
import java.io.FileNotFoundException;
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.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;

import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import android.os.Bundle;
import android.widget.ImageView;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;

public class Image18Activity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // cv::Mat img = cv::imread("../../iamge/lenna.png", 1);
        Mat srcMat = Highgui.imread("/sdcard/OpenCV/sample/lena.jpg", 1);
        if (!srcMat.empty()) {
            double scale = 4.0;
            // cv::Mat gray, smallImg(cv::saturate_cast<int>(img.rows/scale),
            //        cv::saturate_cast<int>(img.cols/scale), CV_8UC1);

            Mat grayMat = new Mat();
            Size smallSize = new Size(srcMat.rows()/scale, srcMat.cols()/scale);
            Mat smallMat = new Mat(smallSize, CvType.CV_8UC1);

            // グレースケール画像に変換
            // cv::cvtColor(img, gray, CV_RGB2GRAY);

            Imgproc.cvtColor(srcMat, grayMat, Imgproc.COLOR_RGB2GRAY);
            // 処理時間短縮のために画像を縮小
            // cv::resize(gray, smallImg, smallImg.size(), 0, 0, cv::INTER_LENEAR;
            // cv::equalizeHist(smallImg, smallImg);

            Imgproc.resize(grayMat, smallMat, smallMat.size(), 0, 0, Imgproc.INTER_LINEAR);
            Imgproc.equalizeHist(smallMat, smallMat);

           
            try {
                // 分類器の読み込み
                // std::string cascadeName = "./haarcascade_frontalface_alt.xml"; // Haar-like

                InputStream inStream = getResources().openRawResource(R.raw.haarcascade_frontalface_alt);
                File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                File cascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt.xml");
       
                // // std::string cascadeName = "./lbpcascade_frontablface.xml"; // LBP
                // InputStream inStream = getResources().openRawResource(R.raw.lbpcascade_frontalface);
                // File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                // File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                FileOutputStream outStream;
                outStream = new FileOutputStream(cascadeFile);
                byte[] buf = new byte[2048];
                int rdBytes;
                while ((rdBytes = inStream.read(buf)) != -1) {
                    outStream.write(buf, 0, rdBytes);
                }
                outStream.close();
                inStream.close();
                // cv::CascadeClassifier cascade;
                // if(!cascade.load(cascadeName))
                //     return -1;

                CascadeClassifier cascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
                if (cascade.empty()) {
                    cascade = null;
                    return;
                }
                else {
                    cascadeDir.delete();
                    cascadeFile.delete();  
                }
                // std::vector<cv::Rect> faces;
                List<Rect> faces = new LinkedList<Rect>();
                /// マルチスケール(顔)探索
                // 画像, 出力矩形, 縮小スケール, 最低矩形数, (フラグ), 最小矩形
                // cascade.detectMultiScale(smallImg, faces,
                //                          1.1, 2
                //                          CV_HAAR_SCALE_IMAGE
                //                          ,
                //                          cv::Size(30,30);

                cascade.detectMultiScale(smallMat, faces,
                        1.1, 2, 2 // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                        , new Size(30, 30));

                // 結果の描画
                // std::vector<cv::Rect>::const_iterator r = faces.begin();
                // for(; r != faces.end(); ++r ) {
                //  cv::Point center;
                //  int radius;
                //  center.x = cv::saturate_cast<int>((r->x + r->width*0.5)*scale);
                //   center.y = cv::saturate_cast<int>((r->y + r->height*0.5)*scale);
                //  radius = cv::saturate_cast<int>((r->width + r->height)*0.25*scale);
                //  cv::sircle(img, center, radious, cv::Scalar(80,80,255), 3, 8, 0);
                // }
                for(Rect r : faces) {
                    Point center = new Point();
                    int radius;
                    center.x = (int)((r.x + r.width*0.5)*scale);
                    center.y = (int)((r.y + r.height*0.5)*scale);
                    radius = (int)((r.width + r.height)*0.25*scale);
                    Core.circle(srcMat, center, radius, new Scalar(80,80,255), 3, 8, 0);
                }
                Bitmap faceImg = convMatToBitmap(srcMat);
                ImageView faceView = (ImageView)findViewById(R.id.face_view);
                faceView.setImageBitmap(faceImg);
            } catch (FileNotFoundException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
   
    // MatからBitmapに変換
    Bitmap convMatToBitmap(Mat src) {
        Mat dst = new Mat();
        // BGR→RGBAに変換
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA, 4);
        Bitmap img = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888);
        // MatからBitmapに変換
        Utils.matToBitmap(dst, img);
        return img;
    }
}

[入力画像]
image02-1

[実行結果]
Image18

今回のポイントは, 顔を認識するための学習結果ファイル(xxxx.xml)をリソースから一旦ファイルに書き出し, 読み込んでいる点である.

----
参照URL:
 [1] OpenCV 2 プログラミングブック OpenCV2.2/2.3対応
 [2] OpenCV逆引きリファレンス―OpenCV-CookBook
 [3] OpenCVで学ぶ画像認識

プロフィール

明日香

アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

記事検索



  • ライブドアブログ