我目前正在尝试开发一种光学字符识别(OCR)应用程序,该应用程序将来自名片的已识别数据传递到我的手机联系人数据库中。我已经设法识别名片中的数据。
项目流程图:
2.名片中的已识别文本将显示为“拍照”按钮下的文本。
使用OCR识别代码(主菜单):
package com.datumdroid.android.ocr.simple;
import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.googlecode.tesseract.android.TessBaseAPI;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class SimpleAndroidOCRActivity extends Activity {
public static final String PACKAGE_NAME = "com.datumdroid.android.ocr.simple";
public static final String DATA_PATH = Environment
.getExternalStorageDirectory().toString() + "/SimpleAndroidOCR/";
// You should have the trained data file in assets folder
public static final String lang = "eng";
private static final String TAG = "SimpleAndroidOCR.java";
protected Button _button;
// protected ImageView _image;
protected EditText _field;
protected String _path;
protected boolean _taken;
protected static final String PHOTO_TAKEN = "photo_taken";
Button button2; //step 1
@
Override
public void onCreate(Bundle savedInstanceState) {
String[] paths = new String[] {
DATA_PATH, DATA_PATH + "tessdata/"
};
for (String path: paths) {
File dir = new File(path);
if (!dir.exists()) {
if (!dir.mkdirs()) {
Log.v(TAG, "ERROR: Creation of directory " + path + " on sdcard failed");
return;
} else {
Log.v(TAG, "Created directory " + path + " on sdcard");
}
}
}
// lang.traineddata file with the app (in assets folder)
if (!(new File(DATA_PATH + "tessdata/" + lang + ".traineddata")).exists()) {
try {
AssetManager assetManager = getAssets();
InputStream in = assetManager.open("tessdata/" + lang + ".traineddata");
//GZIPInputStream gin = new GZIPInputStream(in);
OutputStream out = new FileOutputStream(DATA_PATH + "tessdata/" + lang + ".traineddata");
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
//while ((lenf = gin.read(buff)) > 0) {
while ((len = in .read(buf)) > 0) {
out.write(buf, 0, len);
} in .close();
//gin.close();
out.close();
Log.v(TAG, "Copied " + lang + " traineddata");
} catch (IOException e) {
Log.e(TAG, "Was unable to copy " + lang + " traineddata " + e.toString());
}
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
button2 = (Button) findViewById(R.id.button2); //step 2
// _image = (ImageView) findViewById(R.id.image);
_field = (EditText) findViewById(R.id.field);
_button = (Button) findViewById(R.id.button);
_button.setOnClickListener(new ButtonClickHandler());
_path = DATA_PATH + "/ocr.jpg";
}
public void onClickbutton2(View v) //step 3
{
startActivity(new Intent("android.intent.action.BUTTON2"));
}
public class ButtonClickHandler implements View.OnClickListener {
public void onClick(View view) {
Log.v(TAG, "Starting Camera app");
startCameraActivity();
}
}
// Simple android photo capture:
// http://labs.makemachine.net/2010/03/simple-android-photo-capture/
protected void startCameraActivity() {
File file = new File(_path);
Uri outputFileUri = Uri.fromFile(file);
final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(intent, 0);
}
@
Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "resultCode: " + resultCode);
if (resultCode == -1) {
onPhotoTaken();
} else {
Log.v(TAG, "User cancelled");
}
}
@
Override
protected void onSaveInstanceState(Bundle outState) {
outState.putBoolean(SimpleAndroidOCRActivity.PHOTO_TAKEN, _taken);
}
@
Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.i(TAG, "onRestoreInstanceState()");
if (savedInstanceState.getBoolean(SimpleAndroidOCRActivity.PHOTO_TAKEN)) {
onPhotoTaken();
}
}
protected void onPhotoTaken() {
_taken = true;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(_path, options);
try {
ExifInterface exif = new ExifInterface(_path);
int exifOrientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
Log.v(TAG, "Orient: " + exifOrientation);
int rotate = 0;
switch (exifOrientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
}
Log.v(TAG, "Rotation: " + rotate);
if (rotate != 0) {
// Getting width & height of the given image.
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// Setting pre rotate
Matrix mtx = new Matrix();
mtx.preRotate(rotate);
// Rotating Bitmap
bitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, false);
}
// Convert to ARGB_8888, required by tess
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
} catch (IOException e) {
Log.e(TAG, "Couldn't correct orientation: " + e.toString());
}
// _image.setImageBitmap( bitmap );
Log.v(TAG, "Before baseApi");
TessBaseAPI baseApi = new TessBaseAPI();
baseApi.setDebug(true);
baseApi.init(DATA_PATH, lang);
baseApi.setImage(bitmap);
String recognizedText = baseApi.getUTF8Text();
baseApi.end();
// You now have the text in recognizedText var, you can do anything with it.
// We will display a stripped out trimmed alpha-numeric version of it (if lang is eng)
// so that garbage doesn't make it to the display.
Log.v(TAG, "OCRED TEXT: " + recognizedText);
if (lang.equalsIgnoreCase("eng")) {
recognizedText = recognizedText.replaceAll("[^a-zA-Z0-9]+", " ");
}
recognizedText = recognizedText.trim();
if (recognizedText.length() != 0) {
_field.setText(_field.getText().toString().length() == 0 ? recognizedText : _field.getText() + " " + recognizedText);
_field.setSelection(_field.getText().toString().length());
}
}
}
添加联系人信息字段:
package com.datumdroid.android.ocr.simple;
import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.RawContacts;
import android.provider.MediaStore;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class Contacts extends AppCompatActivity {
EditText edtTxtContactName, edtTxtContactNumber, edtTxtContactEmail;
ImageView imgContactPhoto;
Button btnAddContact;
View view;
String imagePath = null;
Uri uri;
ExifInterface exif;
boolean isButtonClicked = false;
View snackView;
Bitmap rotateBitmap;
InputMethodManager inm;
public static boolean isUpdate;
String cID, cName, cNumber, cEmail; //
Bitmap cPhoto;
public boolean FLAG = true;
Button button3;
@
Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.add_contact_activity_main);
view = findViewById(R.id.rootView);
edtTxtContactName = (EditText) findViewById(R.id.edtTxtContactName);
edtTxtContactNumber = (EditText) findViewById(R.id.edtTxtContactNumber);
edtTxtContactEmail = (EditText) findViewById(R.id.edtTxtContactEmail);
imgContactPhoto = (ImageView) findViewById(R.id.imgContactPhoto);
btnAddContact = (Button) findViewById(R.id.btnAddContact);
inm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
button3 = (Button) findViewById(R.id.button3);
if (isUpdate) {
cID = getIntent().getExtras().getString("cID");
cName = getIntent().getExtras().getString("cName");
cNumber = getIntent().getExtras().getString("cNumber"); //
cEmail = getIntent().getExtras().getString("cEmail");
cPhoto = getIntent().getParcelableExtra("cPhoto");
btnAddContact.setText(getString(R.string.update_contact));
edtTxtContactName.setText(cName);
edtTxtContactNumber.setText(cNumber); //
edtTxtContactEmail.setText(cEmail);
imgContactPhoto.setImageBitmap(cPhoto);
FLAG = false;
} else {
btnAddContact.setText(getString(R.string.add_contact));
}
btnAddContact.setOnClickListener(new View.OnClickListener() {@
Override
public void onClick(View v) {
if (btnAddContact.getText().toString().equals(getString(R.string.add_contact))) {
if (edtTxtContactName.getText().toString().trim().isEmpty()) {
Snackbar.make(v, "You must provide name.", Snackbar.LENGTH_INDEFINITE).setAction("OK", new View.OnClickListener() {@
Override
public void onClick(View v) {
}
}).show();
} else if (edtTxtContactNumber.getText().toString().trim().isEmpty()) {
Snackbar.make(v, "You must provide number.", Snackbar.LENGTH_INDEFINITE).setAction("OK", new View.OnClickListener() {@
Override
public void onClick(View v) {
}
}).show();
} else {
isButtonClicked = true;
snackView = v;
inm.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
addContact();
}
} else if (btnAddContact.getText().toString().equals(getString(R.string.update_contact))) {
String strName = edtTxtContactName.getText().toString();
String strNumber = edtTxtContactNumber.getText().toString();
String strEmail = edtTxtContactEmail.getText().toString();
imgContactPhoto.setDrawingCacheEnabled(true);
Bitmap bitmap = imgContactPhoto.getDrawingCache();
if (updateContact(cID, strName, strNumber, strEmail, bitmap)) {
Snackbar.make(v, "Contact updated successfully.", Snackbar.LENGTH_LONG).show();
} else {
Snackbar.make(v, "Failed to update contact.", Snackbar.LENGTH_LONG).show();
}
} else {
Snackbar.make(v, "Some internal error occured, Please try after some time.", Snackbar.LENGTH_LONG).show();
}
}
});
imgContactPhoto.setOnClickListener(new View.OnClickListener() {@
Override
public void onClick(View v) {
selectImage();
}
});
}
public void onClickbutton3(View v) //step 3
{
startActivity(new Intent("android.intent.action.BUTTON3"));
}
private void selectImage() {
final CharSequence[] option = {
"Take Photo", "Choose from Gallery"
};
AlertDialog.Builder builder = new AlertDialog.Builder(
Contacts.this);
builder.setTitle("Add Photo!");
builder.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {@
Override
public void onClick(DialogInterface dialog, int which) {}
});
builder.setItems(option, new DialogInterface.OnClickListener() {
@
Override
public void onClick(DialogInterface dialog, int item) {
if (option[item].equals("Take Photo")) {
clickPhotoFromCamera();
} else if (option[item].equals("Choose from Gallery")) {
uploadPhotoFromCamera();
}
}
});
builder.show();
}
private void clickPhotoFromCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + ".jpg";
File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), imageFileName);
uri = Uri.fromFile(imageStorageDir);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 1);
}
private void uploadPhotoFromCamera() {
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType("image/*");
startActivityForResult(intent, 2);
}
@
Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == 1) {
imagePath = uri.getPath();
displayImageBitmap(imagePath);
} else if (requestCode == 2) {
Uri selectedImage = data.getData();
String[] filePath = {
MediaStore.Images.Media.DATA
};
Cursor c = getContentResolver().query(selectedImage, filePath,
null, null, null);
c.moveToFirst();
int columnIndex = c.getColumnIndex(filePath[0]);
String picturePath = c.getString(columnIndex);
c.close();
displayImageBitmap(picturePath);
}
}
}
public void displayImageBitmap(String image_path) {
File mediaFile = new File(image_path);
Bitmap myBitmap = BitmapFactory.decodeFile(mediaFile.getAbsolutePath());
int height = (myBitmap.getHeight() * 512 / myBitmap.getWidth());
Bitmap scale = Bitmap.createScaledBitmap(myBitmap, 512, height, true);
int rotate = 0;
try {
exif = new ExifInterface(mediaFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
switch (orientation) {
case ExifInterface.ORIENTATION_NORMAL:
rotate = 0;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
}
Matrix matrix = new Matrix();
matrix.postRotate(rotate);
rotateBitmap = Bitmap.createBitmap(scale, 0, 0, scale.getWidth(), scale.getHeight(), matrix, true);
imgContactPhoto.setImageBitmap(rotateBitmap);
}
public void addContact() {
ArrayList < ContentProviderOperation > insertOperation = new ArrayList < ContentProviderOperation > ();
int rawContactID = insertOperation.size();
// Adding insert operation to operations list
// For insert a new raw contact in the ContactsContract.RawContacts
insertOperation.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(RawContacts.ACCOUNT_NAME, null)
.build());
ByteArrayOutputStream stream = new ByteArrayOutputStream();
if (rotateBitmap != null) { // If an image is selected successfully
rotateBitmap.compress(Bitmap.CompressFormat.PNG, 75, stream);
// For insert Photo in the ContactsContract.Data
insertOperation.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1)
.withValue(ContactsContract.Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, stream.toByteArray())
.build());
try {
stream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
// For insert display name in the ContactsContract.Data
insertOperation.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
.withValue(StructuredName.DISPLAY_NAME, edtTxtContactName.getText().toString())
.build());
// For insert Mobile Number in the ContactsContract.Data
insertOperation.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, edtTxtContactNumber.getText().toString())
.withValue(Phone.TYPE, Phone.TYPE_MOBILE)
.build());
// For insert Work Email in the ContactsContract.Data
insertOperation.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, edtTxtContactEmail.getText().toString())
.withValue(Email.TYPE, Email.TYPE_WORK)
.build());
try {
// Executing all the insert operations as a single database transaction
getContentResolver().applyBatch(ContactsContract.AUTHORITY, insertOperation);
if (isButtonClicked == true) {
edtTxtContactName.setText("");
edtTxtContactNumber.setText("");
edtTxtContactEmail.setText("");
imgContactPhoto.setImageResource(R.drawable.default_contact);
Snackbar.make(snackView, "Contact added successfully", Snackbar.LENGTH_INDEFINITE).setAction("Hide", new View.OnClickListener() {@
Override
public void onClick(View v) {
}
}).show();
} else {
Toast.makeText(getBaseContext(), "Contact is successfully added", Toast.LENGTH_SHORT).show();
finish();
}
} catch (RemoteException e) {
e.printStackTrace();
} catch (OperationApplicationException e) {
e.printStackTrace();
}
}
boolean updateContact(String contactID, String contactName, String contactNumber, String contactEmailAdd, Bitmap bitmap) {
ArrayList < ContentProviderOperation > ops = new ArrayList < > ();
ops.add(ContentProviderOperation
.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?", new String[] {
contactID, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
})
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contactName)
.build());
ops.add(ContentProviderOperation
.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=? AND " + ContactsContract.CommonDataKinds.Organization.TYPE + "=?", new String[] {
contactID, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, String.valueOf(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
})
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, contactNumber)
.build());
ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=? AND " + ContactsContract.CommonDataKinds.Organization.TYPE + "=?", new String[] {
contactID, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, String.valueOf(Email.TYPE_WORK)
})
.withValue(Email.ADDRESS, contactEmailAdd)
.build());
try {
ByteArrayOutputStream image = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, image);
ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?", new String[] {
contactID, Photo.CONTENT_ITEM_TYPE
})
.withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1)
.withValue(Photo.PHOTO, image.toByteArray())
.build());
/*Builder builder;
builder = ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI);
builder.withSelection(ContactsContract.Data.CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + "=?",
new String[]{contactID, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE});
builder.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, image.toByteArray());
ops.add(builder.build());*/
} catch (Exception e) {
e.printStackTrace();
}
try {
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
@
Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.add_contact_menu, menu);
MenuItem item = menu.findItem(R.id.action_save);
if (FLAG) {
item.setVisible(true);
this.invalidateOptionsMenu();
} else {
item.setVisible(false);
this.invalidateOptionsMenu();
}
return true;
}
@
Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
switch (id) {
case android.R.id.home:
onBackPressed();
return true;
}
if (id == R.id.action_save) {
if (edtTxtContactName.getText().toString().trim().isEmpty()) {
Toast.makeText(getApplicationContext(), "You must provide name.", Toast.LENGTH_LONG).show();
} else if (edtTxtContactNumber.getText().toString().trim().isEmpty()) {
Toast.makeText(getApplicationContext(), "You must provide number.", Toast.LENGTH_LONG).show();
} else {
inm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
isButtonClicked = false;
addContact();
}
return true;
}
return super.onOptionsItemSelected(item);
}
@
Override
public void onBackPressed() {
super.onBackPressed();
}
}
问题是我不知道如何将每个文本与识别文本分开,关于名称,联系电话,电子邮件地址和将它们传递到我的联系人数据库中的这些字段。
名片结构/格式不具体,难以假设。但是很少有事情可以假设:
“@”包含的字符串主要是电子邮件ID。
带括号或+号的所有数字大部分都是电话号码。
答案 0 :(得分:1)
从文本中搜索,正确定位和提取特定值的过程称为解析。正如您所说,它有时基于假设,例如如果存在“@”则该字符串是电子邮件。
通过冗余和自适应决策,可以提高解析代码的稳健性。例如:查找包含“@”的字符串表示电子邮件地址。然后,查找“电子邮件”或“电子邮件”标签可以确认以前找到的结果,或者它可以找到丢失的电子邮件地址,以防“O”被OCR误识别并且在第一次找不到搜索范围。
假设OCR很少100%准确,特别是在像名片这样复杂的文档类型上(由于字体小,图片偏斜,颜色和背景,艺术字体)是一个很好的规则,需要冗余和特殊的案例处理。
也可能存在误报,例如“@”可以在不是电子邮件地址的字符串中找到,例如在像“@YourTwitterName”这样的Twitter句柄中。您可以通过排除可预测的误报来改进您的解析代码。