Android Studio3.3.2 +OpenCV3.4.3图像处理

1 前言

大学一门课需要在Android Studio中配置OpenCV,感谢博主们的无私分享,将学习记录于此,文末有列出学习链接和demo源码。如果你也想在Android Studio中配置OpenCV,本文或许会给你帮助,来自小白的感谢。

2 开发环境

  • Android Studio 3.3.2
  • OpenCV for Android 3.4.3
  • Windows 10 Enterprise 64bit

注:查看Android Studio版本方法:打开Android Studio顶部工具栏Help→About即可。
相关下载:Android相关工具下载OpenCV相关下载gradle个版本下载

3 OpenCV for Android的配置

3.1导入OpenCV Module

官网下载好OpenCV的Android SDK,我下载的是OpenCV for Android 3.4.3,解压到本地,创建好一个Android project,开始配置。

第一步,file→new→import module导入刚刚解压好的OpenCV for Android 3.4.3,选择D:\OpenCV for Android 3.4.3\OpenCV-android-sdk\sdk\java,如下图所示,出现openCVLibrary343,就说明被导进来了,然后next→finish即可。导入后可看到我们的project目录下有一个openCVLibrary343的目录。
导入Module若出现sync failed ,根据提示修改即可,我的情况如下。

第二步,在project中添加依赖,如下鼠标操作,适合不熟悉Android的小白们。

在这里插入图片描述
然后选择openCVLibrary343一路OK下去。

第三步,把D:\OpenCV for Android 3.4.3\OpenCV-android-sdk\sdk\native\libs目录下的库复制到我们的project的app\libs目录下,具体如图所示:
在这里插入图片描述

这个目录下存放的是CPU架构相关的库,选择符合自己CPU架构的复制进去。我这里复制了armeabi和armeabi-v7a两个。(注:如果你是X86的架构就把x86的放进去,对应自己CPU架构。)

还要更改下OpenCV的build.gradle里的内容,主要是sdk版本问题,改成和我们app里(build.gradle)的一致就好了,如下图所示:
在这里插入图片描述

第四步,在app的gradle.build里配置刚刚复制进来的库,即把下面的代码添加到gradle.build文件里的android结点下:

sourceSets {
        main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['libs']
        }
    }

在这里插入图片描述
然后点击 sync now同步下,gradle没有报错信息,就配置成功了。下面就可以调用OpenCV库进行开发了。

4 demo(附源码)

在开发的时候,要先在用到的java文件里先加载OpenCV初始化库,即添加下面代码:

static {
        if(!OpenCVLoader.initDebug())
        {
            Log.d("opencv","初始化失败");
        }
    }

在这里插入图片描述

4.1 demo简介

demo简介:找到最大轮廓→加粗画出最大轮廓→获得最大轮廓面积。

1.调用bitmapToMat()和matToBitmap()进行图片格式转换,Android显示Bitmap型,OpenCv处理Mat型。

2.调用cvtColor()进行颜色空间转换,可以实现RGB颜色向HSV,HSI等颜色空间转换。也可以转换为灰度图。在demo中,将原图置灰。

3.调用Canny()进行边缘检测。

4.调用dilate()和erode()对图片进行膨胀和腐蚀。两个函数都是针对高亮部分(白色)进行操作。

5.调用findContours()找出所有轮廓。

6.调用contourArea()找出最大面积,即标尺面积。

7.调用DecimalFormat()对输出数据格式化,规定小数点后最多四位。

demo project目录如下图:
在这里插入图片描述

4.2 核心代码

4.2.1 activity_main.xml

Android ConstraintLayout图文并茂详解(一)
觉得上面那篇Blog用来学习Android Constraint挺好的,当然下面的布局没那么复杂。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/constraintLayout"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/image"
        android:layout_width="400dp"
        android:layout_height="300dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
        app:layout_constraintLeft_toLeftOf="@+id/constraintLayout"
        app:layout_constraintRight_toRightOf="@+id/constraintLayout"
        app:layout_constraintTop_toTopOf="@+id/constraintLayout"
        />
    <Button
        android:id="@+id/btn_choose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="选图"
        app:layout_constraintLeft_toLeftOf="@+id/constraintLayout"
        app:layout_constraintTop_toBottomOf="@+id/image"
        />
    <Button
        android:id="@+id/btn_prtscr"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="截图"
        app:layout_constraintRight_toRightOf="@+id/constraintLayout"
        app:layout_constraintTop_toBottomOf="@+id/image"
        />
    <Button
        android:id = "@+id/btn_max"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:text = "最大面积"
        app:layout_constraintLeft_toLeftOf="@+id/constraintLayout"
        app:layout_constraintTop_toBottomOf="@+id/btn_choose"
        />
    <EditText
        android:id = "@+id/edt_max"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/btn_max"
        app:layout_constraintTop_toBottomOf="@+id/btn_choose"
        />

</android.support.constraint.ConstraintLayout>

4.2.2 MainActivity.java

