安卓OCR使用(Google ML Kit)

OCR是一个很常用的功能,Google ML Kit提供了OCR能力,用起来也很简单,本文介绍一下使用方法。

1. 相关概念

名词概念解释
TextBlock一个段落
Line一行文本
Element元素单词;对汉字来说,类似"开头 (分隔符)中间(分隔符) 结尾"这样含有明显分隔符的才会有多个字在一个Element中,否则就是单个字
Symbol字符字母;对汉字来说就是单个字

2. 代码实现

在build.gradle中添加相关依赖:

// To recognize Chinese script
implementation 'com.google.mlkit:text-recognition-chinese:16.0.1'

添加布局文件activity_ocr.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><FrameLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"><SurfaceViewandroid:id="@+id/camera_preview"android:layout_width="wrap_content"android:layout_height="wrap_content" /><com.example.study.views.DrawViewandroid:id="@+id/ocr_area"android:layout_width="wrap_content"android:layout_height="wrap_content" /></FrameLayout><Buttonandroid:id="@+id/ocr_switch"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center_horizontal|bottom"android:layout_marginBottom="80dp"android:background="@color/夏云灰"android:text="stop" />
</LinearLayout>

绘制文字的OCRDrawView.java:

package com.example.study.views;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.Nullable;import java.util.ArrayList;
import java.util.List;public class OCRDrawView extends View {private final Object lock = new Object();protected Paint paint = new Paint();protected Path path = new Path();private final List<ShapeInfo> cornerPointsList = new ArrayList<>();public OCRDrawView(Context context) {super(context);}public OCRDrawView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public void clear() {synchronized (lock) {cornerPointsList.clear();}postInvalidate();}public void add(Point[] cornerPoints, String text) {synchronized (lock) {cornerPointsList.add(new ShapeInfo(cornerPoints, text));}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);synchronized (lock) {for (ShapeInfo shapeInfo : cornerPointsList) {drawBackground(shapeInfo, canvas);drawText(shapeInfo, canvas);}}}private void drawText(ShapeInfo shapeInfo, Canvas canvas) {Point[] points = shapeInfo.points;// 根据矩形区域的高度设置文字大小double height = calDistance(points[0], points[3]);double width = calDistance(points[2], points[3]);float textSize = (float) Math.min(height, width / shapeInfo.text.length());paint.setColor(Color.BLUE);paint.setTextSize(textSize);path.reset();path.moveTo(points[3].x, points[3].y);path.lineTo(points[2].x, points[2].y);canvas.drawTextOnPath(shapeInfo.text, path, 0, 0, paint);}private double calDistance(Point start, Point end) {return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2));}private void drawBackground(ShapeInfo shapeInfo, Canvas canvas) {Point[] shape = shapeInfo.points;path.reset();path.moveTo(shape[3].x, shape[3].y);for (int i = 0; i < shape.length; i++) {path.lineTo(shape[i].x, shape[i].y);}path.close();paint.setColor(Color.WHITE);canvas.drawPath(path, paint);}static class ShapeInfo {Point[] points;String text;public ShapeInfo(Point[] shape, String text) {this.points = shape;this.text = text;}}
}

activity类:

