레이블이 ocr인 게시물을 표시합니다. 모든 게시물 표시
레이블이 ocr인 게시물을 표시합니다. 모든 게시물 표시

2016년 8월 21일 일요일

tesseract command line 인식 테스트 해보기


tesseract site
https://github.com/tesseract-ocr/tesseract/wiki
https://github.com/tesseract-ocr/tesseract/wiki/Downloads

binary 형태를 받아볼 수 있음, 이러한 이유 <= 빌드가 필요 없음, image를 이용해 training을 해볼 수 있음, image를 이용해 미리 인식 테스트를 해볼 수 있음

exe파일을 받았습니다.
아래 경로 참고

tesseract-ocr-setup-3.02.02.exe

툴의 사용법
setup으로 설치하면 path까지 자동으로 설치됩니다.

D:\Program Files (x86)\Tesseract-OCR\doc 폴더에 테스트용 이미지가 있습니다.
eurotext.tif, phototest.tif

cmd line에서는 아래와 같이 실행하면 됩니다. 첫번째 인자는 이미지가 되고 두번째 인자는 생성되는 파일입니다.

D:\Program Files (x86)\Tesseract-OCR>tesseract doc\eurotext.tif E:/out
Tesseract Open Source OCR Engine v3.02 with Leptonica




여러 언어를 사용할때는 아래와 같이 + 로 연결해서 -l 옵션을 사용하면 됩니다.
It can even be used with multiple languages traineddata at a time eg. English and German:

  tesseract myscan.png out -l eng+deu


Training
아래 링크 참조
https://github.com/tesseract-ocr/tesseract/wiki/TrainingTesseract
트레이닝은 새로운 언어를 추가하거나 입력되는 인식률을 높이기 위해서 하게됩니다.
위에서 받은 binary가 3.02 라서 다음 링크를 사용해야 합니다.
https://github.com/tesseract-ocr/tesseract/wiki/Training-Tesseract-3.00%E2%80%933.02
영어로 된 정확한(동작가능한) 예제를 구하지는 못했습니다.
http://blog.secmem.org/489 여기 중간 부분을 보면 training을 어떻게 하면 되는지 정보가 나옵니다.

D:\Program Files (x86)\Tesseract-OCR\tesseract-ocr\doc\tesseracticdar2007.pdf 파일을 보면 tesseract 에 대한 원리가 나오는데 읽어봐도 이해하기는 힘드네요.



2016년 8월 20일 토요일

Simple Android OCR 소스 분석 TessApi

앞에서 빌드했던것을 바탕으로 소스를 분석 해보도록 하겠습니다.

소스는 아래 링크에서 받을 수 있습니다.
https://github.com/GautamGupta/Simple-Android-OCR

한글 되게 패치한 소스를 기반으로 분석진행하겠습니다.
한글 관련 사항은 이전 글 확인하시기 바랍니다.

TessApi를 사용하는 코드는 아래 코드입니다.

TessBaseAPI baseApi = new TessBaseAPI();
baseApi.setDebug(true);
baseApi.init(DATA_PATH, lang);
baseApi.setImage(bitmap);

String recognizedText = baseApi.getUTF8Text();

baseApi.end();


TessBaseAPI jni로 되어있는 library들로 연결되어있습니다.
이 부분은 따로 추후에 분석 해보도록 하겠습니다.

BaseAPI는 init을 호출하고 setImage를 호출하면 getUTF8Text 함수로 부터 OCR로 변환된 글씨를 받을 수 있습니다.
위코드는 사진을 찍는 부분에 들어가야 할겁니다.
onPhotoTaken() 메소드 안에 해당 코드가 들어있습니다.

카메라 구동은 저당될 파일 경로를 정하고 MediaStore.ACTION_IMAGE_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);
}
사진이 찍히고 나면 다시 돌아오게 되는데 아래 함수로 돌아오게 됩니다.
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {

   Log.i(TAG, "resultCode: " + resultCode);

   if (resultCode == -1) {
      onPhotoTaken();
   } else {
      Log.v(TAG, "User cancelled");
   }
}

