OpenCVで特定の色領域だけ抜き出すコード(JNI版)

OpenCV for Androidで特定の色領域だけ抜き出すコードを書きました。


OpenCV for AndroidではMatクラスのnative instanceはMat.nativeObjで取得できます。
ここには単純にOpenCV内部のMatクラスへのポインタが格納されているので、えいやっとそいつを取ってきて、あとはゴニョゴニョするだけですね。
処理自体はHSV色空間でやっているので、RGB(A)→HSV→RGBAと変換しています。
具体的には特定範囲の色相で、且つ一定値以上の彩度・明度をもった色を残してあとは黒で塗るということをしています。

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <android/log.h>

#define LOG_TAG "android_log_tag"
#define LOGD(...) (void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);

static void throw_CvException(JNIEnv *env, cv::Exception &e);

JNIEXPORT void JNICALL Java_jni_class_fqn_colorMask
  (JNIEnv *env, jclass clazz, jlong src, jlong dst, jint minHue, jint maxHue, jint minSaturation, jint minValue)
{
    try {
        cv::Mat const *srcMat = static_cast<cv::Mat const *>(reinterpret_cast<void const *>(src));
        cv::Mat *dstMat = static_cast<cv::Mat*>(reinterpret_cast<void *>(dst));

        std::vector<cv::Mat> hsv;

        cv::Mat hsvMat;
        cv::cvtColor(*srcMat, hsvMat, CV_BGR2HSV, 3);
        cv::split(hsvMat, hsv);

        int const c = srcMat->cols;
        int const r = srcMat->rows;
        for (int y = 0; y < c; ++y) {
            for (int x = 0; x < r; ++x) {
                int const h = (hsv[0].at<uint8_t>(x, y) & 0xff) << 1;
                bool isMasked = false;
                if ((minHue <= h) && (h <= maxHue)) {
                    int const s = hsv[1].at<uint8_t>(x, y) & 0xff;
                    if (minSaturation <= s) {
                        int const v = hsv[2].at<uint8_t>(x, y) & 0xff;
                        if (minValue <= v) {
                            isMasked = true;
                        }
                    }
                }
                if (!isMasked) {
                    hsv[1].at<uint8_t>(x, y) = 0;
                    hsv[2].at<uint8_t>(x, y) = 0;
                }
            }
        }

        cv::Mat merged;
        cv::merge(hsv, merged);
        cv::cvtColor(merged, *dstMat, CV_HSV2BGR, 4);
    } catch (cv::Exception& e) {
        LOGD("colorMask() catched cv::Exception: %s", e.what());
        throw_CvException(env, e);
    }
}

static void throw_exception(JNIEnv *env, char const * const className, char const * const message)
{
    jclass cls = env->FindClass(className);
    if (!cls) {
        cls = env->FindClass("java/lang/Exception");
    }
    env->ThrowNew(cls, message);
    env->DeleteLocalRef(cls);
}

static void throw_CvException(JNIEnv *env, cv::Exception &e)
{
    throw_exception(env, "org/opencv/core/CvException", e.what());
}

NDKでbuildするにはopencvのshared libraryが必要なので、こんな感じでAndroid.mkを書いてみました。

LOCAL_PATH := $(call my-dir)
OPENCV_PATH := /path/to/opencv
OPENCV_LIBS := /path/to/opencv/libs

# dummy.
# copy libopencv_java.so into "obj/local/armeabi" manually.
include $(CLEAR_VARS)
LOCAL_MODULE    := opencv_java
include $(BUILD_SHARED_LIBRARY)

# build library
include $(CLEAR_VARS)

LOCAL_MODULE    := your_project
LOCAL_CPPFLAGS  := -Wall -Werror -O2
LOCAL_SRC_FILES := your_source.cpp
LOCAL_LDLIBS    += -llog
LOCAL_C_INCLUDES := $(OPENCV_PATH)/modules/core/include $(OPENCV_PATH)/modules/imgproc/include
LOCAL_SHARED_LIBRARIES := libopencv_java

include $(BUILD_SHARED_LIBRARY)

中に書いてあるように、libopencv_java.soをリンクさせるためにdummy projectを作っています。
こんな感じのshell scriptを書いて適当にlibopencv_java.soをコピると良いかと思います。

