問題と対策

AndroidのMLKitでCode39バーコードをスキャンすると、誤読が発生しやすい。
N回連続で同じ値を読み取れた場合に初めて採用することで、誤読を減らすことを考える。

実装

ImageAnalysis.Analyzerを実装したクラスに、連続検出のカウントロジックを追加する。

private class BarcodeAnalyzer(onBarcodeDetected: (String) -> Unit) : ImageAnalysis.Analyzer {
    private val NUMBER_OF_DETECTION = 3 // 規定の検出回数
    private var count = 0 // 連続して読み取った回数
    private var previousValue = "" // 直前に読み取った値

    override fun analyze(imageProxy: ImageProxy) {
        ...

        scanner.process(image).addOnSuccessListener { barcodes ->
            val value = barcodes.firstOrNull()?.rawValue ?: return@addOnSuccessListener

            if (value != previousValue) {
                previousValue = value
                count = 0
            }
            count++

            // N回連続で同じ値を読み取れた場合に採用する
            if (count >= NUMBER_OF_DETECTION) {
                onBarcodeDetected(value)
                count = 0
            }
        }
        .addOnCompleteListener {
            imageProxy.close()
        }
    }
}

前回と同じ値を読み取ったらカウントを増やし、規定の回数に達した場合のみコールバックを呼ぶようにする。

規定の値を大きくするほど誤読は減るが、読み取りに時間がかかるようになる。
精度と性能のトレードオフとなる。

使い方

ImageAnalysisのインスタンスにsetAnalyzerで上記のクラスを設定する。

val imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(...)
    .build()
imageAnalysis.setAnalyzer(executor, BarcodeAnalyzer(
    onBarcodeDetected = { value ->
        /* バーコードに対する処理 */
    }
))

1文字のバーコードについて

この方法でも、読み取り値が1文字の場合は精度が改善しにくい。1文字のバーコードはエンコードされる情報量が極端に少なく、誤認識に対する冗長性の低さが原因と考えられる。

ただし、Code39で1文字のバーコードは実用上ほぼ存在しない。
例えば、誤読による1文字の値が問題になる場合は、文字数でフィルタリングするとよい。

if (value.length <= 1) { return@addOnSuccessListener } // 1文字以下は無視