【Android -- 开源库】ML Kit 实现数字墨水识别功能

这篇具有很好参考价值的文章主要介绍了【Android -- 开源库】ML Kit 实现数字墨水识别功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【Android -- 开源库】ML Kit 实现数字墨水识别功能

前言

机器学习套件是一个移动 SDK,将 Google 的设备端机器学习专业知识运用于 Android 和 iOS 应用。使用我们强大而易用的 Vision API 和 Natural Language API 解决应用中的常见挑战,或打造全新的用户体验。所有功能均由 Google 一流的机器学习模型提供支持,可免费使用。

学习指南:https://developers.google.cn/ml-kit/vision/digital-ink-recognition/android?hl=zh-cn
Google demo:https://github.com/googlesamples/mlkit/tree/master/android/digitalink

效果图

书写识别

准备工作

1. 在 app/build.gradle 添加如下依赖

implementation 'com.google.mlkit:digital-ink-recognition:18.0.0'

2. 创建 StrokeManager.java

/**
 * author: Kevin-Dev
 * date: 2023/2/2
 * desc:
 */
public class StrokeManager {
    /** Interface to register to be notified of changes in the recognized content. */
    public interface ContentChangedListener {

        /** This method is called when the recognized content changes. */
        void onContentChanged();
    }

    /** Interface to register to be notified of changes in the status. */
    public interface StatusChangedListener {

        /** This method is called when the recognized content changes. */
        void onStatusChanged();
    }

    /** Interface to register to be notified of changes in the downloaded model state. */
    public interface DownloadedModelsChangedListener {

        /** This method is called when the downloaded models changes. */
        void onDownloadedModelsChanged(Set<String> downloadedLanguageTags);
    }

    @VisibleForTesting
    static final long CONVERSION_TIMEOUT_MS = 1000;
    private static final String TAG = "MLKD.StrokeManager";
    // This is a constant that is used as a message identifier to trigger the timeout.
    private static final int TIMEOUT_TRIGGER = 1;
    // For handling recognition and model downloading.
    private RecognitionTask recognitionTask = null;
    @VisibleForTesting ModelManager modelManager = new ModelManager();
    // Managing the recognition queue.
    private final List<RecognitionTask.RecognizedInk> content = new ArrayList<>();
    // Managing ink currently drawn.
    private Ink.Stroke.Builder strokeBuilder = Ink.Stroke.builder();
    private Ink.Builder inkBuilder = Ink.builder();
    private boolean stateChangedSinceLastRequest = false;
    @Nullable
    private ContentChangedListener contentChangedListener = null;
    @Nullable private StatusChangedListener statusChangedListener = null;
    @Nullable private DownloadedModelsChangedListener downloadedModelsChangedListener = null;

    private boolean triggerRecognitionAfterInput = true;
    private boolean clearCurrentInkAfterRecognition = true;
    private String status = "";

    public void setTriggerRecognitionAfterInput(boolean shouldTrigger) {
        triggerRecognitionAfterInput = shouldTrigger;
    }

    public void setClearCurrentInkAfterRecognition(boolean shouldClear) {
        clearCurrentInkAfterRecognition = shouldClear;
    }

    // Handler to handle the UI Timeout.
    // This handler is only used to trigger the UI timeout. Each time a UI interaction happens,
    // the timer is reset by clearing the queue on this handler and sending a new delayed message (in
    // addNewTouchEvent).
    private final Handler uiHandler =
            new Handler(
                    msg -> {
                        if (msg.what == TIMEOUT_TRIGGER) {
                            Log.i(TAG, "Handling timeout trigger.");
                            commitResult();
                            return true;
                        }
                        // In the current use this statement is never reached because we only ever send
                        // TIMEOUT_TRIGGER messages to this handler.
                        // This line is necessary because otherwise Java's static analysis doesn't allow for
                        // compiling. Returning false indicates that a message wasn't handled.
                        return false;
                    });

    private void setStatus(String newStatus) {
        status = newStatus;
        if (statusChangedListener != null) {
            statusChangedListener.onStatusChanged();
        }
    }