#!/bin/sh

OPENCV_LIB=/path/to/opencv/libs

mkdir -p ../obj/local/armeabi
mkdir -p ../obj/local/armeabi-v7a

cp $OPENCV_LIB/armeabi/libopencv_java.so ../obj/local/armeabi
cp $OPENCV_LIB/armeabi-v7a/libopencv_java.so ../obj/local/armeabi-v7a

ndk-build

if [ -e ../libs/armeabi/libopencv_java.so ]; then
    rm ../libs/armeabi/libopencv_java.so
fi
if [ -e ../libs/armeabi-v7a/libopencv_java.so ]; then
    rm ../libs/armeabi-v7a/libopencv_java.so
fi

armeabi-v7aがいらないひとは適当にけずってください。

JNIですまぽみたいなの使いたい

はじめてJNIのコード書いていて、すまぽ欲しいよねこれと思ったのでちょこっと書いてみた。
こんな感じ。

static void throw_IndexOutOfBoundsException(JNIEnv *env, char const * const message);

template <typename T> class safe_array {
private:
    JNIEnv *env_;
    jarray const &array_;
    T* ptr_;
    size_t size_;
    bool is_copy_;
    bool is_aborted_;
    T dummy_;
public:
    safe_array(JNIEnv *env, jarray const &array)
        : env_(env), array_(array), ptr_(NULL), size_(0), is_copy_(false), is_aborted_(false), dummy_(0) {
        jboolean is_copy = JNI_FALSE;
        ptr_  = static_cast<T*>(env->GetPrimitiveArrayCritical(array, &is_copy));
        size_ = env->GetArrayLength(array);
        is_copy_ = (JNI_FALSE == is_copy) ? false : true;
    }
    ~safe_array() {
        if (is_aborted_) {
            env_->ReleasePrimitiveArrayCritical(array_, ptr_, JNI_ABORT);
        } else {
            env_->ReleasePrimitiveArrayCritical(array_, ptr_, is_copy_ ? JNI_COMMIT : 0);
        }
    }
    T& operator[] (int const &index) {
        if ((index < 0) || ((size_t)index >= size_)) {
            throw_IndexOutOfBoundsException(env_, "Index out of bounds");
            return dummy_;
        }
        return ptr_[index];
    }
    T const& operator[] (int const &index) const {
        if ((index < 0) || ((size_t)index >= size_)) {
            throw_IndexOutOfBoundsException(env_, "Index out of bounds");
            return dummy_;
        }
        return ptr_[index];
    }
    T* get() {
        return ptr_;
    }
    T const *get() const {
        return ptr_;
    }
    size_t size() const {
        return size_;
    }
    void abort() const {
        is_aborted_ = true;
    }
    bool is_copy() const {
        return is_copy_;
    }
    bool is_aborted() const {
        return is_aborted_;
    }
};

jarrayを中でポインタに変換したまま保持するやつ。
本当はconstructorをjarrayで受けるんじゃなくて、元の型(jbyteArrayとか)で受けて型安全にするべきなんだけど、時間がなかったので手抜き。

Androidはなんかよく知らないけどもGetPrimitiveArrayCriticalやるとコピーが発生しなくて早いとかなんとかって話らしい(要出典)ので、こんな実装。

C++のexception使おうとしたらlinkの関係で怒られたので渋々こんな実装になっている。dummyを返すとかあほらしいことしたくないでござる。


あとはこんなのとか。

template <typename T> class safe_local_ref {
private:
    JNIEnv *env_;
    T ref_;
public:
    safe_local_ref(JNIEnv *env, T obj)
        : env_(env), ref_(obj) {
    }
    ~safe_local_ref() {
        env_->DeleteLocalRef(ref_);
    }
    T get() const {
        return ref_;
    }
    bool operator == (T &opr) const {
        return ref_ == opr;
    }
    bool operator != (T &opr) const {
        return ref_ != opr;
    }
    bool operator ! () const {
        return !ref_;
    }
    friend bool operator == (T left, safe_local_ref<T> const &right) {
        return left == right.ref_;
    }
    friend bool operator != (T left, safe_local_ref<T> const &right) {
        return left != right.ref_;
    }
};