【问题标题】:Android OpenCV poor performance with JNI使用 JNI 的 Android OpenCV 性能不佳
【发布时间】:2016-06-22 12:49:56
【问题描述】:

我对 Android 下的 OpenCV 3.10 有很大的问题。我正在开发一个对相机预览进行模板匹配的应用程序。 第一种方法是使用运行良好的 OpenCV Java Wrapper。一个处理周期大约需要 3.6 秒。为了加快速度,我用 C++ 重新开发了代码。由于某种原因,一个周期的执行开始需要长达 35 秒。 为了加快速度并利用多线程能力,我将 JNI 执行移至 AsyncTask。从那以后,单次执行最多需要 65 秒。

我正在使用 gradle 实验插件 0.7.0,它被认为是稳定的和最新的 NDK(截至目前为 12.1)。

这是我的模块 build.gradle

    ndk {
        moduleName "OpenCVWrapper"
        ldLibs.addAll(["android", "log", "z"])
        cppFlags.add("-std=c++11")
        cppFlags.add("-fexceptions")
        cppFlags.add("-I"+file("src/main/jni").absolutePath)
        cppFlags.add("-I"+file("src/main/jni/opencv2").absolutePath)
        cppFlags.add("-I"+file("src/main/jni/opencv").absolutePath)
        stl = "gnustl_shared"
        debuggable = "true"
    }
    productFlavors {
        create("arm") {
            ndk.with {
                abiFilters.add("armeabi")
                String libsDir = file('../openCVLibrary310/src/main/jniLibs/armeabi/').absolutePath+'/'
                ldLibs.add(libsDir + "libopencv_core.a")
                ldLibs.add(libsDir + "libopencv_highgui.a")
                ldLibs.add(libsDir + "libopencv_imgproc.a")
                ldLibs.add(libsDir + "libopencv_java3.so")
                ldLibs.add(libsDir + "libopencv_ml.a")

            }
        }
        create("armv7") {
            ndk.with {
                abiFilters.add("armeabi-v7a")
                String libsDir = file('../openCVLibrary310/src/main/jniLibs/armeabi-v7a/').absolutePath+'/'
                ldLibs.add(libsDir + "libopencv_core.a")
                [... and so on ...]

以下是在大约 3-4 秒内执行的 Android-Java 代码:

    // data is byte[] from camera
    Mat yuv = new Mat(height+height/2, width, CvType.CV_8UC1);
    yuv.put(0,0,data);
    Mat input = new Mat(height, width, CvType.CV_8UC3);

    Imgproc.cvtColor(yuv, input, Imgproc.COLOR_YUV2RGB_NV12, 3);
    yuv.release();

    int midPoint = Math.min(input.cols(), input.rows())/2;
    Mat rotated = new Mat();
    Imgproc.warpAffine(input, rotated,
            Imgproc.getRotationMatrix2D(new Point(midPoint, midPoint), 270, 1.0),
            new Size(input.rows(), input.cols()));
    input.release();

    android.util.Size packageRect = midRect.getSize();
    input.release();

    Rect r = new Rect(((rotated.cols()/2)-(packageRect.getWidth()/2)),
            ((rotated.rows()/2)-(packageRect.getHeight()/2)),
            packageRect.getWidth(), packageRect.getHeight());
    Mat cut = new Mat(rotated, r);
    Mat scaled = new Mat();
    Imgproc.resize(cut,scaled, new Size(323, 339), 0, 0, Imgproc.INTER_AREA);
    Imgcodecs.imwrite(getExternalFileName("cutout").getAbsolutePath(), cut);
    cut.release();

    Mat output = new Mat();
    Imgproc.matchTemplate(pattern, scaled, output, Imgproc.TM_CCOEFF_NORMED);
    Core.MinMaxLocResult tmplResult = Core.minMaxLoc(output);

    findPackage(tmplResult.maxLoc.x+150);
    scaled.release();
    input.release();
    output.release();
    cut.release();

反过来,这就是 C++ 代码做同样的事情:

JNIEXPORT void JNICALL Java_at_identum_planogramscanner_ScanActivity_scanPackage(JNIEnv *env, jobject instance, jbyteArray input_, jobject data, jlong output, jint width, jint height, jint rectWidth, jint rectHeight) {
jbyte *input = env->GetByteArrayElements(input_, NULL);

jclass resultDataClass = env->GetObjectClass(data);
jmethodID setResultMaxXPos = env->GetMethodID(resultDataClass, "setMaxXPos", "(I)V");
jmethodID setResultMinXPos = env->GetMethodID(resultDataClass, "setMinXPos", "(I)V");
jmethodID setResultMinVal = env->GetMethodID(resultDataClass, "setMinVal", "(F)V");
jmethodID setResultMaxVal = env->GetMethodID(resultDataClass, "setMaxVal", "(F)V");

LOGE("Before work");

Mat convert(height+height/2, width, CV_8UC1, (unsigned char*)input);
Mat img(height, width, CV_8UC3);
cvtColor(convert, img, CV_YUV2RGB_NV12, 3);
convert.release();

LOGE("After Colorconvert");

int midCoord = min(img.cols, img.rows)/2;
Mat rot;
Mat rotMat = getRotationMatrix2D(Point2f(midCoord,midCoord), 270, 1.0);
warpAffine(img, rot, rotMat, Size(img.rows, img.cols));
rotMat.release();

LOGE("After Rotation");

Rect r(
        (rot.cols/2-rectWidth/2),
        (rot.rows/2-rectHeight/2),
        rectWidth, rectHeight );
Mat cut(rot,r);
rot.release();

LOGE("After Cutting");

Mat scaled(Size(323, 339), CV_8UC3);
resize(cut, scaled, Size(323,339),0,0,INTER_AREA);
cut.release();

LOGE("After Scaling");

Mat match(pattern.cols, 1, CV_8UC1);
matchTemplate(pattern, scaled, match, TM_SQDIFF_NORMED);
scaled.release();

LOGE("After Templatematching and normalize");

double minVal; double maxVal; Point minLoc; Point maxLoc;
minMaxLoc(match, &minVal, &maxVal, &minLoc, &maxLoc, Mat());

img.release();
env->CallVoidMethod(data, setResultMinXPos, minLoc.x);
env->CallVoidMethod(data, setResultMaxXPos, maxLoc.x);
env->CallVoidMethod(data, setResultMinVal, minVal);
env->CallVoidMethod(data, setResultMaxVal, maxVal);

LOGE("After Calling JNI funcs");

env->ReleaseByteArrayElements(input_, input, 0);

正如您所看到的,它实际上是完全相同的工作,我希望它的运行速度比用 Android-Java 编写的要快一点,但从 AsyncTask 运行时肯定不会慢 10 倍,也绝对不会慢 20 倍。

我最好的结论是 OpenCV 的 .a 档案需要某种编译器设置来尽可能加快速度。我希望任何人都可以指出我正确的方向!

提前致谢!

【问题讨论】:

    标签: android performance opencv android-ndk java-native-interface


    【解决方案1】:

    我最近使用 OpenCV 的 JAVA 包装器做了一个实时人脸识别应用程序,和你一样,我想从中获得更多性能,所以我实现了一个 JNI 版本。再次像您的情况一样,JNI 版本比 JAVA 包装器版本慢,尽管只是一点点。

    对于您的情况,我可以看到为什么性能突然受到影响,这发生在这里

    jbyte *input = env->GetByteArrayElements(input_, NULL);

    您可以在线阅读更多关于这很慢的信息,因为 JNI 总是将(使用 GetByteArrayElements)从 JAVA 复制到 C++。取决于相机预览大小,副本可能非常重要尤其是对于实时过程

    这是一种加快代码速度的方法,而不是将 Mat 字节发送到 JNI,您可以直接发送 Mat 指针地址,

    在 JAVA 中

    public void processFrame(byte[] data) {
        Mat raw = new Mat();
        raw.put(0, 0, data); //place the bytes into a Mat
        scanPackage(...,raw.native_obj, ...);
    }
    

    其中native_obj是Mat对象的地址,类型为long

    要将 jlong​​ 转换回 C++ 中的 Mat,请将您的 jbyteArray input_ 更改为 jlong input_

    JNIEXPORT void JNICALL Java_at_identum_planogramscanner_ScanActivity_scanPackage(..., jlong input_, ...) {
    
    cv::Mat* pframe_addr = (cv::Mat*)input_;
    Mat img(height, width, CV_8UC3);
    cv::cvtColor(*pframe_addr,img,CV_YUV2RGB_NV12, 3);
    /** The rest of your code */
    

    【讨论】:

    • 很好的输入,很高兴知道。不幸的是,性能杀手是 cv 命令。通过日志,我可以确认在这 35 秒中,matchTemplate(....) 花费了大约 32 秒。
    猜你喜欢
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-26
    • 2014-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多