动机
本身是作影像视觉相关的工作,多数时候都使用 Python 作为开发语言,但OpenCV 本身是C++开发,学 C++ 应该有帮助。
公司有 Android/iOS Team ,有些功能需要与他们整合,多了解对方领域可以减少沟通成本。
网络上有关 OpenCV 与 Android Studio 整合的教学零散文章,这次整合成功后,把这些碎片化资讯整理起来记录,避免之后有同样需求时又辛苦一次,这次整合采了太多坑,如果不做个记录,三个月后就会忘了。
环境
Android Studio Chipmunk | .2.1 Patch 1 (Ubuntu 环境)
JNI 有 cmake 和ndk-build两种方法,我是用Cmake (版本3.22.1 )如果你参考的教学有用到 Android.mk 或Application.mk ,那这篇有关 build 的方式会很不一样。
OpenCV 4.5.5 / 4.6.0
Android Studio 连结 JNI
开启新工程,使用Base Activity 即可。
取名叫MyApp,方便之后模拟使用这个lib 的人。
由于我们最终目的是要做一个 aar lib 。所以 myapp 用来当作 application 使用,另外,再新增一个Module 来作为 C++ lib 开发。
点选 [Files]>[New]>[New Module…] 新增 MyOpenCv。
这时的目录结构如下,除了 app 以外,多了MyOpenCv 这个Module,有CMakeLists.txt, myopencv.cpp, NativeLib三个档案。
此时,到 app 的 build.gradle 新增刚刚的module 作为依赖并执行gradle sync。
sync 后,可以到 app 的MainActivity.java 测试是不是可以抓到 JNI 的信息。
这阶段就完成了从Android 呼叫C++ Lib部分。
备注1: 这边 CMakeLists.txt 能与Android 连接是通过build.gradle 里的这段:
externalNativeBuild{cmake{path"src/main/cpp/CMakeLists.txt"version"3.18.1"}}
备注2: MyOpenCv Module 和app 可以相连是通过settings.gradle 后两行的include。
pluginManagement{repositories{gradlePluginPortal()google()mavenCentral()}}dependencyResolutionManagement{repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories{google()mavenCentral()}}rootProject.name="MyApp"include':app'include':MyOpenCv'
Cmake 与OpenCV 依赖
先至 OpenCV 官网(/releases/)下载Andoird 版本并解压缩到想要的文件夹,这边示例是在/home/user/Documents/
接着在CMakeLists.txt 加入OpenCV 依赖并执行Gradle Sync。
其中 jnigraphics-lib 是等会将图片格式由Android Bitmap 转成OpenCV Mat 格式时会用到的。
cmake_minimum_required(VERSION3.18.1)set(OpenCV_DIR"/home/jason9075/Documents/OpenCV-android-sdk/sdk/native/jni")find_package(OpenCVREQUIRED)project("myopencv")add_library(myopencvSHAREDmyopencv.cpp)include_directories(${OpenCV_INCLUDE_DIRS})find_library(log-liblog)#ForAndroidBitverttocv::Matfind_library(jnigraphics-libjnigraphics)target_link_libraries(myopencv${OpenCV_LIBS}${jnigraphics-lib}#ForAndroidBitmapCoverttocv::Mat${log-lib})
这时你会发现Sync 失败, 问题出在CMakeLists.txt 第五行没有抓到OpenCV 套件。
检视警告页面提示为:无法找到abi binary,位置在OpenCVConfig.cmake:47
然后…经过我漫长的寻找…不断的在cmake file 里用message() 确认各个变数,终于发现在OpenCVConfig.cmake 这个档案里的第39行中ANDROID_NDK_ABI_NAME 的值都是空的!
理论上它应该会是:[arm64-v8a, armeabi-v7a, x86, x86_64] ,各代表在不同环境的CPU架构。
OpenCVConfig.cmake
尝试注解掉原本的ANDROID_NDK_ABI_NAME 后,替换成 ANDROID_ABI 就可以成功的sync。
等 sync 完成后,就可以在myopencv.cpp 档案里引用OpenCV 而不会出错。
备注:如果你自己的电脑本身有安装OpenCV ,不使用官网下载的OpenCV_DIR ,它可能会自己跑去找系统的版本(/usr/local/lib/cmake/opencv4),而发生错误。
我自己有发生set(OpenCV_DIR path) 路径打错,跑去抓/usr/local 底下的版本,然后不断出现:
C/C++:CMakeFiles/cvmodule.dir/cvmodule.cpp.o(.data+0x0):error:undefinedreferenceto'typeinfoforcv::Exception'
的错误。
OpenCV bash 开发
开发图片转灰度功能
新增一个将图片转成灰度的功能测试,这边要注意的是在JNI 使用上,必须定义好function 的signature,像名称要和java 的package name 对应。
extern"C"JNIEXPORTvoidJNICALLJava_com_jason9075_myopencv_NativeLib_toGrey(JNIEnv*env,jobject,jobjectbitmapIn,jobjectbitmapOut){Matsrc,greyOut;bitmapToMat(env,bitmapIn,src,false);cvtColor(src,greyOut,CV_BGR2GRAY);matToBitmap(env,greyOut,bitmapOut,false);}
然后在NativeLib.java 里要宣告与C++对应的function。
publicclassNativeLib{static{System.loadLibrary("myopencv");}publicnativeStringstringFromJNI();publicnativevoidtoGrey(BitmapbitmapIn,BitmapbitmapOut);}
为了测试我们到应用程式app文件夹,把Android绿色机器人图片放到drawable 文件夹,然后在MainActivity 转成灰色。
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);binding=ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());setSupportActionBar(binding.toolbar);NavControllernavController=Navigation.findNavController(this,R.id.nav_host_fragment_content_main);appBarConfiguration=newAppBarConfiguration.Builder(navController.getGraph()).build();NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration);ImageViewiv=findViewById(R.id.imageView);binding.fab.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewview){Snackbar.make(view,"Replacewithyourownaction",Snackbar.LENGTH_LONG).setAction("Action",null).show();}});//TestHelloWorldFromMyOpenCvNativeLibcv=newNativeLib();System.out.println(cv.stringFromJNI());Bitmapimage=BitmapFactory.decodeResource(getResources(),R.drawable.android);cv.toGrey(image,image);iv.setImageBitmap(image);}
开发读取图片宽高功能
比如说今天Android Team 有个需求是传一张图片,我们分析图片宽高等资讯,而且希望他们可以直接拿到 Java Class。
我们先在Module 里新增一个DTO 叫ImageInfo。
packagecom.jason9075.myopencv;publicclassImageInfo{privatefinalintwidth;privatefinalintheight;publicImageInfo(intwidth,intheight){this.width=width;this.height=height;}publicintgetWidth(){returnwidth;}publicintgetHeight(){returnheight;}@OverridepublicStringtoString(){return"ImageInfo{"+"width="+width+",height="+height+'}';}}
在 NativeLib.java 宣告相对应的 function getInfo()
packagecom.jason9075.myopencv;importandroid.graphics.Bitmap;publicclassNativeLib{static{System.loadLibrary("myopencv");}publicnativeStringstringFromJNI();publicnativevoidtoGrey(BitmapbitmapIn,BitmapbitmapOut);publicnativeImageInfogetInfo(Bitmapbitmap);}
然后在myopencv.cpp 新增C++ 实做方式。
须注意的一点是,因为我们最终要回传 Java Object,所以在C++这边要定义clsPath ,要找你预期回传的 Java Class 长的怎么样,还有这个Class 的Constructor 需要什么样的signature (这边 width 和 height 都是 Int 所以是(II)V)。
extern"C"JNIEXPORTjobjectJNICALLJava_com_jason9075_myopencv_NativeLib_getInfo(JNIEnv*env,jobjectthiz,jobjectbitmap){Matsrc;bitmapToMat(env,bitmap,src,false);intwidth=src.cols;intheight=src.rows;//returnjavaobjectconstchar*clsPath="com/jason9075/myopencv/ImageInfo";jclasscls=env->FindClass(clsPath);jmethodIDconstructor=env->GetMethodID(cls,"<init>","(II)V");returnenv->NewObject(cls,constructor,width,height);}
完成后,再回到 app 里实际测试。
//TestHelloWorldFromMyOpenCvNativeLibcv=newNativeLib();System.out.println(cv.stringFromJNI());Bitmapimage=BitmapFactory.decodeResource(getResources(),R.drawable.android);cv.toGrey(image,image);iv.setImageBitmap(image);System.out.println(">>>"+cv.getInfo(image));
我们可以成功读取到宽高分别为2688 和3197。(这边和原图宽高不同的原因是Android 的Drawable 会自动缩放,若想测试原图可以改放Asset文件夹)
打包成 aar 给其他工程使用
上面的工程主要有两个部分,一个是 app 拥有Activity 来模拟使用这个lib的情况,令一个部分是 MyOpenCv 这个module 为实际lib 的内容,这是我们接下来要介绍的,如何将这个module 转成aar。
首先,我们先点击 [Build]>[Select Build Varient] 开启选单,再生成 aar 时可以选择 debug 或是 release 来发布,这边我选用release 做示范。
设成release 之后,再去[Build]>[Rebuild Project]让工程建构一下,完成后会在build 文件夹的outputs 找到我们要的aar。
打开MyOpenCv-release.aar 可以看到在jni 里面存放各个不同架构的.so,代表正常。
我们开启令一个Android Project 叫AnotherApp ,并把MyOpenCv-release.aar 放入app/libs 里。
然后在AnotherApp 的build.gradle 加入依赖。
然后回到AnotherApp 的MainActivity我们加入这四行,测试自己撰写的aar lib 能不能成功使用。
执行结果是程序有抓到图片在画面上的宽高。
以上就完成了运用JNI 连结OpenCV 开发C++,并打包成aar lib 的使用教学。
代码:
MyApp: /jason9075/Android_with_OpenCV_Module
温馨提醒:使用时module 的CMakeLists.txt 请将OpenCV_DIR换成自己的sdk路径。
AnotherApp: /jason9075/Android_use_OpenCV_AAR_lib
Ref
/studio/projects/configure-cmake
/ValYouW/AndroidOpenCVDemo
/questions/9433257/how-to-specify-array-of-class-in-getmethodid-method-signature-parameter
/questions/22300848/return-object-from-java-native-method
/questions/51107185/how-to-create-new-android-aar-in-android-studio
☆ END ☆
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。
↓扫描二维码添加小编↓
如果觉得《如何将 OpenCV 整合 Android JNI 开发 C++ 代码并打包成 aar lib 给其他工程使用》对你有帮助,请点赞、收藏,并留下你的观点哦!