安卓OCR利用(Google ML Kit)

打印 上一主题 下一主题

主题 1022|帖子 1022|积分 3066

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
OCR是一个很常用的功能,Google ML Kit提供了OCR能力,用起来也很简单,本文先容一下利用方法。
1. 相干概念

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

在build.gradle中添加相干依赖:
  1. // To recognize Chinese script
  2. implementation 'com.google.mlkit:text-recognition-chinese:16.0.1'
复制代码
添加结构文件activity_ocr.xml:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.               android:layout_width="match_parent"
  4.               android:layout_height="match_parent"
  5.               android:orientation="vertical">
  6.     <FrameLayout
  7.             android:layout_width="wrap_content"
  8.             android:layout_height="wrap_content">
  9.         <SurfaceView
  10.                 android:id="@+id/camera_preview"
  11.                 android:layout_width="wrap_content"
  12.                 android:layout_height="wrap_content" />
  13.         <com.example.study.views.DrawView
  14.                 android:id="@+id/ocr_area"
  15.                 android:layout_width="wrap_content"
  16.                 android:layout_height="wrap_content" />
  17.     </FrameLayout>
  18.     <Button
  19.             android:id="@+id/ocr_switch"
  20.             android:layout_width="match_parent"
  21.             android:layout_height="match_parent"
  22.             android:layout_gravity="center_horizontal|bottom"
  23.             android:layout_marginBottom="80dp"
  24.             android:background="@color/夏云灰"
  25.             android:text="stop" />
  26. </LinearLayout>
复制代码
绘制笔墨的OCRDrawView.java:
  1. package com.example.study.views;
  2. import android.content.Context;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.graphics.Path;
  7. import android.graphics.Point;
  8. import android.util.AttributeSet;
  9. import android.view.View;
  10. import androidx.annotation.Nullable;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. public class OCRDrawView extends View {
  14.     private final Object lock = new Object();
  15.     protected Paint paint = new Paint();
  16.     protected Path path = new Path();
  17.     private final List<ShapeInfo> cornerPointsList = new ArrayList<>();
  18.     public OCRDrawView(Context context) {
  19.         super(context);
  20.     }
  21.     public OCRDrawView(Context context, @Nullable AttributeSet attrs) {
  22.         super(context, attrs);
  23.     }
  24.     public void clear() {
  25.         synchronized (lock) {
  26.             cornerPointsList.clear();
  27.         }
  28.         postInvalidate();
  29.     }
  30.     public void add(Point[] cornerPoints, String text) {
  31.         synchronized (lock) {
  32.             cornerPointsList.add(new ShapeInfo(cornerPoints, text));
  33.         }
  34.     }
  35.     @Override
  36.     protected void onDraw(Canvas canvas) {
  37.         super.onDraw(canvas);
  38.         synchronized (lock) {
  39.             for (ShapeInfo shapeInfo : cornerPointsList) {
  40.                 drawBackground(shapeInfo, canvas);
  41.                 drawText(shapeInfo, canvas);
  42.             }
  43.         }
  44.     }
  45.     private void drawText(ShapeInfo shapeInfo, Canvas canvas) {
  46.         Point[] points = shapeInfo.points;
  47.         // 根据矩形区域的高度设置文字大小
  48.         double height = calDistance(points[0], points[3]);
  49.         double width = calDistance(points[2], points[3]);
  50.         float textSize = (float) Math.min(height, width / shapeInfo.text.length());
  51.         paint.setColor(Color.BLUE);
  52.         paint.setTextSize(textSize);
  53.         path.reset();
  54.         path.moveTo(points[3].x, points[3].y);
  55.         path.lineTo(points[2].x, points[2].y);
  56.         canvas.drawTextOnPath(shapeInfo.text, path, 0, 0, paint);
  57.     }
  58.     private double calDistance(Point start, Point end) {
  59.         return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2));
  60.     }
  61.     private void drawBackground(ShapeInfo shapeInfo, Canvas canvas) {
  62.         Point[] shape = shapeInfo.points;
  63.         path.reset();
  64.         path.moveTo(shape[3].x, shape[3].y);
  65.         for (int i = 0; i < shape.length; i++) {
  66.             path.lineTo(shape[i].x, shape[i].y);
  67.         }
  68.         path.close();
  69.         paint.setColor(Color.WHITE);
  70.         canvas.drawPath(path, paint);
  71.     }
  72.     static class ShapeInfo {
  73.         Point[] points;
  74.         String text;
  75.         public ShapeInfo(Point[] shape, String text) {
  76.             this.points = shape;
  77.             this.text = text;
  78.         }
  79.     }
  80. }