package com.example.lch.android_opencv_lch;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.icu.text.DecimalFormat;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import static org.opencv.imgproc.Imgproc.MORPH_RECT;
import static org.opencv.imgproc.Imgproc.getStructuringElement;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    static {
        if(!OpenCVLoader.initDebug())
        {
            Log.d("opencv","初始化失败");
        }
    }

    private ImageView imageView;
    private Bitmap bitmap;

    private TextView edt_max;//显示最大面积
    private Button btn_max;//最大面积
    private Button btn_prtscr;//截图
    private Button btn_choose;//从手机选择截图

    private static final int REQUEST_MEDIA_PROJECTION = 1;
    private VirtualDisplay mVirtualDisplay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = (ImageView) findViewById(R.id.image);
        edt_max = (TextView) findViewById(R.id.edt_max);

        btn_choose = (Button) findViewById(R.id.btn_choose);
        btn_choose.setOnClickListener(this);
        btn_max = (Button) findViewById(R.id.btn_max);
        btn_max.setOnClickListener(this);
        btn_prtscr = (Button) findViewById(R.id.btn_prtscr);
        btn_prtscr.setOnClickListener(this);
    }
    @Override
    public void onClick(View view) {
        switch(view.getId()){
            case R.id.btn_choose:ChooseImage();break;
            case R.id.btn_max:Maxarea(); break;
            case R.id.btn_prtscr:PrtScr();break;
        }
    }

    @TargetApi(Build.VERSION_CODES.N)
    private void Maxarea() {
        Bitmap bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();//获取image里的资源
        Mat grayMat = new Mat();
        Mat cannyEdges = new Mat();
        Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
        Utils.bitmapToMat(bitmap, src);
        Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);//置灰
        Imgproc.Canny(grayMat, cannyEdges, 10, 100);//Canny边缘检测
        Mat element = getStructuringElement(MORPH_RECT, new Size(3, 3));
        Imgproc.dilate(cannyEdges, cannyEdges, element);//膨胀
        Imgproc.erode(cannyEdges, cannyEdges, element);//腐蚀
        List<MatOfPoint> contours=new ArrayList<>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(cannyEdges,contours,hierarchy ,Imgproc.RETR_EXTERNAL,Imgproc.CHAIN_APPROX_SIMPLE, new Point(0,0));
        double Max = 0,contourarea;
        int j = 0;//最大轮廓的索引号
        for (int i=0;i<contours.size();i++) {
            contourarea = Imgproc.contourArea(contours.get(i));
            if(Max < contourarea)
            {
                Max = contourarea;
                j = i;
            }
        }
        Imgproc.drawContours(cannyEdges, contours, j, new Scalar(250), 50,8);//将最大面积轮廓加粗画出
        Bitmap processedImage = Bitmap.createBitmap(cannyEdges.cols(), cannyEdges.rows(), Bitmap.Config.ARGB_8888);
        Utils.matToBitmap(cannyEdges, processedImage);
        imageView.setImageBitmap(processedImage);
        DecimalFormat df = new DecimalFormat("#.####");
        if (Max != 0) {
            edt_max.setText("最大面积:" + df.format(Max)+"cm");
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void PrtScr() {
        MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);//也可直接用1替代REQUEST_MEDIA_PROJECTION
    }

    private void ChooseImage() {
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
        startActivityForResult(intent, 2);
    }
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        //从手机读取一张图片
        if ( requestCode == 2) { // 从相册返回的数据
            if (data != null) {
                Uri uri = data.getData(); // 得到图片的全路径
                imageView.setImageURI(uri);
            }
        }
        //截屏操作
        if (requestCode == REQUEST_MEDIA_PROJECTION) {
            if (resultCode != Activity.RESULT_OK) {
                Toast.makeText(this, "用户取消了", Toast.LENGTH_SHORT).show();
                return;
            }
            final ImageReader mImageReader = ImageReader.newInstance(ScreenUtils.getScreenWidth(this), ScreenUtils.getScreenHeight(this), 0x1, 2);
            MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
            MediaProjection mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
            mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture",
                    ScreenUtils.getScreenWidth(this), ScreenUtils.getScreenHeight(this), getResources().getDisplayMetrics().densityDpi,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                    mImageReader.getSurface(), null, null);

            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    Image image = null;
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
                        image = mImageReader.acquireLatestImage();
                    }
                    if (image == null) {
                        return;
                    }
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
                        int width = image.getWidth();
                        int height= image.getHeight();
                        final Image.Plane[] planes = image.getPlanes();
                        final ByteBuffer buffer = planes[0].getBuffer();
                        int pixelStride = planes[0].getPixelStride();
                        int rowStride = planes[0].getRowStride();
                        int rowPadding = rowStride - pixelStride * width;
                        Bitmap mBitmap;
                        mBitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
                        mBitmap.copyPixelsFromBuffer(buffer);
                        mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width, height);//不想截全屏就改这
                        image.close();
                        if (mBitmap != null) {
                            //拿到mitmap
                            final Bitmap finalMBitmap = mBitmap;
                            imageView.setImageBitmap(finalMBitmap);;
                        }
                    }
                }
            }, 300);
        }
    }

    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {

    }
}

4.2.3 ScreenUtils.java

package com.example.lch.android_opencv_lch;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class ScreenUtils {
    /**
     * 获得屏幕相关的辅助类
     */
    private ScreenUtils()
    {
        /* cannot be instantiated */
        throw new UnsupportedOperationException("cannot be instantiated");
    }
    
    /**
     * 获得屏幕高度
     * @param context
     * @return
     */
    public static int getScreenWidth(Context context)
    {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }
    
    /**
     * 获得屏幕宽度
     * @param context
     * @return
     */
    public static int getScreenHeight(Context context)
    {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }
}

4.3 运行结果

run app→Logcat→方便的保存运行情况到PC端。(Android Studio视频录制和截图功能)
在这里插入图片描述
运行结果图太大,未传。

5 参考链接

  1. Android Studio中配置OpenCV
  2. androidstudio添加多个按钮方法
  3. android studio 添加按钮点击事件的三种方法
    源码
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值