    private void commitResult() {
        if (recognitionTask.done() && recognitionTask.result() != null) {
            content.add(recognitionTask.result());
            setStatus("Successful recognition: " + recognitionTask.result().text);
            if (clearCurrentInkAfterRecognition) {
                resetCurrentInk();
            }
            if (contentChangedListener != null) {
                contentChangedListener.onContentChanged();
            }
            reset();
        }
    }

    public void reset() {
        Log.i(TAG, "reset");
        resetCurrentInk();
        content.clear();
        if (recognitionTask != null && !recognitionTask.done()) {
            recognitionTask.cancel();
        }
        setStatus("");
    }

    private void resetCurrentInk() {
        inkBuilder = Ink.builder();
        strokeBuilder = Ink.Stroke.builder();
        stateChangedSinceLastRequest = false;
    }

    public Ink getCurrentInk() {
        return inkBuilder.build();
    }

    /**
     * This method is called when a new touch event happens on the drawing client and notifies the
     * StrokeManager of new content being added.
     *
     * <p>This method takes care of triggering the UI timeout and scheduling recognitions on the
     * background thread.
     *
     * @return whether the touch event was handled.
     */
    public boolean addNewTouchEvent(MotionEvent event) {
        int action = event.getActionMasked();
        float x = event.getX();
        float y = event.getY();
        long t = System.currentTimeMillis();

        // A new event happened -> clear all pending timeout messages.
        uiHandler.removeMessages(TIMEOUT_TRIGGER);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                strokeBuilder.addPoint(Ink.Point.create(x, y, t));
                break;
            case MotionEvent.ACTION_UP:
                strokeBuilder.addPoint(Ink.Point.create(x, y, t));
                inkBuilder.addStroke(strokeBuilder.build());
                strokeBuilder = Ink.Stroke.builder();
                stateChangedSinceLastRequest = true;
                recognize();
       /* if (triggerRecognitionAfterInput) {
          recognize();
        }*/
                break;
            default:
                // Indicate touch event wasn't handled.
                return false;
        }

        return true;
    }

    // Listeners to update the drawing and status.
    public void setContentChangedListener(ContentChangedListener contentChangedListener) {
        this.contentChangedListener = contentChangedListener;
    }

    public void setStatusChangedListener(StatusChangedListener statusChangedListener) {
        this.statusChangedListener = statusChangedListener;
    }

    public void setDownloadedModelsChangedListener(
            DownloadedModelsChangedListener downloadedModelsChangedListener) {
        this.downloadedModelsChangedListener = downloadedModelsChangedListener;
    }

    public List<RecognitionTask.RecognizedInk> getContent() {
        return content;
    }

    public String getStatus() {
        return status;
    }

    // Model downloading / deleting / setting.

    public void setActiveModel(String languageTag) {
        setStatus(modelManager.setModel(languageTag));
    }

    public Task<Void> deleteActiveModel() {
        return modelManager
                .deleteActiveModel()
                .addOnSuccessListener(unused -> refreshDownloadedModelsStatus())
                .onSuccessTask(
                        status -> {
                            setStatus(status);
                            return Tasks.forResult(null);
                        });
    }

    public Task<Void> download() {
        setStatus("Download started.");
        return modelManager
                .download()
                .addOnSuccessListener(unused -> refreshDownloadedModelsStatus())
                .onSuccessTask(
                        status -> {
                            setStatus(status);
                            return Tasks.forResult(null);
                        });
    }

    // Recognition-related.

    public Task<String> recognize() {

        if (!stateChangedSinceLastRequest || inkBuilder.isEmpty()) {
            setStatus("No recognition, ink unchanged or empty");
            return Tasks.forResult(null);
        }
        if (modelManager.getRecognizer() == null) {
            setStatus("Recognizer not set");
            return Tasks.forResult(null);
        }

        return modelManager
                .checkIsModelDownloaded()
                .onSuccessTask(
                        result -> {
                            if (!result) {
                                setStatus("Model not downloaded yet");
                                return Tasks.forResult(null);
                            }

                            stateChangedSinceLastRequest = false;
                            recognitionTask =
                                    new RecognitionTask(modelManager.getRecognizer(), inkBuilder.build());
                            uiHandler.sendMessageDelayed(
                                    uiHandler.obtainMessage(TIMEOUT_TRIGGER), CONVERSION_TIMEOUT_MS);
                            return recognitionTask.run();
                        });
    }

    public void refreshDownloadedModelsStatus() {
        modelManager
                .getDownloadedModelLanguages()
                .addOnSuccessListener(
                        downloadedLanguageTags -> {
                            if (downloadedModelsChangedListener != null) {
                                downloadedModelsChangedListener.onDownloadedModelsChanged(downloadedLanguageTags);
                            }
                        });
    }
}

