EasyOCR을 사용해 티켓 속 문자를 텍스트화 해보자 #1

bokyungkim·2022년 5월 25일
5
post-custom-banner

시작하며

프로젝트 소개

우리의 졸업프로젝트인 공연후기 기록/공유 웹서비스는 후기 작성 시 공연 티켓 이미지를 통해 일시, 장소, 좌석과 같은 정보를 추출해 입력을 자동화해주고, 이를 통해 실제 관람 인증을 할 수 있는 기능을 제공할 것이다.

이 기능은 OCR(광학 문자 인식)을 통해 이미지에 쓰여있는 글씨를 텍스트 데이터화 해야하므로 EasyOCR을 이용해 구현해보려고 한다.

EasyOCR이란?

공식 Github

EasyOCR은 문자 영역 인식(Detection) + 문자 인식(Recognition) 기능을 모두 하는 프레임워크이다. 2020년에 나타난 비교적 최신 OCR로 현재까지 많은 사람들이 이용하고 있고 80가지가 넘는 언어를 지원한다. 현재까지도 활발히 업데이트가 이루어지고 있다.

Detection은 Clova AI의 CRAFT를, Recognition은 CRNN을, 특징 추출을 위해 ResNet을, Sequence labeling을 위해 LSTM을, 그리고 decoder로 CTC를 사용한다. 또한 Recognition의 training pipeline으로 Clova AI의 deep-text-recognition-benchmark를 사용한다.

EasyOCR을 선택한 이유

원래는 EasyOCR이 아니라 Tesseract라는 툴을 사용하려 했는데, 다른 팀원이 직접 설치하고 학습시켜본 결과 다중언어를 인식하는데 문제가 있었다. 우리가 사용할 티켓 이미지에는 좌석과 같이 영어와 한국어가 혼용되는 경우가 많기 때문에 이 문제가 치명적이라 생각되어 다중언어가 지원되믄 EasyOCR로 변경하게 되었다.

Tesseract의 단점으로는 GPU 사용이 안된다는 점이 있었는데, 알아본 결과 GPU용 OpenCL 드라이버를 설치하기만 하면 된다고 한다.

우리는 학교에서 지원해주는 Tencent Cloud의 GPU를 사용하게 되었다.

EasyOCR 사용하기

설치

pip install easyocr

위의 명령어를 실행하면 가장 최신 버전의 EasyOCR을 설치할 수 있다.

간단한 사용


위의 티켓 이미지를 사용하여 간단하게 코드를 실행시켜보겠다.

import easyocr
reader = easyocr.Reader(['ko','en'])
result = reader.readtext('korean.jpg')
  1. ['ko','en']은 읽고 싶은 언어 list이다. 한 번에 여러 언어를 전달할 수 있지만 모든 언어를 함께 사용할 수 있는 것은 아니다. 영어는 모든 언어와 호환되며 공통의 문자를 공유하는 언어들은 보통 서로 호환된다.
  2. korean.jpg라는 파일 경로 대신 OpenCV image object(numpy array) 또는 이미지 파일을 바이트로 전달할 수도 있다. Raw image에 대한 URL도 사용할 수 있다.
  3. reader = easyocr.Reader(['ko','en']) 코드는 모델을 메모리에 올리기 위한 것이다. 시간이 좀 걸리지만 한 번만 실행하면 된다.

위에서 얻은 result 값을 출력해보면