여기에서 resultCode == -1 조건을 넣었는데 다른 예제들은 RESULT_OK를 사용합니다.

RESULT_OK

Added in API level 1
int RESULT_OK
Standard activity result: operation succeeded.
Constant Value: -1 (0xffffffff)

사진을 찍기전에는 sd card에 있는 언어 학습 데이터를 내부영역으로 복사 하는 작업을 합니다.
assets에 있는 traindata를 sdcard영역으로 복사를 합니다.
제가 원본소스를 변형해서 dataCopy 메소드로 이름을 만들었습니다.
private void dataCopy(String one) {
   // lang.traineddata file with the app (in assets folder)
   // You can get them at:
   // http://code.google.com/p/tesseract-ocr/downloads/list
   // This area needs work and optimization
   if (!(new File(DATA_PATH + "tessdata/" + one + ".traineddata")).exists()) {
      try {

         AssetManager assetManager = getAssets();
         InputStream in = assetManager.open("tessdata/" + one + ".traineddata");
         //GZIPInputStream gin = new GZIPInputStream(in);
         OutputStream out = new FileOutputStream(DATA_PATH 
              + "tessdata/" + one + ".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 " + one + " traineddata");
      } catch (IOException e) {
         Log.e(TAG, "Was unable to copy " + one + " traineddata " + e.toString());
      }
   }
}

호출해주는 쪽은 eng+kor 이렇게 붙어 있는 경우 분리해 주기위해 아래와 같이 표현하였습니다.
String lang_one[] = lang.split("\\+");
for(String one : lang_one) {
   dataCopy(one);
}

사진을 찍고 TessBaseAPI를 호출하기전에 이미지를 bitmap으로 가공을 해주어야 하는데요

onPhotoTaken 메소드 앞부분에서 진행합니다.
파일을 읽고 exif 정보를 보고 회전되어있다면 bitmap을 회전시켜서 bitmap ARGB8888 format을 만듭니다.
해당포맷은 alpha 채널을 포함한 rgb 32bit가 하나의 점을 이루게 되는 포맷입니다.
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());
   }









2016년 8월 6일 토요일

ocr tesseract 빌드기 (3) 한글 데이터 사용

기존 빌드한 tresseract 에 한글 처리하는것을 구현해보도록 하겠습니다.

한글 데이터는 아래에서 받을 수 있습니다.
https://github.com/tesseract-ocr/tessdata/blob/master/kor.traineddata

기본 sample은 eng만 가능하였기때문에 여러언어를 사용하는 방법에 대해서 살펴보았습니다.
여러 언어를 사용하는 방법
http://stackoverflow.com/questions/16508796/how-can-i-use-multiple-language-support-on-android-with-tesseract
더하기로 연결해 주면 되네요. ^^;
baseApi.init(dataPath, "eng+kor");

수정 된 소스 입니다.
patch 형태


diff -r D:\work\android\ocr\tesseract2\SimpleAndroidOCRActivity_bak.java D:\work\android\ocr\tesseract2\Simple-Android-OCR-master\simpleAndroidOCR\src\main\java\com\datumdroid\android\ocr\simple\SimpleAndroidOCRActivity.java
37c37
<  public static final String lang = "eng";
---
>  public static final String lang = "eng+kor";
68c68,82
<   
---
> 
>   String lang_one[] = lang.split("\\+");
>   for(String one : lang_one) {
>    dataCopy(one);
>   }
> 
>   // _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";
>  }
> 
>  private void dataCopy(String one) {
73c87,88
<   if (!(new File(DATA_PATH + "tessdata/" + lang + ".traineddata")).exists()) {
---
> 
>   if (!(new File(DATA_PATH + "tessdata/" + one + ".traineddata")).exists()) {
77c92
<     InputStream in = assetManager.open("tessdata/" + lang + ".traineddata");
---
>     InputStream in = assetManager.open("tessdata/" + one + ".traineddata");
80c95
<       + "tessdata/" + lang + ".traineddata");
---
>       + "tessdata/" + one + ".traineddata");
92,93c107,108
<     
<     Log.v(TAG, "Copied " + lang + " traineddata");
---
> 
>     Log.v(TAG, "Copied " + one + " traineddata");
95c110
<     Log.e(TAG, "Was unable to copy " + lang + " traineddata " + e.toString());
---
>     Log.e(TAG, "Was unable to copy " + one + " traineddata " + e.toString());
98,106d112
< 
<   
< 
<   // _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";
226,228c232,234
<   if ( lang.equalsIgnoreCase("eng") ) {
<    recognizedText = recognizedText.replaceAll("[^a-zA-Z0-9]+", " ");
<   }
---
>   //if ( lang.equalsIgnoreCase("eng") ) {
>   // recognizedText = recognizedText.replaceAll("[^a-zA-Z0-9]+", " ");
>   //}


전체 소스 입니다.

package com.datumdroid.android.ocr.simple;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;

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;

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
   // You can get them at:
   // http://code.google.com/p/tesseract-ocr/downloads/list
   public static final String lang = "eng+kor";

   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";

   @Override   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);

      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");
            }
         }

      }

      String lang_one[] = lang.split("\\+");
      for(String one : lang_one) {
         dataCopy(one);
      }

      // _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";
   }

   private void dataCopy(String one) {
      // lang.traineddata file with the app (in assets folder)
      // You can get them at:
      // http://code.google.com/p/tesseract-ocr/downloads/list
      // This area needs work and optimization
      if (!(new File(DATA_PATH + "tessdata/" + one + ".traineddata")).exists()) {
         try {

            AssetManager assetManager = getAssets();
            InputStream in = assetManager.open("tessdata/" + one + ".traineddata");
            //GZIPInputStream gin = new GZIPInputStream(in);
            OutputStream out = new FileOutputStream(DATA_PATH 
                 + "tessdata/" + one + ".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 " + one + " traineddata");
         } catch (IOException e) {
            Log.e(TAG, "Was unable to copy " + one + " traineddata " + e.toString());
         }
      }
   }

   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());
      }
      
      // Cycle done.
   }
   
   // www.Gaut.am was here
   // Thanks for reading!}

실행 화면







 결론은 그렇게 좋지는 않네요. 시간도 좀 걸리고.... 최적화라던가 이미지 처리하기위해서는 소스 검토가 필요해 보이네요.....




2016년 7월 31일 일요일

ocr tesseract 빌드기 (2)

앞에서 tesseract 를 ndk를 이용하여 빌드하여보았습니다.
http://swlock.blogspot.kr/2016/07/ocr-tesseract.html
하지만 안드로이드 어플을 만들기에는 뭐가 뭔지 모르게 복잡합니다.


샘플을 구해봤습니다. 아래에서 소스를 받습니다.
https://github.com/GautamGupta/Simple-Android-OCR
https://github.com/rmtheis/tess-two 이전에 빌드했던것 무시하고...ㅡㅡ;;

폴더 구조를 다음과 같이 맞춥니다.





tess-two-master를 android studio에서 open 합니다.
android studio에 load 합니다. ( 2.0에서 작업하였습니다 )
빌드하는데 굉장히 오래걸립니다.
이렇게 하면 이전에 cmd line에서 했던 작업을 하게되네요.


그리고 나서 Simple-Android-OCR 프로젝트를 import 해줍니다.




Destination 폴더를 설정해야하는데 그건 상위 폴더를 설정하게 합니다.

아래와 같이 프로젝트 로드가 완료됩니다.




하지만 아래와 같은 오류가 있네요.
Error:Execution failed for task ':simpleAndroidOCR:processDebugManifest'.
> Manifest merger failed : uses-sdk:minSdkVersion 8 cannot be smaller than version 9 declared in library [Simple-Android-OCR-master:tesstwo:unspecified] D:\work\android\ocr\tesseract2\Simple-Android-OCR-master\simpleAndroidOCR\build\intermediates\exploded-aar\Simple-Android-OCR-master\tesstwo\unspecified\AndroidManifest.xml
Suggestion: use tools:overrideLibrary="com.googlecode.tesseract.android" to force usage