复制代码
activity类:
  1. package com.example.study.activities;
  2. import android.Manifest;
  3. import android.content.pm.PackageManager;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.graphics.ImageFormat;
  7. import android.graphics.Matrix;
  8. import android.graphics.Point;
  9. import android.graphics.Rect;
  10. import android.graphics.YuvImage;
  11. import android.hardware.Camera;
  12. import android.os.Bundle;
  13. import android.util.Log;
  14. import android.view.SurfaceHolder;
  15. import android.view.SurfaceView;
  16. import android.view.ViewGroup;
  17. import android.widget.Button;
  18. import android.widget.FrameLayout;
  19. import android.widget.Toast;
  20. import androidx.activity.ComponentActivity;
  21. import androidx.annotation.NonNull;
  22. import androidx.annotation.Nullable;
  23. import com.example.study.R;
  24. import com.example.study.views.OCRDrawView;
  25. import com.google.mlkit.vision.text.Text;
  26. import com.google.mlkit.vision.text.TextRecognition;
  27. import com.google.mlkit.vision.text.TextRecognizer;
  28. import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;
  29. import java.io.ByteArrayOutputStream;
  30. public class OCRActivity extends ComponentActivity implements Camera.PreviewCallback, SurfaceHolder.Callback {
  31.     private static final String TAG = "CameraDemoActivity";
  32.     private static final int REQUEST_CAMERA = 1000;
  33.     private static final int HEIGHT = 1920;
  34.     private static final int WIDTH = 1080;
  35.     private static final int ORIENTATION = 90;
  36.     private SurfaceView preview;
  37.     private OCRDrawView ocrArea;
  38.     private Button ocrSwitch;
  39.     private Camera camera;
  40.     private Camera.Parameters parameters;
  41.     private TextRecognizer recognizer;
  42.     private Matrix matrix;
  43.     private boolean isRecognizering = false;
  44.     private boolean stopRecognizer = false;
  45.     @Override
  46.     protected void onCreate(@Nullable Bundle savedInstanceState) {
  47.         super.onCreate(savedInstanceState);
  48.         this.setContentView(R.layout.activity_ocr);
  49.         initView();
  50.         initVar();
  51.         // 检查权限
  52.         if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  53.             requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
  54.         } else {
  55.             preview.getHolder().addCallback(this);
  56.         }
  57.     }
  58.     private void initVar() {
  59.         recognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());
  60.         matrix = new Matrix();
  61.         matrix.setRotate(ORIENTATION);
  62.         // 4个角的坐标是没有旋转过的,所以HEIGHT、WIDTH是反的
  63.         matrix.preTranslate(-HEIGHT >> 1, -WIDTH >> 1);
  64.     }
  65.     private void initView() {
  66.         preview = findViewById(R.id.camera_preview);
  67.         ocrArea = findViewById(R.id.ocr_area);
  68.         ocrSwitch = findViewById(R.id.ocr_switch);
  69.         ocrSwitch.setOnClickListener(view -> {
  70.             stopRecognizer = !stopRecognizer;
  71.             ocrSwitch.setText(stopRecognizer ? "start" : "stop");
  72.             if (camera == null) {
  73.                 return;
  74.             }
  75.             if (stopRecognizer) {
  76.                 camera.stopPreview();
  77.             } else {
  78.                 camera.startPreview();
  79.             }
  80.         });
  81.         adjustSurface(preview, ocrArea);
  82.     }
  83.     private void adjustSurface(SurfaceView cameraPreview, OCRDrawView ocrArea) {
  84.         FrameLayout.LayoutParams cameraPreviewParams = (FrameLayout.LayoutParams) cameraPreview.getLayoutParams();
  85.         cameraPreviewParams.width = WIDTH;
  86.         cameraPreviewParams.height = HEIGHT;
  87.         ViewGroup.LayoutParams ocrAreaParams = ocrArea.getLayoutParams();
  88.         ocrAreaParams.width = WIDTH;
  89.         ocrAreaParams.height = HEIGHT;
  90.     }
  91.     @Override
  92.     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  93.         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  94.         if (requestCode == REQUEST_CAMERA && grantResults.length > 0) {
  95.             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  96.                 preview.getHolder().addCallback(this);
  97.                 surfaceCreated(preview.getHolder());
  98.                 camera.setPreviewCallback(this);
  99.                 camera.startPreview();
  100.             } else {
  101.                 finish();
  102.             }
  103.         }
  104.     }
  105.     @Override
  106.     public void onPreviewFrame(byte[] data, Camera camera) {
  107.         if (isRecognizering || stopRecognizer) {
  108.             return;
  109.         }
  110.         Bitmap bitmap = convertToBitmap(camera, data);
  111.         isRecognizering = true;
  112.         recognizer.process(bitmap, ORIENTATION).addOnSuccessListener(text -> {
  113.             parseOCRResult(text);
  114.         }).addOnFailureListener(exception -> {
  115.             Toast.makeText(this, "Failure", Toast.LENGTH_SHORT).show();
  116.             isRecognizering = false;
  117.         }).addOnCompleteListener(task -> {
  118.             isRecognizering = false;
  119.         }).addOnCanceledListener(() -> {
  120.             Toast.makeText(this, "Canceled", Toast.LENGTH_SHORT).show();
  121.             isRecognizering = false;
  122.         });
  123.     }
  124.     private void parseOCRResult(Text text) {
  125.         // 所有识别到的内容,下同
  126.         String textContent = text.getText();
  127.         if (textContent == null || textContent.trim().length() == 0) {
  128.             return;
  129.         }
  130.         ocrArea.clear();
  131.         // 块,段落
  132.         for (Text.TextBlock textBlock : text.getTextBlocks()) {
  133.             // 一行文本
  134.             for (Text.Line line : textBlock.getLines()) {
  135.                 drawResult(line);
  136.                 // 元素:单词,对汉字来说,需要"开头 (分隔符)中间(分隔符) 结尾"之类比较强烈的分隔符去分隔
  137.                 for (Text.Element element : line.getElements()) {
  138.                     // symbol:字符,字母,字
  139.                     for (Text.Symbol symbol : element.getSymbols()) {
  140.                         symbol.getText();
  141.                     }
  142.                 }
  143.             }
  144.         }
  145.     }
  146.     private void drawResult(Text.Line line) {
  147.         // line的旋转角度(以度为单位,顺时针为正,范围为[-180, 180])
  148.         float angle = line.getAngle() + ORIENTATION;
  149.         // 检测到的文本的轴对齐边界矩形
  150.         Rect boundingBox = line.getBoundingBox();
  151.         // 从左上角开始顺时针方向的四个角点。不带旋转角度,如果设置过旋转角度camera.setDisplayOrientation,需要进行旋转
  152.         Point[] cornerPoints = line.getCornerPoints();
  153.         // 置信度
  154.         float confidence = line.getConfidence();
  155.         // 获取文本中的主要语言(如果有的话)
  156.         String recognizedLanguage = line.getRecognizedLanguage();
  157.         // 置信度太低的过滤掉
  158.         if (confidence < 0.3f) {
  159.             return;
  160.         }
  161.         for (Point cornerPoint : cornerPoints) {
  162.             float[] floats = {cornerPoint.x, cornerPoint.y};
  163.             matrix.mapPoints(floats);
  164.             cornerPoint.x = (int) floats[0] + (WIDTH >> 1);
  165.             cornerPoint.y = (int) floats[1] + (HEIGHT >> 1);
  166.         }
  167.         ocrArea.add(cornerPoints, line.getText());
  168.         ocrArea.postInvalidate();
  169.     }
  170.     /**
  171.      * Convert camera data into bitmap data.
  172.      */
  173.     private Bitmap convertToBitmap(Camera camera, byte[] data) {
  174.         int width = camera.getParameters().getPreviewSize().width;
  175.         int height = camera.getParameters().getPreviewSize().height;
  176.         YuvImage yuv = new YuvImage(data, ImageFormat.NV21, width, height, null);
  177.         ByteArrayOutputStream stream = new ByteArrayOutputStream();
  178.         yuv.compressToJpeg(new Rect(0, 0, width, height), 100, stream);
  179.         return BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.toByteArray().length);
  180.     }
  181.     @Override
  182.     protected void onResume() {
  183.         super.onResume();
  184.     }
  185.     @Override
  186.     protected void onRestart() {
  187.         super.onRestart();
  188.     }
  189.     @Override
  190.     protected void onDestroy() {
  191.         super.onDestroy();
  192.         if (recognizer != null) {
  193.             recognizer.close();
  194.         }
  195.     }
  196.     @Override
  197.     public void surfaceCreated(@NonNull SurfaceHolder holder) {
  198.         try {
  199.             camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
  200.             parameters = camera.getParameters();
  201.             // 旋转了90度,所以height、width互换
  202.             parameters.setPictureSize(HEIGHT, WIDTH);
  203.             parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
  204.             parameters.setPictureFormat(ImageFormat.NV21);
  205.             camera.setPreviewDisplay(holder);
  206.             camera.setDisplayOrientation(ORIENTATION);
  207.             camera.setParameters(parameters);
  208.         } catch (Exception exception) {
  209.             Log.i(TAG, exception.getMessage());
  210.         }
  211.     }
  212.     @Override
  213.     public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
  214.         if (camera != null) {
  215.             camera.stopPreview();
  216.             camera.setPreviewCallback(null);
  217.             camera.startPreview();
  218.             camera.setPreviewCallback(this);
  219.             ocrArea.clear();
  220.             stopRecognizer = true;
  221.             ocrSwitch.performClick();
  222.         }
  223.     }
  224.     @Override
  225.     public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
  226.         if (camera != null) {
  227.             camera.stopPreview();
  228.             camera.setPreviewCallback(null);
  229.             camera.release();
  230.         }
  231.     }
  232. }
复制代码
参考文章


  • 笔墨辨认 v2

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表