[([[1679, 38], [1832, 38], [1832, 92], [1679, 92]], 'TH E', 0.6476037502288818), ([[1876, 41], [2228, 41], [2228, 93], [1876, 93]], 'MU $ ! C A L', 0.258080786147085), ([[102, 132], [1546, 132], [1546, 270], [102, 270]], '누군가 이 세상올 바로잡아야 한다', 0.7385519720772591), ([[1596, 74], [2288, 74], [2288, 334], [1596, 334]], '네스트', 0.4897126853466034), ([[122, 458], [319, 458], [319, 555], [122, 555]], '일시:', 0.9979812502861023), ([[343, 459], [600, 459], [600, 549], [343, 549]], '2022년', 0.9524948784652495), ([[622, 458], [1063, 458], [1063, 555], [622, 555]], '4월 8일(금)', 0.9976543335084993), ([[1088, 465], [1268, 465], [1268, 544], [1088, 544]], '7:30', 0.9756875845013037), ([[1293, 479], [1446, 479], [1446, 552], [1293, 552]], 'pm', 0.995496718899593), ([[115, 568], [310, 568], [310, 657], [115, 657]], '장소:', 0.9999803847538816), ([[342, 568], [807, 568], [807, 661], [342, 661]], '충무아트센터', 0.5856971367769742), ([[836, 565], [1078, 565], [1078, 662], [836, 662]], '대극장', 0.9999905020849161), ([[1544, 645], [2039, 645], [2039, 754], [1544, 754]], 'A석(가변석)', 0.9572623534311621), ([[99, 784], [519, 784], [519, 900], [99, 900]], '예약번호:', 0.9989407839639809), ([[554, 784], [1365, 784], [1365, 888], [554, 888]], 'T1884758730(1/1)', 0.7291078591424647), ([[1535, 752], [1893, 752], [1893, 868], [1535, 868]], '3층 7열', 0.43629919686040514), ([[1925, 752], [2142, 752], [2142, 866], [1925, 866]], '20번', 0.6562137007713318), ([[94, 899], [378, 899], [378, 1004], [94, 1004]], '예매자', 0.9983293788658966), ([[548, 880], [1885, 880], [1885, 1013], [548, 1013]], '**0814(인터파크_모바일-김보민', 0.9491857498205936), ([[1915, 886], [2054, 886], [2054, 989], [1915, 989]], '님)', 0.5225402476891089), ([[88, 1004], [511, 1004], [511, 1120], [88, 1120]], '전화번호:', 0.9999673729839963), ([[543, 1007], [1110, 1007], [1110, 1107], [543, 1107]], '010****1406', 0.7810824203696648), ([[1150, 998], [1347, 998], [1347, 1108], [1150, 1108]], '(87)', 0.6946203141657631), ([[1536, 992], [1714, 992], [1714, 1081], [1536, 1081]], '금액:', 0.9997430099499827), ([[1738, 989], [2216, 989], [2216, 1075], [1738, 1075]], '일반(70,00O원)', 0.42884277232423496), ([[1541, 1075], [1844, 1075], [1844, 1163], [1541, 1163]], '결제수단:', 0.9984372273274015), ([[1871, 1072], [2234, 1072], [2234, 1160], [1871, 1160]], '무통장 입금', 0.8799812982209939), ([[1538, 1152], [2237, 1152], [2237, 1250], [1538, 1250]], '판매일자: 2022/02/15', 0.9963363208147666), ([[74, 1291], [204, 1291], [204, 1367], [74, 1367]], '제작', 0.999928098233573), ([[286, 1283], [666, 1283], [666, 1371], [286, 1371]], '오디컴퍼니주) ,', 0.28185316865829496), ([[702, 1287], [918, 1287], [918, 1365], [702, 1365]], '티비씨 ,', 0.7673708254930159), ([[939, 1290], [1065, 1290], [1065, 1355], [939, 1355]], 'TJB', 0.9994315228953814), ([[72, 1379], [202, 1379], [202, 1454], [72, 1454]], '투자', 0.9992561048966915), ([[280, 1371], [709, 1371], [709, 1455], [280, 1455]], '라이언자산운용', 0.8015074539788276), ([[1395, 1355], [2144, 1355], [2144, 1551], [1395, 1551]], 'IJEATH NoIE', 0.42585979131638274), ([[64, 1458], [408, 1458], [408, 1543], [64, 1543]], '홍보 |마켓팅', 0.2658996673194796), ([[485, 1454], [798, 1454], [798, 1538], [485, 1538]], '오른리부쥐', 0.14574477980345313)]

