EasyOCR는 JaidedAI가 Python용으로 내놓은 바로 쓸 수 있는 OCR 라이브러리다. pip install easyocr 한 줄로 깔고, 코드도 두 줄이면 끝난다. 이미지 안에 박혀 있던 텍스트가 문자열로 바로 돌아온다. Apache-2.0 라이선스이고, 80개가 넘는 언어를 지원한다고 밝힌다. 구조는 PyTorch 모델 두 개를 엮은 파이프라인이다. 먼저 CRAFT 탐지기가 글자로 보이는 영역을 박스로 잡아내고, 그다음 recognizer가 각 박스 안의 문자를 읽는다. 사전 학습된 가중치는 첫 호출 때 자동으로 내려받는다. 방식만 놓고 보면 페이지당 과금되는 클라우드 OCR API가 아니라, Tesseract나 PaddleOCR에 가까운 셀프 호스팅 대안이다.
기본 API는 정말 짧다. Reader(['en'])를 만들고 readtext()를 호출하면 된다. 다만 배포는 생각보다 무겁다. 이 환경에서는 PyTorch만 해도 약 2GB가 들어가고, 새 프로세스에서 측정한 피크 상주 메모리는 대략 1GB 정도였다. 영어 PNG 이미지 36장을 만들어서 easyocr 1.7.2를 CPU에서 돌린 뒤, 생성한 정답과 문자 단위로 비교해 봤다. 크기와 방향이 CER를 가장 크게 망가뜨렸고, 대시보드에서는 작은 토큰 탐지 실패와 달러 기호 인식 문제도 확인됐다.
가장 눈에 띄는 실패는 모두가 추천하는 그 파라미터에서 나왔다. rotation_info는 이슈 트래커에서 회전된 이미지의 해결책처럼 많이 언급된다. 그래서 같은 문장을 90도씩 돌린 세 장으로 테스트했다. 270도에서는 약속대로 잘 작동해서 문자 오류율이 0.83에서 0.10으로 떨어졌다. 180도에서는 절반만 먹혔다. 0.85에서 0.67로 내려가긴 했지만 문장 하나가 통째로 사라졌다. 90도에서는 반대로 더 나빠졌다. 0.81에서 0.92로 올라갔고, recognizer는 거꾸로 읽힌 텍스트를 내보내기 시작했다. 같은 파라미터, 같은 각도 목록인데 결과는 세 가지가 제각각이었다. 그러니 이 옵션을 켠다고 해서 곧바로 “회전 처리가 된다”는 뜻은 아니다. 같은 36개 테스트 파일은 글꼴 크기에 대한 급격한 임계점, 달러 기호 혼동, 그리고 내가 한 예측 하나가 그냥 틀렸다는 사실도 보여줬다.
두 모델이 하나의 껍데기 안에 있다
EasyOCR는 하나의 모델이 아니다. 두 모델이 이어진 체인이고, 어디서 깨졌는지 알아야 디버깅도 제대로 된다.
첫 단계는 CRAFT, 즉 detector다. 역할은 오직 위치를 잡는 것뿐이다. 이미지에서 텍스트가 어디 있는지 판단하고, 박스를 돌려준다. 글자는 단 한 글자도 읽지 않는다. 두 번째 단계는 CRNN recognizer다. ResNet으로 특징을 뽑고, BiLSTM을 거친 뒤, CTC greedy decoding으로 문자를 읽는다. 이 둘은 모두 PyTorch로 돌아간다. CPU에서는 recognizer가 기본적으로 int8 dynamic quantization으로 실행되기 때문에, 파라미터 수만 보면 생각보다 훨씬 가볍고 빠르게 느껴진다.

