OpenCV4Android 处理相册照片、拍照所得照片

上一篇中记录了如何在安卓项目配置OpenCV4,并且运行了一个示例程序,可以将一张预设的照片灰度处理。在实际使用过程中可能会从相册选取图片或者拍照之后马上处理,本文记录一下如何实现这两种功能。菜鸟水平,有问题还请各位大佬指正。

先上测试照片

第2、3张是处理相册里的,4、5是拍照马上处理的。
OpenCV4Android 处理相册照片、拍照所得照片_第1张图片

OpenCV4Android 处理相册照片、拍照所得照片_第2张图片
OpenCV4Android 处理相册照片、拍照所得照片_第3张图片
OpenCV4Android 处理相册照片、拍照所得照片_第4张图片
OpenCV4Android 处理相册照片、拍照所得照片_第5张图片

相关基础知识及准备工作

首先要搞清楚BitmapMat,其分别是安卓平台和OpenCV中的图像对象,所以当我们在安卓平台使用OpenCV处理图片,需要先将Bitmap转为Mat,OpenCV处理完成后再将Mat转回Bitmap。

该APP需要调用摄像头、读写图像数据,所以要在AndroidManifest中声明三项权限。并且记得申请运行时权限。

再说一句,都2020年了,安卓版本最低支持到7.0,也就是API 24。

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

在相册选择图片并在ImageView中显示

大致分为两个步骤,点击“相册”按钮后,执行以下方法,其中CHOOSE_PHOTO为相册的requestCode,当选好图片返回到MainActivity界面,onActivityResult()方法根据requestCode做出响应。
后面的handleImageOnKitKat()和displayImage()是用的郭霖《第一行代码 Android 第二版》里的示例,Uri这东西实在搞得人头昏。。。

    private void selectPic() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, "图像选择。。。"), CHOOSE_PHOTO );
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(resultCode == RESULT_OK && requestCode == CHOOSE_PHOTO) {
            handleImageOnKitKat(data);
        }

        if(resultCode == RESULT_OK && requestCode == TAKE_PHOTO) {
            //该uri就是照片文件夹对应的uri
            Bitmap bit = null;
            try {
                bit = BitmapFactory.decodeStream(getContentResolver().openInputStream(pictureUri));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            // 给相应的ImageView设置图片 未裁剪
            iv.setImageBitmap(bit);
        }
    }
    private void handleImageOnKitKat(Intent data) {
        Uri uri = data.getData();
        Log.d("TAG", "handleImageOnKitKat: uri is " + uri);
        if (DocumentsContract.isDocumentUri(this, uri)) {
            // 如果是document类型的Uri,则通过document id处理
            String docId = DocumentsContract.getDocumentId(uri);
            if("com.android.providers.media.documents".equals(uri.getAuthority())) {
                String id = docId.split(":")[1]; // 解析出数字格式的id
                String selection = MediaStore.Images.Media._ID + "=" + id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
            } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
                imagePath = getImagePath(contentUri, null);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            // 如果是content类型的Uri,则使用普通方式处理
            imagePath = getImagePath(uri, null);
        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
            // 如果是file类型的Uri,直接获取图片路径即可
            imagePath = uri.getPath();
        }
        displayImage(imagePath); // 根据图片路径显示图片
    }

    private void displayImage(String imagePath) {
        if (imagePath != null) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            iv.setImageBitmap(bitmap);
        } else {
            Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
        }
    }
    private String getImagePath(Uri uri, String selection) {
        String path = null;
        // 通过Uri和selection来获取真实的图片路径
        Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }

处理相册所选的图片

因为在选图时已经获取了其路径,可以直接用imread()来加载Mat对象

    private void convert2Gray2() {
        Mat src = Imgcodecs.imread(imagePath);
        if(src.empty()) {
            return;
        }
        Mat dst = new Mat();
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY);
        Bitmap bitmap = grayMat2Bitmap(dst);
        ImageView iv = this.findViewById(R.id.sample_img);
        iv.setImageBitmap(bitmap);
        src.release();
        dst.release();
    }

