1. 介绍
最近在使用OpenCV的一些功能和方法,所以汇总一些关于OpenCV的方法在Android端上的调用吧。
网上更多的是Python和C 写的方法。所以想汇总一下关于Android端上的使用。同时也记录自己对于OpenCV的一些理解。
2. OpenCV SDK
我们要使用OpenCV那么可以先从OpenCV提供的编译后的SDK入手。(等什么时候水平提高了,可以自己编译改源码)
先从OpenCV下载最新releases版本:https://github.com/opencv/opencv/releases
- opencv-4.6.0-android-sdk.zip 225 MB07 Jun 2022
将下载包的压缩包解压,将会得到两个文件夹 samples 和 sdk。
- samples:主要就是官方提供的一些Demo,让我们了解相关API的用法。
- sdk:openCV的一些API接口和文档等库。
我们将sdk 整体导入到项目中来,作为一个library进行依赖就可以了。导入就可以使用了
关于SDK里面的目录和结构,可以通过这篇文章了解:https://zinyan.com/?p=344
在需要依赖的模块的build.gradle文件中添加 implementation project(':opencv') 就可以了。
3. Android 使用OpenCV
当我们依赖完毕后,就可以开始使用了。
第一步:都是调用System.loadLibrary("opencv_java4");方法进行动态库初始化。
OpenCV提供了两种方法进行初始化。我们可以根据需求进行处理.
将下面的代码,放在Activity中进行执行:
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
// Log.i(TAG, "OpenCV loaded successfully");
// mOpenCvCameraView.enableView();
}
break;
default: {
super.onManagerConnected(status);
}
break;
}
}
};
@Override
public void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
// Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
} else {
// Log.d(TAG, "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}但是有一个问题,就是如果我们在onCreate方法中调用了OpenCV的API就会出现崩溃,因为上面的实例是在onResume方法后才会初始化加载OpenCV动态库。
可以采用第二种初始化方法:在Appcation或者Activity的onCreate()方法中初始化即可。
new OpenCVNativeLoader().init();第二步:OpenCV初始化后,我们通过CameraX获取的数据交给OpenCV进行处理即可。
给CameraProvider添加一个AnalysisUseCase处理,分析得到的ImageProxy对象。实例:(在这里省略了CameraX的初始化和权限,大家结合自己的代码进行配置哦)
ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
analysisUseCase = builder.build();
analysisUseCase.setAnalyzer(
// 图像处理器。processImageProxy将使用另一个线程来运行下面的检测,
//因此,我们可以在主线程上运行分析器本身。
ContextCompat.getMainExecutor(this),
imageProxy -> {
//1. 将imageProxy转为 Mat
//2. openCV 处理Mat
//3. 将Mat 转为Bitmap ,输出到ImageView进行显示该Bitmap
//4. 关闭imageProxy对象
});
cameraProvider.bindToLifecycle(/* lifecycleOwner= */ this, cameraSelector, analysisUseCase);按照上面注释的四个步骤,进行处理就能得到处理后的效果并显示了。下面分步骤介绍。
3.1 ImageProxy 转 Mat
有两种方法可以实现:
1.将ImageProxy转为Bitmap,然后再调用OpenCV中的Util类将Bitmap转Mat。(多转了一轮,网上有很多转换方法,我这里就不介绍了)
2.将ImageProxy直接转为Mat对象。
我主要使用的是第二种方法直接将ImageProxy转Mat。关于如何转换,可以参考:https://zinyan.com/?p=345
当我们得到Mat对象后,就可以调用OpenCV进行处理了
3.2 OpenCV API (v4.6.0)
当我们使用OpenCV的时候,要注意CvType类型的不同,有很多算法会针对不同的CvType才能进行计算。否则会出现Type类型错误。
下面介绍一些常见的用法和参数意思。
介绍的并不是很全,只是最近接触到的一些函数的简单介绍。毕竟每一个函数都可以展开来用很多篇幅来进行介绍了。
3.2.1 cvtColor 颜色空间转换
可以实现RGBA转RGB,HSV,HSI,灰度图等的转换
代码语言:javascript复制Mat gray = new Mat();
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_BGR2GRAY); //将mat 转换为灰度图并赋值给gray对象 第一个参数:输入的图像
第二个参数:输出的结果图像
第三个参数:要转换的颜色标识符:Imgproc.COLOR_XXXXX;
第四个参数:可选,输出图片的通道数,如果不填就是和输入图像的通道数一致。
3.2.2 GaussianBlur 高斯模糊
对数据进行高斯模糊可以降噪。模糊程度根据Size的参数决定。
代码语言:javascript复制Mat blur = new Mat();
Imgproc.GaussianBlur(mat, blur, new Size(5, 5), 0);// 执行高斯模糊第一个参数:输入的图像
第二个参数:输出的结果图像
第三个参数:模糊的尺寸大小
第四个参数:X轴方向的高斯核标准偏差。
第五个参数:Y轴方向的高斯核标准偏差。(可选)
3.2.3 Canny 边缘检测
注意:使用Canny前需要将图片设置为灰度图,然后再进行轮廓算法。将会显示成线条图。去掉所有颜色。
代码语言:javascript复制Mat mIntermediateMat = new Mat();
Imgproc.Canny(mat, mIntermediateMat, 50, 150);第一个参数image:输入的图像
第二个参数edges:输出的结果图像
第三个参数threshold1:滞后过程的第一个阈值(低阈值)。
第四个参数threshold2:滞后过程的第二个阈值。(高阈值)
第五个参数apertureSize:Sobel运算符的孔径大小。(可选)
第六个参数L2gradient:更精确的L2番范数。(可选)
3.2.4 HoughLinesP 霍夫直线
可以提取每条直线轮廓线的开始坐标和结束坐标
代码语言:javascript复制Mat mat1 = new Mat();
Imgproc.HoughLinesP(mat, mat1, 1, Math.PI / 180, threshold, minLineLength, maxLineGap);
List<int[]> lines = new ArrayList<>();
for (int i = 0; i < mat1.rows(); i ) {
int[] line = new int[4];
mat1.get(i, 0, line);//将线对应的极点坐标存到line数组中 // 0:开始X,1开始Y,2结束X,3结束Y
lines.add(line);
// Imgproc.line(mat, new Point(line[0], line[1]), new Point(line[2], line[3]),
// new Scalar(255, 0, 0));
}第一个参数image:输入的图像。
第二个参数lines:输出的结果线段集。
第三个参数rho:表示搜索半径的步长(累加值),用像素表示。每次累加的像素个数。
第四个参数theta:表示角度的搜索步长(累加值),用度数表示。通常为π/180 ,表示每次累加1°。
第五个参数threshold:累加器的阈值参数,只有满足阈值数量的点的直线才会被检测出来;
第六个参数minLineLength:最小线段长度,低于该长度的将会舍弃(可选)
第六个参数maxLineGap:同一直线上连接点的最大允许间隙,在间隙内的将会合并成一条直线。(可选)
3.2.5 fillPoly 填充多边形
我们如果通过坐标MateOfPoint标注了一个区域,那么可以用这个函数给图像填充成色块。实例:
List<MatOfPoint> ge = new ArrayList<>();
ge.add(new MatOfPoint(new Point(0, top), new Point(mat.width(), top),
new Point(mat.width(), botton), new Point(0, botton))); //我创建了一个矩形
Mat m = Mat.zeros(mat.size(), mat.type()); //创建一个所有色值为0的Mat,它的尺寸和mat是一样的,type也是一样。
Imgproc.fillPoly(m, ge, Scalar.all(255)); // 将指定区域绘制为白色第一个参数img:要绘制的Mat对象,多边形将会绘制在该Mat中。
第二个参数pts:形状的坐标点集合。
第三个参数color:填充的颜色。
第四个参数lineType:多边形边界线的类型(可选)。
第五个参数shift:顶点坐标中的分数位数(可选)。
第六个参数offset:多边形坐标的偏移值(可选)。
3.2.6 bitwise_and,bitwise_or,bitwise_xor,bitwise_not 位运算
该方法在OpenCV中也会频繁使用。而上面的函数就不是在Imgproc类下了,而是在Core类里面。实例:
Mat bitWiseMat = new Mat();
Core.bitwise_and(m, mat, bitWiseMat); //切割其他区域的图片得到相对坐标值第一个参数src1:参与运算的数据。
第二个参数src2:参与运算的数据。
第三个参数dst:输出的结果。
and:与运算,or:或运算,xor:非异或运算,not:非运算。
3.3 Mat 转Bitmap
由于Android是通过Bitmap显示图片的,所以我们需要将处理结束后的Mat对象转为Bitmap再赋值给View进行显示。
实例:
代码语言:javascript复制Bitmap bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(mat, bitmap);不管mat是什么类型的,灰色的也好,彩色的也罢。都可以直接转换。
最后别忘记了ImageProxy.close()否则图片只显示第一张。