실무적으로는 이 구조 때문에 EasyOCR의 실패도 두 종류로 나뉜다. 고장 유형이 완전히 다르고, 고치는 방법도 다르다. detector가 박스를 하나도 못 잡으면 recognizer를 아무리 만져도 소용없다. 문자 자체가 파이프라인에 들어가지 않았기 때문이다. 반대로 박스는 있는데 문자열이 틀리면 recognition 문제다. 이 경우엔 전처리만 잘해도 살릴 수 있다. 내가 읽은 거의 모든 “EasyOCR이 내 텍스트를 놓쳤어요” 글은 이 둘을 섞어서 말하고 있었다.
프로젝트 상태를 2026년 7월 27일 기준으로 확인해 보면, 별 29,825개, 열린 이슈 528개, Apache-2.0, 그리고 v1.7.2는 2024년 9월 버전이다. master로의 마지막 push는 2025년 12월이었다. 이런 날짜만으로 아키텍처가 안정적인지, 유지보수가 건강한지는 판단할 수 없다. 도입 전에 Python/PyTorch 호환성, 최근 유지보수 반응 속도, 그리고 자신의 입력과 관련된 이슈를 꼭 확인해야 한다.
OCR은 종종 CAPTCHA 우회와 연결해서 생각되지만, 여기서는 그런 용도가 아니다. 봇 차단을 뚫는 테스트는 하지 않았고, 이 글도 그런 우회를 권장하지 않는다. 범위는 사용 권한이 있는 이미지와 스크린샷 속 텍스트를 읽는 데 한정된다.
내가 측정한 것, 그리고 그 숫자가 담지 못하는 것
테스트 세트는 내가 직접 만든 36개의 PNG로 구성했다. 35장은 한 줄짜리 이미지로, 7개 폰트, 8개 크기, 7개 대비 수준, 7개 기울기, 3개 직교 회전, 3개 배경을 조합했다. 여기에 19개의 라벨이 따로 붙은 합성 대시보드 캡처 1장을 추가했다. 모든 이미지는 같은 문장(Sphinx of black quartz, judge my vow. 1234567890 — 48자, 대소문자, 숫자, 구두점 포함)에서 만들었고, 정답 라벨도 같은 코드 경로에서 적었기 때문에 이미지와 라벨이 물리적으로 어긋날 수 없게 했다.
정확도 지표는 **character error rate (CER)**다. 문자 기준 Levenshtein 거리 나누기 정답 길이다. CER가 0이면 완벽하게 읽은 것이다. CER 0.10이면 대략 10개 중 1개 문자가 틀렸다는 뜻이다. 메인 수치는 대소문자에 민감한 CER로 두고, 보조로 대소문자 무시 CER도 같이 봤다. 실제로는 대소문자 때문에 생기는 “오류”가 꽤 많기 때문이다.
숫자보다 더 중요한 건 한계다.
- 영어만 테스트했다. recognizer는
english_g2였다. EasyOCR은 80개 넘는 언어를 지원한다고 하지만, 나는 그중 하나만 시험했다. 아카데믹 OCR 비교에서 중요한 건 특히 비라틴 스크립트인데, 그 부분은 여기 없다. - 합성 데이터만 썼다. 사진이 아니라 렌더링된 텍스트다. 카메라 노이즈, JPEG 아티팩트, 조명, 원근 왜곡이 없다.
- 손글씨는 제외했다. 프로젝트 자체도 손글씨 지원은 아직 완전하지 않다고 말한다.
- CPU만 측정했다. macOS arm64,
gpu=False. 이 머신에는 MPS도 있었지만, EasyOCR는 CUDA가 아니면 CPU를 쓴다. GPU 성능은 측정하지 않았고, 이 글에도 GPU 수치는 없다. - 한 대의 머신, 한 버전. easyocr 1.7.2, torch 2.13.0, Python 3.12.
요약하면, 이건 통제된 단일 변수 곡선이다. 깨끗하게 렌더된 라틴 텍스트에서 어디서 정확도가 무너지는지 정확히 보여준다. 하지만 실제 코퍼스 점수는 아니고, 그걸 대신할 수도 없다.
증거는 노트북 안에만 넣어두지 않았다. fixtures 생성 코드와 정확한 문자열은 tests/build_fixtures.py와 tests/fixtures/ground_truth.json에 있고, 인식 결과·시간 측정·리소스 캡처는 tests/run_easyocr.py에 있다. tests/metrics.py는 raw 출력에서 오류율을 계산한다. 인식 로그와 집계 지표는 artifacts/raw/에 보관돼 있다. 이 흐름을 다시 돌려보는 건 이 머신과 이 버전을 확인하는 데는 유용하다. 하지만 네 휴대폰 사진, 네 언어, 네 레이아웃, 네 전처리 파이프라인에서 EasyOCR가 어떻게 동작할지는 여전히 말해주지 못한다. 프로덕션 판단은 이 fixtures를 인증서처럼 믿는 게 아니라, 실제 입력을 더 얹어서 해야 한다.
테스트용 셋업에서 하나 짚고 넘어가야 할 함정이 있다. 자칫하면 제목 숫자를 완전히 잘못 뽑을 뻔했다. 첫 번째 metric 패스에서는 깨끗한 흑백 Arial 입력에 CER가 0.375로 나왔는데, 가장 쉬운 입력에서 이 정도면 재앙이다. 그런데 EasyOCR 문제가 아니었다. detector가 한 줄로 보이는 텍스트를 단어 박스와 숫자 박스로 쪼갰고, 내가 단순하게 y 다음 x로 정렬해 붙이면서 숫자가 앞에 와 버렸다. vertical overlap 기준으로 줄을 묶고, 왼쪽에서 오른쪽으로 읽는 식으로 수정하니 깔끔한 폰트는 0.04–0.10 수준으로 돌아왔다. OCR 평가를 직접 만들면, 이 함정은 네 차례도 피해 가지 않는다.
설치: 설치 파일은 가볍고, 의존성은 가볍지 않다

