본문 바로가기
안드로이드 코틀린

[Kotlin][Android] 안드로이드 OCR 기능 만들기

by teamnova 2022. 4. 1.
728x90

 

java 버전은 https://stickode.tistory.com/135 에서 확인해주세요

 

 

안녕하세요~

 

오늘은 OCR(Optical character recognition) 기능을 구현해 보겠습니다. OCR는 인간이 종이 위에 써 놓은 글씨를 인지하여 텍스트 데이터로 바꿔주는 기능을 말해요. 이 포스팅에서는 스틱코드를 사용하여 글자가 들어 있는 이미지에서 글자를 인식하는 간단한 앱을 만들어보겠습니다.

 


 

# 환경 세팅

 

1. 라이브러리 추가

▶ 해당 기능을 사용하기 위해서는  tess-two라는 모듈이 필요합니다. 위 그림처럼 build.gaddle(moudle) 파일을 열어서 맨 아래 한 줄을 추가하고 동기화시켜줍니다. 최신 버전은 여기서 확인하실 수 있습니다.

 

 

 

 

2. Language Data 추가

 

▶ 인식할 언어 데이터도 추가해줍니다. 영어와 한글을 인식해서 텍스트로 옮기기 위해 두 가지 언어 데이터를 추가해줬습니다. 언어 데이터는 여기에서 다운로드할 수 있습니다.

 

assets 폴더가 없으신 분들은 [우클릭] -> [New] -> [Forder] -> [Asset Folder]를 만드시면 됩니다.

 

 

바로 붙여넣기가 안되시는 분은 폴더를 찾아가시면 되는데요. 폴더 경로는 아래 사진과 같이 경로를 얻어 폴더를 직접 여셔서 파일을 붙여넣으시면 됩니다.

 

 

3. 샘플 이미지 추가

샘플 이미지 추가

▶ 영어와 한글을 각각 테스트해보기 위해 샘플 이미지를 준비했습니다.

 

 

# 레이아웃 그리기

 

activity_main.xml

 

▶ 분석할 이미지를 띄워주는 화면과 OCR을 동작시키는 버튼, 그리고 결과를 출력하는 화면을 만들어 줍니다. 아래는 레이아웃 전체 소스입니다.

 

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"
    tools:context="com.example.stickcodeocr.MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:background="#ffffff"
        android:id="@+id/ImageContainer">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/imageView"
            android:src="@drawable/sample_eng"/>

    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_below="@id/ImageContainer"
        android:clickable="true"
        android:onClick="processImage"
        android:background="#166e78"
        android:id="@+id/OCRButtonContainer">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="RUN OCR"
            android:textSize= "18dp"
            android:textColor="#ffffff"
            android:layout_centerVertical="true"
            android:layout_centerHorizontal="true"
            android:clickable="true"
            android:onClick="processImage"/>

    </RelativeLayout>

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/OCRButtonContainer"
        android:padding="10dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="여기에 결과 출력"
            android:id="@+id/OCRTextView"
            android:textSize="15dp"
            android:textColor="#169cdf"
            android:layout_centerVertical="true"
            android:layout_centerHorizontal="true"
            android:background="#fff"
            />
    </RelativeLayout>


</RelativeLayout>

 

 

 

 

# OCR 기능 구현

 

MainActivity.java

 

 

▶ 필요한 샘플 이미지 파일과 언어 데이터를 OCR 모듈에 세팅해줍니다. 그리고 MainActivity 아래쪽에 스틱코드를 이용해서 OCR 동작 기능을 추가해줍니다.

 

 

 

▶ 이제 불러온 코드에 OCR 결과를 보여줄 뷰에 연결시켜 줍니다.

 

 

 

▶ 전체 코드입니다.

MainActivity.java


import com.googlecode.tesseract.android.TessBaseAPI

import android.graphics.BitmapFactory

import android.os.Bundle

import android.widget.TextView

import android.graphics.Bitmap
import android.view.View
import android.widget.ImageView

import androidx.appcompat.app.AppCompatActivity
import java.io.*


class MainActivity : AppCompatActivity() {
    var image //사용되는 이미지
            : Bitmap? = null
    private var mTess //Tess API reference
            : TessBaseAPI? = null
    var datapath = "" //언어데이터가 있는 경로
    var OCRTextView // OCR 결과뷰
            : TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        OCRTextView = findViewById(R.id.OCRTextView)

        //이미지 디코딩을 위한 초기화
        image = BitmapFactory.decodeResource(resources, R.drawable.sample_kor) //샘플이미지파일

        val imageView: ImageView = findViewById(R.id.imageView);
        imageView.setImageBitmap(image)
        //언어파일 경로
        datapath = "$filesDir/tesseract/"

        //트레이닝데이터가 카피되어 있는지 체크
        checkFile(File(datapath + "tessdata/"))

        //Tesseract API 언어 세팅
        val lang = "kor"

        //OCR 세팅
        mTess = TessBaseAPI()
        mTess!!.init(datapath, lang)
    }

    /***
     * 이미지에서 텍스트 읽기
     */
    fun processImage(view: View?) {
        var OCRresult: String? = null
        mTess!!.setImage(image)
        OCRresult = mTess!!.utF8Text
        OCRTextView!!.text = OCRresult
    }

    /***
     * 언어 데이터 파일, 디바이스에 복사
     */

    // 언어 파일 이름
    private val langFileName = "kor.traineddata"
    private fun copyFiles() {
        try {
            val filepath = datapath + "tessdata/" + langFileName
            val assetManager = assets
            val instream: InputStream = assetManager.open(langFileName)
            val outstream: OutputStream = FileOutputStream(filepath)
            val buffer = ByteArray(1024)
            var read: Int
            while (instream.read(buffer).also { read = it } != -1) {
                outstream.write(buffer, 0, read)
            }
            outstream.flush()
            outstream.close()
            instream.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    /***
     * 디바이스에 언어 데이터 파일 존재 유무 체크
     * @param dir
     */
    private fun checkFile(dir: File) {
        //디렉토리가 없으면 디렉토리를 만들고 그후에 파일을 카피
        if (!dir.exists() && dir.mkdirs()) {
            copyFiles()
        }
        //디렉토리가 있지만 파일이 없으면 파일카피 진행
        if (dir.exists()) {
            val datafilepath = datapath + "tessdata/" + langFileName
            val datafile = File(datafilepath)
            if (!datafile.exists()) {
                copyFiles()
            }
        }
    }
}

 

 

 

# 테스트

 

1. 영어

 

 

2. 한글

 

▶ 한글의 경우 정확도가 아쉬웠지만, 영어 / 한글 모두 정상적으로 동작하는 걸 확인할 수 있었습니다.