Android OpenCV与JNI表现不佳

时间:2016-06-22 12:49:57

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

Android下的OpenCV 3.10存在很大问题。我正在开发一个应用程序,它可以进行CameraMatching的Camera Preview。 第一种方法是使用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档案需要某种编译器设置才能尽可能加快速度。我希望有人能指出我正确的方向!

提前致谢!

1 个答案:

答案 0 :(得分:1)

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

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

  

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

你可以在网上阅读更多内容,因为JNI总是从JAVA复制(使用GetByteArrayElements)到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

要在c ++中将jlong​​转换回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 */