2011年2月27日日曜日

Mac OS X + Android + OpenCV2.2

参照:Android - OpenCV Wiki
Ubuntu 10.10 + Android + OpenCVの環境をセットアップ(hironemu's blog)

この前「hironemu」さんのブログを参照して自分の環境でAndroid+OpenCV2.2をセットアップしてみて簡単なエッジ効果を試してみたのを今日まとめてみた。

1. 事前に確認するUtility

「swig」、「swig-java」、「apache-ant」、「cmake」
自分の環境で入ってなかったらインストールしておく。
僕はMacPortsからインストールしました。
$ sudo port install swig
$ sudo port install swig-java
$ sudo port install cmake
「swig」と「swig-java」のactiveバージョンが合わないとエラーになるので、
activeバージョンを合わせ

2. NDK

http://developer.android.comで落とした「ndk r4」ではC++の問題で動かないらしい。
(r5はC++サポートしているのでいけるかなと思ったら、ビルド方法が面倒でやめた。)

「http://www.crystax.net/android/ndk-r4.php」サイトで
C++サポートするようにカスタマイズされた Android NDK r4 を落とす。

ndkが「$HOME/android-ndk-r4-crystax」じゃないとダメらしい。
気に入らないが $HOMEにリンクを作る。

3. OpenCV2.2

OpenCVソース取得。
参照サイトにはSVNから落とすように書いてあるが、ビルドで失敗する可能性があるので、
2.2の安定バージョンのソースを落とした。「http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.2/」

落としたのを好きな場所に解凍してビルドする。

OpenCV Wikiのビルド手順
$ cd opencv/android
$ mkdir build
$ cd build
$ cmake ..
$ make

4. android-jni

Android NDKで使用する「android-opencv」を生成する。
$ cd opencv/android/android-jni
$ make
実行後、生成された「local.env.mk」ファイルの
「ANDROID_NDK_ROOT」項目を自分の環境に合わせて変更して、もう一回「make」(hironemu's blog参照)

「opencv/android/android-jni/libs」に
「armeabi/libandroid-opencv.so」、「armeabi-v7a/libandroid-opencv.so」ファイルができたら成功。



5. エッジ効果を試す。

エッジ効果をテストしたアプリのスクリーンショットはこちら。
元の画像
エッジ効果した画像
問題なく動くが、やはり実機でしか動かなかった。

OpenCVアプリを作るときは
OpenCV Wikiに書いてあるように「project_create.sh」を実行するか
自分で作るなら、NDKの設定で「Android.mk」にOpenCVのライブラリとヘッダのパスを追加する。
#Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := OpenCVUtil
LOCAL_SRC_FILES := babukuma_opencv_util.c
LOCAL_LDLIBS    := -lm -llog

#define OPENCV_INCLUDES and OPENCV_LIBS
PATH_TO_OPENCV_ANDROID_BUILD := ~/dev/OpenCV-2.2.0/android/build
include $(PATH_TO_OPENCV_ANDROID_BUILD)/android-opencv.mk

LOCAL_LDLIBS += $(OPENCV_LIBS)
    
LOCAL_C_INCLUDES +=  $(OPENCV_INCLUDES) 

include $(BUILD_SHARED_LIBRARY)
これなら問題なく「ndk-build」でビルドされる。

以下は作ったソース。。
/**
 * OpenCVUtil.java
 */
package com.babukuma.android.demo.opencv.jni;

/**
 * @author babukuma
 * 
 */
public final class OpenCVUtil {
 static {
  System.loadLibrary("OpenCVUtil");
 }

 /**
  * エッジ効果。
  * 
  * @param picData
  *            RGBのピクセルデータ
  * @param width
  *            画像のWidth
  * @param height
  *            画像のHeight
  */
 public static native void edge(int[] picData, int width, int height);
}
/**
 * EdgeActivity.java
 */
package com.babukuma.android.demo.opencv.activity;

import static com.babukuma.android.demo.opencv.Main.LOG_TAG;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;

import com.babukuma.android.demo.opencv.R;
import com.babukuma.android.demo.opencv.jni.OpenCVUtil;

/**
 * エッジ効果テスト用Activity
 * 
 * @author babukuma
 */
public class EdgeActivity extends Activity {
 private ImageView picView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Log.d(LOG_TAG, "EdgeActivity#onCreate");

  setContentView(R.layout.edge);

  picView = (ImageView) findViewById(R.id.picView);
  picView.setOnClickListener(new OnClickListener() {

   public void onClick(View v) {
    Log.d(LOG_TAG, "picView#1onClick");
    BitmapDrawable db = (BitmapDrawable) picView.getDrawable();
    Bitmap bitmap = db.getBitmap();
    // Bitmap.Config.ARGB_8888
    Bitmap copyBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
    int width = copyBitmap.getWidth();
    int height = copyBitmap.getHeight();
    int[] pixels = new int[width * height];
    copyBitmap.getPixels(pixels, 0, width, 0, 0, width, height);

    Log.d(LOG_TAG, "OpenCVUtil#edge");
    OpenCVUtil.edge(pixels, width, height);

    Log.d(LOG_TAG, "finish OpenCVUtil#edge");
    copyBitmap.setPixels(pixels, 0, width, 0, 0, width, height);

    picView.setImageBitmap(copyBitmap);
   }
  });
 }
}
/*
 * babukuma_opencv_util.h
 */
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_babukuma_android_demo_opencv_jni_OpenCVUtil */

#ifndef _Included_com_babukuma_android_demo_opencv_jni_OpenCVUtil
#define _Included_com_babukuma_android_demo_opencv_jni_OpenCVUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_babukuma_android_demo_opencv_jni_OpenCVUtil
 * Method:    edge
 * Signature: ([III)V
 */
JNIEXPORT void JNICALL Java_com_babukuma_android_demo_opencv_jni_OpenCVUtil_edge
  (JNIEnv *, jclass, jintArray, jint, jint);

#ifdef __cplusplus
}
#endif
#endif
/*
 * babukuma_opencv_util.c
 */
#include <stdio.h>

// JNI
#include<jni.h>

// Android
#include <android/log.h>

// OpenCV
#include <opencv/cv.h>

#include "babukuma_opencv_util.h"

// Android LOG
#define  LOG_TAG    "OpenCVDemo"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

// 関数定義
IplImage* pixels2IplImage(int* pixels, int width, int height);
void iplImage2Pixcels(int *pixcels, IplImage *srcImage);


/*
 * エッジ効果。
 * 「picData」データは必ず「ARGB」
 *
 * Class:     com_babukuma_android_demo_opencv_jni_OpenCVUtil
 * Method:    edge
 * Signature: ([III)V
 */
JNIEXPORT void JNICALL Java_com_babukuma_android_demo_opencv_jni_OpenCVUtil_edge
  (JNIEnv *env, jclass class, jintArray picData, jint width, jint height)
{
 LOGD("call Java_com_babukuma_android_demo_opencv_jni_OpenCVUtil_edge");

 jboolean isCopy;

 //Colorの配列を取り出す(配列のポインタを取得)
 jint* picDataPtr = (*env)->GetIntArrayElements(env, picData, &isCopy);

 //Color(int) の配列から、IplImageを作る
 LOGD("call pixels2IplImage");
 IplImage *srcImage = pixels2IplImage(picDataPtr, width, height);

 //画像の作成完了。
 LOGD("CV_BGR2GRAY");
 IplImage *wordImage=cvCreateImage(cvGetSize(srcImage),IPL_DEPTH_8U,1);
 cvCvtColor(srcImage, wordImage, CV_BGR2GRAY);
 LOGD("cvReleaseImage srcImage");
 cvReleaseImage(&srcImage);
 srcImage = NULL;

 // Detect edge
 LOGD("Detect edge");
 IplImage *wordImage2 = cvCreateImage(cvGetSize(wordImage), IPL_DEPTH_8U, 1);
 cvCanny(wordImage, wordImage2, 64, 128, 3);
 cvReleaseImage(&wordImage);
 LOGD("cvReleaseImage wordImage");
 cvReleaseImage(&wordImage);
 wordImage = NULL;


 // Convert black and whilte to 24bit image then convert to UIImage to show
 IplImage *edgedImage = cvCreateImage(cvGetSize(wordImage2), IPL_DEPTH_8U, 3);
 int y, x;
 for(y=0; y<wordImage2->height; y++) {
  for(x=0; x<wordImage2->width; x++) {
   char *p = edgedImage->imageData + y * edgedImage->widthStep + x * 3;
   *p = *(p+1) = *(p+2) = wordImage2->imageData[y * wordImage2->widthStep + x];
  }
 }
 LOGD("cvReleaseImage wordImage2");
 cvReleaseImage(&wordImage2);
 wordImage2 = NULL;

 //IplImageの値を、int配列に戻す
 LOGD("call iplImage2Pixcels");
 iplImage2Pixcels(picDataPtr, edgedImage);

 LOGD("cvReleaseImage edgedImage");
 cvReleaseImage(&edgedImage);
 edgedImage = NULL;

 (*env)->ReleaseIntArrayElements(env, picData, picDataPtr, 0);
}

// jint配列からIplImageを作成
// alphaは捨てる。
IplImage* pixels2IplImage(int *pixels, int width, int height) {
 int x, y, index;

 IplImage *img = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
 unsigned char* base = (unsigned char*) (img->imageData);
 unsigned char* ptr;

 for (y = 0; y < height; y++) {
  ptr = base + y * img->widthStep;
  for (x = 0; x < width; x++) {
   index = x + y * width;
   // B
   ptr[3 * x] = pixels[index] & 0xFF;
   // G
   ptr[3 * x + 1] = pixels[index] >> 8 & 0xFF;
   // R
   ptr[3 * x + 2] = pixels[index] >> 16 & 0xFF;
   // A
   // pixels[index] >> 24 & 0xFF;
  }
 }
 return img;
}

// IplImageからjint配列作成
void iplImage2Pixcels(int *pixcels, IplImage *srcImage)
{
    int x, y;
    int w,h;
    w=srcImage->width;
    h=srcImage->height;

    unsigned char* base = (unsigned char*) (srcImage->imageData);
    unsigned char* ptr;
    for (y = 0; y < h; y++)
    {
        ptr = base + y * srcImage->widthStep;
        for (x = 0; x < w; x++)
        {
            pixcels[x + y * w] =
              (0xFF000000) | // A
              (ptr[3 * x + 2] << 16) | // R
              (ptr[3 * x + 1] << 8) | // G
              (ptr[3 * x]); // B
        }
     }
}

彼女もエッジ効果させてみました。
怖い彼女になった。