問題と対策
AndroidのMLKitでCode39バーコードをスキャンすると、誤読が発生しやすい。
N回連続で同じ値を読み取れた場合に初めて採用することで、誤読を減らすことを考える。
実装
ImageAnalysis.Analyzerを実装したクラスに、連続検出のカウントロジックを追加する。
private class BarcodeAnalyzer(
private val requiredConsecutiveCount: Int,
private val onBarcodeDetected: (String) -> Unit
) : ImageAnalysis.Analyzer {
private val scanner = BarcodeScanning.getClient()
private var pendingValue = ""
private var consecutiveCount = 0 // 直前に読み取った値と同じ値を連続して読み取った回数
private var confirmedValue = ""
@ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: run {
imageProxy.close()
return
}
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
val value = barcodes.firstOrNull()?.rawValue
if (value != null) {
if (value == pendingValue) {
consecutiveCount++
} else {
pendingValue = value
consecutiveCount = 1
}
// N回連続で同じ値を読み取れた場合に採用する
if (consecutiveCount >= requiredConsecutiveCount && value != confirmedValue) {
confirmedValue = value
onBarcodeDetected(value)
}
} else {
pendingValue = ""
consecutiveCount = 0
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
}
ポイントは3つの状態変数である。
pendingValue: 直前に読み取った値consecutiveCount: 同じ値を連続して読み取った回数confirmedValue: 採用済みの値(同じ値を重複してコールバックしないために保持)
バーコードが読み取れなかったフレームでは pendingValue と consecutiveCount をリセットする。読み取れた値が変わった場合も同様にカウントをリセットして、新しい値のカウントを1から始める。
confirmedValue と比較して、同一バーコードを連続スキャンしても重複してコールバックが呼ばれるのを防ぐ。
使い方
ImageAnalysisにアナライザーをセットする際にrequiredConsecutiveCountを指定する。
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.apply {
setAnalyzer(executor, BarcodeAnalyzer(
requiredConsecutiveCount = 3,
onBarcodeDetected = { value -> /* 採用された値を処理 */ }
))
}
requiredConsecutiveCount = 3 の場合、同じ値を3フレーム連続で読み取ったときにコールバックが呼ばれる。値を大きくするほど誤読は減るが、読み取りに時間がかかるようになる。
精度と性能のトレードオフとなる。
1文字のバーコードについて
この方法でも、読み取り値が1文字の場合は精度が改善しにくい。1文字のバーコードはエンコードされる情報量が極端に少なく、誤認識に対する冗長性の低さが原因と考えられる。
ただし、Code39で1文字のバーコードは実用上ほぼ存在しない。
例えば、誤読による1文字の値が問題になる場合は、文字数でフィルタリングするとよい。
if (consecutiveCount >= requiredConsecutiveCount && value != confirmedValue) {
if (value.length <= 1) return // 1文字以下は無視
confirmedValue = value
onBarcodeDetected(value)
}
\確かな知識を身に着けたい、Androidアプリ開発を学びたい人にオススメ!/