package com.example.study.activities;import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;import com.example.study.R;
import com.example.study.views.OCRDrawView;
import com.google.mlkit.vision.text.Text;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.TextRecognizer;
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;import java.io.ByteArrayOutputStream;public class OCRActivity extends ComponentActivity implements Camera.PreviewCallback, SurfaceHolder.Callback {private static final String TAG = "CameraDemoActivity";private static final int REQUEST_CAMERA = 1000;private static final int HEIGHT = 1920;private static final int WIDTH = 1080;private static final int ORIENTATION = 90;private SurfaceView preview;private OCRDrawView ocrArea;private Button ocrSwitch;private Camera camera;private Camera.Parameters parameters;private TextRecognizer recognizer;private Matrix matrix;private boolean isRecognizering = false;private boolean stopRecognizer = false;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(R.layout.activity_ocr);initView();initVar();// 检查权限if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);} else {preview.getHolder().addCallback(this);}}private void initVar() {recognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());matrix = new Matrix();matrix.setRotate(ORIENTATION);// 4个角的坐标是没有旋转过的,所以HEIGHT、WIDTH是反的matrix.preTranslate(-HEIGHT >> 1, -WIDTH >> 1);}private void initView() {preview = findViewById(R.id.camera_preview);ocrArea = findViewById(R.id.ocr_area);ocrSwitch = findViewById(R.id.ocr_switch);ocrSwitch.setOnClickListener(view -> {stopRecognizer = !stopRecognizer;ocrSwitch.setText(stopRecognizer ? "start" : "stop");if (camera == null) {return;}if (stopRecognizer) {camera.stopPreview();} else {camera.startPreview();}});adjustSurface(preview, ocrArea);}private void adjustSurface(SurfaceView cameraPreview, OCRDrawView ocrArea) {FrameLayout.LayoutParams cameraPreviewParams = (FrameLayout.LayoutParams) cameraPreview.getLayoutParams();cameraPreviewParams.width = WIDTH;cameraPreviewParams.height = HEIGHT;ViewGroup.LayoutParams ocrAreaParams = ocrArea.getLayoutParams();ocrAreaParams.width = WIDTH;ocrAreaParams.height = HEIGHT;}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_CAMERA && grantResults.length > 0) {if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {preview.getHolder().addCallback(this);surfaceCreated(preview.getHolder());camera.setPreviewCallback(this);camera.startPreview();} else {finish();}}}@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {if (isRecognizering || stopRecognizer) {return;}Bitmap bitmap = convertToBitmap(camera, data);isRecognizering = true;recognizer.process(bitmap, ORIENTATION).addOnSuccessListener(text -> {parseOCRResult(text);}).addOnFailureListener(exception -> {Toast.makeText(this, "Failure", Toast.LENGTH_SHORT).show();isRecognizering = false;}).addOnCompleteListener(task -> {isRecognizering = false;}).addOnCanceledListener(() -> {Toast.makeText(this, "Canceled", Toast.LENGTH_SHORT).show();isRecognizering = false;});}private void parseOCRResult(Text text) {// 所有识别到的内容,下同String textContent = text.getText();if (textContent == null || textContent.trim().length() == 0) {return;}ocrArea.clear();// 块,段落for (Text.TextBlock textBlock : text.getTextBlocks()) {// 一行文本for (Text.Line line : textBlock.getLines()) {drawResult(line);// 元素:单词,对汉字来说,需要"开头 (分隔符)中间(分隔符) 结尾"之类比较强烈的分隔符去分隔for (Text.Element element : line.getElements()) {// symbol:字符,字母,字for (Text.Symbol symbol : element.getSymbols()) {symbol.getText();}}}}}private void drawResult(Text.Line line) {// line的旋转角度(以度为单位,顺时针为正,范围为[-180, 180])float angle = line.getAngle() + ORIENTATION;// 检测到的文本的轴对齐边界矩形Rect boundingBox = line.getBoundingBox();// 从左上角开始顺时针方向的四个角点。不带旋转角度,如果设置过旋转角度camera.setDisplayOrientation,需要进行旋转Point[] cornerPoints = line.getCornerPoints();// 置信度float confidence = line.getConfidence();// 获取文本中的主要语言(如果有的话)String recognizedLanguage = line.getRecognizedLanguage();// 置信度太低的过滤掉if (confidence < 0.3f) {return;}for (Point cornerPoint : cornerPoints) {float[] floats = {cornerPoint.x, cornerPoint.y};matrix.mapPoints(floats);cornerPoint.x = (int) floats[0] + (WIDTH >> 1);cornerPoint.y = (int) floats[1] + (HEIGHT >> 1);}ocrArea.add(cornerPoints, line.getText());ocrArea.postInvalidate();}/*** Convert camera data into bitmap data.*/private Bitmap convertToBitmap(Camera camera, byte[] data) {int width = camera.getParameters().getPreviewSize().width;int height = camera.getParameters().getPreviewSize().height;YuvImage yuv = new YuvImage(data, ImageFormat.NV21, width, height, null);ByteArrayOutputStream stream = new ByteArrayOutputStream();yuv.compressToJpeg(new Rect(0, 0, width, height), 100, stream);return BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.toByteArray().length);}@Overrideprotected void onResume() {super.onResume();}@Overrideprotected void onRestart() {super.onRestart();}@Overrideprotected void onDestroy() {super.onDestroy();if (recognizer != null) {recognizer.close();}}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {try {camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);parameters = camera.getParameters();// 旋转了90度,所以height、width互换parameters.setPictureSize(HEIGHT, WIDTH);parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);parameters.setPictureFormat(ImageFormat.NV21);camera.setPreviewDisplay(holder);camera.setDisplayOrientation(ORIENTATION);camera.setParameters(parameters);} catch (Exception exception) {Log.i(TAG, exception.getMessage());}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {if (camera != null) {camera.stopPreview();camera.setPreviewCallback(null);camera.startPreview();camera.setPreviewCallback(this);ocrArea.clear();stopRecognizer = true;ocrSwitch.performClick();}}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {if (camera != null) {camera.stopPreview();camera.setPreviewCallback(null);camera.release();}}
}