3. 创建 ModelManager.java

/**
 * author: Kevin-Dev
 * date: 2023/2/2
 * desc:
 */
public class ModelManager {
    private static final String TAG = "MLKD.ModelManager";
    private DigitalInkRecognitionModel model;
    private DigitalInkRecognizer recognizer;
    final RemoteModelManager remoteModelManager = RemoteModelManager.getInstance();

    public String setModel(String languageTag) {
        // Clear the old model and recognizer.
        model = null;
        if (recognizer != null) {
            recognizer.close();
        }
        recognizer = null;

        // Try to parse the languageTag and get a model from it.
        DigitalInkRecognitionModelIdentifier modelIdentifier;
        try {
            modelIdentifier = DigitalInkRecognitionModelIdentifier.fromLanguageTag(languageTag);
        } catch (MlKitException e) {
            Log.e(TAG, "Failed to parse language '" + languageTag + "'");
            return "";
        }
        if (modelIdentifier == null) {
            return "No model for language: " + languageTag;
        }

        // Initialize the model and recognizer.
        model = DigitalInkRecognitionModel.builder(modelIdentifier).build();
        recognizer =
                DigitalInkRecognition.getClient(DigitalInkRecognizerOptions.builder(model).build());
        Log.i(
                TAG,
                "Model set for language '"
                        + languageTag
                        + "' ('"
                        + modelIdentifier.getLanguageTag()
                        + "').");
        return "Model set for language: " + languageTag;
    }

    public DigitalInkRecognizer getRecognizer() {
        return recognizer;
    }

    public Task<Boolean> checkIsModelDownloaded() {
        return remoteModelManager.isModelDownloaded(model);
    }

    public Task<String> deleteActiveModel() {
        if (model == null) {
            Log.i(TAG, "Model not set");
            return Tasks.forResult("Model not set");
        }
        return checkIsModelDownloaded()
                .onSuccessTask(
                        result -> {
                            if (!result) {
                                return Tasks.forResult("Model not downloaded yet");
                            }
                            return remoteModelManager
                                    .deleteDownloadedModel(model)
                                    .onSuccessTask(
                                            aVoid -> {
                                                Log.i(TAG, "Model successfully deleted");
                                                return Tasks.forResult("Model successfully deleted");
                                            });
                        })
                .addOnFailureListener(e -> Log.e(TAG, "Error while model deletion: " + e));
    }

    public Task<Set<String>> getDownloadedModelLanguages() {
        return remoteModelManager
                .getDownloadedModels(DigitalInkRecognitionModel.class)
                .onSuccessTask(
                        (remoteModels) -> {
                            Set<String> result = new HashSet<>();
                            for (DigitalInkRecognitionModel model : remoteModels) {
                                result.add(model.getModelIdentifier().getLanguageTag());
                            }
                            Log.i(TAG, "Downloaded models for languages:" + result);
                            return Tasks.forResult(result);
                        });
    }

    public Task<String> download() {
        if (model == null) {
            return Tasks.forResult("Model not selected.");
        }
        return remoteModelManager
                .download(model, new DownloadConditions.Builder().build())
                .onSuccessTask(
                        aVoid -> {
                            Log.i(TAG, "Model download succeeded.");
                            return Tasks.forResult("Downloaded model successfully");
                        })
                .addOnFailureListener(e -> Log.e(TAG, "Error while downloading the model: " + e));
    }
}
  1. 创建 RecognitionTask.java
