오늘은 trOCR과 여러 OCR 모델의 결과를 결합하기 위해 투표 방식, 신뢰도 기반, 조건부 선택 앙상블 기법을 비교하고, 이진화 전처리(Binary, Otsu, Adaptive Thresholding)가 OCR 성능에 미치는 영향을 실습을 통해 이해해 보았다.
실습 코드를 중심으로 배운 내용을 정리해 보도록 하겠다.
[1] TrOCR
우선 주요 개념을 정리해 보겠다.
| 항목 | 설명 |
| OCR (Optical Character Recognition) | 이미지 속 텍스트를 읽어 문자열로 변환하는 기술 |
| TrOCR (Transformer-based OCR) | Microsoft가 만든 Transformer 기반 OCR 모델 |
| TrOCRProcessor | 이미지 전처리 및 텍스트 디코딩 처리기 |
| VisionEncoderDecoderModel | 이미지 인코더 + 텍스트 디코더로 구성된 모델 |
| generate() | 모델이 이미지를 보고 텍스트를 생성하는 함수 |
| confidence score | 각 단어를 모델이 얼마나 확신했는지 나타내는 평균 확률 |
여기서 의문이 들었던 부분은 이미지 전처리라는 것이다.
이미지 전처리란 이미지를 모델이 이해할 수 있도록 변환해주는 작업이다. 사람에게는 그냥 사진이지만, 모델 입장에서는 숫자의 행렬로 바뀌어야 의미가 생기기 때문이다.
즉 원본 이미지를 딥러닝 모델이 처리할 수 있는 형식으로 바꾸는 과정인 것이다.
또한 이미지 인코딩이란 이미지를 숫자 벡터로 바꾸는 것이고 텍스트 디코딩이랑 그 벡터를 바탕으로 단어를 생성하는 과정을 말한다. 벡터는 이미지 안에 뭐가 들어있는지를 표현하는 의미 요약본이라고 이해했다.
코드를 하나씩 분석해 보겠다.
- 모델 초기화
process,model,device를 각각 설정해서 모델 초기화를 진행한다.
- extract_text() 함수
이미지 입력을 pli 이미지로 변환한 뒤
이미지 전처리를 해서 pixel_values를 생성한다.
generate()로 이미지 -> text ID 시퀀스를 생성한다.
신뢰도는 각 토큰 생성 시의 확률 중 최대값의 평균으로 계산한다.
if return_confidence:
# 신뢰도 계산을 위해 확률 정보도 함께 얻기
outputs = self.model.generate(
pixel_values,
return_dict_in_generate=True,
output_scores=True,
max_length=256
)
generated_ids = outputs.sequences
token_scores = outputs.scores
else:
generated_ids = self.model.generate(pixel_values)
이런식으로 확률 정보도 함께 얻어준다.
다음으로 토큰 ID를 실제 문자열로 바꿔 디코딩을 해주고 generated_text에 저장한다.
최종적으로 디코딩된 값인 generated_text를 return 해준다.
- batch_extract()
여러 이미지를 리스트로 받아서 차례로 extract_text()를 호출해준다.
- compare_models()
TrOCR의 세 가지 모델을 비교한다.각각 모델을 새로 로드하여 같은 이미지에 대한 OCR을 수행한 뒤 결과 비교 후 반환해 준다.
- create_sample_trocr_image()
이 함수는 테스트용 샘플 이미지를 생성해준다..
- 실행 흐름
- TrOCRSystem 생성 → 모델 로딩
- create_sample_trocr_image() → 테스트 이미지 생성
- extract_text() → 텍스트 추출 + 신뢰도
- compare_models() → 다양한 모델 성능 비교
전체 흐름을 요약해보면
- TrOCR 모델을 초기화해서 준비하고 (TrOCRSystem)
- 샘플 이미지를 생성한 뒤 (create_sample_trocr_image)
- 텍스트를 추출 (extract_text)
- 여러 모델(base, handwritten, large)을 비교 (compare_models)
최종적으로 TrOCR 개념 요약을 정리해보면 이런 특징과 매커니즘으로 동작하는 것을 알 수 있다..
- Transformer + OCR
- 이미지 → VisionEncoder → TextDecoder → 텍스트
- generate() 함수로 추론하며 텍스트를 시퀀스로 생성
- 신뢰도 계산은 softmax로 토큰별 확률 추출 후 평균
모델별로 인식한 결과를 보면


