ML Kit を使用してバーコードをスキャンする(Android)

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

ML Kit を使用すると、バーコードを認識してデコードできます。

バーコード スキャンを統合するには 2 つの方法があります。1 つの方法として、モデルをアプリの一部としてバンドルする方法と、Google Play 開発者サービスに依存するバンドルされていないモデルを使用する方法です。バンドルされていないモデルを選択すると、アプリのサイズが小さくなります。詳しくは、以下の表をご覧ください。

特徴バンドルされていません併用型
実装モデルは Google Play 開発者サービスを介して動的にダウンロードされます。モデルはビルド時にアプリに静的にリンクされます。
アプリのサイズ約 600 KB の増加。約 3.2 MB のサイズ増加。
初期化時間モデルをダウンロードする前に、最初の使用を待たなければならない場合があります。モデルはすぐに利用できます。

始める前に

  1. プロジェクト レベルの build.gradle ファイルの buildscript セクションと allprojects セクションの両方に Google の Maven リポジトリを組み込みます。

  2. ML Kit Android ライブラリの依存関係をモジュールのアプリレベルの Gradle ファイル(通常は app/build.gradle)に追加します。必要に応じて、次のいずれかの依存関係を選択します。

    モデルをアプリにバンドルする場合:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:barcode-scanning:17.0.2'
    }
    

    Google Play 開発者サービスでモデルを使用するには:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0'
    }
    
  3. Google Play 開発者サービスでモデルを使用する場合、アプリを Play ストアからインストールした後、自動的にデバイスにモデルをダウンロードするようにアプリを構成できます。これを行うには、アプリの AndroidManifest.xml ファイルに次の宣言を追加します。

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="barcode" >
          <!-- To use multiple models: android:value="barcode,model2,model3" -->
    </application>
    

    モデルの可用性を明示的にチェックし、Google Play 開発者サービスの ModuleInstallClient API を使用してダウンロードをリクエストすることもできます。

    インストール時モデルのダウンロードを有効にしない場合、または明示的なダウンロードをリクエストしていない場合、モデルはスキャナの初回実行時にダウンロードされます。ダウンロードが完了する前にリクエストしても、結果はありません。

入力画像に関するガイドライン

  • ML Kit でバーコードを正確に読み取るには、入力画像に、十分なピクセルデータによって表されるバーコードが含まれている必要があります。

    多くのバーコードは可変サイズのペイロードをサポートしているため、特定のピクセルデータの要件は、バーコードの種類とバーコード内でエンコードされるデータの量によって異なります。一般に、バーコードの最小の単位は 2 ピクセル以上で、2 次元コードの場合は高さが 2 ピクセルである必要があります。

    たとえば、EAN-13 バーコードは幅 1、2、3、または 4 ユニットのバーとスペースで構成されているため、EAN-13 バーコードの画像は少なくとも幅 2、4、6、8 ピクセルのバーとスペースを含むことが理想的です。EAN-13 バーコードの幅は合計 95 単位であるため、バーコードの幅は 190 ピクセル以上にする必要があります。

    PDF417 などの高密度形式は、ML Kit が正確に読み取れるようにするために、より大きなピクセルサイズを必要とします。たとえば、PDF417 コードには、1 行に最大 34 個の 17 ユニットの「単語」を含めることができます。これは、幅が 1,156 ピクセル以上であることが理想的です。

  • 画像がぼやけていると、スキャンの精度に影響する可能性があります。アプリが想定どおりの結果が得られない場合は、ユーザーに画像をキャプチャし直すよう求めます。

  • 一般的なアプリケーションでは、1280x720 や 1920x1080 などの高解像度の画像を使用して、カメラから離れた場所からバーコードをスキャンすることをおすすめします。

    ただし、レイテンシが重要なアプリケーションでは、低解像度で画像をキャプチャすることでパフォーマンスを改善できますが、バーコードで入力画像の大部分を構成する必要があります。リアルタイムのパフォーマンスを改善するためのヒントもご覧ください。

1. バーコード スキャナの設定

読み取る予定のバーコード形式がわかっている場合は、それらの形式のみを検出するように構成することで、バーコード検出器の速度を向上させることができます。