app은 minsdk 8로 설정되어서 문제가 발생하는데 라이브러리는 9로 되있다고 합니다.



빨간색으로 된 부분 8->9로 변경합니다.

그리고 Build > Make Project를 합니다.

이번에는 아래와 같은 오류가 발생합니다.
Error:Execution failed for task ':tesstwo:compileReleaseNdk'.
> Error: NDK integration is deprecated in the current plugin.  Consider trying the new experimental plugin.  For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental.  Set "$USE_DEPRECATED_NDK=true" in gradle.properties to continue using the current NDK integration.

아래와 같은 코드를 build.gradle에 추가해줍니다.
sourceSets {
    main {
        jni.srcDirs = []
    }
}



이제 완성되었습니다.



실행화면


----------------------------------------------------------------------------
이건 난이도가 있는 소스라고 하는데 해보지는 않았습니다.
https://github.com/rmtheis/android-ocr


Error:(1, 0) Plugin is too old, please update to a more recent version, or set ANDROID_DAILY_OVERRIDE environment variable to "abf87974ef3c08e3258fe37c7d5001161385f549"
<a href="fixGradleElements">Fix plugin version and sync project</a><br><a href="openFile:D:\work\android\ocr\tesseract2\Simple-Android-OCR-master\simpleAndroidOCR\build.gradle">Open File</a>

이런 에러 발생시 해결 방법이 여러개 있는것 같은데요.

http://stackoverflow.com/questions/29063968/plugin-is-too-old-please-update-to-a-more-recent-version-or-set-android-daily

아래 처럼 하니까 되네요
classpath 'com.android.tools.build:gradle:2.2.0-alpha4' ->

dependencies {
    classpath 'com.android.tools.build:gradle:2.1.2'}

2016년 7월 24일 일요일

ocr tesseract 빌드기

ocr tesseract 설치 도전 성공기 !!!

OCR은 광학 문자 읽기로 이미지로 부터 글자를 읽기 위한 방법을 말합니다.
안드로이드 환경내에서 OCR을 테스트 해보고 자신의 앱에 탑제하기 위해 이것 저것 테스트 해볼수 있는 환경 구축을 위해 도전함

환경은 windows10 64bit 환경

OCR이 뭔지 대략적인 부분....
http://blog.secmem.org/489

tesseract 3.0을 성공했다는 얘기....
http://www.androidpub.com/1359474

이건 뭐지??
https://code.google.com/archive/p/tesseract-android-tools/downloads

tesseract-android-tools-1.00.tar.gz 다운받음

tesseract-ocr 소스 받음
https://github.com/tesseract-ocr/tesseract
tesseract-master.zip

https://github.com/danbloomberg/leptonica
leptonica-master.zip

https://android.googlesource.com/platform/external/jpeg.git/+/master
jpeg.git-master.tar.gz


위에꺼는 참고용이고, 컴파일 시도 했지만 성공 못함
여기서 부터 다시 시작함

http://gaut.am/making-an-ocr-android-app-using-tesseract/ 이걸로 시도
소스 받음 https://github.com/rmtheis/tess-two

NDK 설치 : 구글 검색후 설치

ndk 설치경로에 받은소스중 tess-two 폴더만 복사합니다.
즉 ndk를 압축푼 경로에 tess-two 폴더가 위치되도록 하고
tess-two 폴더안에서 ..\ndk-build 를 실행합니다.
그러면 빌드시작 됩니다..
둥둥둥


clang++.exe: error: unable to execute command: program not executable 오류 발생시

http://stackoverflow.com/questions/25045472/clang-unable-to-execute-command-program-not-executable 여기 참고

나의 경우 ndk path 경로상에 한글이나 띄워쓰기가 없는지 다시 확인

10분 정도 걸림.. 일단 빌드 완료....  테스트는 다음에 ~~~~