every Tech Blog

株式会社エブリーのTech Blogです。

簡単に高品質なドキュメントスキャナーが実装できる、ML Kit Document Scanner API がリリースされたので使用してみた。

はじめに

トモニテでAndroid開発を担当している岡田です。

先日、ML Kit Document Scanner API のベータ版がリリースされました。
公式ドキュメントやサンプルアプリを参考に、今回はAndroidでの実装方法・内容をご紹介したいと思います。
以下に参考にしたサイトのリンクを示します。是非覗いてみてください。

android-developers.googleblog.com

developers.google.com

github.com

ML Kit とは

Googleの機械学習の機能を、Android/iOSアプリとして提供するモバイルSDKです。
例えば顔検出やバーコードスキャンなどの機能を簡単に実装することができます。

ML Kit Document Scanner API とは

紙の資料をカメラでスキャンして、デジタル資料として読み込むことができる、ドキュメントスキャナーSDKのAPIです。
用意されたスキャナーは自動キャプチャ・切り抜き・自動回転検出機能だけでなく、フィルター機能など編集もできます。
つまり、すごいリッチなスキャナーを簡単に実装できるということです。
スキャナーの使用感については公式のサンプルアプリでご確認ください。

実装の簡単な流れは以下の通りです。

  1. スキャナーのオプションを決める
  2. スキャナーを呼び出す
  3. スキャナーから結果を取得する

主なクラスについて

主に以下の3つのクラスで構成されています。

  • GmsDocumentScannerOptions
  • GmsDocumentScanning
  • GmsDocumentScanningResult

それぞれ名前の通り、スキャナーのオプション、スキャン開始、スキャン結果に関するクラスになっています。
これらについて、実際に確認してみたいと思います。

GmsDocumentScannerOptions (オプション)

GmsDocumentScannerOptions でスキャナーに関してのオプションを設定できます。
公式ドキュメントのコードでは、以下のように紹介されています。

val options = GmsDocumentScannerOptions.Builder()
    .setGalleryImportAllowed(false) // フォトギャラリーからのインポートの可否
    .setPageLimit(2)                // 最大ページ枚
    .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)    // 結果のフォーマット
    .setScannerMode(SCANNER_MODE_FULL)  // スキャナのモード
    .build()

設定できるオプションは以下の通りです。

  • スキャナーのモード
  • 最大ページ枚
  • 結果のフォーマット
  • フォトギャラリーからのインポートの可否

それぞれ見ていきます。

スキャナーのモード

setScannerMode() を用いて、スキャナーの設定ができます。 現段階では3種類のモードが要されています。

public static final int SCANNER_MODE_BASE = 3;
public static final int SCANNER_MODE_BASE_WITH_FILTER = 2;
public static final int SCANNER_MODE_FULL = 1;

SCANNER_MODE_BASE
基本的な編集機能(ページの切り抜き、回転、並べ替えなど)が使用できます。

SCANNER_MODE_BASEのプレビュー画像

SCANNER_MODE_BASE_WITH_FILTER
SCANNER_MODE_BASEモードに画像フィルタ(グレースケール、自動画像補正など)が追加されます。

SCANNER_MODE_BASE_WITH_FILTERのプレビュー画像

SCANNER_MODE_FULL(デフォルト)
SCANNER_MODE_BASE_WITH_FILTERモードの機能に加えて画像クリーニング機能(汚れや指の消去など)が追加されます。

SCANNER_MODE_FULLのプレビュー画像

最大ページ数

setPageLimit() を用いて、最大のページ数を指定できます。int型で指定します。
以下は最大ページ数を2とした場合のスクリーンショットです。最大数に達すると、ページを追加する "+" アイコンが出ないことがわかると思います。

最大ページ数のプレビュー画像

フォトギャラリーからのインポートの可否

フォトギャラリーからのインポートの可否を設定できます。
setGalleryImportAllowed() を用い、Booleanで指定します。

結果のフォーマット

setResultFormats() を用いて、出力結果のフォーマットを指定できます。
現段階では、JPEGかPDF、またはその両方を選択できるようです。

public static final int RESULT_FORMAT_JPEG = 101;
public static final int RESULT_FORMAT_PDF = 102;

GmsDocumentScanning (スキャナーの開始)