参考文章

  1. 文字识别 v2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/65530.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库(3)--针对列的CRUD操作

1.Create 新增 语法&#xff1a; insert into 表名 &#xff08;列名&#xff09;values &#xff08;列&#xff09;... 创建一个学生表用于演示&#xff1a; create table if not exists student( id bigint comment 编号, name varchar(20) comment 姓名 ); 1.1直接增加…

【设计模式-1】软件设计模式概述

1. 软件设计模式的产生背景 “设计模式”这个术语最初并不是出现在软件设计中&#xff0c;而是被用于建筑领域的设计中。 1977 年&#xff0c;美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫亚历山大&#xff08;Christopher Alexander&#xff09;在…

Python爬虫基础——认识网页结构(各种标签的使用)

1、添加<div>标签的代码定义了两个区块的宽度和高度均为100px&#xff0c;边框的格式也相同&#xff0c;只是区块中显示的内容不同&#xff1b; 2、添加<ul>和<ol>标签分别用于定义无序列表和有序列表。<il>标签位于<ul>标签或<ol>标签之…

Spring boot接入xxl-job

Spring boot接入xxl-job 导入maven包加入配置增加配置类创建执行器类&#xff08;写job的业务逻辑&#xff09;去控制台中配置job 导入maven包 <dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>…

去掉el-table中自带的边框线

1.问题:el-table中自带的边框线 2.解决后的效果: 3.分析:明明在el-table中没有添加border,但是会出现边框线. 可能的原因: 由 Element UI 的默认样式或者表格的某些内置样式引起的。比如,<el-table> 会通过 border-collapse 或 border-spacing 等属性影响边框的显示。 4…

空间不足导致Oracle集群内存使用率暴增

一、现象 操作系统内存使用率告警&#xff0c;已达到98%,&#xff0c;告警内容如下&#xff1a; 【全景监控&#xff1a;Oracle主机内存使用监控】 【主机名】&#xff1a;XXXXX11 【主机IP】主机IP&#xff1a;*.126.15 【告警内容】当前内存使用率为98.9%&#xff0c;超警…

spark汇总

目录 描述运行模式1. Windows模式代码示例 2. Local模式3. Standalone模式 RDD描述特性RDD创建代码示例&#xff08;并行化创建&#xff09;代码示例&#xff08;读取外部数据&#xff09;代码示例&#xff08;读取目录下的所有文件&#xff09; 算子DAGSparkSQLSparkStreaming…

React中的合成事件

合成事件与原生事件 区别&#xff1a; 1. 命名不一样&#xff0c;原生用纯小写方式&#xff0c;react用小驼峰的方式 原生&#xff1a;onclick React的&#xff1a;onClick 2. 事件处理函数的写法不一样 原生的是传入一个字符串&#xff0c;react写法传入一个回调函数 3.…

CSS——26. 伪元素2(“::before ,::after”)

::before伪类 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>伪元素</title><style type"text/css">div::before{content: "我最棒";}}</style></head><body><!--…

Openssl1.1.1s rpm包构建与升级