각각의 모델들의 인식 결과,신뢰도를 볼 수 있다.
뒷 부분이 잘리긴 했지만 Base Printed 또는 Large Printed가 가장 좋은 모델이라고 볼 수 있겠다.
[2] 앙상블 기법
이번에는 여러 OCR 모델의 결과를 조합하는 앙상블 기법 세 가지를 정리해보겠다. TrOCR, PaddleOCR, EasyOCR 등 다양한 OCR 엔진을 사용할 경우, 각각 결과가 조금씩 다를 수 있기 때문에 하나의 최종 결과를 선택하기 위한 기준이 필요하다.
이를 위해 아래와 같은 방법들을 구현해봤다.
1. 투표 방식
여러 모델의 예측 결과 중 가장 많이 등장한 결과를 선택하는 방식이다.
from collections import Counter
def voting_ensemble(results):
vote_counts = Counter(results.values())
if not vote_counts:
return None
most_common_item = vote_counts.most_common(1)[0]
winner_text = most_common_item[0]
vote_count = most_common_item[1]
if vote_count == 1 and len(results) > 1:
print("투표 결과가 동점입니다. 신뢰도 기반 선택이 필요합니다.")
return None
print(f"투표 결과: '{winner_text}' ({vote_count}표)")
return winner_text
예시
model_results_1 = {
'PaddleOCR': '안녕하세요',
'EasyOCR': '안녕하세요',
'TrOCR': '안녕하새요'
}
투표 결과는 안녕하세요 (2표)가 된다.
2. 신뢰도 기반 방식
텍스트 결과마다 신뢰도(확률 값)가 있는 경우, 가장 신뢰도가 높은 결과나 동일 텍스트의 누적 신뢰도 합을 기준으로 선택하는 방식이다.
def confidence_based_ensemble(results_with_conf):
best_result = max(results_with_conf, key=lambda x: x[1])
print(f"최고 신뢰도: '{best_result[0]}' (신뢰도: {best_result[1]:.2f}, 모델: {best_result[2]})")
text_confidence = {}
for text, conf, model in results_with_conf:
text_confidence[text] = text_confidence.get(text, 0) + conf
best_weighted = max(text_confidence.items(), key=lambda x: x[1])
print(f"가중 평균 결과: '{best_weighted[0]}' (총 신뢰도: {best_weighted[1]:.2f})")
return best_result[0]
예시
results_with_confidence = [
('안녕하세요', 0.95, 'PaddleOCR'),
('안녕하세요', 0.88, 'EasyOCR'),
('안녕하새요', 0.72, 'TrOCR')
]
가장 높은 신뢰도를 가진 "안녕하세요"가 최종 선택된다.
3. 조건부 선택 방식
텍스트의 문자 구성 비율(한글, 영어, 숫자)을 분석한 뒤, 특정 모델에 유리한 경우 해당 결과를 선택하는 방식이다. 혼합 텍스트의 경우 신뢰도를 기준으로 선택한다.
def select_best_ocr(text, models_results):
korean_ratio = count_korean_chars(text) / len(text)
english_ratio = count_english_chars(text) / len(text)
number_ratio = count_numbers(text) / len(text)
if korean_ratio > 0.7:
selected_model = 'PaddleOCR'
reason = "한글 비율이 높음"
elif english_ratio > 0.7:
selected_model = 'EasyOCR'
reason = "영어 비율이 높음"
elif number_ratio > 0.5:
selected_model = 'PaddleOCR'
reason = "숫자 비율이 높음"
else:
selected_model = max(models_results.items(), key=lambda x: x[1][1])[0]
reason = "혼용 텍스트 - 신뢰도 기준 선택"
print(f"조건부 선택된 모델: {selected_model} ({reason})")
return models_results[selected_model][0]
텍스트 분석용 보조 함수도 함께 사용한다.
def count_korean_chars(text):
return sum(1 for char in text if '\uac00' <= char <= '\ud7a3')
def count_english_chars(text):
return sum(1 for char in text if 'a' <= char.lower() <= 'z')
def count_numbers(text):
return sum(1 for char in text if '0' <= char <= '9')
예시
text_sample = "안녕하세요 오늘 날씨가 좋네요"
model_results_2 = {
'PaddleOCR': ('안녕하세요', 0.95),
'EasyOCR': ('안녕하세요', 0.88),
'TrOCR': ('안녕하새요', 0.72)
}