위와 같은 리스트가 출력되는데, 각 항목은 각각 bounding box의 좌표, 인식한 텍스트 및 신뢰도를 나타낸다.

인식한 텍스트만 확인하려면 detail = 0을 추가하면 되고, GPU 사용 설정도 할 수 있다.

reader = easyocr.Reader(['ko','en'], gpu = True)
result = reader.readtext("./src/data2.jpeg", detail = 0)

결과:

['TH E', 'MU $ ! C A L', '누군가 이 세상올 바로잡아야 한다', '네스트', '일시:', '2022년', '4월 8일(금)', '7:30', 'pm', '장소:', '충무아트센터', '대극장', 'A석(가변석)', '예약번호:', 'T1884758730(1/1)', '3층 7열', '20번', '예매자', '**0814(인터파크_모바일-김보민', '님)', '전화번호:', '010****1406', '(87)', '금액:', '일반(70,00O원)', '결제수단:', '무통장 입금', '판매일자: 2022/02/15', '제작', '오디컴퍼니주) ,', '티비씨 ,', 'TJB', '투자', '라이언자산운용', 'IJEATH NoIE', '홍보 |마켓팅', '오른리부쥐']

하지만 EasyOCR 자체에서는 이미지 위에 bounding box와 인식한 텍스트를 그려주는 기능을 제공하지 않는다. OpenCV와 Pillow를 사용하면 이를 그릴 수 있다.

import easyocr
import numpy as np
import cv2
import random
import matplotlib.pyplot as plt
from PIL import ImageFont, ImageDraw, Image

reader = easyocr.Reader(['ko', 'en'], gpu = True)
result = reader.readtext('korean.jpeg')
img    = cv2.imread('korean.jpeg')
img = Image.fromarray(img)
font = ImageFont.truetype('NanumGothic-Bold.ttf', 50)
draw = ImageDraw.Draw(img)
np.random.seed(42)
COLORS = np.random.randint(0, 255, size=(255, 3),dtype="uint8")
for i in result :
    x = i[0][0][0] 
    y = i[0][0][1] 
    w = i[0][1][0] - i[0][0][0] 
    h = i[0][2][1] - i[0][1][1]

    color_idx = random.randint(0,255) 
    color = [int(c) for c in COLORS[color_idx]]

    draw.rectangle(((x, y), (x+w, y+h)), outline=tuple(color), width=2)
    draw.text((int((x + x + w) / 2) , y-2),str(i[1]), font=font, fill=tuple(color),)

plt.figure(figsize=(50,50))
plt.imshow(img)
plt.show()

결과:

EasyOCR

deep-text-recognition-benchmark

git clone https://github.com/clovaai/deep-text-recognition-benchmark.git

deep-text-recognition-benchmark는 Clova AI에서 제공하는 오픈소스 프로젝트로, 신경망 모델 학습 단계에서 사용할 것이다.

data labeling

학습데이터는 deep-text-recognition-benchmark에서 요구하는 구조로 변환해야 한다. 우리는 직접 구한 티켓 이미지 속 글씨들을 학습시킬 것이므로 이미지 하나하나 labeling 했다.

이미지 파일 목록 구조

/data
├── gt.txt
└── /images
    #  	image_[idx].[ext]
    ├── image_00001.png
    ├── image_00002.png
    ├── image_00003.png
    └── ...

'gt.txt' 파일 구조

# {filename}\t{label}\n
  images/image_00001.png	abcd
  images/image_00002.png	efgh
  images/image_00003.png	ijkl
  ...

filename과 label 사이를 제외하곤 괜한 tab이 들어가지 않도록 조심하자. tab을 기준으로 파싱하기 때문에 변환 시 오류가 발생할 수 있다. (나도 알고 싶지 않았다...)

학습 데이터 변환하기

