动机
取名叫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'
接着在CMakeLists.txt 加入OpenCV 依赖并执行Gradle Sync。其中 jnigraphics-lib 是等会将图片格式由Android Bitmap 转成OpenCV Mat 格式时会用到的。cmake_minimum_required(VERSION 3.18.1) set(OpenCV_DIR "/home/jason9075/Documents/OpenCV-android-sdk/sdk/native/jni") find_package(OpenCV REQUIRED) project("myopencv") add_library(myopencv SHARED myopencv.cpp) include_directories(${OpenCV_INCLUDE_DIRS}) find_library(log-lib log) # For Android Bitvert to cv::Mat find_library(jnigraphics-lib jnigraphics) target_link_libraries(myopencv ${OpenCV_LIBS} ${jnigraphics-lib} # For Android Bitmap Covert to cv::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: undefined reference to 'typeinfo for cv::Exception' 的错误。
extern "C" JNIEXPORT void JNICALL Java_com_jason9075_myopencv_NativeLib_toGrey( JNIEnv *env, jobject, jobject bitmapIn, jobject bitmapOut) { Mat src, greyOut; bitmapToMat(env, bitmapIn, src, false); cvtColor(src, greyOut, CV_BGR2GRAY); matToBitmap(env, greyOut, bitmapOut, false); } 然后在NativeLib.java 里要宣告与C++对应的function。
public class NativeLib { static { System.loadLibrary("myopencv"); } public native String stringFromJNI(); public native void toGrey(Bitmap bitmapIn, Bitmap bitmapOut); } 为了测试我们到应用程式app文件夹,把Android绿色机器人图片放到drawable 文件夹,然后在MainActivity 转成灰色。

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main); appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build(); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); ImageView iv = findViewById(R.id.imageView); binding.fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); // Test Hello World From MyOpenCv NativeLib cv = new NativeLib(); System.out.println(cv.stringFromJNI()); Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.android); cv.toGrey(image, image); iv.setImageBitmap(image); }

package com.jason9075.myopencv; public class ImageInfo { private final int width; private final int height; public ImageInfo(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } @Override public String toString() { return "ImageInfo{" + "width=" + width + ", height=" + height + '}'; } } 在 NativeLib.java 宣告相对应的 function getInfo()
package com.jason9075.myopencv; import android.graphics.Bitmap; public class NativeLib { static { System.loadLibrary("myopencv"); } public native String stringFromJNI(); public native void toGrey(Bitmap bitmapIn, Bitmap bitmapOut); public native ImageInfo getInfo(Bitmap bitmap); } 然后在myopencv.cpp 新增C++ 实做方式。须注意的一点是,因为我们最终要回传 Java Object,所以在C++这边要定义clsPath ,要找你预期回传的 Java Class 长的怎么样,还有这个Class 的Constructor 需要什么样的signature (这边 width 和 height 都是 Int 所以是(II)V)。
extern "C" JNIEXPORT jobject JNICALL Java_com_jason9075_myopencv_NativeLib_getInfo(JNIEnv *env, jobject thiz, jobject bitmap) { Mat src; bitmapToMat(env, bitmap, src, false); int width = src.cols; int height = src.rows; // return java object const char *clsPath = "com/jason9075/myopencv/ImageInfo"; jclass cls = env->FindClass(clsPath); jmethodID constructor = env->GetMethodID(cls, "
// Test Hello World From MyOpenCv NativeLib cv = new NativeLib(); System.out.println(cv.stringFromJNI()); Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.android); cv.toGrey(image, image); iv.setImageBitmap(image); System.out.println(">>> " + cv.getInfo(image)); 我们可以成功读取到宽高分别为2688 和3197。(这边和原图宽高不同的原因是Android 的Drawable 会自动缩放,若想测试原图可以改放Asset文件夹)

设成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 的使用教学。审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !