AIFFEL/Exploration

[E-15] 문자를 읽을 수 있는 딥러닝 (OCR)

알밤바 2022. 2. 25. 15:21
728x90
반응형

해당 포스팅은 AIFFEL에서 제공한 학습자료를 통해 공부한 것을 정리한 것임을 밝힙니다.

 

실습목표

  • OCR의 과정을 이해합니다.
  • 문자인식 결과의 표현방식을 이해합니다.
  • 파이썬을 통해 OCR을 사용할 수 있습니다.

1. OCR이란?

OCR은 Optical Character Recognition으로 광학 문자 인식이라고 한다.

 

구글의 클라우드 기반 OCR API로 손십게 딥러닝 기반의 최신 OCR을 테스트해볼 수 있다.

구글 OCR API

 

구글 API에서는 문자의 영역을 사각형으로 표현하고 우측에 'Block'과 'Paragraph'로 구분해서 인식 결과를 나타낸다. 구글 API가 이미지에 박스를 친 다음 박스 별 텍스트의 내용을 알려준 것처럼 문자 모델은 보통 2단계로 이루어진다.

 

1) Text Detection(문자검출) : 입력받은 사진 속에서 문자의 위치를 찾아낸다.

2) Text Recognition(문자인식) : 찾은 문자 영역으로부터 문자를 읽어낸다.

 

[ 출처: https://brunch.co.kr/@kakao-it/318 ]

위의 카카오 OCR 모델은 문자가 있는 영역의 정보(coord, Text recognition)을 찾아내고 각 영역에서 문자를 인식한다. 여기서 문자의 영역을 표현하는 방법은 사각형의 네 꼭짓점 좌표를 알려주는 방법을 제시한다.

이런 방법 이외에도 다양한 방법들이 있다. 예를 들면 축에 정렬된 사각형인 Bounding box, 그리고 돌아간 사각형 Oriented boundind box, 자유로운 사각형은 Quadrangle 그리고 다각형인 Polygon, Pixel 수준으로 영역을 표현한 Mask 등이 있다.

 

OCR은 이미 실용적인 단계로의 연구가 많이 진척되어, 실전 레벨의 구체적인 정보를 접하는 것이 훨씬 도움이 된다.

[참고 영상]

네이버 데뷰 2018, 이활석님의 CRAFT 모델소개와 연구 경험

엄태웅님 영상 - 사진 속 글자 읽기, OCR

 


2. 딥러닝 문자인식의 시작

[ 출처: http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf ]

위의 그림은 LeNet-5의 구조이다. Convolution 레이어와 최종 출력 레이어로 이루어져 있다.

[ 출처: http://yann.lecun.com/exdb/lenet/stroke-width.html ]

딥러닝 분류 모델 LeNet은 간단한 구조로도 어려운 글자를 읽을 수 있다. 뿐만 아니라 문자가 노이즈에 가려져도 문자를 잘 읽는다. 

 


3. detection (사진 속 문자 찾아내기)

 

[ 출처: https://www.semanticscholar.org/paper/End-To-End-Text-Detection-Using-Deep-Learning-Ibrahim/6d8584a900bd77afec55921c2d2d1cb6f1244b25/figure/0 ]

딥러닝 기반 객체 검츨 방법에는 Regression(회귀) 방식이나 Segmetation(세그멘테이션) 방식이 있다.

Regression은 기준으로 하는 박스 대비 문자의 박스가 얼마나 차이가 나는지를 학습한다. Segmentation은 픽셀 단위로 해당 픽셀이 문자를 표현하는 지를 분류하는 문제(pixel-wise classification)라고 볼 수 있다.

여기서는 문자를 찾아내는 딥러닝 모델이 우리가 흔히 들어본 Object Detection과 큰 차이가 없다는 것만 알아두어도 된다.

 

[ https://it.wikipedia.org/wiki/File:Detected-with-YOLO--Schreibtisch-mit-Objekten.jpg ]
[ https://answers.opencv.org/question/69135/how-can-i-draw-rectangle-with-matofkeypoint-for-text-detection-java/ ]

위의 두 사진을 보고 문자와 일반적인 객체는 어떤 특성이 다른지 알아보자.

일반적인 객체는 물체에 따라서 크기가 일정한 특징을 가진다. 하지만 문자는 영역과 배치가 자유로워 문자를 검출하기 위한 설정이 필요하다. 또한 객체는 물체 간 거리가 충분히 확보되는 데에 반해 글자는 매우 촘촘하게 배치되어 있다. 그래서 우리가 사진 속 문자를 검출할 때, 문자, 단어, 줄, 단락 단위 등으로 검출해낼 수 있다.

 


4. recognition (사진 속 문자 읽어내기)

[ 출처: ICDAR (https://rrc.cvc.uab.es/?ch=4&com=tasks) ]

문자 인식은 사진 속에서 문자를 검출해 내는 검출 모델이 영역을 잘라서 주면 그 영역에 어떤 글자가 포함되어 있는지 읽어내는 과정이다. 사실 이 과정은 이미지 문제보다는 자연어 처리에서 많은 영감을 받았다.

 

[ 출처: Ibrahim, Ahmed Sobhy Elnady. End-To-End Text Detection Using Deep Learning. Diss. Virginia Tech, 2017. (https://arxiv.org/abs/1507.05717) ]

이미지 내의 문자 인식 모델의 기본적인 방법 중 하나가 CNN과 RNN을 결합한 CRNN 모델이다. 이미지 내의 텍스트와 연관된 특징을 CNN을 통해 추출한 후에 스텝 단위의 문자 정보를 RNN으로 인식하는 것이다.

 


5. keras-ocr

필요한 라이브러리인 `keras_ocr`과 인식 결과의 시각화를 위한 `matplotlib.pyplot`을 불러온다.
`keras_ocr.pipeline.Pipeline()`는 인식을 위한 파이프라인을 생성하는데, 이때 초기화 과정에서 미리 학습된 모델의 가중치(weight)를 불러오게 된다. (검출기와 인식기를 위한 가중치)

 

import matplotlib.pyplot as plt
import keras_ocr

# keras-ocr이 detector과 recognizer를 위한 모델을 자동으로 다운로드받게 됩니다. 
pipeline = keras_ocr.pipeline.Pipeline()

 

만들어둔 파이프라인의 `recognize()`에 이미지를 넣어주자.

 

# 테스트에 사용할 이미지 url을 모아 봅니다. 추가로 더 모아볼 수도 있습니다. 
image_urls = [
  'https://source.unsplash.com/M7mu6jXlcns/640x460',
  'https://source.unsplash.com/6jsp4iHc8hI/640x460',
  'https://source.unsplash.com/98uYQ-KupiE',
  'https://source.unsplash.com/j9JoYpaJH3A',
  'https://source.unsplash.com/eBkEJ9cH5b4'
]

images = [ keras_ocr.tools.read(url) for url in image_urls]
prediction_groups = [pipeline.recognize([url]) for url in image_urls]

 

인식된 결과를 `pyplot`으로 시각화해보자. 내부적으로 `recognize()`는 검출기와 인식기를 두고, 검출기로 바운딩 박스를 검출한 뒤, 인식기가 각 박스로부터 문자를 인식하는 과정을 거치도록 한다.
    
[주의사항]  
keras_ocr은 한글 데이터셋으로 훈련이 되어있지 않는 모델이다. 한글 텍스트의 detection은 정상적으로 진행되더라도 recognition 결과가 엉뚱하게 나올 수 있으니 주의!

 

# Plot the predictions
fig, axs = plt.subplots(nrows=len(images), figsize=(20, 20))
for idx, ax in enumerate(axs):
    keras_ocr.tools.drawAnnotations(image=images[idx], 
                                    predictions=prediction_groups[idx][0], ax=ax)

 


6. Tessract

 

테서렉트는 구글에서 후원하는 OCR 오픈소스 라이브러리이다.

 

테서랙트로 문자를 검출하고 이미지를 잘라보자 (detection)
* `crop_word_regions`함수는 테스트 이미지를 받아서 문자 검출을 진행한 후 검출된 문자 영역을 crop한이미지로 만어 그 파일들의 list를 리턴하는 함수이다.
       
기본적으로 `pytesseract.image_to_data()`를 사용한다. 파이썬에서 편하게 사용하기 위해서 `pytesseract`의 `Output`을 사용해서 결괏값의 형식을 딕셔너리 형식으로 설정해 주게 된다. 이렇게 인식된 결과는 바운딩 박스의 left, top, width, height 정보를 가지게 된다. 바운딩 박스를 사용해 이미지의 문자 영역들을 파이썬 `PIL(pillow)` 또는 `opencv` 라이브러리를 사용해 잘라서 `cropped_image_path_list`에 담아 리턴하였다.

 

import os
import pytesseract
from PIL import Image
from pytesseract import Output
import matplotlib.pyplot as plt

# OCR Engine modes(–oem):
# 0 - Legacy engine only.
# 1 - Neural nets LSTM engine only.
# 2 - Legacy + LSTM engines.
# 3 - Default, based on what is available.

# Page segmentation modes(–psm):
# 0 - Orientation and script detection (OSD) only.
# 1 - Automatic page segmentation with OSD.
# 2 - Automatic page segmentation, but no OSD, or OCR.
# 3 - Fully automatic page segmentation, but no OSD. (Default)
# 4 - Assume a single column of text of variable sizes.
# 5 - Assume a single uniform block of vertically aligned text.
# 6 - Assume a single uniform block of text.
# 7 - Treat the image as a single text line.
# 8 - Treat the image as a single word.
# 9 - Treat the image as a single word in a circle.
# 10 - Treat the image as a single character.
# 11 - Sparse text. Find as much text as possible in no particular order.
# 12 - Sparse text with OSD.
# 13 - Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.

def crop_word_regions(image_path='./images/sample.png', output_path='./output'):
    if not os.path.exists(output_path):
        os.mkdir(output_path)
    custom_oem_psm_config = r'--oem 3 --psm 3'
    image = Image.open(image_path)

    recognized_data = pytesseract.image_to_data(
        image, lang='kor',    # 한국어라면 lang='kor'
        config=custom_oem_psm_config,
        output_type=Output.DICT
    )
    
    top_level = max(recognized_data['level'])
    index = 0
    cropped_image_path_list = []
    for i in range(len(recognized_data['level'])):
        level = recognized_data['level'][i]
    
        if level == top_level:
            left = recognized_data['left'][i]
            top = recognized_data['top'][i]
            width = recognized_data['width'][i]
            height = recognized_data['height'][i]
            
            output_img_path = os.path.join(output_path, f"{str(index).zfill(4)}.png")
            print(output_img_path)
            cropped_image = image.crop((
                left,
                top,
                left+width,
                top+height
            ))
            cropped_image.save(output_img_path)
            cropped_image_path_list.append(output_img_path)
            index += 1
    return cropped_image_path_list


work_dir = os.getenv('HOME')+'/aiffel/ocr_python'
img_file_path = work_dir + '/penguin.jpg'   #테스트용 이미지 경로입니다. 본인이 선택한 파일명으로 바꿔주세요. 

cropped_image_path_list = crop_word_regions(img_file_path, work_dir)

 

검출된 바운딩 박스 별로 잘린 이미지를 넣어주면 영역별 텍스트가 결괏값으로 나오는 image_to_string()을 사용하게 된다. 인식된 결과가 실제 이미지와 맞는지 확인해보자.

 

def recognize_images(cropped_image_path_list):
    custom_oem_psm_config = r'--oem 3 --psm 7'
    
    for image_path in cropped_image_path_list:
        image = Image.open(image_path)
        recognized_data = pytesseract.image_to_string(
            image, lang='eng',    # 한국어라면 lang='kor'
            config=custom_oem_psm_config,
            output_type=Output.DICT
        )
        print(recognized_data['text'])
    print("Done")

# 위에서 준비한 문자 영역 파일들을 인식하여 얻어진 텍스트를 출력합니다.
recognize_images(cropped_image_path_list)
728x90
반응형