labeling을 완료한 데이터를 변환해보자.

# deep-text-recognition-benchmark 프로젝트 root에서 실행
python3 create_lmdb_dataset.py \
        --inputPath data/ \
        --gtFile data/gt.txt \	
        --outputPath result/

위의 명령어를 실행하면 result 디렉토리에 data.mdb, lock.mdb 파일이 생성된다.

# training 데이터 변환
create_lmdb_dataset.py --gtFile "<path>\data\gt.txt" --inputPath "<path>\data" --outputPath "<path>\result1\training"
# validation 데이터 변환
create_lmdb_dataset.py --gtFile "<path>\data\gt.txt" --inputPath "<path>\data" --outputPath "<path>\result1\validation"
# test 데이터 변환
create_lmdb_dataset.py --gtFile "<path>\data\gt.txt" --inputPath "<path>\data" --outputPath "<path>\result1\test"

training, validation, test 파일이 각각 다르다면 위와 같이 실행해 각기 다른 mdb 파일을 생성하면 된다. 하지만 우리는 직접 생성한 데이터를 일단 학습시킬 것이기 때문에 한 번만 실행 후 mdb 파일을 복사했다.

프로젝트 및 모델의 정상 동작 확인

deep-text-recognition-benchmark 프로젝트가 정상 동작하는지 테스트 해보자.

pre-trained model 준비

Github
위의 링크에서 Download pretrained model from here 을 클릭해 실제 EasyOCR에서 사용하고 있는 기본 모델과 동일한 네트워크 구조('None-VGG-BiLSTM-CTC')를 갖는 Pre-trained Model을 다운로드 한다. 그리고 그 파일을 deep-text-recognition-benchmark 디렉토리 내에 models라는 디렉토리를 새로 생성해 업로드 한다.

Demo 실행

python3 demo.py \
        --Transformation None \
        --FeatureExtraction VGG \
        --SequenceModeling BiLSTM \
        --Prediction CTC \
        --image_folder demo_image/ \
        --saved_model ./models/None-VGG-BiLSTM-CTC.pth

결과:

데이터 학습시키기 (1차)

한글 클래스를 적용시키기 위해 train.py 파일 285번째 줄에 아래의 코드를 추가한다.

