Android: Как улучшить число в изображении, полученном tesseract ocr?
Я сделал простое приложение, которое читает изображения и извлекает числовое изображение в виде текста с помощью android. Но проблема в том, что точность составляет всего около 60%, а также проявляется некоторый нежелательный шум. Я понимаю, что точность не может быть хорошей, как 100%, однако я считаю, что должен быть способ ее улучшить. Но, поскольку я любитель, мне трудно. Я искал вокруг Google, но не смог получить прочную информацию.
Я хочу прочитать цифры 596, 00 и 012345 из восточных счастливых билетов, как показано ниже.
Ответы
Ответ 1
Tesseract-ocr лучше всего работает на изображениях персонажей, которые отвечают следующим критериям:
-
Входное изображение должно иметь не более 300 dpi
-
Входное изображение должно быть черно-белым
-
На входном изображении должен быть минимальный шум (т.е. Текст должен быть четко различим от фона)
-
Текстовые строки должны быть прямыми
-
Изображение должно быть сосредоточено вокруг текста, подлежащего обнаружению
(Дополнительную информацию см. В wiki wiki tesseract-ocr)
Для данного входного изображения tesseract попытается предварительно обработать и очистить изображение, чтобы соответствовать этим критериям, но чтобы максимизировать точность обнаружения, лучше всего выполнить предварительную обработку.
Основываясь на исходном изображении, которое вы указали, основная проблема заключается в том, что слишком много фонового шума. Чтобы удалить фоновый шум из текста на изображении, я обнаружил, что применение алгоритма преобразования ширины штриха (SWT) с пороговым значением для удаления шума дает многообещающие результаты. Быстрая реализация SWT со многими настраиваемыми параметрами предоставляется в библиотеке libCCV. Насколько хорошо он очищает изображение, зависит от ряда факторов, включая размер изображения, однородность ширины штриха и другие входные параметры алгоритма. Здесь приведен список настраиваемых параметров.
Затем вы передаете выходные данные SWT в tesseract для получения текстовых значений символов на изображении.
Если изображение, переданное tesseract, все еще содержит некоторый шум, оно может возвращать некоторые ложные обнаружения, такие как знаки пунктуации. Учитывая, что изображение, которое вы обрабатываете, скорее всего, будет содержать буквы и цифры az AZ 0-9, вы можете просто применить регулярное выражение к выходу для удаления любых окончательных ложных срабатываний.
Ответ 2
you can use Vision for text detection.
Добавить зависимость в приложении gradle
compile 'com.google.android.gms:play-services-vision:10.0.0'
Добавить в Manifest.xml
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="ocr" />
MainActivity.java
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.widget.TextView;
import com.google.android.gms.vision.Frame;
import com.google.android.gms.vision.text.TextBlock;
import com.google.android.gms.vision.text.TextRecognizer;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_GALLERY = 0;
private static final int REQUEST_CAMERA = 1;
private static final String TAG = MainActivity.class.getSimpleName();
private Uri imageUri;
private TextView detectedTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.choose_from_gallery).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, REQUEST_GALLERY);
}
});
findViewById(R.id.take_a_photo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String filename = System.currentTimeMillis() + ".jpg";
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
imageUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, REQUEST_CAMERA);
}
});
detectedTextView = (TextView) findViewById(R.id.detected_text);
detectedTextView.setMovementMethod(new ScrollingMovementMethod());
}
private void inspectFromBitmap(Bitmap bitmap) {
TextRecognizer textRecognizer = new TextRecognizer.Builder(this).build();
try {
if (!textRecognizer.isOperational()) {
new AlertDialog.
Builder(this).
setMessage("Text recognizer could not be set up on your device").show();
return;
}
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
SparseArray<TextBlock> origTextBlocks = textRecognizer.detect(frame);
List<TextBlock> textBlocks = new ArrayList<>();
for (int i = 0; i < origTextBlocks.size(); i++) {
TextBlock textBlock = origTextBlocks.valueAt(i);
textBlocks.add(textBlock);
}
Collections.sort(textBlocks, new Comparator<TextBlock>() {
@Override
public int compare(TextBlock o1, TextBlock o2) {
int diffOfTops = o1.getBoundingBox().top - o2.getBoundingBox().top;
int diffOfLefts = o1.getBoundingBox().left - o2.getBoundingBox().left;
if (diffOfTops != 0) {
return diffOfTops;
}
return diffOfLefts;
}
});
StringBuilder detectedText = new StringBuilder();
for (TextBlock textBlock : textBlocks) {
if (textBlock != null && textBlock.getValue() != null) {
detectedText.append(textBlock.getValue());
detectedText.append("\n");
}
}
detectedTextView.setText(detectedText);
}
finally {
textRecognizer.release();
}
}
private void inspect(Uri uri) {
InputStream is = null;
Bitmap bitmap = null;
try {
is = getContentResolver().openInputStream(uri);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inSampleSize = 2;
options.inScreenDensity = DisplayMetrics.DENSITY_LOW;
bitmap = BitmapFactory.decodeStream(is, null, options);
inspectFromBitmap(bitmap);
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed to find the file: " + uri, e);
} finally {
if (bitmap != null) {
bitmap.recycle();
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
Log.w(TAG, "Failed to close InputStream", e);
}
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_GALLERY:
if (resultCode == RESULT_OK) {
inspect(data.getData());
}
break;
case REQUEST_CAMERA:
if (resultCode == RESULT_OK) {
if (imageUri != null) {
inspect(imageUri);
}
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
break;
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="org.komamitsu.android_ocrsample.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/choose_from_gallery"
android:id="@+id/choose_from_gallery"
tools:context=".MainActivity"
android:layout_marginTop="23dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/take_a_photo"
android:id="@+id/take_a_photo"
tools:context=".MainActivity"
android:layout_marginTop="11dp"
android:layout_below="@+id/choose_from_gallery"
android:layout_centerHorizontal="true" />
<TextView
android:text=""
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/detected_text"
android:layout_alignParentBottom="true"
android:layout_below="@+id/take_a_photo"
android:layout_margin="25dp"
android:layout_centerHorizontal="true"
android:background="#EEEEEE"
android:scrollbars="vertical" />
</RelativeLayout>