pip install easyocr
끝이다. 그런데 이것만 보고 쉽게 생각하면 안 된다. 패키지 자체는 작지만, 딸려 오는 건 PyTorch 약 2GB다. 그리고 readtext()를 처음 부르면 EasyOCR가 ~/.EasyOCR/model/에 가중치를 조용히 다운로드한다. 총 93.7 MiB이고, CRAFT detector가 79.30 MiB(craft_mlt_25k.pth), 영어 recognizer가 14.44 MiB(english_g2.pth)다.
튜토리얼들이 이 부분을 잘 안 짚어주니, 그냥 분명히 말하자면: 첫 실행에는 네트워크가 필요하고 다운로드 때문에 잠깐 멈춘다. 컨테이너 환경이라면 가중치를 이미지 안에 미리 넣거나, 차가운 시작 시 다운로드 비용을 감수해야 한다. 한번 캐시에 들어가면 그다음부터는 오프라인으로 잘 돈다.
Reader()의 cold start, 즉 디스크에서 RAM으로 모델을 올리고 int8 quantization까지 거치는 초기화는 실행마다 1.3~1.7초 걸렸다. 그다음은 이렇게 쓴다.
import easyocr
reader = easyocr.Reader(['en'], gpu=False)
result = reader.readtext('screenshot.png')
이렇게 끝난다. 두 줄이고, 따로 설정할 것도 없고, 체크포인트를 찾아다닐 필요도 없다. 이 시점에서는 이름의 “easy”가 꽤 맞다. 무거운 건 API가 아니라 의존성이다.
깨끗한 텍스트의 바닥선: 글자는 거의 완벽, 대소문자는 덜 완벽
7개 시스템 폰트, 흑백, 32px, 같은 문장으로만 테스트했다.
| 폰트 | CER (대소문자 민감) | CER (대소문자 무시) |
|---|---|---|
| Georgia | 0.0625 | 0.0000 |
| Times | 0.0417 | 0.0417 |
| Comic Sans | 0.0417 | 0.0208 |
| Arial | 0.0833 | 0.0208 |
| Verdana | 0.0833 | 0.0208 |
| Impact | 0.0833 | 0.0208 |
| Courier | 0.1042 | 0.0417 |
| 평균 | 0.0714 | 0.0238 |
대소문자를 무시하면 평균 CER는 0.071에서 0.024로 내려간다. 이게 핵심이다. EasyOCR는 깨끗한 라틴 텍스트에서 문자를 놓치지 않는다. 다만 형태는 잘 맞히는데, 대소문자를 종종 틀린다.
구체적으로 보면, 소문자 vow는 7개 폰트 중 6개에서 VOW로 나온다. Comic Sans만 Vow였다. Impact는 거기에 of까지 Of로 바꿨다. 또 다른 반복 오류는 구두점이다. 마침표가 여러 폰트에서 :나 _로 바뀌었다. Georgia는 대소문자만 신경 쓰지 않으면 완벽하게 읽었다.
이건 실무에서 꽤 중요한 오류다. 다음 단계가 fuzzy matching, 키워드 검색, 또는 언어 모델로 넘기는 작업이라면 대소문자 변화는 거의 큰 문제가 아니다. 하지만 데이터베이스 키와 정확히 비교해야 한다면 이야기가 다르다. 비교 전에 대소문자를 정규화하면, EasyOCR의 체감 오류 절반은 사라진다.
Courier가 가장 안 좋았던 것(0.1042)도 자연스럽다. monospace 폰트는 문자 간격이 인위적으로 넓어서, 일반적인 글자 간격으로 학습된 CTC 디코더에는 더 까다롭다.
크기 임계점은 문서에 적힌 그대로다

