二(2.2)Android OpenCV NDK開発-画像再Java層とC/C++層伝達問題

16069 ワード

一、java層のピクチャがc/c+層処理にどのように伝達されるか、処理が完了した後にjava層にどのように伝達されるか、以下に用いられる3つの方法をまとめた.
  • Bitmapをint[]配列オブジェクトに変換し、配列をパラメータとしてC/C++レイヤに渡し、処理が完了したらint[]配列で返す.
  • Bitmap mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    ImageProc.getSobel(mBuildedBmp);
    mImageView.setImageBitmap(mBuildedBmp);
     
    //java    
    private static native int getSobel(Bitmap in,Bitmap out);
     
    //   C++          bitmap.h
    #include 
     
     //  C++  
    JNIEXPORT void JNICALL Java_com_dengxy_opencvtest_ImageProc_getSobel(
            JNIEnv * env, jclass obj, jobject bmpIn) {
        AndroidBitmapInfo inBmpInfo;
        void* inPixelsAddress;
        int ret;
        if ((ret = AndroidBitmap_getInfo(env, bmpIn, &inBmpInfo)) < 0) {
            LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
            return;
        }
        LOGI("original image :: width is %d; height is %d; stride is %d; format is %d;flags is   %d,stride is %u", inBmpInfo.width, inBmpInfo.height, inBmpInfo.stride, inBmpInfo.format, inBmpInfo.flags, inBmpInfo.stride);
        if ((ret = AndroidBitmap_lockPixels(env, bmpIn, &inPixelsAddress)) < 0) {
            LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        }
        Mat inMat(inBmpInfo.height, inBmpInfo.width,
                CV_8UC4, inPixelsAddress);
        Sobel(inMat, inMat, inMat.depth(), 1, 1);
        AndroidBitmap_unlockPixels(env, bmpIn);
        LOGI("Return !! ");
        return;
    }
    
  • Bitmapオブジェクトを直接層に渡し、C/C++Bitmapデータを取得するポインタをMatに変換する方法は、bitmapのメモリ空間を下層で直接操作し、操作前後にこのアドレス空間をロックし、java層がインタフェースをリフレッシュすればよい.ここのコードにはCV_が使用されていないAssert()は、他の実装方法
  • がある
    Bitmap mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    ImageProc.getSobel(mBuildedBmp);
    mImageView.setImageBitmap(mBuildedBmp);
     
    //java    
    private static native int getSobel(Bitmap in,Bitmap out);
     
    //   C++          bitmap.h
    #include 
     
     //  C++  
    JNIEXPORT void JNICALL Java_com_dengxy_opencvtest_ImageProc_getSobel(
            JNIEnv * env, jclass obj, jobject bmpIn) {
        AndroidBitmapInfo inBmpInfo;
        void* inPixelsAddress;
        int ret;
        if ((ret = AndroidBitmap_getInfo(env, bmpIn, &inBmpInfo)) < 0) {
            LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
            return;
        }
        LOGI("original image :: width is %d; height is %d; stride is %d; format is %d;flags is   %d,stride is %u", inBmpInfo.width, inBmpInfo.height, inBmpInfo.stride, inBmpInfo.format, inBmpInfo.flags, inBmpInfo.stride);
        if ((ret = AndroidBitmap_lockPixels(env, bmpIn, &inPixelsAddress)) < 0) {
            LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        }
        Mat inMat(inBmpInfo.height, inBmpInfo.width,
                CV_8UC4, inPixelsAddress);
        Sobel(inMat, inMat, inMat.depth(), 1, 1);
        AndroidBitmap_unlockPixels(env, bmpIn);
        LOGI("Return !! ");
        return;
    
    }
    

    3.Bitmapを直接Matに変換した後、matメモリアドレスを取得して下位層に転送し、処理後にjava層にメモリアドレスを返し、アドレスに基づいてMatオブジェクトをロードしてbitmapに変換する.この方法は前の方法より主にメモリスペースを変えることができますが、使うときにシステムがGCで画像を回収すると、底に空のポインタが現れ、底のMATの構造関数を見ると非空の判断をしていないので、opencv 4 androidを自分でコンパイルしてみました.2日間もコンパイルに失敗しました.涙-o-
    //Java   
            Bitmap oldBmp mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
            Mat bmpMat = new Mat();
            Utils.bitmapToMat(mBuildedBmp, bmpMat);
            long resultAddress = -1;
            resultAddress = ImageProc.getLaplacian(bmpMat.getNativeObjAddr());
            Log.d(TAG, "doLaplacian:resultAddress="+resultAddress);        
            if(resultAddress<0){
                return ;
            }
            Mat resultLaplacianMat = new Mat(resultAddress);
            Utils.matToBitmap(resultLaplacianMat, mBuildedBmp);
            mImageView.setImageBitmap(mBuildedBmp);
     
            //jni  
            public static native long getLaplacian(long bitmap);
     
            //c++  
    JNIEXPORT jlong JNICALL Java_com_dengxy_opencvtest_ImageProc_getLaplacian
          (JNIEnv * env, jclass obj, jlong bmAddress){
            LOGD("Java_com_dengxy_opencvtest_ImageProc_getLaplacian:start");
            Mat *bitmpaMat = (Mat*) bmAddress;
            if (NULL == bitmpaMat) {
                LOGD("Java_com_dengxy_opencvtest_ImageProc_getLaplacian:the bitmpaMat is Null");
                return -1;
            }
            Laplacian(*bitmpaMat,*bitmpaMat,bitmpaMat->depth());
            jlong resultAddress = (jlong)bitmpaMat;
            LOGD("Java_com_dengxy_opencvtest_ImageProc_getLaplacian:end");
            return resultAddress;
        }
    

    二、MatとBitmapの相互回転
    Javaレイヤコードで実装
    //1.    Bitmap   ,  opencv.android Utils    bitmapToMat     Mat  
    Bitmap oldBmp mBuildedBmp = BitmapFactory.decodeResource(getResources(), R.drawable.test);
            Mat bmpMat = new Mat();
            Utils.bitmapToMat(mBuildedBmp, bmpMat);
    //2.     :
    //      openCV JavaAPI     ;
    //    JNI  , C++         ,          Java   C/C++        。JNI           
    
    //3.   Mat     Bitmap    Java    。
    Mat resultLaplacianMat = new Mat(resultAddress);
            Utils.matToBitmap(resultLaplacianMat, mBuildedBmp);
            mImageView.setImageBitmap(mBuildedBmp);
    

    c++でコード実装
    #include 
    #include 
    #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "error", __VA_ARGS__))
    #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "debug", __VA_ARGS__))
    
    void BitmapToMat2(JNIEnv *env, jobject& bitmap, Mat& mat, jboolean needUnPremultiplyAlpha) {
        AndroidBitmapInfo info;
        void *pixels = 0;
        Mat &dst = mat;
    
        try {
            LOGD("nBitmapToMat");
            CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
            CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                      info.format == ANDROID_BITMAP_FORMAT_RGB_565);
            CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
            CV_Assert(pixels);
            dst.create(info.height, info.width, CV_8UC4);
            if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
                LOGD("nBitmapToMat: RGBA_8888 -> CV_8UC4");
                Mat tmp(info.height, info.width, CV_8UC4, pixels);
                if (needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA);
                else tmp.copyTo(dst);
            } else {
                // info.format == ANDROID_BITMAP_FORMAT_RGB_565
                LOGD("nBitmapToMat: RGB_565 -> CV_8UC4");
                Mat tmp(info.height, info.width, CV_8UC2, pixels);
                cvtColor(tmp, dst, COLOR_BGR5652RGBA);
            }
            AndroidBitmap_unlockPixels(env, bitmap);
            return;
        } catch (const cv::Exception &e) {
            AndroidBitmap_unlockPixels(env, bitmap);
            LOGE("nBitmapToMat catched cv::Exception: %s", e.what());
            jclass je = env->FindClass("org/opencv/core/CvException");
            if (!je) je = env->FindClass("java/lang/Exception");
            env->ThrowNew(je, e.what());
            return;
        } catch (...) {
            AndroidBitmap_unlockPixels(env, bitmap);
            LOGE("nBitmapToMat catched unknown exception (...)");
            jclass je = env->FindClass("java/lang/Exception");
            env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
            return;
        }
    }
    
    void BitmapToMat(JNIEnv *env, jobject& bitmap, Mat& mat) {
        BitmapToMat2(env, bitmap, mat, false);
    }
    
    void MatToBitmap2
            (JNIEnv *env, Mat& mat, jobject& bitmap, jboolean needPremultiplyAlpha) {
        AndroidBitmapInfo info;
        void *pixels = 0;
        Mat &src = mat;
    
        try {
            LOGD("nMatToBitmap");
            CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
            CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                      info.format == ANDROID_BITMAP_FORMAT_RGB_565);
            CV_Assert(src.dims == 2 && info.height == (uint32_t) src.rows &&
                      info.width == (uint32_t) src.cols);
            CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
            CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
            CV_Assert(pixels);
            if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
                Mat tmp(info.height, info.width, CV_8UC4, pixels);
                if (src.type() == CV_8UC1) {
                    LOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888");
                    cvtColor(src, tmp, COLOR_GRAY2RGBA);
                } else if (src.type() == CV_8UC3) {
                    LOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888");
                    cvtColor(src, tmp, COLOR_RGB2RGBA);
                } else if (src.type() == CV_8UC4) {
                    LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
                    if (needPremultiplyAlpha)
                        cvtColor(src, tmp, COLOR_RGBA2mRGBA);
                    else
                        src.copyTo(tmp);
                }
            } else {
                // info.format == ANDROID_BITMAP_FORMAT_RGB_565
                Mat tmp(info.height, info.width, CV_8UC2, pixels);
                if (src.type() == CV_8UC1) {
                    LOGD("nMatToBitmap: CV_8UC1 -> RGB_565");
                    cvtColor(src, tmp, COLOR_GRAY2BGR565);
                } else if (src.type() == CV_8UC3) {
                    LOGD("nMatToBitmap: CV_8UC3 -> RGB_565");
                    cvtColor(src, tmp, COLOR_RGB2BGR565);
                } else if (src.type() == CV_8UC4) {
                    LOGD("nMatToBitmap: CV_8UC4 -> RGB_565");
                    cvtColor(src, tmp, COLOR_RGBA2BGR565);
                }
            }
            AndroidBitmap_unlockPixels(env, bitmap);
            return;
        } catch (const cv::Exception &e) {
            AndroidBitmap_unlockPixels(env, bitmap);
            LOGE("nMatToBitmap catched cv::Exception: %s", e.what());
            jclass je = env->FindClass("org/opencv/core/CvException");
            if (!je) je = env->FindClass("java/lang/Exception");
            env->ThrowNew(je, e.what());
            return;
        } catch (...) {
            AndroidBitmap_unlockPixels(env, bitmap);
            LOGE("nMatToBitmap catched unknown exception (...)");
            jclass je = env->FindClass("java/lang/Exception");
            env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
            return;
        }
    }
    
    void MatToBitmap(JNIEnv *env, Mat& mat, jobject& bitmap) {
        MatToBitmap2(env, mat, bitmap, false);
    }
    

    jni呼び出しカラムサブ
    JNIEXPORT void JNICALL Java_com_hzzj_opencv_demo_CppMosaicUtils_opencvImageblur(
            JNIEnv *env, jobject jobj, jobject jsrcBitmap){
    
        Mat mat_image_src ;
        BitmapToMat(env,jsrcBitmap,mat_image_src);//     mat
        Mat mat_image_dst;
        blur(mat_image_src, mat_image_dst, Size2i(10,10));
        //   :  java  ->  
        MatToBitmap(env,mat_image_dst,jsrcBitmap);//mat     
    }
    

    三、関連知識
    3.1 AndroidではJNIでBitmapを操作します.
    AndroidでJNIでBitmapを呼び出し、CMakeでsoダイナミックリンクライブラリを作成する場合は、jnigraphicsイメージライブラリを追加する必要があります.
    target_link_libraries( # Specifies the target library.
                           native-operation
                           jnigraphics
                           ${log-lib} )
    

    AndroidではJNI Bitmapの操作についてbitmap.hのヘッダファイルに定義されていますが、主に3つの関数について、その意味が分かれば実践することができます.
    3.1.1 Bitmapオブジェクト情報の検索-Android Bitmap_getInfo AndroidBitmapInfo
    AndroidBitmap_getInfo関数を使用すると、Bitmapオブジェクトのサイズ、ピクセルフォーマットなどのオリジナルコードを取得できます.関数署名は次のとおりです.
    /**
     * Given a java bitmap object, fill out the AndroidBitmapInfo struct for it.
     * If the call fails, the info parameter will be ignored.
     */
    int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                              AndroidBitmapInfo* info);
    

    ここで、1番目のパラメータはJNIインタフェースポインタであり、2番目のパラメータはBitmapオブジェクトの参照であり、3番目のパラメータはAndroidBitmapInfo構造体へのポインタである.
    AndroidBitmapInfo構造体は以下の通り.
    /** Bitmap info, see AndroidBitmap_getInfo(). */
    typedef struct {
        /** The bitmap width in pixels. */
        uint32_t    width;
        /** The bitmap height in pixels. */
        uint32_t    height;
        /** The number of byte per row. */
        uint32_t    stride;
        /** The bitmap pixel format. See {@link AndroidBitmapFormat} */
        int32_t     format;
        /** Unused. */
        uint32_t    flags;      // 0 for now
    } AndroidBitmapInfo;
    

    ここでwidthはBitmapの幅であり、heightは高さであり、formatは画像のフォーマットであり、strideは各行のバイト数である.
    画像のフォーマットは次のようにサポートされています.
    /** Bitmap pixel format. */
    enum AndroidBitmapFormat {
        /** No format. */
        ANDROID_BITMAP_FORMAT_NONE      = 0,
        /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
        ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
        /** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
        ANDROID_BITMAP_FORMAT_RGB_565   = 4,
        /** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/
        ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
        /** Alpha: 8 bits. */
        ANDROID_BITMAP_FORMAT_A_8       = 8,
    };
    

    Android Bitmap_getInfoの実行に成功すると、0が返されます.そうしないと、実行されたエラーコードのリストを表す負の数が返されます.
    /** AndroidBitmap functions result code. */
    enum {
        /** Operation was successful. */
        ANDROID_BITMAP_RESULT_SUCCESS           = 0,
        /** Bad parameter. */
        ANDROID_BITMAP_RESULT_BAD_PARAMETER     = -1,
        /** JNI exception occured. */
        ANDROID_BITMAP_RESULT_JNI_EXCEPTION     = -2,
        /** Allocation failed. */
        ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = -3,
    };
    

    3.1.2オリジナルピクセルキャッシュへのアクセス-Android Bitmap_lockPixels
    AndroidBitmap_lockPixels関数は、ピクセルキャッシュをロックして、ピクセルのメモリが移動しないようにします.
    Nativeレイヤがピクセルデータにアクセスして操作したい場合、このメソッドはピクセルキャッシュのオリジナルポインタを返します.
    /**
     * Given a java bitmap object, attempt to lock the pixel address.
     * Locking will ensure that the memory for the pixels will not move
     * until the unlockPixels call, and ensure that, if the pixels had been
     * previously purged, they will have been restored.
     *
     * If this call succeeds, it must be balanced by a call to
     * AndroidBitmap_unlockPixels, after which time the address of the pixels should
     * no longer be used.
     *
     * If this succeeds, *addrPtr will be set to the pixel address. If the call
     * fails, addrPtr will be ignored.
     */
    int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
    

    ここで、1番目のパラメータはJNIインタフェースポインタであり、2番目のパラメータはBitmapオブジェクトの参照であり、3番目のパラメータは画素キャッシュアドレスへのポインタである.
    AndroidBitmap_lockPixelsの実行に成功すると0を返します.そうしないと負の数を返します.エラーコードのリストは前述のとおりです.
    3.1.3オリジナルピクセルキャッシュの解放-Android Bitmap_unlockPixels
    BitmapにAndroidBitmapを呼び出しました_lockPixelsの後にAndroid Bitmapを呼び出すべきです.unlockPixelsは、オリジナルのピクセルキャッシュを解放するために使用されます.
    元のピクセルキャッシュの読み書きが完了したら、それを解放する必要があります.いったん解放されると、Bitmap JavaオブジェクトはJavaレイヤで使用できます.関数署名は次のとおりです.
    /**
     * Call this to balance a successful call to AndroidBitmap_lockPixels.
     */
    int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
    

    ここで、最初のパラメータはJNIインタフェースポインタであり、2番目のパラメータはBitmapオブジェクトの参照であり、実行に成功したら0を返し、そうでなければ1を返します.
    Bitmapの操作で一番大切なのはAndroid Bitmap_lockPixels関数は、すべてのピクセルのキャッシュアドレスを取得し、各ピクセル値を操作してBitmapを変更します.
    3.2 openCV API : CV_Assert
    CV_Assert()関数はC++標準ライブラリのassert()関数とほぼ同じです.
    CV_Assert()の役割:CV_Assert()カッコ内の式の値がfalseの場合、エラーメッセージが返され、プログラムの実行が終了します.
    ssertマクロのプロトタイプ定義はassert.hにあり、その条件がエラーを返すとプログラム実行を終了し、プロトタイプ定義:#include assert.h void assert(int expression);
    assertの役割は、計算式expressionであり、値が偽(すなわち0)の場合、stderrにエラー情報を印刷し、abortを呼び出してプログラムの実行を終了することです.次のプログラムのリストを見てください.
    assert()を使用する欠点は、頻繁な呼び出しがプログラムのパフォーマンスに大きく影響し、追加のオーバーヘッドが増加することです.デバッグが終了すると、#include assert.hを含む前の文の前に#define NDEBUGを挿入してassert呼び出しを無効にできます.サンプルコードは次のとおりです.
    リファレンス
    AndroidはOpencvピクチャを使用してMatとBitmapの相互回転を処理します.https://www.jianshu.com/p/08dcc910b088
    Android JNIのBitmap操作-https://www.jianshu.com/p/8efb655d9305
    Jniでの画像配信の3つの方法(回転)-アヒル船長-ブログ園https://www.cnblogs.com/zl1991/p/7778394.html