OpenCV Android端使用,基本版

2023-07-14 11:01:49 浏览数 (2)

1. 介绍

最近在使用OpenCV的一些功能和方法,所以汇总一些关于OpenCV的方法在Android端上的调用吧。

网上更多的是PythonC 写的方法。所以想汇总一下关于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

将下载包的压缩包解压,将会得到两个文件夹 samplessdk

  • 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中进行执行:

代码语言:javascript复制
 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或者ActivityonCreate()方法中初始化即可。

代码语言:javascript复制
 new OpenCVNativeLoader().init();

第二步OpenCV初始化后,我们通过CameraX获取的数据交给OpenCV进行处理即可。

CameraProvider添加一个AnalysisUseCase处理,分析得到的ImageProxy对象。实例:(在这里省略了CameraX的初始化和权限,大家结合自己的代码进行配置哦)

代码语言:javascript复制
        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类将BitmapMat。(多转了一轮,网上有很多转换方法,我这里就不介绍了)

2.将ImageProxy直接转为Mat对象。

我主要使用的是第二种方法直接将ImageProxyMat。关于如何转换,可以参考: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标注了一个区域,那么可以用这个函数给图像填充成色块。实例:

代码语言:javascript复制
 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类里面。实例:

代码语言:javascript复制
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()否则图片只显示第一张。

0 人点赞