Skip to content

Commit

Permalink
Merge pull request #28 from freedelity/mrz-v2
Browse files Browse the repository at this point in the history
improve mrz reader
  • Loading branch information
Verbruik authored Aug 9, 2024
2 parents 0c49fad + 3f12eeb commit 8054f35
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 87 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.0.11
- Improve MRZ reader with text and image callback

## 1.0.10
- Kotlin DSL for example app
- Bug fix : Fix Android supported version to 1.8
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# native_barcode_scanner
___# native_barcode_scanner

A fast flutter plugin to scan barcodes and QR codes using the device camera. This plugin also supports text and MRZ recognition from the camera.

Expand Down Expand Up @@ -46,7 +46,7 @@ Add this to your package's `pubspec.yaml` file:

```yaml
dependencies:
native_barcode_scanner: ^1.0.10
native_barcode_scanner: ^1.0.11
```
## Usage
Expand All @@ -61,13 +61,27 @@ Then, create a `BarcodeScannerWidget` in your widget tree where you want to show

```dart
@override
Widget build(BuildContext context) {
return BarcodeScannerWidget(
Widget build(BuildContext context) {
return BarcodeScannerWidget(
onBarcodeDetected: (barcode) {
print('Barcode detected: ${barcode.value} (format: ${barcode.format.name})');
}
);
}
```

Depending on what you want to scan, change the `scannerType` which is default to `ScannerType.barcode` and use the associated callback:

```dart
@override
Widget build(BuildContext context) {
return BarcodeScannerWidget(
scannerType: ScannerType.mrz,
onMrzDetected: (String mrz, Uint8List bytes) {
print('MRZ detected: $mrz');
}
);
}
```

If you need to manipulate the behaviour of the barcode scanning process, you may use the static methods of the `BarcodeScanner` class.
If you need to manipulate the behaviour of the barcode scanning process, you may use the static methods of the `BarcodeScanner` class.___
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import BarcodeFormats
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import android.hardware.camera2.CameraAccessException
import android.util.Log
Expand All @@ -33,9 +35,11 @@ import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.runBlocking
import java.io.ByteArrayOutputStream
import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit


class BarcodeScannerController(private val activity: Activity, messenger: BinaryMessenger, methodChannelName: String, scanEventChannelName: String) : MethodChannel.MethodCallHandler, EventChannel.StreamHandler {

private var methodChannel: MethodChannel = MethodChannel(messenger, methodChannelName)
Expand All @@ -56,6 +60,7 @@ class BarcodeScannerController(private val activity: Activity, messenger: Binary

private var scanSucceedTimestamp: Long = System.currentTimeMillis()
private var mrzResult: MutableList<String>? = null
private var mrzBitmap: Bitmap? = null

init {
methodChannel.setMethodCallHandler(this)
Expand Down Expand Up @@ -192,12 +197,16 @@ class BarcodeScannerController(private val activity: Activity, messenger: Binary
var textRecognizer: TextRecognizer? = null

if (cameraParams?.get("scanner_type") == "mrz" || cameraParams?.get("scanner_type") == "text") {
Log.i("native_scanner", "Start for MRZ scanner")

textRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

if (cameraParams?.get("scanner_type") == "mrz") {
Log.i("native_scanner", "Start for MRZ scanner")
mrzResult = mutableListOf()
}

} else {

Log.i("native_scanner", "Start for barcode scanner")
val options = BarcodeScannerOptions.Builder().setBarcodeFormats(
Barcode.FORMAT_CODE_39,
Expand All @@ -213,6 +222,7 @@ class BarcodeScannerController(private val activity: Activity, messenger: Binary
Barcode.FORMAT_QR_CODE
).build()
barcodeScanner = BarcodeScanning.getClient(options)

}

imageAnalysis.setAnalyzer(executor) { imageProxy ->
Expand Down Expand Up @@ -316,8 +326,9 @@ class BarcodeScannerController(private val activity: Activity, messenger: Binary
val convertImageToBitmap = BarcodeScannerUtil.convertToBitmap(mediaImage)
val cropRect = Rect(0, 0, imageWidth, imageHeight)

val heightCropPercent = 60
val heightCropPercent = 50
val widthCropPercent = 1

val (widthCrop, heightCrop) = when (rotationDegrees) {
90, 270 -> Pair(heightCropPercent / 100f, widthCropPercent / 100f)
else -> Pair(widthCropPercent / 100f, heightCropPercent / 100f)
Expand All @@ -327,9 +338,10 @@ class BarcodeScannerController(private val activity: Activity, messenger: Binary
(imageWidth * widthCrop / 2).toInt(),
(imageHeight * heightCrop / 2).toInt()
)
val croppedBitmap = BarcodeScannerUtil.rotateAndCrop(convertImageToBitmap, rotationDegrees, cropRect)

InputImage.fromBitmap(croppedBitmap, 0)
mrzBitmap = BarcodeScannerUtil.rotateAndCrop(convertImageToBitmap, rotationDegrees, cropRect)

InputImage.fromBitmap(mrzBitmap!!, 0)
} else {
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
}
Expand Down Expand Up @@ -370,15 +382,105 @@ class BarcodeScannerController(private val activity: Activity, messenger: Binary
if (cameraParams?.get("scanner_type") == "mrz") {

val mrz: String? = MrzUtil.extractMRZ(visionText.textBlocks, mrzResult!!)

if (mrz != null) {

var points: Array<Point>? = null

visionText.textBlocks.forEach {

if (points == null) {

var bitmapWithoutMrz = true

if (it.lines.size >= 3) {

var countMrzCommonChar = 0

it.lines.forEach { line ->
if (line.text.contains("<<")) {
countMrzCommonChar++
}
}

if (countMrzCommonChar >= 2) {
bitmapWithoutMrz = false
}

}

if (!bitmapWithoutMrz) {
points = it.cornerPoints
}

}

}

if (points?.isNotEmpty() == true) {

Log.i("native_scanner", "Extract MRZ done with result $mrz")

var x = points!![0].x
var y = points!![0].y
var width = points!![1].x - points!![0].x
var height = points!![2].y - points!![0].y

if (width > mrzBitmap!!.width) {
x = 0
width = mrzBitmap!!.width
}
if (height > mrzBitmap!!.height) {
y = 0
height = mrzBitmap!!.height
}

val croppedBitmap = Bitmap.createBitmap(
mrzBitmap!!,
x,
y,
width,
height,
)

val stream = ByteArrayOutputStream()
croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
val mrzByteArray = stream.toByteArray()

eventSink?.success(mapOf(
"mrz" to mrz,
"img" to mrzByteArray,
))

mrzResult!!.clear()
imageProxy.image?.close()
imageProxy.close()

} else {

Log.i("native_scanner", "Extract MRZ done with result $mrz but image is not loaded yet")

eventSink?.success(mapOf(
"progress" to 90,
))

}

} else {

var progress = 5
if (mrzResult!!.size == 1) {
progress = 25
} else if (mrzResult!!.size == 2) {
progress = 75
}

Log.i("native_scanner", "Extract MRZ progress with current $mrzResult (progress $progress)")

eventSink?.success(mapOf(
"mrz" to mrz,
"progress" to progress,
))

mrzResult!!.clear()
imageProxy.image?.close()
imageProxy.close()
}

} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ object MrzUtil {

mrzLines.forEach { line ->

val text = line.text.replace("«", "<").replace(" ", "").uppercase().trim()
var text = line.text.replace("«", "<").replace(" ", "").uppercase().trim()

if (text.matches("^[A-Z0-9<]*$".toRegex())) {

while (text.matches("<<<[KC]".toRegex())) {
text = text.replaceFirst("<<<[KC]".toRegex(), "<<<<")
}

if (((mrzResult.size < 3 && text.length == 30) || mrzResult.size < 2 && (text.length == 36 || text.length == 44))) {

if (!mrzResult.any { res -> res.substring(0, 20) == text.substring(0, 20) } && (mrzResult.isEmpty() || mrzResult.first().length == text.length)) {
Expand All @@ -78,8 +82,6 @@ object MrzUtil {
val map = mutableMapOf<Int, String>()
val missed = mutableListOf<Int>()

Log.i("native_scanner_res", "result : $mrzResult")

mrzResult.forEachIndexed{ index, it ->

if (mrzResult.size == 3) {
Expand All @@ -105,8 +107,13 @@ object MrzUtil {
}

if (missed.isNotEmpty()) {
missed.forEach { mrzResult.removeAt(it) }
missed.forEach {
if (it < mrzResult.size) {
mrzResult.removeAt(it)
}
}
} else {

var result = "${map[0]}\n${map[1]}"
if (mrzResult.size == 3) result += "\n${map[2]}"
return result
Expand Down
Loading

0 comments on commit 8054f35

Please sign in to comment.