该方法grayMat2Bitmap()主要增加了一个降低分辨率的步骤,在后续使用中发现不降也行,还没有遇到图太大导致OOM(Out Of Memory)的情况。

    private Bitmap grayMat2Bitmap(Mat result) {
        Mat image = null;
        if(result.cols() > 1000 || result.rows() > 1000) {
            image = new Mat();
            Imgproc.resize(result, image, new Size(result.cols() / 4, result.rows() / 4));
        } else {
            image = result;
        }
        Bitmap bitmap = Bitmap.createBitmap(image.cols(),image.rows(), Bitmap.Config.ARGB_8888);
        Imgproc.cvtColor(image, image, Imgproc.COLOR_GRAY2RGBA);
        Utils.matToBitmap(image, bitmap);
        image.release();
        return bitmap;
    }

拍照并将其存储在指定位置

这里需要使用到content provider,先在AndroidManifest中声明一下,并在res目录下新建一个xml目录,在xml中新建一个file_paths.xml文件。

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".FirstActivity"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:authorities="com.nuaa.lrqopencvdemo.provider"
            android:name="androidx.core.content.FileProvider"
            android:grantUriPermissions="true"
            android:exported="false" >
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

    </application>

file_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="my_images"
        path=""/>

</paths>

按下拍照按钮执行该方法。TAKE_PHOTO是拍照的requestCode。

private void takePhoto() {

        //创建File对象,用于存储拍照后的图片。getExternalCacheDir 得到专门用于存放当前应用缓存数据的目录
        outputImage = new File(getExternalCacheDir(), "output_image.jpg");

        try {
            if(outputImage.exists()) {
                outputImage.delete();
            }
            outputImage.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        pictureUri = FileProvider.getUriForFile(this, "com.nuaa.lrqopencvdemo.provider", outputImage);

        //启动相机程序
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
        // 使用startActivityForResult来启动活动,引测拍完照后会有结果返回到onActivityResult中
        startActivityForResult(intent, TAKE_PHOTO);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(resultCode == RESULT_OK && requestCode == CHOOSE_PHOTO) {
            handleImageOnKitKat(data);
        }

        if(resultCode == RESULT_OK && requestCode == TAKE_PHOTO) {
            //该uri就是照片文件夹对应的uri
            Bitmap bit = null;
            try {
                bit = BitmapFactory.decodeStream(getContentResolver().openInputStream(pictureUri));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            // 给相应的ImageView设置图片 未裁剪
            iv.setImageBitmap(bit);
        }
    }

处理拍照所得照片

这里是直接根据Uri获取Bitmap对象。

    private void convert2Gray3() {
        try {
            ContentResolver cs = getContentResolver();
            Bitmap bp = BitmapFactory.decodeStream(cs.openInputStream(pictureUri));
            Mat src = new Mat();
            Mat dst = new Mat();
            Utils.bitmapToMat(bp, src);
            Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGRA2GRAY);
            Utils.matToBitmap(dst, bp);
            ImageView iv = this.findViewById(R.id.sample_img);
            iv.setImageBitmap(bp);
            src.release();
            dst.release();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
	}

三种不同来源图像处理的实现

自己瞎写的,因为会处理预设图像、相册图像、拍照图像,来源不一样所以写了三个不同的处理方法,毕竟不熟练导致代码复杂了,不过功能还是能实现。
点击“相册”或者“拍照”按钮都会给标志数album_or_camera 赋值,这样在点击“灰度”按钮后,只需要进行简单判断即可知道该调用哪个方法了。
BTW,打开相册也没申请运行时权限,不知道为什么没报错。。。

@Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.process_btn:
                if(album_or_camera == 0) {
                    convert2Gray2();
                } else if(album_or_camera == 1) {
                    convert2Gray3();
                } else {
                    convert2Gray();
                }
                break;

            case R.id.select_btn:
                album_or_camera = 0;
                selectPic();
                break;

            case R.id.shoot_btn:
                album_or_camera = 1;
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
                    || ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
                    PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE
                    )!= PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 300);
                } else {
                    takePhoto();
                }
                break;
        }
    }

你可能感兴趣的:(OpenCV4Android 处理相册照片、拍照所得照片)