/**
 * author: Kevin-Dev
 * date: 2023/2/2
 * desc:
 */
public class RecognitionTask {
    private static final String TAG = "MLKD.RecognitionTask";
    private final DigitalInkRecognizer recognizer;
    private final Ink ink;
    @Nullable
    private RecognizedInk currentResult;
    private final AtomicBoolean cancelled;
    private final AtomicBoolean done;

    public RecognitionTask(DigitalInkRecognizer recognizer, Ink ink) {
        this.recognizer = recognizer;
        this.ink = ink;
        this.currentResult = null;
        cancelled = new AtomicBoolean(false);
        done = new AtomicBoolean(false);
    }

    public void cancel() {
        cancelled.set(true);
    }

    public boolean done() {
        return done.get();
    }

    @Nullable
    public RecognizedInk result() {
        return this.currentResult;
    }

    /** Helper class that stores an ink along with the corresponding recognized text. */
    public static class RecognizedInk {
        public final Ink ink;
        public final String text;

        RecognizedInk(Ink ink, String text) {
            this.ink = ink;
            this.text = text;
        }
    }

    public Task<String> run() {
        Log.i(TAG, "RecoTask.run");
        return recognizer
                .recognize(this.ink)
                .onSuccessTask(
                        result -> {
                            if (cancelled.get() || result.getCandidates().isEmpty()) {
                                return Tasks.forResult(null);
                            }
                            currentResult = new RecognizedInk(ink, result.getCandidates().get(0).getText());
                            Log.i(TAG, "result: " + currentResult.text);
                            done.set(true);
                            return Tasks.forResult(currentResult.text);
                        });
    }
}

自定义 View

1. DrawingView.java

public class DrawingView extends View implements StrokeManager.ContentChangedListener {
  private static final String TAG = "MLKD.DrawingView";
  private static final int STROKE_WIDTH_DP = 3;
  private static final int MIN_BB_WIDTH = 10;
  private static final int MIN_BB_HEIGHT = 10;
  private static final int MAX_BB_WIDTH = 256;
  private static final int MAX_BB_HEIGHT = 256;

  private final Paint recognizedStrokePaint;
  private final TextPaint textPaint;
  private final Paint currentStrokePaint;
  private final Paint canvasPaint;

  private final Path currentStroke;
  private Canvas drawCanvas;
  private Bitmap canvasBitmap;
  private StrokeManager strokeManager;

  public DrawingView(Context context) {
    this(context, null);
  }

  public DrawingView(Context context, AttributeSet attributeSet) {
    super(context, attributeSet);
    currentStrokePaint = new Paint();
    currentStrokePaint.setColor(Color.BLACK);
    currentStrokePaint.setAntiAlias(true);
    // Set stroke width based on display density.
    currentStrokePaint.setStrokeWidth(
            TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, STROKE_WIDTH_DP, getResources().getDisplayMetrics()));
    currentStrokePaint.setStyle(Paint.Style.STROKE);
    currentStrokePaint.setStrokeJoin(Paint.Join.ROUND);
    currentStrokePaint.setStrokeCap(Paint.Cap.ROUND);

    recognizedStrokePaint = new Paint(currentStrokePaint);
    recognizedStrokePaint.setColor(Color.BLACK);

    textPaint = new TextPaint();
    textPaint.setColor(Color.GREEN);