readtext()에는 min_size=10이라는 문서화된 파라미터가 있다. 10픽셀보다 짧은 탐지 박스는 버린다. 많은 사람이 이걸 대충 넘긴다. 그런데 스크린샷이나 PDF를 다루는 사람에게는 API에서 제일 중요한 숫자다. 렌더된 글자 높이를 바꿔 보면 이렇다.
| 렌더 px | CER | 실제로 일어난 일 |
|---|---|---|
| 8 | 0.7708 | 붕괴 — 박스가 min_size 필터 아래로 떨어져 삭제됨. 파편만 남음 |
| 10 | 0.1458 | 경계선 — 임계값 바로 위아래에서 detector가 줄을 3개 박스로 쪼갬 |
| 12 | 0.0417 | 회복 |
| 16 | 0.0000 | 완벽한 판독 |
| 20 | 0.0208 | 양호 |
| 28 | 0.0208 | 양호 |
| 40 | 0.0625 | 양호 (대소문자 뒤집힘이 다시 나타남) |
| 64 | 0.0625 | 양호 (대소문자 뒤집힘) |
12px에서 8px로 갈 때 CER가 0.04에서 0.77로 뛰는 건 점진적 악화가 아니다. 문서에 적힌 대로 정확히 동작하는 필터다. 결과적으로 10px 아래에서는 기본 설정의 EasyOCR에 텍스트가 거의 보이지 않는다.
가장 좋은 구간은 12~28px이고, 16px에서는 총 CER가 0이었다. 40px 이상에서는 CER가 조금 다시 올라가는데, 글자가 더 읽기 어려워져서가 아니라 vow → VOW 같은 대소문자 뒤집힘이 다시 나타나기 때문이다. 큰 글자가 더 어려운 게 아니라, 16px에서 딱 맞던 spacing 이점이 사라진 것이다.
스크린샷에서 텍스트를 뽑는다면, 모델 탓하기 전에 먼저 글자 렌더 높이를 확인해야 한다. HiDPI 화면을 1×로 캡처하거나, PDF를 72 DPI로 래스터화하면 평범한 텍스트도 10px 밑으로 쉽게 떨어진다. OCR 전에 2×로 캡처하거나 확대하면 “EasyOCR가 페이지 절반을 무시했다”는 티켓의 상당수를 피할 수 있다. 그게 어렵다면 min_size를 낮출 수는 있지만, 그만큼 잡음도 늘어난다. 이 필터가 원래 가짜 검출을 줄이려고 있는 거라서 그렇다.
회전: 10도 정도는 버티지만, 보정은 대칭적이지 않다
먼저 기울기부터 보자. 작은 각도, Arial 32px, 기본 설정과 rotation_info=[90,180,270] 비교는 아래와 같다.
| 기울기 각도 | 기본 CER | rotation_info 적용 CER |
|---|---|---|
| 0° | 0.0833 | 0.0833 |
| 5° | 0.0417 | 0.0417 |
| 10° | 0.0208 | 0.0833 |
| 15° | 0.2917 | 0.3750 |
| 20° | 0.7500 | 0.7708 |
| 30° | 0.8958 | 0.8750 |
| 45° | 0.8958 | 0.8750 |
기본 EasyOCR는 대략 10°까지는 무난하게 버틴다(CER ≤ 0.083). 15°에서 흔들리고, 20°부터는 크게 무너진다. rotation_info는 기울기 자체에는 아무 도움도 안 된다. 이 기능은 네가 넣은 각도만 다시 시도하기 때문이다. 15°는 90, 180, 270 중 어느 쪽도 아니다. 오히려 10°에서는 약간 더 나빠졌다(0.021 → 0.083). 잘못된 각도를 시도한 결과가 신뢰도 투표에서 이겨버릴 수 있기 때문이다.
직교 회전은 더 이상하다.
| 회전 | 기본 CER | rotation_info 적용 CER | 회복됨? |
|---|---|---|---|
| 90° | 0.8125 | 0.9167 | 아니오 — 더 나쁨 |
| 180° | 0.8542 | 0.6667 | 부분적으로 |
| 270° | 0.8333 | 0.1042 | 예 |
같은 파라미터, 같은 각도 목록, 결과는 세 가지다.
270°에서는 rotation_info가 이슈 스레드에서 기대하는 그대로 작동한다. CER가 0.83에서 0.10으로 내려가서 실제로 쓸 수 있는 수준이 된다. 180°에서는 반쯤만 된다. CER는 0.67까지 내려가지만 my vow.가 통째로 사라졌다. 90°에서는 반대로 더 나빠진다. 0.81에서 0.92로 올라가고, raw output을 보면 이유가 보인다. recognizer가 거울에 비친 듯한 문자열을 내놓는다. VOW는 MOA가 되고, quartz는 zuuenb가 된다. 거울에 비추어 읽으면 맞는 말이지만, 데이터 파이프라인에는 최악이다.
나는 이걸 집계 지표가 아니라 raw prediction으로 확인했다. 처음엔 “병합 순서가 꼬였나?”라고 생각했기 때문이다. 그런데 그게 아니라 정말 EasyOCR가 그렇게 반환한 거였다.
메커니즘은 아직 가설일 뿐이고, orientation convention에 대한 별도 실험은 하지 않았다. Pillow는 양의 각도를 반시계 방향으로 표시하므로, 렌더된 270° 케이스만 recognizer가 잘 다루는 방향과 맞아떨어지고, 90° 케이스는 가장 잘 맞는 재시도 방향이 뒤집힌 형태로 들어간다. 정확한 원인이 무엇이든, 실무적 결론은 같다.
이 fixture는 rotation_info를 어느 방향에서도 대칭적으로 믿으면 안 된다는 걸 보여준다. 입력에서 예상되는 회전은 직접 검증해야 한다. 먼저 orientation을 정규화하는 건 완화책이지, 세 가지 렌더 예제로 증명된 필수 조건은 아니다.
내가 틀렸던 예측
나는 처음에 저대비가 EasyOCR의 약점일 거라고 확신했다. 연한 회색 글자를 흰 배경에 얹으면 OCR이 잘 깨지니까, 아주 전형적인 실패 사례다. 게다가 이를 위한 fallback 경로도 문서화되어 있다. contrast_ths=0.1와 adjust_contrast=0.5를 쓰면, 대비가 낮은 박스를 다시 증폭해서 가장 자신 있는 결과를 남긴다.
하지만 실제로는 한 번도 발동하지 않았다. 필요가 없었기 때문이다.
| 전경 회색값 | Weber 대비 | 기본 CER | adjust_contrast=1.0 CER |
|---|---|---|---|
| 0 (검정) | 1.000 | 0.0833 | 0.0833 |
| 64 | 0.749 | 0.0833 | 0.0833 |
| 110 | 0.569 | 0.0833 | 0.0833 |
| 150 | 0.412 | 0.1042 | 0.1042 |
| 180 | 0.294 | 0.0625 | 0.0625 |
| 200 | 0.216 | 0.0417 | 0.0417 |
| 220 | 0.137 | 0.0417 | 0.0417 |
Weber 0.14, 즉 흰색 위의 회색 220까지도 CER는 깨끗한 수준에서 벗어나지 않았다. 그리고 대비 증폭 컬럼은 모든 단계에서 기본값과 완전히 같았다. 기본 설정만으로 충분했기 때문이다.
배경도 같은 이야기를 했다. 텍스트는 모두 검정색이었다.
| 배경 | CER |
|---|---|
| 단색 밝은 파란 패널 | 0.083 |
| 세로 그라데이션 | 0.021 |
| Gaussian noise (μ200, σ22) | 0.000 |
이 세트에서 가장 noisy한 fixture가 오히려 완벽 판독이었다.
다만 범위는 여전히 좁다. 여기서 말하는 건 단색 저대비, 노이즈 없는 이미지이지, 센서 노이즈와 JPEG 압축이 섞인 사진 영수증이 아니다. 이 fixtures에서는 색상 변화와 합성 노이즈보다 geometry와 짧은 토큰이 더 큰 실패를 만들었다.
실제 사례: 대시보드 캡처에서 숫자 뽑기
OCR 작업이 Python에서 결국 이렇게 귀결되는 경우가 많다. 누군가 내부 대시보드 스크린샷을 보내거나, Python 웹 스크래핑 파이프라인을 분석 페이지에 돌리는데 숫자는 렌더된 픽셀로만 존재할 때, 그 값을 데이터로 다시 꺼내고 싶은 상황이다.
나는 “Sales Dashboard” 화면을 렌더했다. 어두운 헤더에 제목과 원형 아바타 배지, 세 개의 KPI 패널, 세 개의 버튼, 2×3 테이블 구조였다. 그다음 19개 텍스트 요소를 각각 정확한 문자열과 픽셀 박스로 라벨링하고, 박스 겹침 기준으로 EasyOCR 출력과 대응시켰다.
탐지 결과: 19개 중 16개. 빠진 3개는 다음이다.
- 한 글자짜리 배지 "A"
- 테이블 셀 "Q1"
- 테이블 셀 "Q2"
그리고 "Q3"는 탐지됐다. 글꼴도 같고, 크기도 같고, 같은 컬럼이었다. 그런데 detector는 2글자 토큰 하나는 살리고, 다른 둘은 버렸다. 비슷해 보이는 셀에서도 탐지가 들쭉날쭉했다. 이 실행들이 결정적이었기 때문에, 이걸 동전 던지기 같은 랜덤성의 증거로 볼 수는 없다. 비슷한 스크린샷 품질 문제는 #460에 보고돼 있다.
탐지된 16개는 텍스트 자체가 거의 완벽했다. 평균 CER는 0.027이었고, 16개 중 13개가 완전 일치였다. 제목, 버튼 라벨(Save, Cancel, Export CSV), 컬럼 헤더, 콤마가 들어간 숫자까지 모두 CER 0에 가까웠다. 1,284도 콤마 포함으로 정확하게 읽혔다.
불완전한 3개는 모두 같은 종류의 오류였다. 달러 금액이다.
| 정답 | EasyOCR 판독 |
|---|---|
$57,912 | S57,912 |
$18,330 | S18,330 |
$25,178 | S25,178 |
$12,004 | $12,004 (정확) |
달러 기호 4개 중 3개가 대문자 S로 바뀌었다. 눈으로 보면 이해는 된다. 하지만 추출된 모든 통화 필드가 한 글자 차이로 깨질 수 있다는 뜻이고, 단순한 float()로는 전부 실패한다는 뜻이기도 하다.
스크린샷 파이프라인에 짧은 라벨이나 금액이 있다면, 확대, 여백을 둔 crop, 필드 제약을 둔 기대값, 기호에 민감한 후처리 같은 완화책을 먼저 검증해야 한다. 여기서는 어떤 것도 벤치마크하지 않았다. 그리고 앞의 S를 '$'로 바꾸는 regex는 정상 값도 망가뜨릴 수 있다. 스키마와 검증 규칙이 안전을 보장할 때만 고쳐야 한다.
실행 비용은 어느 정도인가
같은 머신(macOS arm64, CPU, 단일 호스트, 동시 부하 가능성 하에서 측정된 값이며 보편적 벤치마크가 아니라는 점을 감안)에서의 수치다.
| 측정값 | 수치 |
|---|---|
| 디스크 상 모델 크기 | 93.7 MiB (탐지기 79.30 + recognizer 14.44) |
| 새 CPU 프로세스의 피크 상주 메모리 | 984.5 MiB |
Reader() cold start | 1.3–1.7초 |
| 깨끗한 48자 한 줄의 warm latency (p50) | ~0.062초 (p25–p75: 0.059–0.067초, n=20) |
detail=0 vs detail=1 | 거의 동일 (중앙값 0.062 vs 0.063초) |
핵심 비용은 94MB 모델 파일이 아니다. 프로세스 worker당 약 1GB의 상주 메모리가 진짜 문제이고, 여기에 약 2GB짜리 torch 설치가 더해진다. 이 수치가 컨테이너에 들어가는지 결정한다. 그런데 거의 아무도 이걸 말하지 않는다.
속도는 단순한 경우엔 괜찮다. CPU에서 깨끗한 한 줄을 0.1초도 안 되게 처리하니 실사용 가능하다. 하지만 그건 단순한 케이스다. 짧고 대비 높은 한 줄이다. “EasyOCR가 CPU에서 수십 초 걸린다”는 불만은 큰 문서, 다중 영역, 전체 캔버스 규모 입력에서 나온다. 나는 그 상황까지 재현하지는 않았다. 다른 부하이기 때문이다.
작은 오해 하나도 정리하자. detail=0은 EasyOCR를 더 빠르게 만들지 않는다. 반환값에서 박스와 신뢰도 점수만 빼는 것이다. 계산은 이미 끝난 뒤다. 중앙값 차이는 1밀리초 수준이어서 사실상 잡음이다.
장점과 단점
장점
- 깨끗하게 렌더된 라틴 텍스트 인식은 거의 완벽하다. 대소문자 민감 CER 평균 0.071, 대소문자 무시 0.024, 16px에서 총 CER 0 가능.
- 진짜로 두 줄 API다.
Reader(['en'])다음readtext()면 끝. 설정도, 모델 선택도 거의 없다. - 대비에 생각보다 강하다. Weber 0.14까지는 깨끗한 텍스트에서 무너지지 않았고, 노이즈/그라데이션/컬러 배경도 결과를 크게 흔들지 않았다(가우시안 노이즈 fixture는 완벽 판독).
- 스크린샷에서 탐지된 항목은 거의 완벽하다. 평균 CER 0.027, 16개 중 13개 정확, 콤마가 있는 숫자도 포함.
- 결정적이다. 여기 적은 정확도 수치는 서로 다른 두 실행에서도 바이트 단위로 동일했고, 달라진 건 시간뿐이었다.
- Apache-2.0이고 셀프 호스팅이라 공급업체 사용료가 없다. 대신 연산, 메모리, 저장소, 큐 비용은 직접 감당해야 한다.
단점
min_size=10문서화 임계값 아래에서 급격히 무너진다. 8px에서 CER 0.77. 작은 UI 텍스트는 기본값에선 안 보인다.- 기울기 허용 범위는 대략 10°까지이고, 20°에서 크게 붕괴한다.
rotation_info는 대칭적 수정이 아니다. 270°는 복구되지만, 180°는 부분적이고, 90°는 더 나빠지며 거울 글자를 반환한다.- detector가 짧은 단일 토큰을 자주 놓친다. 한 글자 배지와 2글자 셀 두 개가 사라졌는데, 비슷한 셀 하나는 살아남았다.
- 달러 기호가 통화 값에서 일관되게 S로 바뀐다(4개 중 3개).
- worker당 상주 메모리 약 1GB, 여기에 torch 약 2GB.
- 최신 버전이 2024년 9월이라, 매우 활발하다기보다 안정적인 쪽에 가깝다.
누가 써야 하고, 누가 피해야 하나
입력이 깨끗하고, 방향이 제대로 맞고, 글자가 충분히 큰 렌더 텍스트라면 EasyOCR를 검토할 만하다. 스크린샷, UI, rasterized PDF, 생성 보고서 같은 경우다. 셀프 호스팅 Python 파이프라인이 필요하고 공급업체 사용료를 피하고 싶다면 특히 그렇다. 여기서 얻은 영어 합성 CPU 결과는 그 범위 안에서만 유효하다. 사진, 손글씨, 다른 스크립트는 별도로 테스트해야 한다.
다음 경우라면 그냥 다른 길을 가는 게 낫다. 사진 — 여기 수치는 합성 렌더 텍스트 기준이라 카메라 노이즈, 원근, 조명에 대해 아무 말도 못 한다. 손글씨 — 프로젝트 자체도 아직 완전 지원을 주장하지 않는다. 비라틴 스크립트 — 80개 이상 언어를 지원하지만, 나는 하나만 시험했다. 이 영역은 합성 영어 스캔보다 공개된 학술 비교가 기준이다. 아무 각도로 돌아간 입력 — 먼저 자체 orientation correction을 하지 않는 한. 메모리 제약이 심한 배포 — worker당 1GB는 금방 한계를 만든다.
OCR을 고르기 전에 DOM과 네트워크 응답을 먼저 확인하자. 원하는 값이 이미 구조화된 텍스트로 존재한다면, 그 소스를 직접 추출하는 게 탐지와 인식 오류를 피하는 길이다. OCR은 픽셀만 남아 있을 때 유용하다.
대안, 그리고 우리 스택의 위치
EasyOCR는 Apache-2.0이고 셀프 호스팅이라 공급업체 사용료는 없지만, 계산과 운영 비용은 분명히 든다. PaddleOCR, Tesseract, vision-language 모델은 이 벤치에서 돌리지 않았으므로 직접 비교 결론은 없다.
가장 흥미로운 비교는 OCR끼리의 싸움이 아니다. 애초에 OCR을 해야 하느냐는 질문이다.
내가 보는 스크린샷 추출 작업의 대부분은 사실 더 까다로운 웹페이지를 우회하려는 시도다. JavaScript로 렌더된 표, 로그인 뒤의 대시보드, 성가신 사이트들 말이다. 스크린샷을 떠서 OCR로 돌리는 건 쉬워 보이지만, 사실은 원래 구조화된 텍스트를 버리고, 나중에 달러 기호까지 틀리는 더 나쁜 버전을 되찾기 위해 세금을 내는 셈이다.
작성자 메모: Thunderbit는 웹페이지 추출을 위한 우리의 관리형 솔루션이다. 이 이미지 fixtures에서는 테스트하지 않았다. 중요한 기준은 원본 표현 방식이다. 구조화된 웹 데이터가 있으면 DOM/네트워크 추출을 쓰고, 픽셀만 남았을 때 OCR을 평가하자.
같은 벤치에서 나온 관련 읽을거리로는 오픈 소스 scraper 전체 비교, Crawl4AI 테스트, 그리고 셀렉터에 잘 안 잡히는 페이지를 위한 AI 기반 추출 개요가 있다.
결론
EasyOCR를 써야 하나? 이미지가 올바른 방향이고, 글자 높이가 최소 12픽셀이며, 라틴 텍스트를 읽는다면 그렇다. 이 범위 안에서는 꽤 강하다. 깨끗한 텍스트에서 평균 CER 0.071, 대소문자 정규화 후 0.024, 16px에서 완벽 판독, 대비에 대한 강인함도 기대보다 좋았다. API는 진짜 두 줄이고 출력도 결정적이라, 파이프라인 디버깅할 때 특히 중요하다.
그 범위를 벗어나면, 실패도 꽤 정확하고 예측 가능하다. 10px 아래 텍스트는 min_size 필터에서 사라진다. 20° 넘는 기울기는 판독을 망친다. rotation_info는 한 방향 회전은 고쳐 주고, 다른 하나는 부분적으로만 고치며, 세 번째는 거울 글자로 더 망친다. 단일 글자와 2글자 토큰은 주변이 살아 있어도 떨어져 나간다. 달러 기호는 S로 바뀐다.
이 fixtures에서 나온 실패는 두 단계 모두에서 왔다. 기하학 쪽에서는 박스를 놓치거나 방향을 잘못 잡았고, recognizer 쪽에서는 달러 기호를 헷갈렸다. 확대, orientation normalization, 여백을 둔 crop, 스키마 기반 기호 복구는 보편적 정답이 아니라 검증해야 할 가설로 보자.
무엇보다, 내가 거의 했던 것처럼 깨진 평가 세트와 이해하지 못한 숫자로 테스트하지는 마라. 직접 fixtures를 만들고, 정답을 정확히 알고, 네 환경의 파국 지점을 직접 찾아야 한다.
웹 데이터 추출에 Thunderbit 사용해 보기 Get Started Free
FAQ
EasyOCR는 스크린샷의 텍스트를 프로덕션 수준으로 추출할 만큼 정확한가?
합성 대시보드에서는 매칭된 요소들의 평균 CER가 0.027이었고, 16개 중 13개가 정확했다. 하지만 19개 중 3개는 탐지되지 않았고, 4개의 달러 기호 중 3개가 S로 바뀌었다. 이 정도가 받아들일 만한지, 그리고 확대나 스키마 기반 보정이 도움이 되는지는 네 타깃 레이아웃에서 직접 봐야 한다.
EasyOCR가 읽을 수 있는 최소 글꼴 크기는 얼마인가?
실제로는 렌더된 글자 높이 기준 약 12픽셀이다. readtext()의 min_size=10은 10px보다 작은 탐지 박스를 잘라내고, 효과는 완만한 하락이 아니라 급격한 경계다. 8px에서 CER 0.77, 10px에서 0.15, 12px에서 0.04, 16px에서는 0이었다. 내 스캔에서 깨끗한 구간은 12~28px였다. HiDPI 화면을 1×로 캡처했거나 PDF를 72 DPI로 래스터화했다면, min_size를 낮추기보다 먼저 확대해라. 이 필터는 원래 가짜 탐지를 막으려고 있는 거다.
rotation_info가 EasyOCR에서 회전 이미지를 고쳐 주나?
신뢰할 수 있게는 아니고, 대칭적이지도 않다. 같은 문장을 90도씩 돌린 세 장에 rotation_info=[90,180,270]를 적용했더니, 270° 이미지는 제대로 복구됐고(CER 0.83 → 0.10), 180°는 일부만 회복됐으며(0.85 → 0.67, 한 문장 삭제), 90°는 오히려 악화됐다(0.81 → 0.92). VOW → MOA 같은 거울 문자도 나왔다. 작은 기울기에는 아무 효과도 없다. 네가 준 각도에서만 재시도하기 때문이다. EasyOCR를 부르기 전에 먼저 orientation을 정리하는 편이 낫다.
EasyOCR는 메모리와 디스크를 얼마나 쓰나?
모델 가중치는 93.7 MiB이고, 첫 사용 시 ~/.EasyOCR/model/에 내려받는다. detector가 79.30 MiB, 영어 recognizer가 14.44 MiB다. 새 CPU 프로세스에서 측정한 피크 상주 메모리는 984.5 MiB였고, 여기에 torch 설치 약 2GB가 더해진다. Reader()의 cold start는 1.3~1.7초였다. 깨끗한 한 줄은 이후 이 머신에서 p50 약 0.062초로 처리됐다. detail=0은 출력 형태만 바꾸고 실행 시간은 거의 바꾸지 않았다.
EasyOCR는 상업적으로 무료로 써도 되나, 아직 유지보수되고 있나? Apache-2.0이라 상업적 사용에 허용적이다. 2026년 7월 27일 기준으로 별은 29,825개, 열린 이슈는 528개다. 최신 릴리스는 2024년 9월의 v1.7.2이고, master로의 마지막 push는 2025년 12월이다. 해석하자면 “버려졌다”기보다 “안정적이다”에 가깝다. 아키텍처는 크게 안 바뀌었고 활동은 이슈 관리 쪽으로 이동했다. 직접 라이선스와 릴리스 상태를 확인하고 쓰는 게 좋다.


