Ver3.0 ๐ ๋ฌธ์ OCR ์ธ์ ํ๋ก๊ทธ๋จ ์์ค์ฝ๋ ์๋ฒฝ ๊ฐ์ด๋: ์ค์ ๊ตฌํ๋ถํฐ ์ต์ ํ๊น์ง

๐ ๋ฌธ์ OCR ์ธ์ ํ๋ก๊ทธ๋จ ์์ค์ฝ๋ ์๋ฒฝ ๊ฐ์ด๋: ์ค์ ๊ตฌํ๋ถํฐ ์ต์ ํ๊น์ง
์ด๋ฏธ์ง ์ ํ ์คํธ๋ฅผ ๋ง๋ฒ์ฒ๋ผ ์ถ์ถํ๋ OCR ๊ธฐ์ , ์ง์ ๋ง๋ค์ด๋ณด์! ๐
์๋
! ์ค๋์ ์ ๋ง ์ฌ๋ฏธ์๋ ์ฃผ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ๋ฐ๋ก ๋ฌธ์ OCR ์ธ์ ํ๋ก๊ทธ๋จ์ ์ง์ ๋ง๋๋ ๋ฐฉ๋ฒ์ด์ผ! ๐ธโจ
ํน์ ์ฌ์ง ์ฐ์ ๋ฌธ์์์ ํ
์คํธ๋ฅผ ์๋์ผ๋ก ๋ฝ์๋ด๋ ํ๋ก๊ทธ๋จ ๋ณธ ์ ์์ง? ์ค์บํ ์ฑ
์ ํ
์คํธ๋ก ๋ณํํ๊ฑฐ๋, ๋ช
ํจ์ ์ฐ์ผ๋ฉด ์๋์ผ๋ก ์ฐ๋ฝ์ฒ๊ฐ ์ ์ฅ๋๋ ๊ทธ๋ฐ ์ ๊ธฐํ ๊ธฐ๋ฅ ๋ง์ด์ผ. ์ด๊ฒ ๋ฐ๋ก OCR(Optical Character Recognition, ๊ดํ ๋ฌธ์ ์ธ์) ๊ธฐ์ ์ด๊ฑฐ๋ !
์ฒ์์๋ "์ด๊ฑฐ ์์ฒญ ์ด๋ ค์ด ๊ฑฐ ์๋์ผ?" ํ๊ณ ์๊ฐํ ์ ์๋๋ฐ, ์์ฆ์ ์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์ ๋ง ์ ๋์ด ์์ด์ ์๊ฐ๋ณด๋ค ์ฝ๊ฒ ๊ตฌํํ ์ ์์ด. ๋ฌผ๋ก ์ ๋๋ก ์ดํดํ๊ณ ์ต์ ํํ๋ ค๋ฉด ์์์ผ ํ ๊ฒ ๋ง์ง๋ง, ๊ทธ๋์ ๋ด๊ฐ ์ค๋ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์ค๋ช
ํด์ค๊ฒ! ๐
OCR์ Optical Character Recognition์ ์ฝ์๋ก, ์ฐ๋ฆฌ๋ง๋ก๋ '๊ดํ ๋ฌธ์ ์ธ์'์ด๋ผ๊ณ ํด. ์ฝ๊ฒ ๋งํ๋ฉด ์ด๋ฏธ์ง๋ ์ค์บํ ๋ฌธ์์์ ํ
์คํธ๋ฅผ ์ธ์ํด์ ์ปดํจํฐ๊ฐ ํธ์งํ ์ ์๋ ๋ฌธ์ ๋ฐ์ดํฐ๋ก ๋ณํํ๋ ๊ธฐ์ ์ด์ผ. ๐โก๏ธ๐ป
์๋ฅผ ๋ค์ด๋ณผ๊ฒ. ์๋ ์ฑ
์ ์ค์บํ๋ค๊ณ ์๊ฐํด๋ด. ๊ทธ๋ฅ ์ด๋ฏธ์ง ํ์ผ์ด๋ฉด ๊ฒ์๋ ์ ๋๊ณ , ๋ณต์ฌ๋ ์ ๋๊ณ , ๋ฒ์ญ๋ ์ ๋์์? ๊ทธ๋ฐ๋ฐ OCR์ ๊ฑฐ์น๋ฉด ๊ทธ ์ด๋ฏธ์ง ์ ๊ธ์๋ค์ด ์ง์ง ํ
์คํธ๋ก ๋ณํ๋๋ ๊ฑฐ์ง! ๐
โ ๋ฌธ์ ์ค์บ๋ ์ฑ (CamScanner, Adobe Scan ๋ฑ)
โ ๋ฒ์ญ ์ฑ (Google ๋ฒ์ญ์ ์นด๋ฉ๋ผ ๊ธฐ๋ฅ)
โ ์๋์ฐจ ๋ฒํธํ ์ธ์ ์์คํ
โ ์ํ์ ์ํ/์ง๋ก ์๋ ์ธ์
โ ๋์๊ด์ ์ฑ ๋์งํธํ ํ๋ก์ ํธ
โ ์์์ฆ ์๋ ์ ๋ ฅ ๊ฐ๊ณ๋ถ ์ฑ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก OCR ํ๋ก๊ทธ๋จ์ ๋ง๋ค๊ธฐ ์ํด ๋ญ๊ฐ ํ์ํ์ง ์์๋ณด์! ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ถํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊น์ง ํ๋์ฉ ์ดํด๋ณผ๊ฒ. ๐ง
| ๊ตฌ๋ถ | ๊ธฐ์ /๋๊ตฌ | ์ค๋ช |
|---|---|---|
| ํ๋ก๊ทธ๋๋ฐ ์ธ์ด | Python | ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ฉฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ๋ถํจ |
| OCR ์์ง | Tesseract | ๊ตฌ๊ธ์ด ๊ด๋ฆฌํ๋ ์คํ์์ค OCR ์์ง |
| ์ด๋ฏธ์ง ์ฒ๋ฆฌ | OpenCV, PIL/Pillow | ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ๋ฐ ์ต์ ํ |
| ๋ฅ๋ฌ๋(์ ํ) | TensorFlow, PyTorch | ๊ณ ๊ธ OCR ๋ชจ๋ธ ๊ตฌ์ถ์ ์ฌ์ฉ |
| PDF ์ฒ๋ฆฌ | PyPDF2, pdf2image | PDF ๋ฌธ์ ์ฒ๋ฆฌ์ฉ |
๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ์ ๊ตฌ์ถํด์ผ ํด. Python์ ๊ธฐ์ค์ผ๋ก ์ค๋ช ํ ๊ฒ. Python์ด ์ค์น๋์ด ์๋ค๊ณ ๊ฐ์ ํ๊ณ ์งํํ ๊ฒ! ๐
Tesseract๋ OCR์ ํต์ฌ ์์ง์ด์ผ. ๋จผ์ ์์คํ ์ ์ค์นํด์ผ ํด.
Windows:
๊ณต์ GitHub์์ ์ค์น ํ์ผ ๋ค์ด๋ก๋
(https://github.com/UB-Mannheim/tesseract/wiki)
Mac:
brew install tesseractLinux (Ubuntu):
sudo apt-get install tesseract-ocr
sudo apt-get install libtesseract-dev
ํ์ํ Python ํจํค์ง๋ค์ ์ค์นํด๋ณด์:
pip install pytesseract
pip install Pillow
pip install opencv-python
pip install numpy
pip install pdf2image์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๊ฐ๊ฐ ๋ค์ ์ญํ ์ ํด:
โข pytesseract: Tesseract์ Python ๋ํผ
โข Pillow: ์ด๋ฏธ์ง ํ์ผ ์ฒ๋ฆฌ
โข opencv-python: ๊ณ ๊ธ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ
โข numpy: ์์น ์ฐ์ฐ
โข pdf2image: PDF๋ฅผ ์ด๋ฏธ์ง๋ก ๋ณํ
Tesseract๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์์ด๋ง ์ง์ํด. ํ๊ธ์ ์ธ์ํ๋ ค๋ฉด ํ๊ธ ํ์ต ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ๋ก ์ค์นํด์ผ ํด! ๐ฐ๐ท
# Linux/Mac
sudo apt-get install tesseract-ocr-kor
# ๋๋ ์๋ ๋ค์ด๋ก๋
# https://github.com/tesseract-ocr/tessdata
# kor.traineddata ํ์ผ์ Tesseract์ tessdata ํด๋์ ๋ณต์ฌ
์, ์ด์ ๋๋์ด ์ฝ๋๋ฅผ ์์ฑํด๋ณผ ์๊ฐ์ด์ผ! ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ OCR ํ๋ก๊ทธ๋จ๋ถํฐ ์์ํด๋ณด์. ๐
import pytesseract
from PIL import Image
# Tesseract ์คํ ํ์ผ ๊ฒฝ๋ก ์ค์ (Windows์ ๊ฒฝ์ฐ)
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
def simple_ocr(image_path):
"""
๊ธฐ๋ณธ OCR ํจ์
Args:
image_path: ์ด๋ฏธ์ง ํ์ผ ๊ฒฝ๋ก
Returns:
์ถ์ถ๋ ํ
์คํธ
"""
try:
# ์ด๋ฏธ์ง ์ด๊ธฐ
image = Image.open(image_path)
# OCR ์ํ (ํ๊ธ + ์์ด)
text = pytesseract.image_to_string(image, lang='kor+eng')
return text
except Exception as e:
return f"์ค๋ฅ ๋ฐ์: {str(e)}"
# ์ฌ์ฉ ์์
if __name__ == "__main__":
image_path = "sample_document.jpg"
result = simple_ocr(image_path)
print("์ถ์ถ๋ ํ
์คํธ:")
print(result)
๊ธฐ๋ณธ OCR๋ง์ผ๋ก๋ ์ธ์๋ฅ ์ด ๋ฎ์ ์ ์์ด. ํนํ ์ด๋ฏธ์ง ํ์ง์ด ์ ์ข๊ฑฐ๋, ๊ธฐ์ธ์ด์ ธ ์๊ฑฐ๋, ๋ ธ์ด์ฆ๊ฐ ๋ง์ผ๋ฉด ์ ๋๋ก ์ธ์์ด ์ ๋ผ. ๊ทธ๋์ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๊ฐ ์ ๋ง ์ค์ํด! ๐
- โ ๋ฎ์ ํด์๋
- โ ๊ธฐ์ธ์ด์ง ํ ์คํธ
- โ ๋ถ๊ท ์ผํ ์กฐ๋ช
- โ ๋ฐฐ๊ฒฝ ๋ ธ์ด์ฆ
- โ ํ๋ฆฟํ ์ด๋ฏธ์ง
- โ ๋ณต์กํ ํฐํธ๋ ์๊ธ์จ
import cv2
import numpy as np
from PIL import Image
import pytesseract
class ImagePreprocessor:
"""์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ํด๋์ค"""
@staticmethod
def convert_to_grayscale(image):
"""๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ"""
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
@staticmethod
def apply_threshold(image):
"""์ด์งํ ์ฒ๋ฆฌ (ํ๋ฐฑ์ผ๋ก ๋ช
ํํ๊ฒ)"""
# Otsu's ์ด์งํ ๋ฐฉ๋ฒ ์ฌ์ฉ
_, binary = cv2.threshold(
image, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
return binary
@staticmethod
def remove_noise(image):
"""๋
ธ์ด์ฆ ์ ๊ฑฐ"""
# ๋ชจํด๋ก์ง ์ฐ์ฐ์ผ๋ก ๋
ธ์ด์ฆ ์ ๊ฑฐ
kernel = np.ones((1, 1), np.uint8)
image = cv2.dilate(image, kernel, iterations=1)
image = cv2.erode(image, kernel, iterations=1)
# ๊ฐ์ฐ์์ ๋ธ๋ฌ๋ก ์ถ๊ฐ ๋
ธ์ด์ฆ ์ ๊ฑฐ
image = cv2.GaussianBlur(image, (5, 5), 0)
return image
@staticmethod
def deskew(image):
"""๊ธฐ์ธ์ด์ง ์ด๋ฏธ์ง ๋ณด์ """
coords = np.column_stack(np.where(image > 0))
angle = cv2.minAreaRect(coords)[-1]
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(
image, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE
)
return rotated
@staticmethod
def resize_image(image, scale_percent=150):
"""์ด๋ฏธ์ง ํฌ๊ธฐ ์กฐ์ (ํด์๋ ํฅ์)"""
width = int(image.shape[1] * scale_percent / 100)
height = int(image.shape[0] * scale_percent / 100)
dim = (width, height)
return cv2.resize(image, dim, interpolation=cv2.INTER_CUBIC)
def advanced_ocr(image_path, preprocess=True):
"""
๊ณ ๊ธ OCR ํจ์ (์ ์ฒ๋ฆฌ ํฌํจ)
Args:
image_path: ์ด๋ฏธ์ง ํ์ผ ๊ฒฝ๋ก
preprocess: ์ ์ฒ๋ฆฌ ์ํ ์ฌ๋ถ
Returns:
์ถ์ถ๋ ํ
์คํธ
"""
# ์ด๋ฏธ์ง ์ฝ๊ธฐ
image = cv2.imread(image_path)
if preprocess:
preprocessor = ImagePreprocessor()
# 1. ๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ
gray = preprocessor.convert_to_grayscale(image)
# 2. ๋
ธ์ด์ฆ ์ ๊ฑฐ
denoised = preprocessor.remove_noise(gray)
# 3. ์ด์งํ
binary = preprocessor.apply_threshold(denoised)
# 4. ๊ธฐ์ธ๊ธฐ ๋ณด์
deskewed = preprocessor.deskew(binary)
# 5. ํด์๋ ํฅ์
resized = preprocessor.resize_image(deskewed)
# PIL Image๋ก ๋ณํ
processed_image = Image.fromarray(resized)
else:
processed_image = Image.open(image_path)
# OCR ์ํ
custom_config = r'--oem 3 --psm 6'
text = pytesseract.image_to_string(
processed_image,
lang='kor+eng',
config=custom_config
)
return text
# ์ฌ์ฉ ์์
if __name__ == "__main__":
image_path = "document.jpg"
print("=== ์ ์ฒ๋ฆฌ ์์ด OCR ===")
result_basic = advanced_ocr(image_path, preprocess=False)
print(result_basic)
print("\n=== ์ ์ฒ๋ฆฌ ํ OCR ===")
result_advanced = advanced_ocr(image_path, preprocess=True)
print(result_advanced)
์ปฌ๋ฌ ์ ๋ณด๋ฅผ ์ ๊ฑฐํ๊ณ ๋ช ์๋ง ๋จ๊ฒจ์ ์ฒ๋ฆฌ ์๋๋ฅผ ๋์ด๊ณ ํ ์คํธ์ ์ง์คํ ์ ์๊ฒ ํด.
2. ์ด์งํ (Thresholding)
์ด๋ฏธ์ง๋ฅผ ํ๋ฐฑ์ผ๋ก ๋ช ํํ๊ฒ ๊ตฌ๋ถํด. Otsu ๋ฐฉ๋ฒ์ ์๋์ผ๋ก ์ต์ ์ ์๊ณ๊ฐ์ ์ฐพ์์ค.
3. ๋ ธ์ด์ฆ ์ ๊ฑฐ
๋ชจํด๋ก์ง ์ฐ์ฐ๊ณผ ๊ฐ์ฐ์์ ๋ธ๋ฌ๋ฅผ ์ฌ์ฉํด์ ์กํฐ๋ ์ผ๋ฃฉ์ ์ ๊ฑฐํด.
4. ๊ธฐ์ธ๊ธฐ ๋ณด์ (Deskewing)
์ค์บํ ๋ ๋น๋ค์ด์ง ๋ฌธ์๋ฅผ ๋ฐ๋ฅด๊ฒ ํด์ฃผ๋ ๊ฑฐ์ผ.
5. ํด์๋ ํฅ์
์ด๋ฏธ์ง๋ฅผ ํ๋ํด์ ์์ ๊ธ์จ๋ ์ ์ธ์๋๊ฒ ๋ง๋ค์ด.
์ค๋ฌด์์๋ ์ด๋ฏธ์ง๋ณด๋ค PDF ๋ฌธ์๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๊ฐ ๋ ๋ง์. PDF๋ฅผ OCR๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐
from pdf2image import convert_from_path
import pytesseract
from PIL import Image
import os
class PDFOCRProcessor:
"""PDF OCR ์ฒ๋ฆฌ ํด๋์ค"""
def __init__(self, tesseract_path=None):
if tesseract_path:
pytesseract.pytesseract.tesseract_cmd = tesseract_path
def pdf_to_images(self, pdf_path, dpi=300):
"""
PDF๋ฅผ ์ด๋ฏธ์ง๋ก ๋ณํ
Args:
pdf_path: PDF ํ์ผ ๊ฒฝ๋ก
dpi: ํด์๋ (๋์์๋ก ํ์ง ์ข์, ๊ธฐ๋ณธ 300)
Returns:
์ด๋ฏธ์ง ๋ฆฌ์คํธ
"""
try:
images = convert_from_path(pdf_path, dpi=dpi)
return images
except Exception as e:
print(f"PDF ๋ณํ ์ค๋ฅ: {str(e)}")
return []
def process_pdf(self, pdf_path, output_txt_path=None, lang='kor+eng'):
"""
PDF ์ ์ฒด๋ฅผ OCR ์ฒ๋ฆฌ
Args:
pdf_path: PDF ํ์ผ ๊ฒฝ๋ก
output_txt_path: ๊ฒฐ๊ณผ ํ
์คํธ ํ์ผ ๊ฒฝ๋ก (์ ํ)
lang: ์ธ์ ์ธ์ด
Returns:
์ถ์ถ๋ ์ ์ฒด ํ
์คํธ
"""
print(f"PDF ์ฒ๋ฆฌ ์์: {pdf_path}")
# PDF๋ฅผ ์ด๋ฏธ์ง๋ก ๋ณํ
images = self.pdf_to_images(pdf_path)
if not images:
return "PDF ๋ณํ ์คํจ"
full_text = ""
# ๊ฐ ํ์ด์ง ์ฒ๋ฆฌ
for i, image in enumerate(images, 1):
print(f"ํ์ด์ง {i}/{len(images)} ์ฒ๋ฆฌ ์ค...")
# OCR ์ํ
page_text = pytesseract.image_to_string(image, lang=lang)
# ํ์ด์ง ๊ตฌ๋ถ์ ์ถ๊ฐ
full_text += f"\n{'='*50}\n"
full_text += f"[ํ์ด์ง {i}]\n"
full_text += f"{'='*50}\n"
full_text += page_text
full_text += "\n"
# ํ์ผ๋ก ์ ์ฅ (์ต์
)
if output_txt_path:
with open(output_txt_path, 'w', encoding='utf-8') as f:
f.write(full_text)
print(f"๊ฒฐ๊ณผ ์ ์ฅ ์๋ฃ: {output_txt_path}")
return full_text
def process_pdf_with_preprocessing(self, pdf_path, output_txt_path=None):
"""์ ์ฒ๋ฆฌ๋ฅผ ํฌํจํ PDF OCR ์ฒ๋ฆฌ"""
import cv2
import numpy as np
images = self.pdf_to_images(pdf_path)
full_text = ""
for i, pil_image in enumerate(images, 1):
print(f"ํ์ด์ง {i}/{len(images)} ์ฒ๋ฆฌ ์ค (์ ์ฒ๋ฆฌ ํฌํจ)...")
# PIL Image๋ฅผ OpenCV ํ์์ผ๋ก ๋ณํ
opencv_image = cv2.cvtColor(
np.array(pil_image),
cv2.COLOR_RGB2BGR
)
# ์ ์ฒ๋ฆฌ
gray = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2GRAY)
denoised = cv2.fastNlMeansDenoising(gray)
_, binary = cv2.threshold(
denoised, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
# ๋ค์ PIL Image๋ก ๋ณํ
processed_image = Image.fromarray(binary)
# OCR ์ํ
page_text = pytesseract.image_to_string(
processed_image,
lang='kor+eng'
)
full_text += f"\n[ํ์ด์ง {i}]\n{page_text}\n"
if output_txt_path:
with open(output_txt_path, 'w', encoding='utf-8') as f:
f.write(full_text)
return full_text
# ์ฌ์ฉ ์์
if __name__ == "__main__":
processor = PDFOCRProcessor()
# ๊ธฐ๋ณธ PDF OCR
pdf_path = "sample_document.pdf"
output_path = "extracted_text.txt"
result = processor.process_pdf(pdf_path, output_path)
print("์ถ์ถ ์๋ฃ!")
print(result[:500]) # ์ฒ์ 500์๋ง ์ถ๋ ฅ
์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ์ ๋ค์ ์์๋ณผ๊น? ์ค๋ฌด์์ ์ ๋ง ์ ์ฉํ ํ๋ค์ด์ผ! ๐ช
import pytesseract
from PIL import Image
class OptimizedOCR:
"""์ต์ ํ๋ OCR ํด๋์ค"""
# PSM (Page Segmentation Mode) ๋ชจ๋
PSM_MODES = {
'auto': 3, # ์๋ ํ์ด์ง ๋ถํ
'single_column': 4, # ๋จ์ผ ์ปฌ๋ผ
'single_block': 6, # ๋จ์ผ ํ
์คํธ ๋ธ๋ก
'single_line': 7, # ํ ์ค ํ
์คํธ
'single_word': 8, # ๋จ์ผ ๋จ์ด
'sparse_text': 11, # ํฌ์ ํ
์คํธ
}
# OEM (OCR Engine Mode) ๋ชจ๋
OEM_MODES = {
'legacy': 0, # ๋ ๊ฑฐ์ ์์ง
'lstm': 1, # LSTM ์ ๊ฒฝ๋ง ์์ง
'legacy_lstm': 2, # ๋ ๊ฑฐ์ + LSTM
'default': 3, # ๊ธฐ๋ณธ๊ฐ
}
@staticmethod
def ocr_with_config(image_path, psm_mode='single_block',
oem_mode='default', lang='kor+eng',
whitelist=None, blacklist=None):
"""
์ค์ ์ ์ปค์คํฐ๋ง์ด์งํ OCR
Args:
image_path: ์ด๋ฏธ์ง ๊ฒฝ๋ก
psm_mode: ํ์ด์ง ๋ถํ ๋ชจ๋
oem_mode: OCR ์์ง ๋ชจ๋
lang: ์ธ์ด
whitelist: ํ์ฉํ ๋ฌธ์๋ค (์: '0123456789' - ์ซ์๋ง)
blacklist: ์ ์ธํ ๋ฌธ์๋ค
Returns:
์ถ์ถ๋ ํ
์คํธ
"""
image = Image.open(image_path)
# ์ค์ ๋ฌธ์์ด ์์ฑ
psm = OptimizedOCR.PSM_MODES.get(psm_mode, 6)
oem = OptimizedOCR.OEM_MODES.get(oem_mode, 3)
config = f'--oem {oem} --psm {psm}'
# ํ์ดํธ๋ฆฌ์คํธ/๋ธ๋๋ฆฌ์คํธ ์ถ๊ฐ
if whitelist:
config += f' -c tessedit_char_whitelist={whitelist}'
if blacklist:
config += f' -c tessedit_char_blacklist={blacklist}'
# OCR ์ํ
text = pytesseract.image_to_string(
image,
lang=lang,
config=config
)
return text
@staticmethod
def ocr_with_confidence(image_path, lang='kor+eng'):
"""
์ ๋ขฐ๋ ์ ๋ณด์ ํจ๊ป OCR ์ํ
Returns:
๋์
๋๋ฆฌ ํํ์ ์์ธ ์ ๋ณด
"""
image = Image.open(image_path)
# ์์ธ ๋ฐ์ดํฐ ์ถ์ถ
data = pytesseract.image_to_data(
image,
lang=lang,
output_type=pytesseract.Output.DICT
)
# ์ ๋ขฐ๋๊ฐ ๋์ ํ
์คํธ๋ง ํํฐ๋ง
filtered_text = []
for i, conf in enumerate(data['conf']):
if int(conf) > 60: # ์ ๋ขฐ๋ 60% ์ด์
text = data['text'][i]
if text.strip():
filtered_text.append({
'text': text,
'confidence': conf,
'position': {
'x': data['left'][i],
'y': data['top'][i],
'width': data['width'][i],
'height': data['height'][i]
}
})
return filtered_text
@staticmethod
def extract_numbers_only(image_path):
"""์ซ์๋ง ์ถ์ถ (์ ํ๋ฒํธ, ๊ณ์ข๋ฒํธ ๋ฑ)"""
return OptimizedOCR.ocr_with_config(
image_path,
psm_mode='single_line',
whitelist='0123456789-+()'
)
@staticmethod
def extract_korean_only(image_path):
"""ํ๊ธ๋ง ์ถ์ถ"""
text = OptimizedOCR.ocr_with_config(
image_path,
lang='kor'
)
# ํ๊ธ๋ง ํํฐ๋ง
import re
korean_only = re.findall(r'[๊ฐ-ํฃ\s]+', text)
return ' '.join(korean_only)
# ์ฌ์ฉ ์์
if __name__ == "__main__":
ocr = OptimizedOCR()
# 1. ์ผ๋ฐ ๋ฌธ์ (๋จ์ผ ๋ธ๋ก)
text1 = ocr.ocr_with_config(
"document.jpg",
psm_mode='single_block'
)
print("์ผ๋ฐ ๋ฌธ์:", text1)
# 2. ๋ช
ํจ (ํฌ์ ํ
์คํธ)
text2 = ocr.ocr_with_config(
"business_card.jpg",
psm_mode='sparse_text'
)
print("๋ช
ํจ:", text2)
# 3. ์ ํ๋ฒํธ ์ถ์ถ
phone = ocr.extract_numbers_only("phone_number.jpg")
print("์ ํ๋ฒํธ:", phone)
# 4. ์ ๋ขฐ๋์ ํจ๊ป ์ถ์ถ
detailed = ocr.ocr_with_confidence("document.jpg")
for item in detailed[:5]: # ์ฒ์ 5๊ฐ๋ง
print(f"ํ
์คํธ: {item['text']}, ์ ๋ขฐ๋: {item['confidence']}%")
โข ๋ช ํจ, ๊ฐํ: sparse_text (11)
โข ํ ์ค ํ ์คํธ: single_line (7)
โข ๋จ์ผ ๋จ์ด: single_word (8)
โข ๋ณต์กํ ๋ ์ด์์: auto (3)
์ด๋ก ๋ง ๋ฐฐ์ฐ๋ฉด ์ฌ๋ฏธ์์์? ์ค์ ๋ก ํ์ฉํ ์ ์๋ ๋ช ํจ ์ธ์ ์์คํ ์ ๋ง๋ค์ด๋ณด์! ์ด๊ฑด ์ ๋ง ์ค์ฉ์ ์ด์ผ. ๐
import pytesseract
from PIL import Image
import cv2
import numpy as np
import re
class BusinessCardOCR:
"""๋ช
ํจ ์ธ์ ์ ๋ฌธ ํด๋์ค"""
def __init__(self):
self.patterns = {
'email': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
'phone': r'0\d{1,2}[-\s]?\d{3,4}[-\s]?\d{4}',
'mobile': r'01[0-9][-\s]?\d{3,4}[-\s]?\d{4}',
'url': r'(?:www\.|https?://)[^\s]+',
}
def preprocess_card(self, image_path):
"""๋ช
ํจ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ"""
# ์ด๋ฏธ์ง ์ฝ๊ธฐ
image = cv2.imread(image_path)
# ํฌ๊ธฐ ์กฐ์ (๋๋ฌด ์์ผ๋ฉด ํ๋)
height, width = image.shape[:2]
if width < 1000:
scale = 1000 / width
new_width = int(width * scale)
new_height = int(height * scale)
image = cv2.resize(
image,
(new_width, new_height),
interpolation=cv2.INTER_CUBIC
)
# ๊ทธ๋ ์ด์ค์ผ์ผ
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# ์ ์ํ ์ด์งํ (๋ช
ํจ์ ์กฐ๋ช
์ด ๋ถ๊ท ์ผํ ์ ์์)
binary = cv2.adaptiveThreshold(
gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
# ๋
ธ์ด์ฆ ์ ๊ฑฐ
denoised = cv2.fastNlMeansDenoising(binary)
return Image.fromarray(denoised)
def extract_text(self, image_path):
"""๋ช
ํจ์์ ํ
์คํธ ์ถ์ถ"""
# ์ ์ฒ๋ฆฌ
processed_image = self.preprocess_card(image_path)
# OCR ์ํ (ํฌ์ ํ
์คํธ ๋ชจ๋)
custom_config = r'--oem 3 --psm 11'
text = pytesseract.image_to_string(
processed_image,
lang='kor+eng',
config=custom_config
)
return text
def parse_card_info(self, text):
"""์ถ์ถ๋ ํ
์คํธ์์ ์ ๋ณด ํ์ฑ"""
info = {
'name': None,
'company': None,
'position': None,
'email': None,
'phone': None,
'mobile': None,
'address': None,
'url': None,
'raw_text': text
}
lines = [line.strip() for line in text.split('\n') if line.strip()]
# ์ด๋ฉ์ผ ์ฐพ๊ธฐ
email_match = re.search(self.patterns['email'], text)
if email_match:
info['email'] = email_match.group()
# ์ ํ๋ฒํธ ์ฐพ๊ธฐ
phone_match = re.search(self.patterns['phone'], text)
if phone_match:
info['phone'] = phone_match.group()
# ํด๋ํฐ ๋ฒํธ ์ฐพ๊ธฐ
mobile_match = re.search(self.patterns['mobile'], text)
if mobile_match:
info['mobile'] = mobile_match.group()
# URL ์ฐพ๊ธฐ
url_match = re.search(self.patterns['url'], text)
if url_match:
info['url'] = url_match.group()
# ์ด๋ฆ ์ถ์ (ํ๊ธ 2-4์)
korean_names = re.findall(r'\b[๊ฐ-ํฃ]{2,4}\b', text)
if korean_names:
info['name'] = korean_names[0]
# ์ง์ฑ
ํค์๋
positions = ['๋ํ', '์ด์ฌ', '๋ถ์ฅ', '์ฐจ์ฅ', '๊ณผ์ฅ', '๋๋ฆฌ',
'์ฌ์', 'ํ์ฅ', '์ค์ฅ', 'CEO', 'CTO', 'CFO']
for line in lines:
for pos in positions:
if pos in line:
info['position'] = line
break
# ํ์ฌ๋ช
์ถ์ (์ฃผ์ํ์ฌ, (์ฃผ), ๋ฑ)
company_keywords = ['์ฃผ์ํ์ฌ', '(์ฃผ)', 'ใ', 'Inc', 'Corp', 'Co']
for line in lines:
for keyword in company_keywords:
if keyword in line:
info['company'] = line
break
return info
def process_business_card(self, image_path):
"""๋ช
ํจ ์ ์ฒด ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ"""
print(f"๋ช
ํจ ์ฒ๋ฆฌ ์์: {image_path}")
# ํ
์คํธ ์ถ์ถ
text = self.extract_text(image_path)
# ์ ๋ณด ํ์ฑ
info = self.parse_card_info(text)
return info
def format_output(self, info):
"""๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ ์ข๊ฒ ํฌ๋งทํ
"""
output = "\n" + "="*50 + "\n"
output += "๐ ๋ช
ํจ ์ ๋ณด\n"
output += "="*50 + "\n\n"
if info['name']:
output += f"๐ค ์ด๋ฆ: {info['name']}\n"
if info['company']:
output += f"๐ข ํ์ฌ: {info['company']}\n"
if info['position']:
output += f"๐ผ ์ง์ฑ
: {info['position']}\n"
if info['email']:
output += f"๐ง ์ด๋ฉ์ผ: {info['email']}\n"
if info['phone']:
output += f"โ๏ธ ์ ํ: {info['phone']}\n"
if info['mobile']:
output += f"๐ฑ ํด๋ํฐ: {info['mobile']}\n"
if info['url']:
output += f"๐ ์น์ฌ์ดํธ: {info['url']}\n"
output += "\n" + "="*50 + "\n"
output += "๐ ์๋ณธ ํ
์คํธ:\n"
output += "="*50 + "\n"
output += info['raw_text']
return output
# ์ฌ์ฉ ์์
if __name__ == "__main__":
ocr = BusinessCardOCR()
# ๋ช
ํจ ์ฒ๋ฆฌ
card_info = ocr.process_business_card("business_card.jpg")
# ๊ฒฐ๊ณผ ์ถ๋ ฅ
formatted = ocr.format_output(card_info)
print(formatted)
# JSON์ผ๋ก ์ ์ฅํ๊ณ ์ถ๋ค๋ฉด
import json
with open('card_info.json', 'w', encoding='utf-8') as f:
json.dump(card_info, f, ensure_ascii=False, indent=2)
Tesseract๋ ์ข์ง๋ง, ์ต์ ๋ฅ๋ฌ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ๋ฉด ํจ์ฌ ๋ ์ ํํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ด! ํนํ ์๊ธ์จ๋ ๋ณต์กํ ๋ ์ด์์์์ ๊ฐ๋ ฅํด. ๐ง
โข PaddleOCR: ๋ฐ์ด๋๊ฐ ๋ง๋ ๊ณ ์ฑ๋ฅ OCR
โข TrOCR: ํธ๋์คํฌ๋จธ ๊ธฐ๋ฐ ์ต์ ๋ชจ๋ธ
โข Keras-OCR: Keras ๊ธฐ๋ฐ ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ
# EasyOCR ์ค์น
# pip install easyocr
import easyocr
import cv2
from PIL import Image
import numpy as np
class DeepLearningOCR:
"""๋ฅ๋ฌ๋ ๊ธฐ๋ฐ OCR ํด๋์ค"""
def __init__(self, languages=['ko', 'en'], gpu=False):
"""
์ด๊ธฐํ
Args:
languages: ์ธ์ํ ์ธ์ด ๋ฆฌ์คํธ
gpu: GPU ์ฌ์ฉ ์ฌ๋ถ
"""
print("EasyOCR ๋ชจ๋ธ ๋ก๋ฉ ์ค... (์ฒ์์ ์๊ฐ์ด ๊ฑธ๋ฆด ์ ์์ด์)")
self.reader = easyocr.Reader(languages, gpu=gpu)
print("๋ชจ๋ธ ๋ก๋ฉ ์๋ฃ!")
def recognize_text(self, image_path, detail=1):
"""
ํ
์คํธ ์ธ์
Args:
image_path: ์ด๋ฏธ์ง ๊ฒฝ๋ก
detail: 0=ํ
์คํธ๋ง, 1=์์ธ์ ๋ณด ํฌํจ
Returns:
์ธ์ ๊ฒฐ๊ณผ
"""
result = self.reader.readtext(image_path, detail=detail)
return result
def extract_text_only(self, image_path):
"""ํ
์คํธ๋ง ์ถ์ถ"""
results = self.reader.readtext(image_path, detail=1)
texts = [text for (bbox, text, conf) in results]
return '\n'.join(texts)
def extract_with_confidence(self, image_path, min_confidence=0.5):
"""์ ๋ขฐ๋ ํํฐ๋งํ์ฌ ์ถ์ถ"""
results = self.reader.readtext(image_path, detail=1)
filtered = []
for (bbox, text, conf) in results:
if conf >= min_confidence:
filtered.append({
'text': text,
'confidence': conf,
'bbox': bbox
})
return filtered
def visualize_results(self, image_path, output_path='result.jpg'):
"""
์ธ์ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ฏธ์ง์ ์๊ฐํ
"""
# ์ด๋ฏธ์ง ์ฝ๊ธฐ
image = cv2.imread(image_path)
results = self.reader.readtext(image_path)
# ๊ฐ ํ
์คํธ ์์ญ์ ๋ฐ์ค ๊ทธ๋ฆฌ๊ธฐ
for (bbox, text, conf) in results:
# ์ขํ ์ถ์ถ
top_left = tuple(map(int, bbox[0]))
bottom_right = tuple(map(int, bbox[2]))
# ๋ฐ์ค ๊ทธ๋ฆฌ๊ธฐ
cv2.rectangle(image, top_left, bottom_right, (0, 255, 0), 2)
# ํ
์คํธ ํ์
cv2.putText(
image, text,
(top_left[0], top_left[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.8, (0, 255, 0), 2
)
# ์ ์ฅ
cv2.imwrite(output_path, image)
print(f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง ์ ์ฅ: {output_path}")
return image
def compare_with_tesseract(self, image_path):
"""Tesseract์ ๋น๊ต"""
import pytesseract
# EasyOCR
easy_text = self.extract_text_only(image_path)
# Tesseract
tess_text = pytesseract.image_to_string(
Image.open(image_path),
lang='kor+eng'
)
return {
'easyocr': easy_text,
'tesseract': tess_text
}
# ์ฌ์ฉ ์์
if __name__ == "__main__":
# ๋ชจ๋ธ ์ด๊ธฐํ (ํ๊ตญ์ด + ์์ด)
ocr = DeepLearningOCR(languages=['ko', 'en'], gpu=False)
image_path = "document.jpg"
# 1. ๊ธฐ๋ณธ ํ
์คํธ ์ถ์ถ
print("=== ํ
์คํธ ์ถ์ถ ===")
text = ocr.extract_text_only(image_path)
print(text)
# 2. ์ ๋ขฐ๋์ ํจ๊ป ์ถ์ถ
print("\n=== ์์ธ ์ ๋ณด ===")
detailed = ocr.extract_with_confidence(image_path, min_confidence=0.7)
for item in detailed:
print(f"ํ
์คํธ: {item['text']}")
print(f"์ ๋ขฐ๋: {item['confidence']:.2%}")
print(f"์์น: {item['bbox']}\n")
# 3. ์๊ฐํ
ocr.visualize_results(image_path, 'result_visualized.jpg')
# 4. Tesseract์ ๋น๊ต
print("\n=== ์์ง ๋น๊ต ===")
comparison = ocr.compare_with_tesseract(image_path)
print("EasyOCR ๊ฒฐ๊ณผ:")
print(comparison['easyocr'])
print("\nTesseract ๊ฒฐ๊ณผ:")
print(comparison['tesseract'])
โ ์๊ธ์จ ์ธ์ ์ฐ์
โ ๋ณต์กํ ๋ฐฐ๊ฒฝ์์๋ ์ ์๋
โ 80๊ฐ ์ด์ ์ธ์ด ์ง์
โ ๋ณ๋ ์ค์น ๋ถํ์
EasyOCR ๋จ์ :
โ ์ด๊ธฐ ๋ชจ๋ธ ๋ก๋ฉ ์๊ฐ
โ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋์
โ ์ฒ๋ฆฌ ์๋๊ฐ Tesseract๋ณด๋ค ๋๋ฆผ
Tesseract ์ฅ์ :
โ ๋น ๋ฅธ ์ฒ๋ฆฌ ์๋
โ ๊ฐ๋ฒผ์
โ ์ปค์คํฐ๋ง์ด์ง ์ฉ์ด
Tesseract ๋จ์ :
โ ์๊ธ์จ ์ธ์ ์ฝํจ
โ ๋ณต์กํ ๋ฐฐ๊ฒฝ์ ์ทจ์ฝ
โ ๋ณ๋ ์ค์น ํ์
์ค๋ฌด์์ OCR์ ์ฌ์ฉํ ๋ ๊ผญ ์์์ผ ํ ์ต์ ํ ๊ธฐ๋ฒ๋ค์ ์๋ ค์ค๊ฒ! ๐
์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฉด ํจ์ฌ ๋นจ๋ผ์ ธ!
import concurrent.futures
import pytesseract
from PIL import Image
import os
class BatchOCRProcessor:
"""๋ฐฐ์น OCR ์ฒ๋ฆฌ ํด๋์ค"""
def __init__(self, max_workers=4):
self.max_workers = max_workers
def process_single_image(self, image_path):
"""๋จ์ผ ์ด๋ฏธ์ง ์ฒ๋ฆฌ"""
try:
image = Image.open(image_path)
text = pytesseract.image_to_string(image, lang='kor+eng')
return {
'file': image_path,
'text': text,
'status': 'success'
}
except Exception as e:
return {
'file': image_path,
'text': None,
'status': 'error',
'error': str(e)
}
def process_batch(self, image_paths):
"""๋ฐฐ์น ์ฒ๋ฆฌ (๋ฉํฐ์ค๋ ๋ฉ)"""
results = []
with concurrent.futures.ThreadPoolExecutor(
max_workers=self.max_workers
) as executor:
# ๋ชจ๋ ์์
์ ์ถ
future_to_path = {
executor.submit(self.process_single_image, path): path
for path in image_paths
}
# ๊ฒฐ๊ณผ ์์ง
for future in concurrent.futures.as_completed(future_to_path):
result = future.result()
results.append(result)
print(f"์ฒ๋ฆฌ ์๋ฃ: {result['file']}")
return results
def process_directory(self, directory_path, extensions=['.jpg', '.png']):
"""๋๋ ํ ๋ฆฌ ๋ด ๋ชจ๋ ์ด๋ฏธ์ง ์ฒ๋ฆฌ"""
image_paths = []
for filename in os.listdir(directory_path):
if any(filename.lower().endswith(ext) for ext in extensions):
full_path = os.path.join(directory_path, filename)
image_paths.append(full_path)
print(f"์ด {len(image_paths)}๊ฐ ์ด๋ฏธ์ง ๋ฐ๊ฒฌ")
return self.process_batch(image_paths)
# ์ฌ์ฉ ์์
processor = BatchOCRProcessor(max_workers=4)
results = processor.process_directory('./images')
# ๊ฒฐ๊ณผ ์ ์ฅ
for result in results:
if result['status'] == 'success':
output_file = result['file'].replace('.jpg', '.txt')
with open(output_file, 'w', encoding='utf-8') as f:
f.write(result['text'])
๊ฐ์ ์ด๋ฏธ์ง๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฒ๋ฆฌํ์ง ์๋๋ก ์บ์ฑ์ ํ์ฉํด!
import hashlib
import pickle
import os
class CachedOCR:
"""์บ์ฑ ๊ธฐ๋ฅ์ด ์๋ OCR"""
def __init__(self, cache_dir='./ocr_cache'):
self.cache_dir = cache_dir
os.makedirs(cache_dir, exist_ok=True)
def get_file_hash(self, file_path):
"""ํ์ผ ํด์ ๊ณ์ฐ"""
with open(file_path, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def get_cache_path(self, file_hash):
"""์บ์ ํ์ผ ๊ฒฝ๋ก"""
return os.path.join(self.cache_dir, f"{file_hash}.pkl")
def ocr_with_cache(self, image_path):
"""์บ์๋ฅผ ํ์ฉํ OCR"""
# ํ์ผ ํด์ ๊ณ์ฐ
file_hash = self.get_file_hash(image_path)
cache_path = self.get_cache_path(file_hash)
# ์บ์ ํ์ธ
if os.path.exists(cache_path):
print(f"์บ์์์ ๋ก๋: {image_path}")
with open(cache_path, 'rb') as f:
return pickle.load(f)
# OCR ์ํ
print(f"OCR ์ฒ๋ฆฌ ์ค: {image_path}")
image = Image.open(image_path)
text = pytesseract.image_to_string(image, lang='kor+eng')
# ์บ์ ์ ์ฅ
with open(cache_path, 'wb') as f:
pickle.dump(text, f)
return text
# ์ฌ์ฉ
cached_ocr = CachedOCR()
text = cached_ocr.ocr_with_cache('document.jpg') # ์ฒซ ์คํ: OCR ์ํ
text = cached_ocr.ocr_with_cache('document.jpg') # ๋ ๋ฒ์งธ: ์บ์์์ ๋ก๋
์ค๋ฌด์์๋ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ์ ๋ง ์ค์ํด!
import logging
from datetime import datetime
class RobustOCR:
"""๊ฒฌ๊ณ ํ OCR ์์คํ
"""
def __init__(self, log_file='ocr.log'):
# ๋ก๊ฑฐ ์ค์
self.logger = logging.getLogger('OCR')
self.logger.setLevel(logging.INFO)
# ํ์ผ ํธ๋ค๋ฌ
fh = logging.FileHandler(log_file, encoding='utf-8')
fh.setLevel(logging.INFO)
# ํฌ๋งทํฐ
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
fh.setFormatter(formatter)
self.logger.addHandler(fh)
def safe_ocr(self, image_path, max_retries=3):
"""์์ ํ OCR (์ฌ์๋ ํฌํจ)"""
for attempt in range(max_retries):
try:
self.logger.info(f"OCR ์๋ {attempt + 1}/{max_retries}: {image_path}")
# ์ด๋ฏธ์ง ์ด๊ธฐ
image = Image.open(image_path)
# ์ด๋ฏธ์ง ๊ฒ์ฆ
if image.size[0] < 100 or image.size[1] < 100:
raise ValueError("์ด๋ฏธ์ง๊ฐ ๋๋ฌด ์์ต๋๋ค")
# OCR ์ํ
text = pytesseract.image_to_string(image, lang='kor+eng')
# ๊ฒฐ๊ณผ ๊ฒ์ฆ
if not text.strip():
self.logger.warning(f"ํ
์คํธ๊ฐ ์ถ์ถ๋์ง ์์: {image_path}")
return None
self.logger.info(f"OCR ์ฑ๊ณต: {image_path} ({len(text)} ๊ธ์)")
return text
except FileNotFoundError:
self.logger.error(f"ํ์ผ์ ์ฐพ์ ์ ์์: {image_path}")
return None
except Exception as e:
self.logger.error(f"OCR ์ค๋ฅ (์๋ {attempt + 1}): {str(e)}")
if attempt == max_retries - 1:
self.logger.error(f"์ต์ข
์คํจ: {image_path}")
return None
# ์ฌ์๋ ์ ๋๊ธฐ
import time
time.sleep(1)
return None
# ์ฌ์ฉ
robust_ocr = RobustOCR()
result = robust_ocr.safe_ocr('document.jpg')
CLI๋ ์ข์ง๋ง, GUI๊ฐ ์์ผ๋ฉด ํจ์ฌ ์ฌ์ฉํ๊ธฐ ํธํ์์? Tkinter๋ก ๊ฐ๋จํ OCR GUI๋ฅผ ๋ง๋ค์ด๋ณด์! ๐ฅ๏ธ
import tkinter as tk
from tkinter import filedialog, scrolledtext, messagebox
from PIL import Image, ImageTk
import pytesseract
import cv2
import numpy as np
class OCRApplication:
"""OCR GUI ์ ํ๋ฆฌ์ผ์ด์
"""
def __init__(self, root):
self.root = root
self.root.title("๐ ๋ฌธ์ OCR ํ๋ก๊ทธ๋จ")
self.root.geometry("1000x700")
self.current_image_path = None
self.create_widgets()
def create_widgets(self):
"""์์ ฏ ์์ฑ"""
# ์๋จ ํ๋ ์ (๋ฒํผ๋ค)
top_frame = tk.Frame(self.root, bg='#f0f0f0', pady=10)
top_frame.pack(fill=tk.X)
tk.Button(
top_frame,
text="๐ ์ด๋ฏธ์ง ์ด๊ธฐ",
command=self.open_image,
bg='#4CAF50',
fg='white',
font=('Arial', 12, 'bold'),
padx=20,
pady=10
).pack(side=tk.LEFT, padx=10)
tk.Button(
top_frame,
text="๐ OCR ์คํ",
command=self.run_ocr,
bg='#2196F3',
fg='white',
font=('Arial', 12, 'bold'),
padx=20,
pady=10
).pack(side=tk.LEFT, padx=10)
tk.Button(
top_frame,
text="๐พ ํ
์คํธ ์ ์ฅ",
command=self.save_text,
bg='#FF9800',
fg='white',
font=('Arial', 12, 'bold'),
padx=20,
pady=10
).pack(side=tk.LEFT, padx=10)
# ์ค์ ํ๋ ์ (์ด๋ฏธ์ง + ํ
์คํธ)
center_frame = tk.Frame(self.root)
center_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# ์ผ์ชฝ: ์ด๋ฏธ์ง ํ์
left_frame = tk.LabelFrame(
center_frame,
text="์ด๋ฏธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ",
font=('Arial', 10, 'bold')
)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
self.image_label = tk.Label(left_frame, bg='white')
self.image_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# ์ค๋ฅธ์ชฝ: ํ
์คํธ ๊ฒฐ๊ณผ
right_frame = tk.LabelFrame(
center_frame,
text="์ถ์ถ๋ ํ
์คํธ",
font=('Arial', 10, 'bold')
)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5)
self.text_area = scrolledtext.ScrolledText(
right_frame,
wrap=tk.WORD,
font=('Arial', 11),
bg='#ffffff'
)
self.text_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# ํ๋จ ์ํ๋ฐ
self.status_bar = tk.Label(
self.root,
text="์ค๋น",
bd=1,
relief=tk.SUNKEN,
anchor=tk.W,
bg='#e0e0e0'
)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def open_image(self):
"""์ด๋ฏธ์ง ํ์ผ ์ด๊ธฐ"""
file_path = filedialog.askopenfilename(
title="์ด๋ฏธ์ง ์ ํ",
filetypes=[
("์ด๋ฏธ์ง ํ์ผ", "*.jpg *.jpeg *.png *.bmp"),
("๋ชจ๋ ํ์ผ", "*.*")
]
)
if file_path:
self.current_image_path = file_path
self.display_image(file_path)
self.status_bar.config(text=f"์ด๋ฏธ์ง ๋ก๋๋จ: {file_path}")
def display_image(self, image_path):
"""์ด๋ฏธ์ง ํ์"""
# ์ด๋ฏธ์ง ๋ก๋
image = Image.open(image_path)
# ํฌ๊ธฐ ์กฐ์ (์ต๋ 400x400)
image.thumbnail((400, 400), Image.Resampling.LANCZOS)
# Tkinter ์ด๋ฏธ์ง๋ก ๋ณํ
photo = ImageTk.PhotoImage(image)
# ๋ผ๋ฒจ์ ํ์
self.image_label.config(image=photo)
self.image_label.image = photo # ์ฐธ์กฐ ์ ์ง
def run_ocr(self):
"""OCR ์คํ"""
if not self.current_image_path:
messagebox.showwarning("๊ฒฝ๊ณ ", "๋จผ์ ์ด๋ฏธ์ง๋ฅผ ์ ํํ์ธ์!")
return
self.status_bar.config(text="OCR ์ฒ๋ฆฌ ์ค...")
self.root.update()
try:
# ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ
image = cv2.imread(self.current_image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(
gray, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
# PIL Image๋ก ๋ณํ
pil_image = Image.fromarray(binary)
# OCR ์ํ
text = pytesseract.image_to_string(
pil_image,
lang='kor+eng'
)
# ๊ฒฐ๊ณผ ํ์
self.text_area.delete(1.0, tk.END)
self.text_area.insert(tk.END, text)
self.status_bar.config(
text=f"OCR ์๋ฃ! ({len(text)} ๊ธ์ ์ถ์ถ๋จ)"
)
except Exception as e:
messagebox.showerror("์ค๋ฅ", f"OCR ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์:\n{str(e)}")
self.status_bar.config(text="์ค๋ฅ ๋ฐ์")
def save_text(self):
"""ํ
์คํธ ์ ์ฅ"""
text = self.text_area.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("๊ฒฝ๊ณ ", "์ ์ฅํ ํ
์คํธ๊ฐ ์์ต๋๋ค!")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("ํ
์คํธ ํ์ผ", "*.txt"), ("๋ชจ๋ ํ์ผ", "*.*")]
)
if file_path:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(text)
messagebox.showinfo("์ฑ๊ณต", "ํ
์คํธ๊ฐ ์ ์ฅ๋์์ต๋๋ค!")
self.status_bar.config(text=f"์ ์ฅ ์๋ฃ: {file_path}")
# ์คํ
if __name__ == "__main__":
root = tk.Tk()
app = OCRApplication(root)
root.mainloop()
โจ ์ํด๋ฆญ OCR ์ฒ๋ฆฌ
โจ ์ค์๊ฐ ํ ์คํธ ํ์
โจ ํ ์คํธ ํ์ผ๋ก ์ ์ฅ
โจ ์ํ๋ฐ๋ก ์งํ ์ํฉ ํ์
โจ ์๋ฌ ์ฒ๋ฆฌ ๋ฐ ์ฌ์ฉ์ ์๋ฆผ
์ด์ OCR ํ๋ก๊ทธ๋จ์ ๋ง๋ค ์ ์๊ฒ ๋์ด! ์ด๋ฐ ๊ธฐ์ ์ ๊ฐ์ง๊ณ ์์ผ๋ฉด ์ฌ๋ฅ๋ท์์ ์ ๋ง ๋ค์ํ ๋ฐฉ์์ผ๋ก ํ์ฉํ ์ ์์ด. ๐ฏ
์๋ฅผ ๋ค๋ฉด:
โข ๋ฌธ์ ๋์งํธํ ์๋น์ค ์ ๊ณต
โข ๋ช
ํจ ์ธ์ ์ฑ ๊ฐ๋ฐ
โข OCR ํ๋ก๊ทธ๋จ ์ปค์คํฐ๋ง์ด์ง
โข ๋ฐ์ดํฐ ์
๋ ฅ ์๋ํ ์๋ฃจ์
โข OCR ๊ธฐ์ ์ปจ์คํ
์ฌ๋ฅ๋ท์ ์ด๋ฐ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ํ์๋ก ํ๋ ์ฌ๋๋ค๊ณผ ์ฐ๊ฒฐํด์ฃผ๋ ํ๋ซํผ์ด์ผ. ๋์ OCR ๊ธฐ์ ์ ํ์ฉํด์ ๋ค๋ฅธ ์ฌ๋๋ค์ ๋์์ฃผ๊ณ , ๋์์ ์์ต๋ ์ฐฝ์ถํ ์ ์์ง! ๐
| ๋ฆฌ์์ค | ์ค๋ช | ๋งํฌ |
|---|---|---|
| Tesseract ๊ณต์ ๋ฌธ์ | Tesseract์ ๋ชจ๋ ๊ฒ | github.com/tesseract-ocr |
| EasyOCR | ๋ฅ๋ฌ๋ OCR ๋ผ์ด๋ธ๋ฌ๋ฆฌ | github.com/JaidedAI/EasyOCR |
| OpenCV ํํ ๋ฆฌ์ผ | ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ํ์ต | docs.opencv.org |
| PyTesseract | Python Tesseract ๋ํผ | pypi.org/project/pytesseract |
โ Tesseract OCR ์์ง์ด ์ค์น๋์ง ์์์ด. ์์ ์ค์น ๊ฐ์ด๋๋ฅผ ๋ฐ๋ผ ์ค์นํด์ค.
2. ํ๊ธ์ด ์ธ์๋์ง ์์
โ ํ๊ธ ์ธ์ด ๋ฐ์ดํฐ(kor.traineddata)๊ฐ ์๋ ๊ฑฐ์ผ. tessdata ํด๋์ ์ถ๊ฐํด์ค.
3. ์ธ์๋ฅ ์ด ๋๋ฌด ๋ฎ์
โ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ํด๋ด! ๊ทธ๋ ์ด์ค์ผ์ผ, ์ด์งํ, ๋ ธ์ด์ฆ ์ ๊ฑฐ ๋ฑ์ ์ ์ฉํด.
4. ์ฒ๋ฆฌ ์๋๊ฐ ๋๋ฌด ๋๋ฆผ
โ ์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ ์ค์ด๊ฑฐ๋, ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํด๋ด. GPU๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ ๋ฐฉ๋ฒ์ด์ผ.
5. ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ค๋ฅ
โ ์ด๋ฏธ์ง๋ฅผ ํ ๋ฒ์ ๋๋ฌด ๋ง์ด ์ฒ๋ฆฌํ์ง ๋ง๊ณ , ๋ฐฐ์น ํฌ๊ธฐ๋ฅผ ์ค์ฌ๋ด.
๋ฐฐ์ด ๋ด์ฉ์ ํ์ฉํด์ ์ด๋ฐ ํ๋ก์ ํธ๋ค์ ๋ง๋ค์ด๋ณผ ์ ์์ด! ๐
2. ๐ ์ฑ ์ค์บ๋
์ฑ ํ์ด์ง๋ฅผ ์ฐ์ผ๋ฉด ํ ์คํธ๋ก ๋ณํํด์ ์ ์์ฑ ๋ง๋ค๊ธฐ
3. ๐ ์ฃผ์ฐจ์ฅ ๋ฒํธํ ์ธ์
์ฐจ๋ ๋ฒํธํ์ ์๋์ผ๋ก ์ธ์ํด์ ์ถ์ ๊ด๋ฆฌ
4. ๐ ๋ฌธ์ ๊ฒ์ ์์คํ
์ค์บํ ๋ฌธ์๋ค์ OCR ์ฒ๋ฆฌํด์ ๊ฒ์ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ธฐ
5. ๐ ์ค์๊ฐ ๋ฒ์ญ๊ธฐ
์นด๋ฉ๋ผ๋ก ์ธ๊ตญ์ด ๊ฐํ์ ์ฐ์ผ๋ฉด ์ค์๊ฐ์ผ๋ก ๋ฒ์ญ
6. ๐ ํ๊ธฐ ๋ ธํธ ๋์งํธํ
์์ผ๋ก ์ด ๋ ธํธ๋ฅผ ๋์งํธ ํ ์คํธ๋ก ๋ณํ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ์ฐ๋ฆฌ๊ฐ ํจ๊ป ๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด์๋ฉด: ๐
โ
OCR์ ๊ธฐ๋ณธ ๊ฐ๋
๊ณผ ์๋ฆฌ
โ
Tesseract ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
โ
์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ๊ธฐ๋ฒ๋ค
โ
PDF ๋ฌธ์ ์ฒ๋ฆฌ
โ
๊ณ ๊ธ ์ค์ ๋ฐ ์ต์ ํ
โ
๋ฅ๋ฌ๋ ๊ธฐ๋ฐ OCR (EasyOCR)
โ
์ค์ ํ๋ก์ ํธ (๋ช
ํจ ์ธ์)
โ
GUI ํ๋ก๊ทธ๋จ ๊ฐ๋ฐ
โ
์ฑ๋ฅ ์ต์ ํ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ
์ด์ ๋๋ OCR ์ ๋ฌธ๊ฐ์ผ! ๐ ์ฒ์์๋ ์ด๋ ค์ ๋ณด์์ง๋ง, ํ๋์ฉ ๋ฐ๋ผํ๋ค ๋ณด๋ ํ ์ ์๊ฒ ๋์ง?
์ค์ํ ๊ฑด ๊ณ์ ์ฐ์ตํ๊ณ ์คํํด๋ณด๋ ๊ฑฐ์ผ. ๋ค์ํ ์ด๋ฏธ์ง๋ก ํ
์คํธํด๋ณด๊ณ , ์ ์ฒ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐ์ ํด๋ณด๊ณ , ์๋ก์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์๋ํด๋ด. ๊ทธ๋ฌ๋ค ๋ณด๋ฉด ์ ์ ๋ ์ ํํ๊ณ ๋น ๋ฅธ OCR ์์คํ
์ ๋ง๋ค ์ ์์ ๊ฑฐ์ผ! ๐ช
๊ทธ๋ฆฌ๊ณ ์ด๋ฐ ๊ธฐ์ ์ ๊ฐ์ง๊ณ ์์ผ๋ฉด ์ ๋ง ๋ง์ ๊ณณ์์ ํ์ฉํ ์ ์์ด. ํ์ฌ ์
๋ฌด ์๋ํ, ๊ฐ์ธ ํ๋ก์ ํธ, ๋๋ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ๋ฆฌ๋์๋ก ํ๋ํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ง!
๐ ๋น์ ์ OCR ์ฌ์ ์ ์์ํฉ๋๋ค! ๐
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋์์ด ํ์ํ๋ฉด ์ธ์ ๋ ์ง ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํด๋ด.
ํจ๊ป ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ๋ ๊ฒ ํ๋ก๊ทธ๋๋ฐ์ ๋งค๋ ฅ์ด๋๊น! ๐
Happy Coding! ๐
๐ผ ์ด ๊ธ์ด ๋์์ด ๋์๋์?
์ฌ๋ฅ๋ท์์ ๋ ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ง์์ ๋ง๋๋ณด์ธ์!
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.