加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

OpenCV Detect效率研究(ARM平台)

(2015-04-15 16:53:00)
标签:

android

opencv

opencv4android

haardetect

tbb

分类: linux
作者:Sam (甄峰)  sam_code@hotmail.com

之前移植过OpenCV2.0到Android平台上(但只支持NativeC调用)。基于OpenCV开发识别程序。 当时不少作算法的同事都对运行效率表示担忧和不解。最为集中的疑问是:
OpenCV计算部分为什么在双核甚至4核 1.2G, 1.6G ARM CPU 会比 4核1.8-2.4G X86 64bit CPU慢如此之多。强烈怀疑是交叉编译OpenCV时出错导致。

Sam当时只能解释虽然从CPU频率来看差距不大,但哪怕的同一架构情况下(如都是X86),也并没有一条确定的公式能够实现主频和实际的运算速度两者之间的数值关系,同主频的AMD和IntelCPU,实际计算能力就差别很大。所以x86 4核 2.4G实际计算能力应该是远超ARM 4核1.2G 的。 
但这种说法没有数据验证,无法让只相信数据的工程人员心服(其实连自己也没能完全说服,毕竟精简指令集理论速度也应该快于IA-32吧)。 
后来的一些数据,也让我感到很疑惑:在同样的ARM机器上,运行OpenCV2.4.10自带的Android 脸部识别程序(APK),速度竟然能达到10桢以上。这也比我们在NativeC(使用OpenCV2.0交叉编译库)层的速度要快很多。

这段时间再次接触到这一领域,决心把这个问题搞清楚。


0. 准备工作:
0.1: 测试代码的准备
工欲善其事,比先利其器。Sam首先请同事用C++ 和Java分别写了同样流程的程序。利用cv::CascadeClassifier 来做detect  (detectMultiScale).
载入的classifier xml完全一致,共分析3000张同样的图片(640x480). scale_factor等参数完全相同。


0.2:选定OpenCV2.4.10作为基础对比库
使用同一版本OpenCV,保证不受OpenCV不同版本效率不同的干扰。


