From b7828a21eb637ced07526d84e37df793d5482254 Mon Sep 17 00:00:00 2001 From: alimertozdemir Date: Sat, 24 Oct 2020 17:58:22 +0300 Subject: [PATCH] MRZ Scanning improvement. --- .../mlkit/text/TextRecognitionProcessor.java | 91 +++++++++++++++++-- .../passportreader/ui/CaptureActivity.java | 6 +- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/alimert/passportreader/mlkit/text/TextRecognitionProcessor.java b/app/src/main/java/com/alimert/passportreader/mlkit/text/TextRecognitionProcessor.java index 0d476a4..36c10d5 100644 --- a/app/src/main/java/com/alimert/passportreader/mlkit/text/TextRecognitionProcessor.java +++ b/app/src/main/java/com/alimert/passportreader/mlkit/text/TextRecognitionProcessor.java @@ -8,6 +8,7 @@ import com.alimert.passportreader.mlkit.other.FrameMetadata; import com.alimert.passportreader.mlkit.other.GraphicOverlay; +import com.alimert.passportreader.model.DocType; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; @@ -17,11 +18,15 @@ import com.google.mlkit.vision.text.TextRecognition; import com.google.mlkit.vision.text.TextRecognizer; +import net.sf.scuba.data.Gender; + import org.jmrtd.lds.icao.MRZInfo; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class TextRecognitionProcessor { @@ -33,15 +38,28 @@ public class TextRecognitionProcessor { private String scannedTextBuffer; + private DocType docType; + public static final String TYPE_PASSPORT = "P<"; public static final String TYPE_ID_CARD = "I<"; + public static final String ID_CARD_TD_1_LINE_1_REGEX = "([A|C|I][A-Z0-9<]{1})([A-Z]{3})([A-Z0-9<]{31})"; + + public static final String ID_CARD_TD_1_LINE_2_REGEX = "([0-9]{6})([0-9]{1})([M|F|X|<]{1})([0-9]{6})([0-9]{1})([A-Z]{3})([A-Z0-9<]{11})([0-9]{1})"; + + public static final String ID_CARD_TD_1_LINE_3_REGEX = "([A-Z0-9<]{30})"; + + public static final String PASSPORT_TD_3_LINE_1_REGEX = "(P[A-Z0-9<]{1})([A-Z]{3})([A-Z0-9<]{39})"; + + public static final String PASSPORT_TD_3_LINE_2_REGEX = "([A-Z0-9<]{9})([0-9]{1})([A-Z]{3})([0-9]{6})([0-9]{1})([M|F|X|<]{1})([0-9]{6})([0-9]{1})([A-Z0-9<]{14})([0-9]{1})([0-9]{1})"; + // Whether we should ignore process(). This is usually caused by feeding input data faster than // the model can handle. private final AtomicBoolean shouldThrottle = new AtomicBoolean(false); - public TextRecognitionProcessor(ResultListener resultListener) { + public TextRecognitionProcessor(DocType docType, ResultListener resultListener) { + this.docType = docType; this.resultListener = resultListener; textRecognizer = TextRecognition.getClient(); } @@ -100,12 +118,57 @@ protected void onSuccess(@NonNull Text results, @NonNull FrameMetadata frameMeta private void filterScannedText(GraphicOverlay graphicOverlay, Text.Element element) { GraphicOverlay.Graphic textGraphic = new TextGraphic(graphicOverlay, element, Color.GREEN); scannedTextBuffer += element.getText(); - String docPrefix; - if(scannedTextBuffer.contains(TYPE_PASSPORT) || scannedTextBuffer.contains(TYPE_ID_CARD)) { - graphicOverlay.add(textGraphic); - docPrefix = scannedTextBuffer.contains(TYPE_PASSPORT) ? TYPE_PASSPORT : TYPE_ID_CARD; - scannedTextBuffer = scannedTextBuffer.substring(scannedTextBuffer.indexOf(docPrefix)); - finishScanning(scannedTextBuffer); + + if(docType == DocType.ID_CARD) { + + Pattern patternIDCardTD1Line1 = Pattern.compile(ID_CARD_TD_1_LINE_1_REGEX); + Matcher matcherIDCardTD1Line1 = patternIDCardTD1Line1.matcher(scannedTextBuffer); + + Pattern patternIDCardTD1Line2 = Pattern.compile(ID_CARD_TD_1_LINE_2_REGEX); + Matcher matcherIDCardTD1Line2 = patternIDCardTD1Line2.matcher(scannedTextBuffer); + + if(matcherIDCardTD1Line1.find() && matcherIDCardTD1Line2.find()) { + graphicOverlay.add(textGraphic); + String line1 = matcherIDCardTD1Line1.group(0); + String line2 = matcherIDCardTD1Line2.group(0); + if (line1.indexOf(TYPE_ID_CARD) > 0) { + line1 = line1.substring(line1.indexOf(TYPE_ID_CARD)); + String documentNumber = line1.substring(5, 14); + documentNumber = documentNumber.replace("O", "0"); + String dateOfBirthDay = line2.substring(0, 6); + String expiryDate = line2.substring(8, 14); + + Log.d(TAG, "Scanned Text Buffer ID Card ->>>> " + "Doc Number: " + documentNumber + " DateOfBirth: " + dateOfBirthDay + " ExpiryDate: " + expiryDate); + + MRZInfo mrzInfo = buildTempMrz(documentNumber, dateOfBirthDay, expiryDate); + + if (mrzInfo != null) + finishScanning(mrzInfo); + } + } + } else if (docType == DocType.PASSPORT) { + + Pattern patternPassportTD3Line1 = Pattern.compile(PASSPORT_TD_3_LINE_1_REGEX); + Matcher matcherPassportTD3Line1 = patternPassportTD3Line1.matcher(scannedTextBuffer); + + Pattern patternPassportTD3Line2 = Pattern.compile(PASSPORT_TD_3_LINE_2_REGEX); + Matcher matcherPassportTD3Line2 = patternPassportTD3Line2.matcher(scannedTextBuffer); + + if(matcherPassportTD3Line1.find() && matcherPassportTD3Line2.find()) { + graphicOverlay.add(textGraphic); + String line2 = matcherPassportTD3Line2.group(0); + String documentNumber = line2.substring(0, 9); + documentNumber = documentNumber.replace("O", "0"); + String dateOfBirthDay = line2.substring(13, 19); + String expiryDate = line2.substring(21, 27); + + Log.d(TAG, "Scanned Text Buffer Passport ->>>> " + "Doc Number: " + documentNumber + " DateOfBirth: " + dateOfBirthDay + " ExpiryDate: " + expiryDate); + + MRZInfo mrzInfo = buildTempMrz(documentNumber, dateOfBirthDay, expiryDate); + + if (mrzInfo != null) + finishScanning(mrzInfo); + } } } @@ -138,9 +201,8 @@ public void onFailure(@NonNull Exception e) { shouldThrottle.set(true); } - private void finishScanning(final String mrzText) { + private void finishScanning(final MRZInfo mrzInfo) { try { - MRZInfo mrzInfo = new MRZInfo(mrzText); if(isMrzValid(mrzInfo)) { // Delay returning result 1 sec. in order to make mrz text become visible on graphicOverlay by user // You want to call 'resultListener.onSuccess(mrzInfo)' without no delay @@ -152,6 +214,17 @@ private void finishScanning(final String mrzText) { } } + private MRZInfo buildTempMrz(String documentNumber, String dateOfBirth, String expiryDate) { + MRZInfo mrzInfo = null; + try { + mrzInfo = new MRZInfo("P","NNN", "", "", documentNumber, "NNN", dateOfBirth, Gender.UNSPECIFIED, expiryDate, ""); + } catch (Exception e) { + Log.d(TAG, "MRZInfo error : " + e.getLocalizedMessage()); + } + + return mrzInfo; + } + private boolean isMrzValid(MRZInfo mrzInfo) { return mrzInfo.getDocumentNumber() != null && mrzInfo.getDocumentNumber().length() >= 8 && mrzInfo.getDateOfBirth() != null && mrzInfo.getDateOfBirth().length() == 6 && diff --git a/app/src/main/java/com/alimert/passportreader/ui/CaptureActivity.java b/app/src/main/java/com/alimert/passportreader/ui/CaptureActivity.java index 6d7e824..4611296 100644 --- a/app/src/main/java/com/alimert/passportreader/ui/CaptureActivity.java +++ b/app/src/main/java/com/alimert/passportreader/ui/CaptureActivity.java @@ -31,6 +31,8 @@ public class CaptureActivity extends AppCompatActivity implements TextRecognitio public static final String MRZ_RESULT = "MRZ_RESULT"; public static final String DOC_TYPE = "DOC_TYPE"; + private DocType docType = DocType.OTHER; + private static String TAG = CaptureActivity.class.getSimpleName(); @Override @@ -43,7 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.setCustomView(R.layout.action_bar_title); if(getIntent().hasExtra(DOC_TYPE)) { - DocType docType = (DocType) getIntent().getSerializableExtra(DOC_TYPE); + docType = (DocType) getIntent().getSerializableExtra(DOC_TYPE); if(docType == DocType.PASSPORT) { actionBar.hide(); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); @@ -93,7 +95,7 @@ private void createCameraSource() { cameraSource.setFacing(CameraSource.CAMERA_FACING_BACK); } - cameraSource.setMachineLearningFrameProcessor(new TextRecognitionProcessor(this)); + cameraSource.setMachineLearningFrameProcessor(new TextRecognitionProcessor(docType, this)); } private void startCameraSource() {