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_; } };