Skip to content
coooldoggy.dev

OCR API 비교

Android1 min read

OCR(Optical character recognition)은 사람이 쓰거나 기계로 인쇄한 문자의 영상을 기계가 읽을 수 있는 문자로 변환하는것이다.

최근 ML이 트렌드로 떠오르면서 다양한 기업들에서 이를 기반으로 한 OCR API를 제공하고 있는데 몇개를 사용해보고 정리해보고자 한다.

Firebase ML Kit

Google에서 제공하는 모바일용 ML kit이다. 무료이지만 라틴 문자만 인식이 가능하다.

인식속도가 빠르고 이미지만 전송하면 텍스트를 뽑아주기 때문에 사용도 편리하다.

사용법

  1. Firebase 프로젝트를 생성한다. Firebase 콘솔 에 로그인하여 프로젝트 추가 버튼을 누르고, 가이드대로 따라한다.
  2. 프로젝트의 왼쪽사이드바에서 개발> ML kit를 클릭한다.
  3. 텍스트 인식을 눌러 시작한다.

예제 코드

1private fun googleAnalyze(path: String){
2 val image: FirebaseVisionImage
3 try {
4 image = FirebaseVisionImage.fromFilePath(this, Uri.fromFile(File(path)))
5 val detector = FirebaseVision.getInstance().onDeviceTextRecognizer
6 val result = detector.processImage(image)
7 .addOnSuccessListener {
8 Log.d(TAG, it.text)
9 }.addOnFailureListener {
10 Log.d(TAG, it.message)
11 }
12 }catch (e: IOException){
13 e.printStackTrace()
14 }
15 }

KaKao Vision API

카카오에서 제공하는 Rest API에 속해있다.

이미지에서 문자영역을 감지하여 문자영역의 좌표값과 이미지를 던지면 텍스트가 출력된다.

이미지에서 문자영역을 추출하는 API와 텍스트를 인식하는 API가 따로따로 있기때문에 API를 두번 콜해야 문자 값을 얻을 수 있다.

사용법

  1. 카카오 개발자 페이지 에 접속하여 로그인한다.
  2. 새로운 앱을 만든다.
  3. Rest API 키 값을 얻어 API를 호출한다.

예제코드

먼저 이미지 파일을 https://kapi.kakao.com/v1/vision/text/detect 에 POST 방식으로 넘겨 텍스트 영역의 좌표값을 얻는다.

APIManager.kt

카카오 비전 API 호출 시 이미지 사이즈는 4096x4096이하여야 하고 최대 용량은 2MB이기 때문에 파일을 POST로 넘기기 전에 용량을 줄여주었다.

1object ApiManager {
2 private val TAG = ApiManager::class.java.simpleName
3
4 fun getKakaoBoxes(fileUri: String): Observable<DataModel.boxResult> {
5 val kakaoBoxService = KakaoVisionApiService.createKakaoOCR()
6 scaleDown(fileUri)
7 val file = File(fileUri)
8 val requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
9
10 return kakaoBoxService.getKakaoBoxresult(KAKAO_KEY, requestBody)
11 }
12
13 fun scaleDown(path: String) {
14 var photo = BitmapFactory.decodeFile(path)
15 photo = Bitmap.createScaledBitmap(photo, 1000, 1000, false)
16 var bytes = ByteArrayOutputStream()
17 photo.compress(Bitmap.CompressFormat.JPEG, 80, bytes)
18
19 var file = File(path)
20 file.createNewFile()
21 var output = FileOutputStream(file)
22 output.write(bytes.toByteArray())
23 output.close()
24
25 }
26}
DataModel.kt
1object DataModel {
2 data class boxResult(@SerializedName("result") val result : boxes)
3 data class boxes( @SerializedName("boxes") val boxes : JsonArray)
4}

API의 응답 형태가 아래와 같으므로 data class를 만들어 준다.

1{"result":{"boxes":[[[345,554],[417,554],[417,638],[345,638]]]}}
KaKaoVisionApiService.kt
1interface KakaoVisionApiService {
2
3 @POST(KAKAO_BOX_SUB_URL)
4 @Multipart
5 fun getKakaoBoxresult(@Header("Authorization") key: String, @Part("file\"; filename=\"photo.jpg\"") file: RequestBody) : Observable<DataModel.boxResult>
6
7 companion object{
8
9 fun createKakaoOCR(): KakaoVisionApiService{
10 return getKakaoOCRApiService().create(KakaoVisionApiService::class.java)
11 }
12
13 private fun getKakaoOCRApiService() : Retrofit {
14 return Retrofit.Builder()
15 .client(provideOkHttpClient(provideLoggingInterceptor()))
16 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
17 .addConverterFactory(GsonConverterFactory.create())
18 .baseUrl(KAKAO_BASE_URL)
19 .build()
20 }
21
22
23 private fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient {
24 val b = OkHttpClient.Builder()
25 b.addInterceptor(interceptor)
26 return b.build()
27 }
28
29
30 private fun provideLoggingInterceptor(): HttpLoggingInterceptor {
31 val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
32 Log.d("DEBUG-API-LOG", it)
33 })
34 interceptor.level = HttpLoggingInterceptor.Level.BODY
35
36 return interceptor
37 }
38
39 }
40}