opt.character = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~가각간갇갈감갑값강갖같갚갛개객걀거걱건걷걸검겁것겉게겨격겪견결겹경곁계고곡곤곧골곰곱곳공과관광괜괴굉교구국군굳굴굵굶굽궁권귀규균그극근글긁금급긋긍기긴길김깅깊까깎깐깔깜깝깥깨꺼꺾껍껏껑께껴꼬꼭꼴꼼꼽꽂꽃꽉꽤꾸꿀꿈뀌끄끈끊끌끓끔끗끝끼낌나낙낚난날낡남납낫낭낮낯낱낳내냄냉냐냥너넉널넓넘넣네넥넷녀녁년념녕노녹논놀놈농높놓놔뇌뇨누눈눕뉘뉴늄느늑는늘늙능늦늬니닐님다닥닦단닫달닭닮담답닷당닿대댁댐더덕던덜덤덥덧덩덮데델도독돈돌돕동돼되된두둑둘둠둡둥뒤뒷드득든듣들듬듭듯등디딩딪따딱딴딸땀땅때땜떠떡떤떨떻떼또똑뚜뚫뚱뛰뜨뜩뜯뜰뜻띄라락란람랍랑랗래랜램랫략량러럭런럴럼럽럿렁렇레렉렌려력련렬렵령례로록론롬롭롯료루룩룹룻뤄류륙률륭르른름릇릎리릭린림립릿마막만많말맑맘맙맛망맞맡맣매맥맨맵맺머먹먼멀멈멋멍멎메멘멩며면멸명몇모목몰몸몹못몽묘무묵묶문묻물뭄뭇뭐뭣므미민믿밀밉밌및밑바박밖반받발밝밟밤밥방밭배백뱀뱃뱉버번벌범법벗베벤벼벽변별볍병볕보복볶본볼봄봇봉뵈뵙부북분불붉붐붓붕붙뷰브블비빌빗빚빛빠빨빵빼뺨뻐뻔뻗뼈뽑뿌뿐쁘쁨사삭산살삶삼상새색샌생서석섞선설섬섭섯성세센셈셋션소속손솔솜솟송솥쇄쇠쇼수숙순술숨숫숲쉬쉽슈스슨슬슴습슷승시식신싣실싫심십싱싶싸싹쌀쌍쌓써썩썰썹쎄쏘쏟쑤쓰쓸씀씌씨씩씬씹씻아악안앉않알앓암압앗앙앞애액야약얇양얗얘어억언얹얻얼엄업없엇엉엌엎에엔엘여역연열엷염엽엿영옆예옛오옥온올옮옳옷와완왕왜왠외왼요욕용우욱운울움웃웅워원월웨웬위윗유육율으윽은을음응의이익인일읽잃임입잇있잊잎자작잔잖잘잠잡장잦재쟁저적전절젊점접젓정젖제젠젯져조족존졸좀좁종좋좌죄주죽준줄줌줍중쥐즈즉즌즐즘증지직진질짐집짓징짙짚짜짝짧째쨌쩌쩍쩐쪽쫓쭈쭉찌찍찢차착찬찮찰참창찾채책챔챙처척천철첫청체쳐초촉촌총촬최추축춘출춤춥춧충취츠측츰층치칙친칠침칭카칸칼캐캠커컨컬컴컵컷켓켜코콜콤콩쾌쿠퀴크큰클큼키킬타탁탄탈탑탓탕태택탤터턱털텅테텍텔템토톤톱통퇴투툼퉁튀튜트특튼튿틀틈티틱팀팅파팎판팔패팩팬퍼퍽페펴편펼평폐포폭표푸푹풀품풍퓨프플픔피픽필핏핑하학한할함합항해핵핸햄햇행향허헌험헤헬혀현혈협형혜호혹혼홀홍화확환활황회획횟효후훈훌훔훨휘휴흉흐흑흔흘흙흡흥흩희흰히힘"

train.py는 default로 epoch를 30만 번 돌리게 되어있다. 그렇게까지 필요하진 않아

parser.add_argument('--num_iter', type=int, default=1000, help='number of iterations to train for')

위와 같이 수정해줘서 1000번만 돌리도록 해주었다.

또한 2000번에 한 번씩만 로그를 남기게 되어있는 게 불편해 아래와 같이 수정하여 5번에 한 번으로 바꾸었다.

    parser.add_argument('--valInterval', type=int, default=5, help='Interval between each validation')
python3 train.py \
        --train_data "../result1/training" \
        --valid_data "../result1/validation" \
        --select_data / \
        --batch_ratio 1 \
        --Transformation None \
        --FeatureExtraction "VGG" \
        --SequenceModeling "BiLSTM" \
        --Prediction "CTC" \
        --input_channel 1 \
        --output_channel 256 \
        --hidden_size 256 \
        --saved_model "../pre_trained_model/korean_g2.pth" \
        --FT

위의 명령어를 실행시키면 학습을 시작할 수 있다.

lmdb.Error: ../result1/validation/.ipynb_checkpoints: No such file or directory

jupyter notebook을 사용하는 경우 위와 같은 에러를 마주하게 될 수도 있다. (나도 마주하고 싶지 않았다.) 이런 경우 각 디렉토리에 숨어있는 .ipynb_checkpoints 디렉토리를 삭제해주면 된다.

.ipynb_checkpoints 디렉토리는 jupyter notebook의 로그 같은 건데 학습 시에 저 디렉토리까지 열어보려 시도하기 때문에 에러가 나는 듯 하다.

학습을 시작하면 saved_models 디렉토리 내 log_train.txt 파일에 학습 로그가 업데이트 된다.

