一、前言

1.什么是Firebase

Firebase是由Google提供的实时全方位的移动后端云服务平台,这是一个主要功能都基于云端的服务平台,其提供多项远程功能如:远程数据库、认证、云存储、云函数;同样也提供一些本地功能如:本地数据库,性能检测,崩溃策略。本文提到的MLkit也是其提供的本地客户端功能之一

2.什么是ML Kit

ML Kit是谷歌官方提供的一款机器学习 SDK,同样的也提供了“设备端”和“云端”两种API。它包含了一系列预训练模型,提供诸如文字/人脸/条码识别、图像标注、智能回复等功能,帮助开发者在移动端上快速添加机器学习功能


二、基础准备

在开发中加入ML Kit需要我们已有一个安卓项目(可以是空项目,也可以是已有内容的项目),用于绑定至Firebase的云项目中,绑定方式可以查看谷歌官方给出的文档:
如何将Firebase依赖添加至安卓项目中
在Firebase中注册应用包的过程:
Firebase控制台中显示的内容
注册好之后按照步骤,添加对应的依赖重新构建项目即可

三、实现过程

1.布局实现

<LinearLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <Button
        android:id="@+id/btn_capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/get_image"/>


    <TextView
        android:id="@+id/text_result"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:text="@string/ocr_result"/>
</LinearLayout>

这里总共涉及三个部件:
显示图片的视图,可以用于绑定拍摄后获得的照片,也可以用于后续丰富扩展内容,比如通过相册选取图片进行识别时,获得更好的用户交互体验;
拍摄图片的按钮,单击这个按钮将会触发获取照相机权限的逻辑,进入拍照界面
显示识别出文字的文本内容,如果没有识别出文字将不作更改

布局的实现效果:
请添加图片描述

2.逻辑实现

预先定义的常量以及预先定义的对象

    //调用拍照功能的请求码
    private static final int REQUEST_IMAGE_CAPTURE = 1;
    //调用相机权限的请求码
    private static final int CAMERA_PERMISSION_REQUEST_CODE = 2;
    
    private ImageView imageView;
    private TextView textViewResult;

添加相机权限至AndroidManifest.xml

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" android:required="true" />

onCreate中绑定唤起照相机的监听器,这里先判断是否已获得相机权限Manifest.permission.CAMERA如果没有权限,则请求相机权限 ActivityCompat.requestPermissions如果已有权限,则直接调用 dispatchTakePictureIntent() 方法通过intent挨唤起拍照程序

        btnCapture.setOnClickListener(v -> {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                    != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.CAMERA},
                        CAMERA_PERMISSION_REQUEST_CODE);
            } else {
                dispatchTakePictureIntent();
            }
        });

这是第一次得到权限请求时,进行的处理,此时我们的虚拟机上会弹出一个请示框,提示我们这个app申请打开xx应用或者内容(我在实现到这里第一反应就是万恶的摇一摇广告跳转app,或许我们也可以不用弹窗直接通过请求?):

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                dispatchTakePictureIntent();
            }
        }
    }

自定义的dispatchTakePictureIntent()函数,我们在这里唤起照相机

    private void dispatchTakePictureIntent() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }

重写onActivityResult()函数,当用户拍完照片从照相机返回至app时触发,并且进行检测,当请求码为我们定义的REQUEST_IMAGE_CAPTURE、返回码为RESULT_OK时,说明拍照被调用成功并且我们成功获取到了拍照返回的照片;我们只需要从intent中提取出照片,在调用关键函数recognizeTextFromImage()进行识别即可实现功能

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            Bundle extras = data.getExtras();
            Bitmap imageBitmap = (Bitmap) extras.get("data");
            imageView.setImageBitmap(imageBitmap);

            recognizeTextFromImage(imageBitmap);
        }
    }

这是上文提到的关键函数recognizeTextFromImage(),我们使用它来完成中文的识别,这里使用ML Kit提供的中文识别器ChineseTextRecognizerOptions创建了一个Builder来拼接从照片中识别出的文字,并且添加了两个监听器,当识别成功时,输出识别的内容(什么都没识别到也是识别成功,如果想要完善这个部分可以新增一个空值判断,当识别到的text为空时,可以输出提示);失败时则会输出失败信息

    private void recognizeTextFromImage(Bitmap bitmap) {
        InputImage image = InputImage.fromBitmap(bitmap, 0);
        TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build())
                .process(image)
                .addOnSuccessListener(text -> {
                    textViewResult.setText(text.getText());
                })
                .addOnFailureListener(e -> {
                    textViewResult.setText("Failed to recognize text: " + e.getMessage());
                });
    }

识别效果(调用的虚拟机照相机,所以拍出来的图片也是虚拟的,图中不存在任何文字,所以按钮下方也没有任何文本):
识别效果


四、扩展

基本功能的实现在前文都已经有提及,但是这显然很粗糙,我们可以对其进行更多的完善,这边可以给出一些完善举例

扩展案例:添加通过相册选取图片

想要通过相册来选取图片进行识别,就要添加新的逻辑。我们一样先添加两个用于满足该功能的常量:

    private static final int PICK_IMAGE_REQUEST = 3;
    private static final int READ_EXTERNAL_STORAGE_REQUEST_CODE = 4;

添加相册权限:

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

添加唤起相册的按钮:

	<Button
        android:id="@+id/btn_gallery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/open_gallery"/>

绑定按钮的唤起逻辑,添加监听器,当请求码满足对应条件时(条件类似于前文,满足对应的常量即可),调用openGallery()函数:

        Button btnGallery = findViewById(R.id.btn_gallery);
        btnGallery.setOnClickListener(v -> {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                        READ_EXTERNAL_STORAGE_REQUEST_CODE);
            } else {
                openGallery();
            }
        });

打开相册的openGallery()方法,一样是通过intent来打开:

    private void openGallery() {
        Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        startActivityForResult(intent, PICK_IMAGE_REQUEST);
    }

onActivityResult()函数中添加新的判断条件,当得到的请求为PICK_IMAGE_REQUEST(即打开相册的请求)时,执行下面的逻辑来通过intent得到从相册中选取到的图片进行识别:

                try {
                    android.net.Uri imageUri = data.getData();
                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri);
                    imageView.setImageBitmap(bitmap);
                    recognizeTextFromImage(bitmap);
                } catch (Exception e) {
                    textViewResult.setText("Error loading image: " + e.getMessage());
                }

onRequestPermissionsResult()函数中补充首次请求的逻辑,if判断的条件逻辑跟之前的类似,更换对应的常量即可:

            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                openGallery();
            }

最终实现的通过相册选取效果,选择了照片之后识别效果是一样的:
通过相册选取图片

五、总结

本文提供了作者使用Firebase中的ML Kit实现对图片进行中文识别的功能的一些思路与过程,在当今的ai热潮下,使用这个已经集成的模块并不困难;所以我在文中还添加了一份简单的扩展,当然,这个功能还能集成到其他app内,这需要一定的想象力和创造力,正是当前的ai所做不到的事情;好好的发挥自己的创造力才能不被淹没在现在这个ai时代的浪潮中

作者:吴鑫
原文链接:在安卓中使用Firebase ML Kit实现中文OCR识别功能

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