スキャナーはGmsDocumentScanningを用いて、以下のように記述できます。
コードは公式のドキュメントとサンプルを参考にしました。

GmsDocumentScanning.getClient(options)  // optionsは先ほど紹介したGmsDocumentScannerOptions
  .getStartScanIntent(activity)
  .addOnSuccessListener { intentSender ->
     scannerLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
   }
  .addOnFailureListener {
    // 失敗した際の処理
  }

メソッドチェーンでわかりにくいので、順を追って説明します。

はじめに、GmsDocumentScanninggetClient()を呼び出します。 getClient()GmsDocumentScannerOptionsを引数にとり、GmsDocumentScannerを返します。

public final class GmsDocumentScanning {
    @androidx.annotation.NonNull
    public static com.google.mlkit.vision.documentscanner.GmsDocumentScanner getClient(@androidx.annotation.NonNull com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions options) { /* compiled code */ }

    private GmsDocumentScanning() { /* compiled code */ }
}

返ってくるGmsDocumentScannerはInterfaceです。getStartScanIntent()というメソッドが用意されています。
こちらはActivityを引数にとり、Task<IntentSender>を返します。

Taskが返されるので、addOnSuccessListeneraddOnFailureListenerが使えます。
成功時にIntentSenderが返ってきます。IntentSenderはスキャナーを起動するために使用します。

public interface GmsDocumentScanner extends com.google.android.gms.common.api.OptionalModuleApi {
    @androidx.annotation.NonNull
    com.google.android.gms.tasks.Task<android.content.IntentSender> getStartScanIntent(@androidx.annotation.NonNull android.app.Activity activity);
}

scannerLauncherは後述する、ActivityResultLauncher<IntentSenderRequest>型の変数です。 こちらは終了したActivityの結果を受け取り、処理します。

GmsDocumentScanningResult (結果処理)

GmsDocumentScanningResultを用いて、結果を処理できます。
公式ドキュメントにて、以下のように記述されています。

val scannerLauncher = registerForActivityResult(StartIntentSenderForResult()) {
  result -> {
    if (result.resultCode == RESULT_OK) {
      val result =
        GmsDocumentScanningResult.fromActivityResultIntent(result.data) // ここで結果を受け取る
      result.getPages()?.let { pages ->
        for (page in pages) {
          val imageUri = pages.get(0).getImageUri()
          // imageUriを用いた処理
        }
      }
      result.getPdf()?.let { pdf ->
        val pdfUri = pdf.getUri()
        val pageCount = pdf.getPageCount()
        // pdfUriやpageCountを用いた処理
      }
    }
  }
}

GmsDocumentScanningResultfromActivityResultIntent()を用いて、結果を受け取ります。 Intentを引数に取り、GmsDocumentScanningResultとして返してくれます。

@androidx.annotation.Nullable
public static com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult fromActivityResultIntent(@androidx.annotation.Nullable android.content.Intent data) { /* compiled code */ }

返されるGmsDocumentScanningResultですが、PagePdfを持っています。

public abstract class GmsDocumentScanningResult implements android.os.Parcelable {   
    ...
    public static abstract class Page implements android.os.Parcelable {
        @androidx.annotation.NonNull
        public abstract android.net.Uri getImageUri();

        public Page() { /* compiled code */ }
    }

    public static abstract class Pdf implements android.os.Parcelable {
        public abstract int getPageCount();

        @androidx.annotation.NonNull
        public abstract android.net.Uri getUri();

        public Pdf() { /* compiled code */ }
    }
}

現時点でpageは画像のUriPdfはページ数とUriを取得できるようです。
それぞれ結果に合わせて、処理を記述できます。

説明については、以上になります。

まとめ

ML Kit Document Scanner API を用いると、簡単に高品質なドキュメントスキャナーが実装できました。
実装の簡単な流れは以下の通りです。

  1. スキャナーのオプションを決める
  2. スキャナーを呼び出す
  3. スキャナーから結果を取得する

余談

未実装のキャプチャーモード

com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions のコードには CaptureMode なるものが存在しました。

public static final int CAPTURE_MODE_AUTO = 1;
public static final int CAPTURE_MODE_MANUAL = 2;

現在はオートのみだが、今後はマニュアルで選択できるような機能が追加されるかもしれない……?
アップデートが楽しみです。今後も追っていきたいと思います!