    currentStroke = new Path();
    canvasPaint = new Paint(Paint.DITHER_FLAG);
  }

  private static Rect computeBoundingBox(Ink ink) {
    float top = Float.MAX_VALUE;
    float left = Float.MAX_VALUE;
    float bottom = Float.MIN_VALUE;
    float right = Float.MIN_VALUE;
    for (Ink.Stroke s : ink.getStrokes()) {
      for (Ink.Point p : s.getPoints()) {
        top = Math.min(top, p.getY());
        left = Math.min(left, p.getX());
        bottom = Math.max(bottom, p.getY());
        right = Math.max(right, p.getX());
      }
    }
    float centerX = (left + right) / 2;
    float centerY = (top + bottom) / 2;
    Rect bb = new Rect((int) left, (int) top, (int) right, (int) bottom);
    // Enforce a minimum size of the bounding box such that recognitions for small inks are readable
    bb.union(
            (int) (centerX - MIN_BB_WIDTH / 2),
            (int) (centerY - MIN_BB_HEIGHT / 2),
            (int) (centerX + MIN_BB_WIDTH / 2),
            (int) (centerY + MIN_BB_HEIGHT / 2));
    // Enforce a maximum size of the bounding box, to ensure Emoji characters get displayed
    // correctly
    /*if (bb.width() > MAX_BB_WIDTH) {
      bb.set(bb.centerX() - MAX_BB_WIDTH / 2, bb.top, bb.centerX() + MAX_BB_WIDTH / 2, bb.bottom);
    }
    if (bb.height() > MAX_BB_HEIGHT) {
      bb.set(bb.left, bb.centerY() - MAX_BB_HEIGHT / 2, bb.right, bb.centerY() + MAX_BB_HEIGHT / 2);
    }*/
    return bb;
  }

  void setStrokeManager(StrokeManager strokeManager) {
    this.strokeManager = strokeManager;
  }

  @Override
  protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
    Log.i(TAG, "onSizeChanged");
    canvasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    drawCanvas = new Canvas(canvasBitmap);
    invalidate();
  }

  public void redrawContent() {
    clear();
    Ink currentInk = strokeManager.getCurrentInk();
    drawInk(currentInk, currentStrokePaint);

    List<RecognitionTask.RecognizedInk> content = strokeManager.getContent();
    for (RecognitionTask.RecognizedInk ri : content) {
      drawInk(ri.ink, recognizedStrokePaint);
      final Rect bb = computeBoundingBox(ri.ink);
      drawTextIntoBoundingBox(ri.text, bb, textPaint);
    }
    invalidate();
  }

  private void drawTextIntoBoundingBox(String text, Rect bb, TextPaint textPaint) {
    final float arbitraryFixedSize = 20.f;
    // Set an arbitrary text size to learn how high the text will be.
    textPaint.setTextSize(arbitraryFixedSize);
    textPaint.setTextScaleX(1.f);

    // Now determine the size of the rendered text with these settings.
    Rect r = new Rect();
    textPaint.getTextBounds(text, 0, text.length(), r);

    // Adjust height such that target height is met.
    float textSize = arbitraryFixedSize * (float) bb.height() / (float) r.height();
    textPaint.setTextSize(textSize);

    // Redetermine the size of the rendered text with the new settings.
    textPaint.getTextBounds(text, 0, text.length(), r);

    // Adjust scaleX to squeeze the text.
    textPaint.setTextScaleX((float) bb.width() / (float) r.width());

    // And finally draw the text.
    drawCanvas.drawText(text, bb.left, bb.bottom, textPaint);
  }

  private void drawInk(Ink ink, Paint paint) {
    for (Ink.Stroke s : ink.getStrokes()) {
      drawStroke(s, paint);
    }
  }

  private void drawStroke(Ink.Stroke s, Paint paint) {
    Log.i(TAG, "drawstroke");
    Path path = null;
    for (Ink.Point p : s.getPoints()) {
      if (path == null) {
        path = new Path();
        path.moveTo(p.getX(), p.getY());
      } else {
        path.lineTo(p.getX(), p.getY());
      }
    }
    drawCanvas.drawPath(path, paint);
  }

  public void clear() {
    currentStroke.reset();
    onSizeChanged(
            canvasBitmap.getWidth(),
            canvasBitmap.getHeight(),
            canvasBitmap.getWidth(),
            canvasBitmap.getHeight());
  }

  @Override
  protected void onDraw(Canvas canvas) {
    canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
    canvas.drawPath(currentStroke, currentStrokePaint);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    int action = event.getActionMasked();
    float x = event.getX();
    float y = event.getY();

    switch (action) {
      case MotionEvent.ACTION_DOWN:
        currentStroke.moveTo(x, y);
        break;
      case MotionEvent.ACTION_MOVE:
        currentStroke.lineTo(x, y);
        break;
      case MotionEvent.ACTION_UP:
        currentStroke.lineTo(x, y);
        drawCanvas.drawPath(currentStroke, currentStrokePaint);
        currentStroke.reset();
        break;
      default:
        break;
    }
    strokeManager.addNewTouchEvent(event);
    invalidate();
    return true;
  }

  @Override
  public void onContentChanged() {
    redrawContent();
  }
}

