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()
否则图片只显示第一张。