1. 第一步测试数据
对比项:
A:OpenCV2.4.10 Manager APK + Java例子代码
B:OpenCV2.4.10 Native C 版本库 + C++例子代码。其中OpenCV2.4.10 Native C库,是Sam通过opencv-2.4.10 Source Code编译的(http://blog.sina.com.cn/s/blog_602f87700102vdnw.html)
其中platforms/scripts/cmake_android_arm.sh中编译选项为:
cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON  -DBUILD_EXAMPLES=1 -DANDROID_ABI="armeabi-v7a
"  -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../..

C++代码采用Release版本(-O2), 并添加了-fomit-frame-pointer -ffast-math。

A:运行时间为:9分40秒。
B:运行时间为:32分钟。

差距如此之大,Sam怀疑是编译选项出错。于是决定C++代码的底层OpenCV2.4.10库采用OpenCV2.4.10自带的NativeC库来运行看看。

2. 第二步测试数据:
C: OpenCV2.4.10 NativeC 版本库+ C++例子代码
OpenCV-2.4.10-android-sdk/sdk/native/libs/中有所想要的库,其实就是:libopencv_java.so
C:运行结果:32分钟。
没有任何改善。

3. 第三步测试数据
看到第二步数据,一下失去了方向,所以决定采用自己编译的libopencv_java.so和Java代码组合,看看效果如何
D: Sam使用OpenCV2.4.10 SourceCode编译的libopencv_java.so 与Java测试代码组合。 
为了能不与OpenCV Manager打交道(脱离开OpenCV Manager,让APK单独存在),所以把libopencv_java.so合并到Java代码的lib中。(详情见附录)
这个库与B的库完全像同。理论上不该有差异。所以期待结果是32分钟,结果很残酷。

D:运行结果:19分钟。

分析1:用同样的库,但Java和C++ 代码速度有 100:60 的差异。 于是觉得Java调用OpenCV和C++调用OpenCV最终没有调用相同的模块。


于是查代码:

Java()调用序列:
Java代码中,采用的是:org.opencv.objdetect.CascadeClassifier.detectMultiScale()方法。
它调用了 org.opencv.objdetect.CascadeClassifier.detectMultiScale_0()
注意:这个方法是个native 方法。所以应该是C++代码实现的。
    private static native void detectMultiScale_0(long nativeObj, long image_nativeObj, long objects_mat_nativeObj, double scaleFactor, int minNeighbors, int flags, double minSize_width, double minSize_height, double maxSize_width, double maxSize_height);

可以在opencv-2.4.10/platforms/build_android_arm/modules/java/objdetect.cpp
找到这段代码的实现:
JNIEXPORT void JNICALL Java_org_opencv_objdetect_CascadeClassifier_detectMultiScale_10
  (JNIEnv* env, jclass , jlong self, jlong image_nativeObj, jlong objects_mat_nativeObj, jdouble scaleFactor, jint minNeighbors, jint flags, jdouble minSize_width, jdouble minSize_height, jdouble maxSize_width, jdouble maxSize_height)

注意:这个文件是在 sh script/cmake_anrdoid_arm.sh 时生成的。Sam没仔细查它如何生成,但怀疑是由python生成的。 
 
而这个函数直接调用:
opencv-2.4.10/modules/objdetect/src/cascadedetect.cpp 
CascadeClassifier::detectMultiScale()   --->cvHaarDetectObjectsForROC()


C++调用序列
直接调用:
opencv-2.4.10/modules/objdetect/src/cascadedetect.cpp 
CascadeClassifier::detectMultiScale()   --->cvHaarDetectObjectsForROC()
而且连参数都一样。 这就非常奇怪了。


Sam 分别用 printf, android log 输出。发现Java,C++ 均调用到此处,且所用时间确实是6:10。
参数都一致。 
gettimeofday(&begin, NULL);
__android_log_print(ANDROID_LOG_INFO, "SamInfo", "Enter CascadeClassifier::detectMultiScale 1");
printf("\nSamInfo: Enter CascadeClassifier::detectMultiScale [%ld:%ld]\n", begin.tv_sec , begin.tv_usec);

打印参数:
_android_log_print(ANDROID_LOG_INFO, "SamInfo", "Enter %s. [image.cols:%d. image.rows:%d. scaleFactor:%f.  minNeigh
bors:%d. flags:%d.  min: %f:%f.   max:%f:%f", method_name, image.cols, image.rows ,scaleFactor, minNeighbors, flags, minSize_width,
minSize_height, maxSize_width, maxSize_height);
参数也一致。



为和有这6:10的差异。Sam实在想不通。 但Sam库+Java和原始库+Java还有不小差距,所以转到这边来研究。9:19的差异。


Sam仔细观察libopencv_java.so在载入时,logcat对它参数的打印。发现主要的区别有两点:Eigen, TBB。
之前感觉TBB在ARM下不一定能实现,所以没有深究,这次突然想到一个很容易验证的方法。Java+原始libopencv_java.so 运行时,察看CPU使用,发现确实多个核都有较大负载。证明了TBB是关键。



4. 第四步测试数据
重新编译OpenCV,加上TBB和Eigen(http://blog.sina.com.cn/s/blog_602f87700102vdnw.html)
E: Sam 编译的libopencv_java.so (有TBB,Eigen) +Java测试代码:
E:测试结果:9分40秒。 
与原始的一致。


5. 第五步测试数据:
F:C++测试代码 + Sam 编译的libopencv_java.so (有TBB,Eigen)
F:测试结果:16分钟。

结果虽然进步不小。但Java:C++ 还是为:6:10。这一点还是未能解决。
如果有朋友知道原因,请帮忙告知。

产生C++ 代码时,需要添加Flags:
-DTBB_USE_GCC_BUILTINS=1 -D__TBB_GCC_BUILTIN_ATOMICS_PRESENT=1

结论:这是这次Sam 使用C++代码优化的极限。当然还是比使用Java慢(原因未知)
那么它和PC相比,速度是怎样的呢?

Sam使用C++测试代码 + OpenCV2.4.10标准库。测试,在PC上需要2.7分钟。

对比PC和Android-ARM CPU .
PC: 4核,BogoMIPS:6000。
ARM:4核。BogoMIPS:1000。
BogoMIPS为6:1. 速度差不多也是这个比例。 看来这个优化当前已经可以接受了。



最后的总结:
BogoMIPS比较真实地展示了计算能力,PC和ARM BogoMIPS比例为  6:1。 所以,这台 64位 4核 2.4G CPU计算能力是 这台ARM 4核 1.6G CPU计算能力的6倍。 所以,同样的例子代码,在不同平台上的运行速度大致也差别6倍。
但前提是,两者都是用了TBB。

之前OpenCV2.0不支持TBB。所以速度会更慢一些。测试结果大概慢一倍左右。(具体数据和核心数量有关)
TBB在OpenCV 2.4.0才支持。(2.3.1时仅仅是用来测试几个例子)

疑点:
同样的库,同样的写法,采用C++和Java运行。速度有6:10的差异。至今让我很疑惑。唯一的解释是:Java编译时,用到了什么特殊的设置,加快了速度。但具体原因未知。









附录:
Java 使用OpenCV库时,如何不依赖于OpenCVManager。 
Sam想要修改libopencv_java.so用来测试,则希望程序结构单纯一些,比想和OpenCVManager联系在一起。
首先观察OpenCV Manager, 
OpenCV-2.4.10-android-sdk/apk/OpenCV_2.4.10_Manager_2.19_armv7a-neon.apk
它安装后,会把OpenCV的库放在: /data/app-lib/org.opencv.engine-1/下供OpenCV 程序使用。
其中最重要的就是libopencv_java.so  libopencv_info.so

按照逻辑,Sam只需要卸载OpenCV Manager, 把libopencv_java.so libopencv_info.so放到工程libs里面即可。

Java OpenCV 例子程序会调用:
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
来连接OpenCV Manager。 并load libopencv_java.so  libopencv_info.so 甚至GLES系列库。

Sam用OpenCVLoader.initDebug();取代之。 它则不连接OpenCV Manager。只是Load 库。这里需要再调用
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);

下面是例子。Sam在onManagerConnected()里创建了一个Thread用来haarDetect()


public void onResume()
    {
        super.onResume();
        //OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
        OpenCVLoader.initDebug();
        mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
    }






    private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i("SamInfo", "OpenCV loaded successfully");
                   
                    thread_Detection = new DetectionThread();
                       thread_Detection.start();

                    // Load native library after(!) OpenCV initialization
                       System.loadLibrary("opencv_java");
                    System.loadLibrary("detection_based_tracker");

                    try {
                        // load cascade file from application resources
                        InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
                        File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
                        mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                        FileOutputStream os = new FileOutputStream(mCascadeFile);

                        byte[] buffer = new byte[4096];
                        int bytesRead;
                        while ((bytesRead = is.read(buffer)) != -1) {
                            os.write(buffer, 0, bytesRead);
                        }
                        is.close();
                        os.close();

                        mJavaDetector = new CascadeClassifier(mCascadeFile.getAbsolutePath());
                        if (mJavaDetector.empty()) {
                            Log.e(TAG, "Failed to load cascade classifier");
                            mJavaDetector = null;
                        } else
                            Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());

                        //sam del it
                        //mNativeDetector = new DetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);

                        //cascadeDir.delete();

                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
                    }

                    // sam del it
                    //mOpenCvCameraView.enableView();
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };

   

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有