직접 제작한 티켓 데이터 (100개)

.
.
.
--------------------------------------------------------------------------------
[110/1000] Train loss: 0.02963, Valid loss: 0.02496, Elapsed_time: 41.35597
Current_accuracy : 84.328, Current_norm_ED  : 0.90
Best_accuracy    : 84.328, Best_norm_ED     : 0.90
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
3층 5열 21번                 | 3층 5열 21번                 | 0.5670	True
장 소: 잠실종합운동장 주경기장         | 장 소: 잠실종합운동장 주경기장         | 0.0990	True
vip석                      | vip석                      | 0.9774	True
장 소: 세종문화회관 대극장           | 장 소: 세종문화회관 대극장           | 0.0834	True
d석                        | d석                        | 0.9980	True
--------------------------------------------------------------------------------
[115/1000] Train loss: 0.02444, Valid loss: 0.01989, Elapsed_time: 43.33090
Current_accuracy : 84.701, Current_norm_ED  : 0.89
Best_accuracy    : 84.701, Best_norm_ED     : 0.90
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
일 시: 2021-11-20 (토) 14:00 | 장 209 830 ( :0            | 0.0000	False
장 소: 명보아트홀 다온홀            | 장 소: 명보아트홀 다온홀            | 0.5950	True
r석                        | r석                        | 0.9982	True
예술의전당 오페라극장               | 예술의전당 오페라극장               | 0.6288	True
2018-01-21 (일) 14:00      | 2018-01-21 (일) 14:00      | 0.6926	True
--------------------------------------------------------------------------------
[120/1000] Train loss: 0.02276, Valid loss: 0.01892, Elapsed_time: 44.98781
Current_accuracy : 84.701, Current_norm_ED  : 0.89
Best_accuracy    : 84.701, Best_norm_ED     : 0.90
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
s석                        | s석                        | 0.9160	True
1층 a구역 6열 10번             | 1층 a구역 6열 10번             | 0.4476	True
b석                        | b석                        | 0.9951	True
vip석                      | vip석                      | 0.9879	True
일 시: 2021년 5월7일 오후 7시20분  | 일 시: 2021년 5월7일 오후 7시20분  | 0.0293	True
--------------------------------------------------------------------------------
[125/1000] Train loss: 0.01909, Valid loss: 0.01582, Elapsed_time: 46.91444
Current_accuracy : 84.701, Current_norm_ED  : 0.90
Best_accuracy    : 84.701, Best_norm_ED     : 0.90

우리가 직접 만든 티켓 데이터 100장으로만 학습 시켰을 때 120epoch만에 Best_accuray가 최대 84.701까지만 나오는 걸 확인할 수 있었다.

한국어 인쇄체 증강데이터 (41,575개)

.
.
.
--------------------------------------------------------------------------------
[985/1000] Train loss: 0.02000, Valid loss: 0.03341, Elapsed_time: 532.28686
Current_accuracy : 97.711, Current_norm_ED  : 1.00
Best_accuracy    : 99.157, Best_norm_ED     : 1.00
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
가 훌륭하다.                   | 가 훌륭하다.                   | 0.2678	True
가 훌륭하다.                   | 가 훌륭하다.                   | 0.2152	True
할 예정이다.                   | 할 예정이다.                   | 0.4569	True
송수진입니다."                  | 송수진입니다."                  | 0.1787	True
가 훌륭하다.                   | 가 훌륭하다.                   | 0.0763	True
--------------------------------------------------------------------------------
[990/1000] Train loss: 0.03101, Valid loss: 0.01504, Elapsed_time: 535.09736
Current_accuracy : 99.880, Current_norm_ED  : 1.00
Best_accuracy    : 99.880, Best_norm_ED     : 1.00
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
와의 동일여부 등을 확인할 예정이다.      | 와의 동일여부 등을 확인할 예정이다.      | 0.2634	True
가 훌륭하다.                   | 가 훌륭하다.                   | 0.1876	True
다.                        | 다.                        | 0.6053	True
대할 계획이다.                  | 대할 계획이다.                  | 0.1482	True
입니다.                      | 입니다.                      | 0.6758	True
--------------------------------------------------------------------------------

AI Hub에서 제공해주는 한국어 인쇄체 증강데이터 41,575장을 학습시키니 Best accuracy가 99.880까지 나왔다.

직접 제작한 티켓 데이터 + 한국어 인쇄체 증강데이터 (41,675개)

.
.
.
--------------------------------------------------------------------------------
[1280/2000] Train loss: 0.02067, Valid loss: 0.00738, Elapsed_time: 734.35460
Current_accuracy : 96.084, Current_norm_ED  : 0.97
Best_accuracy    : 96.266, Best_norm_ED     : 0.98
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
이다.                       | 이다.                       | 0.6737	True
입니다.                      | 입니다.                      | 0.3460	True
입니다.                      | 입니다.                      | 0.9295	True
수 기자                      | 수 기자                      | 0.9299	True
소문이 확대된 것으로 보인다.          | 소문이 확대된 것으로 보인다.          | 0.2063	True
--------------------------------------------------------------------------------
[1285/2000] Train loss: 0.00762, Valid loss: 0.00586, Elapsed_time: 737.42185
Current_accuracy : 96.266, Current_norm_ED  : 0.97
Best_accuracy    : 96.266, Best_norm_ED     : 0.98
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
다.                        | 다.                        | 0.7664	True
일 시: 2019년 11월9일 오후 2시00분 | -1 장 열 바열열2b대3            | 0.0001	False
대할 계획이다.                  | 대할 계획이다.                  | 0.1855	True
1층 c구역6열 3번               | 1층 c구역6열 3번               | 0.4271	True
빛바랜 영광입니다.ytn 이형원입니다.     | 빛바랜 영광입니다.ytn 이형원입니다.     | 0.6472	True
--------------------------------------------------------------------------------
[1290/2000] Train loss: 0.00662, Valid loss: 0.00551, Elapsed_time: 739.95539
Current_accuracy : 96.266, Current_norm_ED  : 0.97
Best_accuracy    : 96.266, Best_norm_ED     : 0.98
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
장 소: 추후공개                 | 장 소: 추후공개                 | 0.5345	True
이다.                       | 이다.                       | 0.3585	True
와의 동일여부 등을 확인할 예정이다.      | 와의 동일여부 등을 확인할 예정이다.      | 0.4457	True
1층 14열 10번                | 1층 14열 10번                | 0.5317	True
이다.                       | 이다.                       | 0.9374	True

위의 두 모델에 사용한 데이터를 합쳐서 학습시킨 결과 Best accuracy가 96.266까지 나왔다. 이때부터는 1000 epoch는 적은 거 같아 2000번씩 실행했다.

학습시킨 모델 실행 (1차)


학습시킨 세 가지 모델에 위의 티켓 이미지를 읽도록 해보았다.

직접 제작한 티켓 데이터 학습 모델


난장판인 걸 확인할 수 있다.

한국어 인쇄체 증강데이터 학습 모델


이것도 난장판이다.

직접 제작한 티켓 데이터 + 한국어 인쇄체 증강데이터 학습 모델


이마저도 난장판이다.

심각성을 깨닫고 데이터를 더 구해서 학습시켜보기로 했다.

데이터 학습시키기 (2차)

부제: 데이터 광인
AI Hub에서 데이터를 어마무시하게 가져오기로 했다. 그리고 그 데이터들을 6:2:2 비율로 나누어 각각 training, validation, test용으로 사용했다.

한국어 인쇄체 데이터 (303,057개)

.
.
.
[1945/2000] Train loss: 0.00144, Valid loss: 0.00721, Elapsed_time: 21554.87843
Current_accuracy : 99.649, Current_norm_ED  : 1.00
Best_accuracy    : 99.649, Best_norm_ED     : 1.00
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
수도권                       | 수도권                       | 0.9988	True
찌다                        | 찌다                        | 0.9754	True
영향                        | 영향                        | 0.9235	True
쉽다                        | 쉽다                        | 0.9982	True
똑같다                       | 똑같다                       | 0.9986	True

한국어 인쇄체 증강데이터 (513,718개)

.
.
.
--------------------------------------------------------------------------------
[325/2000] Train loss: 0.18469, Valid loss: 0.20205, Elapsed_time: 6915.73457
Current_accuracy : 89.744, Current_norm_ED  : 0.95
Best_accuracy    : 89.744, Best_norm_ED     : 0.95
--------------------------------------------------------------------------------
Ground Truth              | Prediction                | Confidence Score & T/F
--------------------------------------------------------------------------------
식생활                       | 식생활                       | 0.9407	True
중단되다                      | 중단되다                      | 0.9540	True
기후                        | 기후                        | 0.9995	True
강의하다                      | 강의하다                      | 0.9979	True
금세                        | 금세                        | 0.9995	True

학습시킨 모델 실행 (2차)

한국어 인쇄체 데이터

한국어 인쇄체 증강데이터


그래도 한글만 있는 경우에는 좀 나아진 걸 확인할 수 있었다. 영어와 숫자, 특수문자가 포함된 경우 여전히 난장판이다...

결론

Korean pretrained 모델에 아무리 학습을 시켜도 기존 EasyOCR의 default 모델만 못하다는 걸 깨달았다. 결국 우리가 읽고자하는 티켓에는 영어와 특수문자가 섞여있기 때문에, 기존 EasyOCR의 default model에 일시나 장소, 좌석 정보만 추가적으로 학습을 시키는 것이 효과적일 거라는 생각을 했다. 위의 모든 과정들을 통해 OCR 모델 학습법을 많이 익혔으니, 이를 자탕으로 이후에는 자체 제작 데이터를 대량으로 만들어 기존 모델에 학습을 시켜보도록 할 것이다.

참고

https://wandukong.tistory.com/8
https://dsbook.tistory.com/298
https://developer-youn.tistory.com/46?category=764229
https://davelogs.tistory.com/78
https://ropiens.tistory.com/35
https://github.com/JaidedAI/EasyOCR/blob/master/custom_model.md

post-custom-banner

3개의 댓글

comment-user-thumbnail
2022년 8월 5일

안녕하세요. 혹시 모델 훈련하시는데 시간 얼마나 걸렸어요? 저도 비슷한 작업을 하고 있는데 2주 정도 걸려서 crop이미지 테스트 결과 좋지만 막상 실제 이미지로 테스트하니까 엄망이에요 ㅠㅠ

1개의 답글
comment-user-thumbnail
2024년 2월 15일

안녕하세요, 좋은 자료 감사합니다.
코드를 돌려보는 중
font = ImageFont.truetype("NanumGothic-Bold.ttf", 50)이 부분에서

WARNING:easyocr.easyocr:Neither CUDA nor MPS are available - defaulting to CPU. Note: This module is much faster with a GPU.

OSError Traceback (most recent call last)
in <cell line: 12>()
10 img = cv2.imread("/content/drive/MyDrive/0.png")
11 img = Image.fromarray(img)
---> 12 font = ImageFont.truetype("NanumGothic-Bold.ttf", 50)
13 draw = ImageDraw.Draw(img)
14 np.random.seed(42)

2 frames
/usr/local/lib/python3.10/dist-packages/PIL/ImageFont.py in init(self, font, size, index, encoding, layout_engine)
253 load_from_bytes(f)
254 return
--> 255 self.font = core.getfont(
256 font, size, index, encoding, layout_engine=layout_engine
257 )

OSError: cannot open resource

error가 발생하는데 혹시 이유를 알 수 있을까요?

답글 달기