たとえば、Aztec コードと QR コードのみを検出するには、次の例のように BarcodeScannerOptions オブジェクトをビルドします。

Kotlin

val options = BarcodeScannerOptions.Builder()
        .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE,
                Barcode.FORMAT_AZTEC)
        .build()

Java

BarcodeScannerOptions options =
        new BarcodeScannerOptions.Builder()
        .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE,
                Barcode.FORMAT_AZTEC)
        .build();

次の形式がサポートされています。

  • コード 128(FORMAT_CODE_128
  • コード 39(FORMAT_CODE_39
  • コード 93(FORMAT_CODE_93
  • コダバー(FORMAT_CODABAR
  • EAN-13(FORMAT_EAN_13
  • EAN-8(FORMAT_EAN_8
  • ITF(FORMAT_ITF
  • UPC-A(FORMAT_UPC_A
  • UPC-E(FORMAT_UPC_E
  • QR コード(FORMAT_QR_CODE
  • PDF417(FORMAT_PDF417
  • アズテック(FORMAT_AZTEC
  • データ マトリックス(FORMAT_DATA_MATRIX

2. 入力画像を準備する

画像内のバーコードを認識するには、Bitmapmedia.ImageByteBuffer、バイト配列、またはデバイス上のファイルから InputImage オブジェクトを作成します。次に、InputImage オブジェクトを BarcodeScannerprocess メソッドに渡します。

InputImage オブジェクトは、さまざまなソースから作成できます。それぞれについて、以下で説明します。

media.Image の使用

デバイスのカメラから画像をキャプチャする場合など、media.Image オブジェクトから InputImage オブジェクトを作成するには、media.Image オブジェクトと画像の回転を InputImage.fromMediaImage() に渡します。

CameraX ライブラリを使用する場合は、OnImageCapturedListener クラスと ImageAnalysis.Analyzer クラスによって回転値が計算されます。

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

画像の回転角度を取得するカメラ ライブラリを使用しない場合は、デバイスの回転角度とデバイス内のカメラセンサーの向きから計算できます。

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Throws(CameraAccessException::class)
private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    val deviceRotation = activity.windowManager.defaultDisplay.rotation
    var rotationCompensation = ORIENTATIONS.get(deviceRotation)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing)
        throws CameraAccessException {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int rotationCompensation = ORIENTATIONS.get(deviceRotation);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

次に、media.Image オブジェクトと回転角度値を InputImage.fromMediaImage() に渡します。

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

ファイルの URI を使用する

InputImage オブジェクトをファイルの URI から作成するには、アプリ コンテキストとファイルの URI を InputImage.fromFilePath() に渡します。これは、ACTION_GET_CONTENT インテントを使用して、ギャラリー アプリから画像を選択するようにユーザーに促すときに便利です。

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

ByteBuffer または ByteArray の使用

ByteBuffer または ByteArray から InputImage オブジェクトを作成するには、まず media.Image 入力について上記のように、画像の回転角度を計算します。 次に、画像の高さ、幅、カラー エンコード形式、回転角度とともに、バッファまたは配列を含む InputImage オブジェクトを作成します。

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Bitmap の使用

Bitmap オブジェクトから InputImage オブジェクトを作成するには、次の宣言を行います。

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

画像は Bitmap オブジェクトと回転角度で表されます。

3. BarcodeScanner のインスタンスを取得する

Kotlin

val scanner = BarcodeScanning.getClient()
// Or, to specify the formats to recognize:
// val scanner = BarcodeScanning.getClient(options)

Java

BarcodeScanner scanner = BarcodeScanning.getClient();
// Or, to specify the formats to recognize:
// BarcodeScanner scanner = BarcodeScanning.getClient(options);

4. 画像を処理する

画像を process メソッドに渡します。

Kotlin

val result = scanner.process(image)
        .addOnSuccessListener { barcodes ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener {
            // Task failed with an exception
            // ...
        }

Java

Task<List<Barcode>> result = scanner.process(image)
        .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() {
            @Override
            public void onSuccess(List<Barcode> barcodes) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

5. バーコードの情報を確認する

バーコード認識オペレーションが成功すると、Barcode オブジェクトのリストが成功リスナーに渡されます。各 Barcode オブジェクトは画像内で検出されたバーコードを表します。バーコードごとに、入力画像の境界座標と、バーコードによってエンコードされた元データを取得できます。また、バーコード スキャナがエンコードされたデータの種類をバーコード スキャナが判別できた場合は、解析されたデータを含むオブジェクトを取得できます。

例:

Kotlin

for (barcode in barcodes) {
    val bounds = barcode.boundingBox
    val corners = barcode.cornerPoints

    val rawValue = barcode.rawValue

    val valueType = barcode.valueType
    // See API reference for complete list of supported types
    when (valueType) {
        Barcode.TYPE_WIFI -> {
            val ssid = barcode.wifi!!.ssid
            val password = barcode.wifi!!.password
            val type = barcode.wifi!!.encryptionType
        }
        Barcode.TYPE_URL -> {
            val title = barcode.url!!.title
            val url = barcode.url!!.url
        }
    }
}

Java

for (Barcode barcode: barcodes) {
    Rect bounds = barcode.getBoundingBox();
    Point[] corners = barcode.getCornerPoints();

    String rawValue = barcode.getRawValue();

    int valueType = barcode.getValueType();
    // See API reference for complete list of supported types
    switch (valueType) {
        case Barcode.TYPE_WIFI:
            String ssid = barcode.getWifi().getSsid();
            String password = barcode.getWifi().getPassword();
            int type = barcode.getWifi().getEncryptionType();
            break;
        case Barcode.TYPE_URL:
            String title = barcode.getUrl().getTitle();
            String url = barcode.getUrl().getUrl();
            break;
    }
}

リアルタイムのパフォーマンスを改善するためのヒント

リアルタイムのアプリケーションでバーコードをスキャンする場合は、適切なフレームレートを得るために次のガイドラインに従ってください。

  • カメラのネイティブ解像度で入力をキャプチャしないでください。一部のデバイスでは、ネイティブ解像度で入力をキャプチャすると非常に大きな(10 メガピクセル以上)画像が生成されるため、レイテンシが極めて低く、精度が向上することはありません。代わりに、バーコード検出に必要なサイズ(通常は 2 メガピクセル以下)をカメラにリクエストしてください。

    スキャン速度が重要な場合は、画像キャプチャ解像度をさらに下げることができます。ただし、前述の最小バーコード サイズの要件に注意してください。

    ストリーミング動画フレームのシーケンスからバーコードを認識しようとすると、認識機能によってフレームごとに異なる結果が生成されることがあります。同じ値が連続して表示されるまで待ってから、良い結果が返されることを確認してください。

    ITF と CODE-39 では、チェックサムの数字はサポートされていません。

  • Camera または camera2 API を使用する場合は、検出機能の呼び出しのスロットル調整を行います。検出器の実行中に新しい動画フレームが使用可能になった場合は、そのフレームをドロップします。例については、クイックスタート サンプルアプリの VisionProcessorBase クラスをご覧ください。
  • CameraX API を使用する場合は、バックプレッシャー方式がデフォルト値 ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST に設定されていることを確認します。 これにより、分析のために一度に配信される画像が 1 つのみになります。アナライザがビジー状態になったときに生成された画像は自動的に破棄され、配信のキューに追加されません。ImageProxy.close() を呼び出して分析中の画像を閉じると、次の最新の画像が配信されます。
  • 検出器の出力を使用して入力画像の上にグラフィックスをオーバーレイする場合は、まず ML Kit から検出結果を取得し、画像とオーバーレイを 1 つのステップでレンダリングします。これにより、ディスプレイ サーフェスへのレンダリングは入力フレームごとに 1 回で済みます。例については、クイックスタート サンプルアプリの CameraSourcePreview クラスと GraphicOverlay クラスをご覧ください。
  • Camera2 API を使用する場合は、ImageFormat.YUV_420_888 形式で画像をキャプチャします。古い Camera API を使用する場合は、ImageFormat.NV21 形式で画像をキャプチャします。