Retrofit 객체를 생성하는 인터페이스를 만들어준다.

Multipart로 파일을 넘기는 부분에서 삽질을 많이했는데, 검색하면 죄다 MultipartBody.Part로 넘기라고 나온다. 그런데 그렇게 넘기면 illegal type 이라고 오류가 리턴된다. RequestBody로 넘겨주고 Part에는 요청 파라미터 이름인 file을 설정해준다.

MainActivity.kt
1private fun kakaoAnalyzeBox(path: String){
2 ApiManager.getKakaoBoxes(path).subscribeOn(Schedulers.io())
3 .observeOn(AndroidSchedulers.mainThread())
4 .doOnNext {
5 Log.d(TAG, "type= ${it.result.boxes}")
6 kakaoAnalyzeOcr(path, "${it.result.boxes}")
7 }
8 .subscribe({
9 Log.d(TAG, "subscribe" + it.result)
10 }, {t: Throwable? ->
11 Log.e(TAG, "kakaoAnalyzeBox"+t?.message.toString())
12 })
13 }

메인액티비티에서 카메라로 사진을 찍은 후 Path를 넣어 콜해준다. 그리고 이후에 텍스트 인식 API를 콜한다.

MainActivity.kt

kakaoAnalyzeBox 함수에서 호출하는 OCR함수는 다음과 같다.

1private fun kakaoAnalyzeOcr(path: String, box: String){
2 ApiManager.getKakaoOCR(path, box).subscribeOn(Schedulers.io())
3 .observeOn(AndroidSchedulers. mainThread())
4 .subscribe ({
5 tv_result.text = it.result.get("recognition_words").asString
6 },{t: Throwable? ->
7 Log.e(TAG, "kakaoAnalyzeOcr"+t?.message.toString())
8 tv_result.text = "사진이 인식되지 않습니다. 다시 촬영해주세요."
9 })
10 }

마찬가지로 ApiManager와 KakaoVisionApiService에 함수를 만들고 DataModel에도 응답 JSON 형식의 객체를 만들어 준다.

DataModel.kt
1data class ocrResult(var result: JsonObject, var recognition_words: JsonArray)
ApiManager.kt
1fun getKakaoOCR(fileUri: String, box: String): Observable<DataModel.ocrResult> {
2 val kakaoOcrService = KakaoVisionApiService.createKakaoOCR()
3 val file = File(fileUri)
4 val requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
5 return kakaoOcrService.getKakaoOCRresult(KAKAO_KEY, requestBody, box)
6 }
KakoVisionApiService.kt
1@POST(KAKAO_OCR_SUB_URL)
2 @Multipart
3 fun getKakaoOCRresult(@Header("Authorization") key: String,@Part("file\"; filename=\"photo.jpg\"") file: RequestBody, @Query("boxes")box: String) : Observable<DataModel.ocrResult>

이렇게 해서 실행하면 kakao OCR API를 사용하여 텍스트를 추출할 수 있다.

Google Cloud Vision

Firebase ML kit와 같이 구글에서 제공하는 서비스이지만, ML kit와 다르게 Cloud Vision은 라틴어 뿐만아니라 한국어를 포함한 다양한 언어를 제공한다. (물론 FIrebase에서 Blazer 요금제를 사용해야한다.) 하지만 1000회까지는 무료이고, Blazer요금제를 사용하더라도 무료 사용량 안에서는 과금이 되지 않기 때문에 사용량이 적다면 사용해볼 만 하다.

사용방법은 Firebase ML Kit와 거의 유사하다. 한가지 다른 점은 Option을 설정해서 감지 언어를 설정할 수 있다.

1private fun gcpAnalyze(path: String){
2 val image: FirebaseVisionImage = FirebaseVisionImage.fromFilePath(this, Uri.fromFile(File(path)))
3 val options = FirebaseVisionCloudTextRecognizerOptions.Builder().setLanguageHints(listOf("ko")).build()
4 val dectector = FirebaseVision.getInstance().getCloudTextRecognizer(options)
5 val result = dectector.processImage(image).addOnSuccessListener {
6 Log.d(TAG, it.text)
7 }.addOnFailureListener { e->
8 Log.d(TAG, e.message)
9 }
10 }

Full Code는 여기 에서 확인할 수 있다.