전체 요약
| 방식 | 기준 | 장점 | 단점 |
| 투표 방식 | 가장 많은 결과 | 구현이 간단함 | 결과가 엇갈릴 경우 무의미 |
| 신뢰도 기반 | 확률 기반 선택 | 수치적으로 명확 | 신뢰도 추정이 필요 |
| 조건부 방식 | 텍스트 특성 기반 | 유연하고 직관적 | 사전 지식 필요 |
OCR은 모델마다 특화된 영역이 존재하기 때문에, 이런 방식들을 적절히 조합해 사용하는 것이 중요하다. 예를 들어, 한글 문서 OCR은 PaddleOCR, 손글씨는 TrOCR, 영문 텍스트는 EasyOCR이 더 좋은 결과를 낼 수 있다는 것을 알 수 있다.!
[3] Binary, Otsu, Adaptive Threshold
OCR을 수행하기 전에 꼭 거쳐야 하는 과정 중 하나가 이미지 전처리, 그중에서도 이진화이다.
이진화란 말 그대로 흑백으로 이미지를 단순화하는 작업인데, 글자가 더 선명하게 드러나도록 배경과 전경을 분리해주는 역할을 한다.
이번에는 대표적인 세 가지 이진화 기법을 직접 비교해보았다.
사용한 코드
import cv2
import matplotlib.pyplot as plt
import os
image = cv2.imread('image.png', 0) # 흑백 모드로 이미지 로딩
# 1. 기본 임계값 처리
ret, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
# 2. Otsu 알고리즘 사용
ret2, otsu = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 3. 적응형 임계값 처리
adattive = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 11, 2)
# 시각화
plt.figure(figsize=(15,5))
plt.subplot(1,4,1)
plt.imshow(image, cmap='gray')
plt.title('original')
plt.axis('off')
plt.subplot(1,4,2)
plt.imshow(binary, cmap='gray')
plt.title('binary')
plt.axis('off')
plt.subplot(1,4,3)
plt.imshow(otsu, cmap='gray')
plt.title("otsu's")
plt.axis('off')
plt.subplot(1,4,4)
plt.imshow(adattive, cmap='gray')
plt.title('adaptive')
plt.axis('off')
plt.tight_layout()
plt.show()
직접 시각화해보면 차이가 명확하게 드러난다. 기본 임계값 방식은 조명이 균일할 때는 잘 동작하지만, 조금만 조명이 흐트러져도 글자가 뭉개지거나 날아간다. Otsu 방식은 전체 이미지의 히스토그램 분포를 분석해서 자동으로 임계값을 잡아주기 때문에 일반적인 상황에서는 꽤 쓸만하다
반면 적응형 이진화는 각 블록별로 임계값을 다르게 잡기 때문에 가장 안정적인 결과를 보여준다. 특히 OCR에 활용할 때는 대부분 이 방식이 가장 성능이 좋은 것 같다.
본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.
'카카오클라우드 AIaaS 교육 > AIaaS를 위한 머신러닝&AI' 카테고리의 다른 글
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY - FastAPI (0) | 2025.06.03 |
|---|---|
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 44 - 데이터 전처리 (1) | 2025.05.28 |
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 42 - python 심화 (0) | 2025.05.27 |
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 43 - numpy,pandas (0) | 2025.05.26 |
| [스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY 41 - python 심화 (0) | 2025.05.26 |