2. StatusTextView.java

public class StatusTextView extends TextView implements StrokeManager.StatusChangedListener {

  private StrokeManager strokeManager;

  public StatusTextView(@NonNull Context context) {
    super(context);
  }

  public StatusTextView(Context context, AttributeSet attributeSet) {
    super(context, attributeSet);
  }

  @Override
  public void onStatusChanged() {
    this.setText(this.strokeManager.getStatus());
  }

  void setStrokeManager(StrokeManager strokeManager) {
    this.strokeManager = strokeManager;
  }
}

使用

1. 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.kjd.gesturedemo.ai.DrawingView
        android:id="@+id/drawing_view"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_weight="1"
        android:background="#80FFFFFF" />
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <com.kjd.gesturedemo.ai.StatusTextView
                android:id="@+id/status_text_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Status text..."
                android:textIsSelectable="true" />
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:orientation="horizontal">

                <Button
                    android:id="@+id/clear_button"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:onClick="clearClick"
                    android:text="清除" />
            </LinearLayout>
        </LinearLayout>
    </FrameLayout>

</LinearLayout>

2. RecognitionActivity.java文章来源地址https://www.toymoban.com/news/detail-490347.html

public class RecognitionActivity extends AppCompatActivity implements StrokeManager.DownloadedModelsChangedListener {
    private static final String TAG = "MLKDI.Activity";
    
    @VisibleForTesting
    final StrokeManager strokeManager = new StrokeManager();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recognition);
        
        DrawingView drawingView = findViewById(R.id.drawing_view);
        StatusTextView statusTextView = findViewById(R.id.status_text_view);
        drawingView.setStrokeManager(strokeManager);
        statusTextView.setStrokeManager(strokeManager);

        strokeManager.setStatusChangedListener(statusTextView);
        strokeManager.setContentChangedListener(drawingView);
        strokeManager.setActiveModel("zh-Hani-CN");
        strokeManager.setDownloadedModelsChangedListener(this);
        strokeManager.setClearCurrentInkAfterRecognition(true);
        strokeManager.setTriggerRecognitionAfterInput(false);

        strokeManager.download();

        strokeManager.recognize();
        
        strokeManager.refreshDownloadedModelsStatus();
        
        strokeManager.reset();
    }

    public void clearClick(View v) {
        strokeManager.reset();
        DrawingView drawingView = findViewById(R.id.drawing_view);
        drawingView.clear();
    }

    @Override
    public void onDownloadedModelsChanged(Set<String> downloadedLanguageTags) {

    }

}