rpmbuild入门知识 openssh/ssl二进制升级 文章目录 前言一、资源准备1.下载openssh、openssl二进制包2.安装rpmbuild工具3.拷贝源码包到SOURCES目录下4.系统开启telnet&#xff0c;防止意外导致shh无法连接5.编译工具安装6.补充说明 二、制作 OpenSSL RPM 包1.编写 SPEC 文件2.…

patchwork++地面分割学习笔记

参考资料&#xff1a;古月居 - ROS机器人知识分享社区 https://zhuanlan.zhihu.com/p/644297447 patchwork算法一共包含四部分内容&#xff1a;提出了以下四个部分&#xff1a;RNR、RVPF、A-GLE 和 TGR。 1&#xff09;基于 3D LiDAR 反射模型的反射噪声消除 (RNR)&#xff…

基于Spring Boot的海滨体育馆管理系统的设计与实现

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的海滨体育馆管理系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 宠物医院…

通过Android Studio修改第三方jar包并重新生成jar包

最近接手了来自公司其他同事的一个Unity项目,里面有一个封装的jar包要改动一下,无奈关于这个jar包的原工程文件丢失了,于是自己动手来修改下jar包,并做下记录。 一、导入第三方jar包 1、新建项目EditJarDemo(项目名随便取) 2、新建libs文件夹,把你要修改的third.jar 复制…

计算机网络之---物理层设备

什么是物理层设备 物理层设备是指负责数据在物理媒介上传输的硬件设备&#xff0c;它们主要处理数据的转换、信号的传输与接收&#xff0c;而不涉及数据的内容或意义。常见的物理层设备包括网卡、集线器、光纤收发器、调制解调器等。 物理层设备有哪些 1、网卡&#xff08;N…

flink的EventTime和Watermark

时间机制 Flink中的时间机制主要用在判断是否触发时间窗口window的计算。 在Flink中有三种时间概念&#xff1a;ProcessTime、IngestionTime、EventTime。 ProcessTime&#xff1a;是在数据抵达算子产生的时间&#xff08;Flink默认使用ProcessTime&#xff09; IngestionT…

Ubuntu 下测试 NVME SSD 的读写速度

在 Ubuntu 系统下&#xff0c;测试 NVME SSD 的读写速度&#xff0c;有好多种方法&#xff0c;常用的有如下几种&#xff1a; 1. Gnome-disks Gnome-disks&#xff08;也称为“Disks”&#xff09;是 GNOME 桌面环境中的磁盘管理工具&#xff0c;有图形界面&#xff0c;是测试…

SpringBoot之核心配置

学习目标&#xff1a; 1.熟悉Spring Boot全局配置文件的使用 2.掌握Spring Boot配置文件属性值注入 3.熟悉Spring Boot自定义配置 4.掌握Profile多环境配置 5.了解随机值设置以及参数间引用 1.全局配置文件 Spring Boot使用 application.properties 或者application.yaml 的文…

后端服务集成ElasticSearch搜索功能技术方案

文章目录 一、为什么选用ElasticSearch二、ElasticSearch基本概念1、文档和字段2、索引和映射3、倒排索引、文档和词条4、分词器 三、ElasticSearch工作原理1、Term Dictionary、Term index2、Stored Fields3、Docs Values4、Segment5、Lucene6、高性能、高扩展性、高可用①高性…

【JavaEE进阶】获取Cookie/Session

&#x1f340;Cookie简介 HTTP协议自身是属于 "⽆状态"协议. "⽆状态"的含义指的是: 默认情况下 HTTP 协议的客⼾端和服务器之间的这次通信,和下次通信之间没有直接的联系.但是实际开发中,我们很多时候是需要知道请求之间的关联关系的. 例如登陆⽹站成…

《Spring Framework实战》3:概览

欢迎观看《Spring Framework实战》视频教程 Spring Framework 为基于现代 Java 的企业应用程序提供了全面的编程和配置模型 - 在任何类型的部署平台上。 Spring 的一个关键要素是应用程序级别的基础设施支持&#xff1a;Spring 专注于企业应用程序的 “管道”&#xff0c;以便…