Click here to Skip to main content
15,867,308 members
Articles / Mobile Apps / Android

Android OCR Application Based on Tesseract

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
29 Jan 2019CPOL2 min read 66.7K   6.1K   13   23
Easy way to make Android OCR application

Introduction

This application uses Tesseract OCR engine of Tesseract 3 which works by recognizing character patterns (https://github.com/tesseract-ocr/tesseract). Tesseract has unicode (UTF-8) support, and can recognize more than 100 languages "out of the box".

Background

I tried the Google Text Recognition API- https://developers.google.com/vision/android/text-overview, but it was not suitable for me, so I found this amazing engine.

Using the Code

Let's start! Create a new project in Android studio (I used version 3.2.1) or you can download the source files and choose: File-New-Import project.

Image 1

Add to build.gradle app level:

Java
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

implementation 'com.rmtheis:tess-two:9.0.0'

I use Butterknife library, it's very useful and the main library is - 'tess-two:9.0.0'' - it contains a fork of Tesseract Tools for Android (tesseract-android-tools) that adds some additional functions. Also, we need camera and write permissions, so add it to AndroidManifest.xml.

Java
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />

Make a simple layout file with Button, TextView and ImageView:

XML
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fillViewport="true"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <Button
                android:id="@+id/scan_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="scan" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/ocr_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="fill"
                android:text=" text">

            </TextView>

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/ocr_image"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

        </LinearLayout>

    </LinearLayout>
</ScrollView>

We get something like this:

Image 2

Write some code to check permissions:

Java
void checkPermissions() {
    if (!hasPermissions(context, PERMISSIONS)) {
        requestPermissions(PERMISSIONS,
                PERMISSION_ALL);
        flagPermissions = false;
    }
    flagPermissions = true;
}

public static boolean hasPermissions(Context context, String... permissions) {
    if (context != null && permissions != null) {
        for (String permission : permissions) {
            if (ActivityCompat.checkSelfPermission(context, permission) 
                                       != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
    }
    return true;
}

And code to create a file:

Java
public File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("MMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );
    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}

First, we need write onClickScanButton function, it:

Java
@OnClick(R.id.scan_button)
void onClickScanButton() {
    // check permissions
    if (!flagPermissions) {
        checkPermissions();
        return;
    }
    //prepare intent
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

    if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            Toast.makeText(context, errorFileCreate, Toast.LENGTH_SHORT).show();
            Log.i("File error", ex.toString());
        }
        // Continue only if the File was successfully created
        if (photoFile != null) {
            oldPhotoURI = photoURI1;
            photoURI1 = Uri.fromFile(photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI1);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE1_CAPTURE);
        }
    }
}

We can check the result here:

Java
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    switch (requestCode) {
        case REQUEST_IMAGE1_CAPTURE: {
            if (resultCode == RESULT_OK) {
                Bitmap bmp = null;
                try {
                    InputStream is = context.getContentResolver().openInputStream(photoURI1);
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    bmp = BitmapFactory.decodeStream(is, null, options);

                } catch (Exception ex) {
                    Log.i(getClass().getSimpleName(), ex.getMessage());
                    Toast.makeText(context, errorConvert, Toast.LENGTH_SHORT).show();
                }

                firstImage.setImageBitmap(bmp);
                doOCR(bmp);

                OutputStream os;
                try {
                    os = new FileOutputStream(photoURI1.getPath());
                    if (bmp != null) {
                        bmp.compress(Bitmap.CompressFormat.JPEG, 100, os);
                    }
                    os.flush();
                    os.close();
                } catch (Exception ex) {
                    Log.e(getClass().getSimpleName(), ex.getMessage());
                    Toast.makeText(context, errorFileCreate, Toast.LENGTH_SHORT).show();
                }

            } else {
                {
                    photoURI1 = oldPhotoURI;
                    firstImage.setImageURI(photoURI1);
                }
            }
        }
    }
}

Next integrate Tesseract to our project, make additional class: TesseractOCR.

I put trained data file "eng.traineddata" for an English language in Assets folder, so we need copy this from APK to internal memory files directory and then init the Tesseract system: mTess.init(dstInitPathDir, language).

Java
public class TesseractOCR {

    private final TessBaseAPI mTess;

    public TesseractOCR(Context context, String language) {
        mTess = new TessBaseAPI();
        boolean fileExistFlag = false;

        AssetManager assetManager = context.getAssets();

        String dstPathDir = "/tesseract/tessdata/";

        String srcFile = "eng.traineddata";
        InputStream inFile = null;

        dstPathDir = context.getFilesDir() + dstPathDir;
        String dstInitPathDir = context.getFilesDir() + "/tesseract";
        String dstPathFile = dstPathDir + srcFile;
        FileOutputStream outFile = null;

        try {
            inFile = assetManager.open(srcFile);

            File f = new File(dstPathDir);

            if (!f.exists()) {
                if (!f.mkdirs()) {
                    Toast.makeText(context, srcFile + " can't be created.", Toast.LENGTH_SHORT).show();
                }
                outFile = new FileOutputStream(new File(dstPathFile));
            } else {
                fileExistFlag = true;
            }

        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage());

        } finally {

            if (fileExistFlag) {
                try {
                    if (inFile != null) inFile.close();
                    mTess.init(dstInitPathDir, language);
                    return;

                } catch (Exception ex) {
                    Log.e(TAG, ex.getMessage());
                }
            }

            if (inFile != null && outFile != null) {
                try {
                    //copy file
                    byte[] buf = new byte[1024];
                    int len;
                    while ((len = inFile.read(buf)) != -1) {
                        outFile.write(buf, 0, len);
                    }
                    inFile.close();
                    outFile.close();
                    mTess.init(dstInitPathDir, language);
                } catch (Exception ex) {
                    Log.e(TAG, ex.getMessage());
                }
            } else {
                Toast.makeText(context, srcFile + " can't be read.", Toast.LENGTH_SHORT).show();
            }
        }
    }

    public String getOCRResult(Bitmap bitmap) {
        mTess.setImage(bitmap);
        return mTess.getUTF8Text();
    }

    public void onDestroy() {
        if (mTess != null) mTess.end();
    }
}

OCR code is simple - we need pass image (bitmap BMP) to this object and get the result:

Java
public String getOCRResult(Bitmap bitmap) { 
mTess.setImage(bitmap); 
return mTess.getUTF8Text(); }

OCR can take a long time, so we'll need to make it in another Thread:

Java
private void doOCR(final Bitmap bitmap) {
    if (mProgressDialog == null) {
        mProgressDialog = ProgressDialog.show(this, "Processing",
                "Doing OCR...", true);
    } else {
        mProgressDialog.show();
    }
    new Thread(new Runnable() {
        public void run() {
            final String srcText = mTessOCR.getOCRResult(bitmap);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    if (srcText != null && !srcText.equals("")) {
                        ocrText.setText(srcText);
                    }
                    mProgressDialog.dismiss();
                }
            });
        }
    }).start();
}

The source image is as follows:

Image 3

Result of OCR is given below:

Image 4

Points of Interest

If you are interested in using Tesseract OCR engines, I hope this simple article will help you. So you can easily improve this application. I like to develop applications, so you can try some of it on https://play.google.com/store/apps/developer?id=VOLOSHYN+SERGIY

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Junior)
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionIs it possible to detect multiple languages in the OCR? Pin
Bosco Wong 20228-Jul-22 0:07
Bosco Wong 20228-Jul-22 0:07 
QuestionApp closed when use Api 29 or above Pin
Member 1536428520-Sep-21 2:05
Member 1536428520-Sep-21 2:05 
QuestionDo I want to integrate the tesseract before following this method Pin
Member 149875929-Nov-20 0:39
Member 149875929-Nov-20 0:39 
Questionunable to download Pin
Ubed Thaheem27-Sep-20 20:52
Ubed Thaheem27-Sep-20 20:52 
QuestionCant edit the text extracted Pin
Jeya Samuel.T cse201712-Jun-20 0:36
Jeya Samuel.T cse201712-Jun-20 0:36 
QuestionInterested in your colaboration in a project Pin
Member 1483249614-May-20 9:02
Member 1483249614-May-20 9:02 
QuestionResult: Displaying Symbols instead of generated text Pin
Member 1480340215-Apr-20 21:44
Member 1480340215-Apr-20 21:44 
AnswerRe: Result: Displaying Symbols instead of generated text Pin
Member 1491111310-Aug-20 6:54
Member 1491111310-Aug-20 6:54 
GeneralRe: Result: Displaying Symbols instead of generated text Pin
tkogias22-Feb-22 7:38
tkogias22-Feb-22 7:38 
QuestionAwesome Pin
Member 1478693730-Mar-20 1:06
Member 1478693730-Mar-20 1:06 
QuestionProblem with the application Pin
Member 1476061423-Mar-20 23:41
Member 1476061423-Mar-20 23:41 
AnswerRe: Problem with the application Pin
Member 1480340215-Apr-20 19:29
Member 1480340215-Apr-20 19:29 
Same, build is successful but the app is not displaying the text
QuestionTesseract trained data Pin
Member 1456924828-Aug-19 11:31
Member 1456924828-Aug-19 11:31 
AnswerRe: Tesseract trained data Pin
Jeya Samuel.T cse201712-Jun-20 0:41
Jeya Samuel.T cse201712-Jun-20 0:41 
AnswerRe: Tesseract trained data Pin
Muhammed Ali Ilgaz28-Jun-21 13:08
Muhammed Ali Ilgaz28-Jun-21 13:08 
PraiseThe Best Android OCR Demo that I've seen! Pin
Green Tree9-Jul-19 19:46
Green Tree9-Jul-19 19:46 
QuestionThanks... it worked (kind of) Pin
Member 1437652517-May-19 8:19
Member 1437652517-May-19 8:19 
QuestionWhat about other frameworks for OCR Pin
KarstenK28-Jan-19 6:53
mveKarstenK28-Jan-19 6:53 
AnswerRe: What about other frameworks for OCR Pin
Jim Meadors30-Jan-19 17:49
Jim Meadors30-Jan-19 17:49 
GeneralRe: What about other frameworks for OCR Pin
KarstenK30-Jan-19 20:08
mveKarstenK30-Jan-19 20:08 
PraiseNice ! Pin
RickZeeland28-Jan-19 3:59
mveRickZeeland28-Jan-19 3:59 
QuestionPlease check the download for this Pin
MadMyche28-Jan-19 3:56
professionalMadMyche28-Jan-19 3:56 
AnswerRe: Please check the download for this Pin
SergVoloshyn28-Jan-19 4:24
SergVoloshyn28-Jan-19 4:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.