当前环境:Android Studio 3.5.1
Ndk r16b
OpenCv 4.1.0
OpenCv下载地址:https://opencv.org/releases/ 点安卓那个下载
本文主要讲两种方式集成
1.安卓导入 下载的OpenCv Sdk和so库 直接通过Java代码来调用Opencv实现需求
2.安卓导入so库和OpenCv的头文件,通过Jni的方式来调用OpenCv实现需求
先说第一种方式:
新建一个项目,导入Module选中下载的OpenCv里面的java文件夹,导入后添加依赖,如果添加依赖的时候没找到导入的opencv库
然后把这个库添加到主项目依赖中,再然后在你主项目src/main下新建一个jniLibs文件夹,把
文件夹下的东西原样复制到jniLibs文件夹下,在build.grade文件里添加cpu架构,然后你就可以在代码中使用了,Activity的代码我贴在下边,xml文件里就一个ImageView和一个Button
package com.sciptv.testopencv import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle import android.provider.Settings import android.util.Log import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.opencv.android.BaseLoaderCallback import org.opencv.android.LoaderCallbackInterface import org.opencv.android.OpenCVLoader import org.opencv.android.Utils import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.imgproc.Imgproc class MainActivity : AppCompatActivity() { private var imageMat: Mat? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) click.setOnClickListener { GlobalScope.launch(Dispatchers.IO) { val time = System.currentTimeMillis() val bitmap = BitmapFactory.decodeResource(resources, R.drawable.cat) val matSrc = Mat(bitmap.width, bitmap.height, CvType.CV_8UC4) Utils.bitmapToMat(bitmap, matSrc) val matGray = Mat(bitmap.width, bitmap.height, CvType.CV_8UC1) Imgproc.cvtColor(matSrc, matGray, Imgproc.COLOR_BGRA2GRAY, 1) val bmpDst = Bitmap.createBitmap(matGray.cols(), matGray.rows(), Bitmap.Config.ARGB_8888) Utils.matToBitmap(matGray, bmpDst) withContext(Dispatchers.Main) { cat.setImageBitmap(bmpDst) Log.e("OpenCV Time2:", (System.currentTimeMillis() - time).toString()) } } } } private val baseLoaderCallback = object : BaseLoaderCallback(this) { override fun onManagerConnected(status: Int) { when (status) { LoaderCallbackInterface.SUCCESS -> { Log.i("MyOpenCV", "OpenCV loaded successfully") imageMat = Mat() } else -> { Log.i("MyOpenCV", "OpenCV loaded Failed") super.onManagerConnected(status) } } } } override fun onResume() { super.onResume() if (!OpenCVLoader.initDebug()) { Log.d( "MyOpenCV", "Internal OpenCV library not found. Using OpenCV Manager for initialization" ) OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, baseLoaderCallback) } else { Log.d("MyOpenCV", "OpenCV library found inside package. Using it!") baseLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS) } } }
至此第一种方式结束
来看第二种方式
新建一个Ndk项目,同样的位置建立jniLibs文件夹导入so库,把opencv2文件夹粘贴在src/main/include(没有就新建)文件夹下
小红框里的东西要写,因为OpenCv4.1(或者是4.0搞忘了)有东西变了,接下来重点来了,配置CmakeList文件
教学一下,当你们配置的时候路径死活不对频繁出错心烦意乱的时候,必杀技:配置成绝对路径 ,记得改路径,链接库的时候记得加上jnigraphics库,因为后面我们需要用到Bitmap,环境配置完毕
Activity代码
package com.sciptv.testopencvndk import android.graphics.Bitmap import android.graphics.BitmapFactory import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class MainActivity : AppCompatActivity() { companion object { init { System.loadLibrary("native-lib") System.loadLibrary("opencv_java4") } } private lateinit var bitmap: Bitmap override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bitmap = BitmapFactory.decodeResource(resources, R.drawable.cat); normal.setOnClickListener { image.setImageBitmap(bitmap) } gray.setOnClickListener { val time = System.currentTimeMillis() GlobalScope.launch(Dispatchers.IO) { val w = bitmap.width val h = bitmap.height var piexls = IntArray(w * h) bitmap.getPixels(piexls, 0, w, 0, 0, w, h) val resultData = OpenCv.toGray(piexls, w, h) val resultImage = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) resultImage.setPixels(resultData, 0, w, 0, 0, w, h) withContext(Dispatchers.Main) { image.setImageBitmap(resultImage) Log.e("OpenCV NDK Time:", (System.currentTimeMillis() - time).toString()) } } } } }
Ndk代码
#include#include "include/opencv2/opencv.hpp" #include using namespace cv; using namespace std; extern "C" JNIEXPORT jintArray JNICALL Java_com_sciptv_testopencvndk_OpenCv_toGray(JNIEnv *env, jclass clazz, jintArray buf, jint w, jint h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, JNI_FALSE); if (cbuf == NULL) { return 0; } Mat imgData(h, w, CV_8UC4, (unsigned char *) cbuf); uchar *ptr = imgData.ptr(0); for (int i = 0; i < w * h; ++i) { // 计算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B // 对于一个int四字节,其彩色值存储方式为:BGRA int grayScale = (int) (ptr[4 * i + 2] * 0.299 + ptr[4 * i + 1] * 0.587 + ptr[4 * i + 0] * 0.114); ptr[4 * i + 1] = grayScale; ptr[4 * i + 2] = grayScale; ptr[4 * i + 0] = grayScale; } int size = w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, cbuf); env->ReleaseIntArrayElements(buf, cbuf, 0); return result; }
OpenCv类的代码
package com.sciptv.testopencvndk;
public class OpenCv {
public native static int[] toGray(int[] array, int w, int h);
}
总结一下:第一种方式适合没有ndk开发基础的人,可以直接在Java代码里使用
第二种方适合:当Java代码已经满足不了你的需求了,需要自己根据c代码来扩展需求,或者觉得Java库太过庞大了,这时候就需要你自己来根据c代码裁剪库的大小了,我还比较了两种方式的耗时,我发现ndk的实现要比java代码实现平均耗时多1/3左右,可能是跟灰度算法有关,jni传递也需要时间,如果配置过程中出现了什么错误可以在下方留言,我可以帮忙分析一下