最近帮一位读者朋友在Android平台上集成部分OpenCV功能,考虑到移动端的特殊性,不建议采用全包集成,同时,市面上Android手机基本上都是基于arm的指令集。综合考虑,采用
c++ & native库
的方式来实现OpenCV能力的集成。
环境
- Android Studio 4.1.1
- OpenCV 4.5.0
- NDK 21.1.6352462
- CMake 3.10.2
环境搭建
下载opencv-4.5.0-android-sdk
下载地址:https://sourceforge.net/projects/opencvlibrary/files/4.5.0/opencv-4.5.0-android-sdk.zip/download
目录结构:
OpenCV-android-sdk
|_ samples
|_ sdk
| |_ etc
| |_ java
| |_ libcxx_helper
| |_ native
| |_ 3rdparty
| |_ jni
| |_ libs
| |_ arm64-v8a
| |_ armeabi-v7a
| |_ x86
| |_ x86_64
| |_ staticlibs
| |_ arm64-v8a
| |_ armeabi-v7a
| |_ x86
| |_ x86_64
|
|_ LICENSE
|_ README.android
目录 | 文件 |
---|---|
samples | OpenCV运行案例 |
sdk | OpenCV API以及依赖库 |
sdk/etc | Haar和LBP级联分类器 |
sdk/java | OpenCV Java API |
sdk/libcxx_helper | bring libc++_shared.so into packages |
sdk/native | OpenCV 静态库、动态库以及JNI文件 |
后续会使用到的静态库在sdk/native/staticlibs、sdk/native/3rdparty目录下,然后编译依赖关系参考sdk/native/jni目录下的cmake文件。
下载安装NDK
下载安装CMake
创建Android Native C++ Project
OpenCV Native C++集成
编译配置
-
设置NDK版本
ndkVersion '21.1.6352462'
-
设置cmake编译条件
defaultConfig { …… // defaultConfig节点下 externalNativeBuild { cmake { abiFilters 'armeabi-v7a','arm64-v8a' cppFlags "-std=c++11","-frtti", "-fexceptions -lz" } } ndk { abiFilters 'armeabi-v7a','arm64-v8a' } } // android 节点下 externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.10.2" } } // 新建jniLibs存放静态库 sourceSets { main { jniLibs.srcDirs = ['src/main/jniLibs', 'lib'] } }
OpenCV头文件拷贝
将Native 库头文件(sdk/native/jni/include)
拷贝到 src/main/cpp
目录下。结果如下:
OpenCV静态库拷贝
由于我们实现灰度图片,只需要使用opencv_core和opencv_imgproc两个库,根据编译文件梳理大致的依赖关系,需要导入的库不多,具体如下。
CMakeLists.txt
参考sdk/native/jni下的
OpenCVModules.cmake
文件调整对应的编译内容。
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("simplifyopencv")
set(libs ${CMAKE_SOURCE_DIR}/..)
include_directories(${libs}/cpp/include)
# Create imported target libcpufeatures
add_library(libcpufeatures STATIC IMPORTED)
set_target_properties(libcpufeatures PROPERTIES
IMPORTED_LOCATION "${libs}/jniLibs/${ANDROID_ABI}/libcpufeatures.a")
# Create imported target ittnotify
add_library(ittnotify STATIC IMPORTED)
set_target_properties(ittnotify PROPERTIES
INTERFACE_LINK_LIBRARIES "dl"
)
set_target_properties(ittnotify PROPERTIES
IMPORTED_LOCATION "${libs}/jniLibs/${ANDROID_ABI}/libittnotify.a")
# Create imported target tegra_hal
add_library(tegra_hal STATIC IMPORTED)
set_target_properties(tegra_hal PROPERTIES
IMPORTED_LOCATION "${libs}/jniLibs/${ANDROID_ABI}/libtegra_hal.a")
# Create imported target tbb
add_library(tbb STATIC IMPORTED)
set_target_properties(tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "TBB_USE_GCC_BUILTINS=1;__TBB_GCC_BUILTIN_ATOMICS_PRESENT=1;TBB_SUPPRESS_DEPRECATED_MESSAGES=1"
INTERFACE_LINK_LIBRARIES "c;m;dl"
)
set_target_properties(tbb PROPERTIES
IMPORTED_LOCATION "${libs}/jniLibs/${ANDROID_ABI}/libtbb.a")
# Create imported target opencv_core
add_library(opencv_core STATIC IMPORTED)
set_target_properties(opencv_core PROPERTIES
INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:dl>;\$<LINK_ONLY:m>;\$<LINK_ONLY:log>;\$<LINK_ONLY:tegra_hal>;\$<LINK_ONLY:tbb>;\$<LINK_ONLY:z>;\$<LINK_ONLY:libcpufeatures>;\$<LINK_ONLY:ittnotify>;\$<LINK_ONLY:tegra_hal>"
)
set_target_properties(opencv_core PROPERTIES
IMPORTED_LOCATION "${libs}/jniLibs/${ANDROID_ABI}/libopencv_core.a")
# Create imported target opencv_imgproc
add_library(opencv_imgproc STATIC IMPORTED)
set_target_properties(opencv_imgproc PROPERTIES
INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:opencv_core>;opencv_core;\$<LINK_ONLY:dl>;\$<LINK_ONLY:m>;\$<LINK_ONLY:log>;\$<LINK_ONLY:tegra_hal>"
)
set_target_properties(opencv_imgproc PROPERTIES
IMPORTED_LOCATION "${libs}/jniLibs/${ANDROID_ABI}/libopencv_imgproc.a")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
opencv_core
opencv_imgproc
# Links the target library to the log library
# included in the NDK.
${log-lib})
代码
定义Native方法
private external fun doGray(imgData: IntArray?, width: Int, height: Int): IntArray?
实现Native方法
#include <jni.h>
#include <string>
#include "include/opencv2/core/hal/interface.h"
#include "include/opencv2/imgproc/types_c.h"
#include "include/opencv2/imgproc.hpp"
using namespace std;
using namespace cv;
extern "C" JNIEXPORT jintArray JNICALL
Java_tech_kicky_simplifyopencv_MainActivity_doGray(
JNIEnv *env, jobject thiz, jintArray source,
jint width, jint height) {
// TODO: implement convertToGray()
jint *input;
input = env->GetIntArrayElements(source, JNI_FALSE);
Mat sourceMat(height, width, CV_8UC4, (unsigned char *) input);
Mat grayMat;
cvtColor(sourceMat, grayMat, CV_BGRA2GRAY);
Mat resultMat;
cvtColor(grayMat, resultMat, CV_GRAY2BGRA);
int size = width * height;
jintArray result = env->NewIntArray(size);
uchar *output = resultMat.data;
env->SetIntArrayRegion(result, 0, size, (const jint *) output);
env->ReleaseIntArrayElements(source, input, 0);
return result;
}
Kotlin调用
class MainActivity : AppCompatActivity() {
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mBinding.ivLena.setImageResource(R.drawable.lena)
val source = BitmapFactory.decodeResource(resources, R.drawable.lena)
.copy(Bitmap.Config.ARGB_8888, true)
val width = source.width
val height = source.height
val pixel = IntArray(width * height)
source.getPixels(pixel, 0, width, 0, 0, width, height)
val grayPixels: IntArray? = doGray(pixel, width, height)
val gray = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
gray.setPixels(grayPixels, 0, width, 0, 0, width, height)
mBinding.ivResult.setImageBitmap(gray)
}
private external fun doGray(imgData: IntArray?, width: Int, height: Int): IntArray?
}
效果
源码
https://github.com/onlyloveyd/SimplifyOpenCV
个人公众号【OpenCV or Android】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注Android、OpenCV、Kotlin、Flutter等有趣的技术。