到了这里,关于【Android -- 开源库】ML Kit 实现数字墨水识别功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【iOS】——基于Vision Kit框架实现图片文字识别

    根据苹果的官方文档,Vision可以执行面部检测、文本检测、条形码识别、图像注册和一般功能跟踪。Vision还允许将自定义Core ML模型用于分类或对象检测等任务。下面只是对文本识别的一个学习。 Vision的文本识别分为两种方式。 第一种是 快速路径(fast) ,它使用框架的字符

    2024年01月19日
    浏览(37)
  • Android基于opencv4.6.0实现人脸识别功能

    步骤: 1.整合opencv 2.获取相机的SurfaceView传到native层去检测(亦或是不断的获取SurfaceView的Bitmap,传到native层) 3.检测人脸,在本地保存人脸特征信息 4.上传至后台(不实现) 人脸识别实现的思路(例:人脸登录) 1.人脸信息录入 1.1获取相机的Bitmap,检测人脸(保证人脸信息比较精准

    2024年02月03日
    浏览(46)
  • 【深度学习实战—1】:基于Keras的手写数字识别(非常详细、代码开源)

    ✨博客主页:王乐予🎈 ✨年轻人要:Living for the moment(活在当下)!💪 🏆推荐专栏:【图像处理】【千锤百炼Python】【深度学习】【排序算法】    本来想着多更新一些关于深度学习的文章,但这方面知识专业度很高,如果作者本身都掌握不好,又怎么能写出好文章分享

    2024年02月07日
    浏览(41)
  • [开源工具]Win10-87键盘如何使用数字小键盘功能?

    87键盘小巧好用,但是经常有困扰,如何使用数字小键盘功能,win10帮我们解决了这个问题!!! 1.首先点击开始,找到“运行”。(或者win+r) 2.点开“运行”,输入“osk”,点击确认,虚拟小键盘出现如图。 3.点击选项–开启小键盘功能 5.直接使用数字小键盘即可

    2024年02月11日
    浏览(51)
  • Umi-OCR:开源、免费、离线、多功能的 OCR 图片文字识别软件

    官方版本说明 不同版本仅OCR引擎插件不同,其它功能完全一致。 均支持 win7 x64 及以上的系统,附带多国语言识别库。 .7z.exe 为自解压包,可以用压缩软件打开,也可以在没有安装压缩软件的电脑上直接双击解压。 Paddle 引擎插件版 (性能好,速度快,占用率高,适合高配机

    2024年01月19日
    浏览(77)
  • 华为运动健康服务Health Kit 6.10.0版本新增功能速览!

    华为运动健康服务(HUAWEI Health Kit)6.10.0 版本新增的能力有哪些? 阅读本文寻找答案,一起加入运动健康服务生态大家庭! 一、支持三方应用查询用户测量的连续血糖数据 符合申请Health Kit服务中开发者申请资质要求的企业开发者,可申请访问用户的心率、压力、血糖等健康

    2023年04月11日
    浏览(64)
  • 吴恩达ML2022-用于手写数字识别的神经网络

    导入在这个分配过程中需要的所有包。 Numpy 是使用 Python 进行科学计算的基本软件包。 Matplotlib 是在 Python 中绘制图形的流行库。 tensorflow是一种流行的机器学习平台。 Tensorflow 是由 Google 开发的一个机器学习软件包。2019年,谷歌将 keras 整合到 Tensorflow,并发布了 Tensorflow 2.

    2024年02月17日
    浏览(31)
  • uniapp - 安卓|苹果App软件实现调用百度人脸识别接口服务及人脸活体认证功能,uniapp苹果ios、安卓Android手机app平台,人脸认证、活体检测、身份证与人脸验证(示例代码,一键复制

    在uniapp手机App开发中(安卓Android|苹果ios系统),利用百度人脸识别api接口对接uniapp APP进行人脸识别、人脸检测、活体验证、人脸对比、人脸搜索、身份证信息是否与人脸匹配,支持离线SDK集成、离线无网络正常使用功能、自定义人脸识别框附近的页面样式和大小等。 提供详

    2024年04月11日
    浏览(61)
  • 给科研人的 ML 开源发布工具包

    什么是开源发布工具包? 恭喜你的论文成功发表,这是一个巨大的成就!你的研究成果将为学界做出贡献。 其实除了发表论文之外,你还可以通过发布研究的其他部分,如代码、数据集、模型等,来增加研究的可见度和采用率。这将使更多人能够使用你的研究,并推动研究

    2024年01月18日
    浏览(42)
  • 物联网小项目——墨水屏时钟(STM32+ESP8266实现)

    无意间在网上看到开源的使用墨水屏打造的桌面时钟,当个桌面小摆件可谓是十分优雅,于是就萌生出了自己DIY一个的想法。这个墨水屏时钟具有以下特点 时间日期的显示和自动校准 自动获取实时天气 半夜自动进入休眠 支持微信智能配网 目前已经实现软件功能,但是硬件

    2024年02月09日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包