์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ“„ ๋ฌธ์„œ OCR ์ธ์‹ ํ”„๋กœ๊ทธ๋žจ ์†Œ์Šค์ฝ”๋“œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์‹ค์ „ ๊ตฌํ˜„๋ถ€ํ„ฐ ์ตœ์ ํ™”๊นŒ์ง€

๐Ÿ“„ ๋ฌธ์„œ OCR ์ธ์‹ ํ”„๋กœ๊ทธ๋žจ ์†Œ์Šค์ฝ”๋“œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์‹ค์ „ ๊ตฌํ˜„๋ถ€ํ„ฐ ์ตœ์ ํ™”๊นŒ์ง€

์ด๋ฏธ์ง€ ์† ํ…์ŠคํŠธ๋ฅผ ๋งˆ๋ฒ•์ฒ˜๋Ÿผ ์ถ”์ถœํ•˜๋Š” OCR ๊ธฐ์ˆ , ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด์ž! ๐Ÿš€

์•ˆ๋…•! ์˜ค๋Š˜์€ ์ •๋ง ์žฌ๋ฏธ์žˆ๋Š” ์ฃผ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์™”์–ด. ๋ฐ”๋กœ ๋ฌธ์„œ OCR ์ธ์‹ ํ”„๋กœ๊ทธ๋žจ์„ ์ง์ ‘ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด์•ผ! ๐Ÿ“ธโœจ

ํ˜น์‹œ ์‚ฌ์ง„ ์ฐ์€ ๋ฌธ์„œ์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๋ฝ‘์•„๋‚ด๋Š” ํ”„๋กœ๊ทธ๋žจ ๋ณธ ์  ์žˆ์ง€? ์Šค์บ”ํ•œ ์ฑ…์„ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜, ๋ช…ํ•จ์„ ์ฐ์œผ๋ฉด ์ž๋™์œผ๋กœ ์—ฐ๋ฝ์ฒ˜๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ทธ๋Ÿฐ ์‹ ๊ธฐํ•œ ๊ธฐ๋Šฅ ๋ง์ด์•ผ. ์ด๊ฒŒ ๋ฐ”๋กœ OCR(Optical Character Recognition, ๊ด‘ํ•™ ๋ฌธ์ž ์ธ์‹) ๊ธฐ์ˆ ์ด๊ฑฐ๋“ !

์ฒ˜์Œ์—๋Š” "์ด๊ฑฐ ์—„์ฒญ ์–ด๋ ค์šด ๊ฑฐ ์•„๋‹ˆ์•ผ?" ํ•˜๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์š”์ฆ˜์€ ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์ •๋ง ์ž˜ ๋˜์–ด ์žˆ์–ด์„œ ์ƒ๊ฐ๋ณด๋‹ค ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ๋ฌผ๋ก  ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ์ตœ์ ํ™”ํ•˜๋ ค๋ฉด ์•Œ์•„์•ผ ํ•  ๊ฒŒ ๋งŽ์ง€๋งŒ, ๊ทธ๋ž˜์„œ ๋‚ด๊ฐ€ ์˜ค๋Š˜ ์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ค„๊ฒŒ! ๐Ÿ˜Š

OCR OUTPUT TEXT Hello World Document Recognition OCR Technology Text Extraction Image Processing AI Recognition Success! OCR Processing Flow
๐ŸŽฏ OCR์ด ๋ญ”์ง€ ์ œ๋Œ€๋กœ ์•Œ๊ณ  ๊ฐ€์ž!

OCR์€ Optical Character Recognition์˜ ์•ฝ์ž๋กœ, ์šฐ๋ฆฌ๋ง๋กœ๋Š” '๊ด‘ํ•™ ๋ฌธ์ž ์ธ์‹'์ด๋ผ๊ณ  ํ•ด. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ์ด๋ฏธ์ง€๋‚˜ ์Šค์บ”ํ•œ ๋ฌธ์„œ์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ธ์‹ํ•ด์„œ ์ปดํ“จํ„ฐ๊ฐ€ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ๐Ÿ“–โžก๏ธ๐Ÿ’ป

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ. ์˜›๋‚  ์ฑ…์„ ์Šค์บ”ํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ด. ๊ทธ๋ƒฅ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ด๋ฉด ๊ฒ€์ƒ‰๋„ ์•ˆ ๋˜๊ณ , ๋ณต์‚ฌ๋„ ์•ˆ ๋˜๊ณ , ๋ฒˆ์—ญ๋„ ์•ˆ ๋˜์ž–์•„? ๊ทธ๋Ÿฐ๋ฐ OCR์„ ๊ฑฐ์น˜๋ฉด ๊ทธ ์ด๋ฏธ์ง€ ์† ๊ธ€์ž๋“ค์ด ์ง„์งœ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜๋˜๋Š” ๊ฑฐ์ง€! ๐ŸŽ‰

๐Ÿ’ก OCR์ด ํ™œ์šฉ๋˜๋Š” ์‹ค์ƒํ™œ ์˜ˆ์‹œ โœ… ๋ช…ํ•จ ์Šค์บ” ์•ฑ (๋ช…ํ•จ ์ฐ์œผ๋ฉด ์ž๋™์œผ๋กœ ์—ฐ๋ฝ์ฒ˜ ์ €์žฅ)
โœ… ๋ฌธ์„œ ์Šค์บ๋„ˆ ์•ฑ (CamScanner, Adobe Scan ๋“ฑ)
โœ… ๋ฒˆ์—ญ ์•ฑ (Google ๋ฒˆ์—ญ์˜ ์นด๋ฉ”๋ผ ๊ธฐ๋Šฅ)
โœ… ์ž๋™์ฐจ ๋ฒˆํ˜ธํŒ ์ธ์‹ ์‹œ์Šคํ…œ
โœ… ์€ํ–‰์˜ ์ˆ˜ํ‘œ/์ง€๋กœ ์ž๋™ ์ธ์‹
โœ… ๋„์„œ๊ด€์˜ ์ฑ… ๋””์ง€ํ„ธํ™” ํ”„๋กœ์ ํŠธ
โœ… ์˜์ˆ˜์ฆ ์ž๋™ ์ž…๋ ฅ ๊ฐ€๊ณ„๋ถ€ ์•ฑ
๐Ÿ› ๏ธ OCR ํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๊ธฐ์ˆ  ์Šคํƒ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ OCR ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋ญ๊ฐ€ ํ•„์š”ํ•œ์ง€ ์•Œ์•„๋ณด์ž! ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ถ€ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊นŒ์ง€ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณผ๊ฒŒ. ๐Ÿ”ง

๊ตฌ๋ถ„ ๊ธฐ์ˆ /๋„๊ตฌ ์„ค๋ช…
ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด Python ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋ฉฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ’๋ถ€ํ•จ
OCR ์—”์ง„ Tesseract ๊ตฌ๊ธ€์ด ๊ด€๋ฆฌํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค OCR ์—”์ง„
์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ OpenCV, PIL/Pillow ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ๋ฐ ์ตœ์ ํ™”
๋”ฅ๋Ÿฌ๋‹(์„ ํƒ) TensorFlow, PyTorch ๊ณ ๊ธ‰ OCR ๋ชจ๋ธ ๊ตฌ์ถ•์‹œ ์‚ฌ์šฉ
PDF ์ฒ˜๋ฆฌ PyPDF2, pdf2image PDF ๋ฌธ์„œ ์ฒ˜๋ฆฌ์šฉ
๐Ÿ“ฆ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ…ํ•˜๊ธฐ

๋จผ์ € ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•ด์•ผ ํ•ด. Python์„ ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ• ๊ฒŒ. Python์ด ์„ค์น˜๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์ง„ํ–‰ํ• ๊ฒŒ! ๐Ÿ

1 Tesseract OCR ์—”์ง„ ์„ค์น˜

Tesseract๋Š” OCR์˜ ํ•ต์‹ฌ ์—”์ง„์ด์•ผ. ๋จผ์ € ์‹œ์Šคํ…œ์— ์„ค์น˜ํ•ด์•ผ ํ•ด.

Windows:
๊ณต์‹ GitHub์—์„œ ์„ค์น˜ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
(https://github.com/UB-Mannheim/tesseract/wiki)

Mac:
brew install tesseract

Linux (Ubuntu):
sudo apt-get install tesseract-ocr
sudo apt-get install libtesseract-dev
2 Python ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

ํ•„์š”ํ•œ 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๋ฅผ ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜
3 ํ•œ๊ธ€ ์–ธ์–ด ๋ฐ์ดํ„ฐ ์„ค์น˜

Tesseract๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์˜์–ด๋งŒ ์ง€์›ํ•ด. ํ•œ๊ธ€์„ ์ธ์‹ํ•˜๋ ค๋ฉด ํ•œ๊ธ€ ํ•™์Šต ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•ด์•ผ ํ•ด! ๐Ÿ‡ฐ๐Ÿ‡ท

# Linux/Mac
sudo apt-get install tesseract-ocr-kor

# ๋˜๋Š” ์ˆ˜๋™ ๋‹ค์šด๋กœ๋“œ
# https://github.com/tesseract-ocr/tessdata
# kor.traineddata ํŒŒ์ผ์„ Tesseract์˜ tessdata ํด๋”์— ๋ณต์‚ฌ
๐Ÿ’ป ๊ธฐ๋ณธ OCR ํ”„๋กœ๊ทธ๋žจ ์†Œ์Šค์ฝ”๋“œ

์ž, ์ด์ œ ๋“œ๋””์–ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณผ ์‹œ๊ฐ„์ด์•ผ! ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ OCR ํ”„๋กœ๊ทธ๋žจ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž. ๐Ÿ˜Ž

๐Ÿ”ฐ ์ดˆ๊ธ‰: ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ํ…์ŠคํŠธ ์ถ”์ถœ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ํ˜•ํƒœ์˜ 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)
๐ŸŽ“ ์ฝ”๋“œ ์„ค๋ช… ์ด ์ฝ”๋“œ๋Š” ์ •๋ง ๊ฐ„๋‹จํ•ด! Image.open()์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์—ด๊ณ , pytesseract.image_to_string()์œผ๋กœ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•˜๋Š” ๊ฑฐ์•ผ. lang='kor+eng' ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•œ๊ธ€๊ณผ ์˜์–ด๋ฅผ ๋™์‹œ์— ์ธ์‹ํ•˜๋ผ๋Š” ์˜๋ฏธ์•ผ. ๐Ÿ‘
๐ŸŽจ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ๋กœ ์ธ์‹๋ฅ  ๋†’์ด๊ธฐ

๊ธฐ๋ณธ OCR๋งŒ์œผ๋กœ๋Š” ์ธ์‹๋ฅ ์ด ๋‚ฎ์„ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ์ด ์•ˆ ์ข‹๊ฑฐ๋‚˜, ๊ธฐ์šธ์–ด์ ธ ์žˆ๊ฑฐ๋‚˜, ๋…ธ์ด์ฆˆ๊ฐ€ ๋งŽ์œผ๋ฉด ์ œ๋Œ€๋กœ ์ธ์‹์ด ์•ˆ ๋ผ. ๊ทธ๋ž˜์„œ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ๊ฐ€ ์ •๋ง ์ค‘์š”ํ•ด! ๐Ÿ”

โš ๏ธ 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)
๐Ÿ”ฌ ์ „์ฒ˜๋ฆฌ ๊ธฐ๋ฒ• ์ƒ์„ธ ์„ค๋ช… 1. ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณ€ํ™˜
์ปฌ๋Ÿฌ ์ •๋ณด๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ๋ช…์•”๋งŒ ๋‚จ๊ฒจ์„œ ์ฒ˜๋ฆฌ ์†๋„๋ฅผ ๋†’์ด๊ณ  ํ…์ŠคํŠธ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด.

2. ์ด์ง„ํ™” (Thresholding)
์ด๋ฏธ์ง€๋ฅผ ํ‘๋ฐฑ์œผ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•ด. Otsu ๋ฐฉ๋ฒ•์€ ์ž๋™์œผ๋กœ ์ตœ์ ์˜ ์ž„๊ณ„๊ฐ’์„ ์ฐพ์•„์ค˜.

3. ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ
๋ชจํด๋กœ์ง€ ์—ฐ์‚ฐ๊ณผ ๊ฐ€์šฐ์‹œ์•ˆ ๋ธ”๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์žกํ‹ฐ๋‚˜ ์–ผ๋ฃฉ์„ ์ œ๊ฑฐํ•ด.

4. ๊ธฐ์šธ๊ธฐ ๋ณด์ • (Deskewing)
์Šค์บ”ํ•  ๋•Œ ๋น„๋šค์–ด์ง„ ๋ฌธ์„œ๋ฅผ ๋ฐ”๋ฅด๊ฒŒ ํŽด์ฃผ๋Š” ๊ฑฐ์•ผ.

5. ํ•ด์ƒ๋„ ํ–ฅ์ƒ
์ด๋ฏธ์ง€๋ฅผ ํ™•๋Œ€ํ•ด์„œ ์ž‘์€ ๊ธ€์”จ๋„ ์ž˜ ์ธ์‹๋˜๊ฒŒ ๋งŒ๋“ค์–ด.
๐Ÿ“‹ PDF ๋ฌธ์„œ OCR ์ฒ˜๋ฆฌํ•˜๊ธฐ

์‹ค๋ฌด์—์„œ๋Š” ์ด๋ฏธ์ง€๋ณด๋‹ค 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์ž๋งŒ ์ถœ๋ ฅ
๐ŸŽฏ OCR ์ •ํ™•๋„ ํ–ฅ์ƒ์„ ์œ„ํ•œ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•

์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ๋“ค์„ ์•Œ์•„๋ณผ๊นŒ? ์‹ค๋ฌด์—์„œ ์ •๋ง ์œ ์šฉํ•œ ํŒ๋“ค์ด์•ผ! ๐Ÿ’ช

๐Ÿš€ Tesseract ์„ค์ • ์ตœ์ ํ™” Tesseract์—๋Š” ๋‹ค์–‘ํ•œ ์„ค์ • ์˜ต์…˜์ด ์žˆ์–ด. ์ด๊ฑธ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ์ธ์‹๋ฅ ์„ ํฌ๊ฒŒ ๋†’์ผ ์ˆ˜ ์žˆ์–ด!
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']}%")
๐Ÿ’ก PSM ๋ชจ๋“œ ์„ ํƒ ๊ฐ€์ด๋“œ โ€ข ์ผ๋ฐ˜ ๋ฌธ์„œ: single_block (6)
โ€ข ๋ช…ํ•จ, ๊ฐ„ํŒ: 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)
๐Ÿ”ฅ ๋”ฅ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜ ๊ณ ๊ธ‰ OCR

Tesseract๋„ ์ข‹์ง€๋งŒ, ์ตœ์‹  ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ›จ์”ฌ ๋” ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด! ํŠนํžˆ ์†๊ธ€์”จ๋‚˜ ๋ณต์žกํ•œ ๋ ˆ์ด์•„์›ƒ์—์„œ ๊ฐ•๋ ฅํ•ด. ๐Ÿง 

๐Ÿค– ์ธ๊ธฐ ์žˆ๋Š” ๋”ฅ๋Ÿฌ๋‹ OCR ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ โ€ข EasyOCR: 80๊ฐœ ์ด์ƒ ์–ธ์–ด ์ง€์›, ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์›€
โ€ข 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'])
๐ŸŽฏ EasyOCR vs Tesseract ๋น„๊ต EasyOCR ์žฅ์ :
โœ… ์†๊ธ€์”จ ์ธ์‹ ์šฐ์ˆ˜
โœ… ๋ณต์žกํ•œ ๋ฐฐ๊ฒฝ์—์„œ๋„ ์ž˜ ์ž‘๋™
โœ… 80๊ฐœ ์ด์ƒ ์–ธ์–ด ์ง€์›
โœ… ๋ณ„๋„ ์„ค์น˜ ๋ถˆํ•„์š”

EasyOCR ๋‹จ์ :
โŒ ์ดˆ๊ธฐ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹œ๊ฐ„
โŒ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋†’์Œ
โŒ ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ Tesseract๋ณด๋‹ค ๋А๋ฆผ

Tesseract ์žฅ์ :
โœ… ๋น ๋ฅธ ์ฒ˜๋ฆฌ ์†๋„
โœ… ๊ฐ€๋ฒผ์›€
โœ… ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์šฉ์ด

Tesseract ๋‹จ์ :
โŒ ์†๊ธ€์”จ ์ธ์‹ ์•ฝํ•จ
โŒ ๋ณต์žกํ•œ ๋ฐฐ๊ฒฝ์— ์ทจ์•ฝ
โŒ ๋ณ„๋„ ์„ค์น˜ ํ•„์š”
โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ์‹ค์ „ ํŒ

์‹ค๋ฌด์—์„œ OCR์„ ์‚ฌ์šฉํ•  ๋•Œ ๊ผญ ์•Œ์•„์•ผ ํ•  ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋“ค์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿš€

1 ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋กœ ์†๋„ ํ–ฅ์ƒ

์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋ฉด ํ›จ์”ฌ ๋นจ๋ผ์ ธ!

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'])
2 ์บ์‹ฑ์œผ๋กœ ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ์ง€

๊ฐ™์€ ์ด๋ฏธ์ง€๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋„๋ก ์บ์‹ฑ์„ ํ™œ์šฉํ•ด!

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')  # ๋‘ ๋ฒˆ์งธ: ์บ์‹œ์—์„œ ๋กœ๋“œ
3 ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กœ๊น…

์‹ค๋ฌด์—์„œ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์ •๋ง ์ค‘์š”ํ•ด!

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')
๐ŸŽจ GUI ํ”„๋กœ๊ทธ๋žจ ๋งŒ๋“ค๊ธฐ

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()
๐ŸŽ‰ GUI ํ”„๋กœ๊ทธ๋žจ ๊ธฐ๋Šฅ โœจ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์„ ํƒ ๋ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
โœจ ์›ํด๋ฆญ OCR ์ฒ˜๋ฆฌ
โœจ ์‹ค์‹œ๊ฐ„ ํ…์ŠคํŠธ ํ‘œ์‹œ
โœจ ํ…์ŠคํŠธ ํŒŒ์ผ๋กœ ์ €์žฅ
โœจ ์ƒํƒœ๋ฐ”๋กœ ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
โœจ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์‚ฌ์šฉ์ž ์•Œ๋ฆผ
๐ŸŒŸ ์žฌ๋Šฅ๋„ท์—์„œ 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
๐Ÿ”ง ๋ฌธ์ œ ํ•ด๊ฒฐ (Troubleshooting)
โš ๏ธ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค 1. "tesseract is not installed" ์˜ค๋ฅ˜
โ†’ Tesseract OCR ์—”์ง„์ด ์„ค์น˜๋˜์ง€ ์•Š์•˜์–ด. ์œ„์˜ ์„ค์น˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ผ ์„ค์น˜ํ•ด์ค˜.

2. ํ•œ๊ธ€์ด ์ธ์‹๋˜์ง€ ์•Š์Œ
โ†’ ํ•œ๊ธ€ ์–ธ์–ด ๋ฐ์ดํ„ฐ(kor.traineddata)๊ฐ€ ์—†๋Š” ๊ฑฐ์•ผ. tessdata ํด๋”์— ์ถ”๊ฐ€ํ•ด์ค˜.

3. ์ธ์‹๋ฅ ์ด ๋„ˆ๋ฌด ๋‚ฎ์Œ
โ†’ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ๋ฅผ ํ•ด๋ด! ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ, ์ด์ง„ํ™”, ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ ๋“ฑ์„ ์ ์šฉํ•ด.

4. ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋„ˆ๋ฌด ๋А๋ฆผ
โ†’ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ฑฐ๋‚˜, ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ด. GPU๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด์•ผ.

5. ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ์˜ค๋ฅ˜
โ†’ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๋ฒˆ์— ๋„ˆ๋ฌด ๋งŽ์ด ์ฒ˜๋ฆฌํ•˜์ง€ ๋ง๊ณ , ๋ฐฐ์น˜ ํฌ๊ธฐ๋ฅผ ์ค„์—ฌ๋ด.
๐Ÿ’ก ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

๋ฐฐ์šด ๋‚ด์šฉ์„ ํ™œ์šฉํ•ด์„œ ์ด๋Ÿฐ ํ”„๋กœ์ ํŠธ๋“ค์„ ๋งŒ๋“ค์–ด๋ณผ ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

1. ๐Ÿ“ฑ ์˜์ˆ˜์ฆ ๊ด€๋ฆฌ ์•ฑ ์˜์ˆ˜์ฆ์„ ์ฐ์œผ๋ฉด ์ž๋™์œผ๋กœ ๋‚ ์งœ, ๊ธˆ์•ก, ์ƒ์ ๋ช…์„ ์ถ”์ถœํ•ด์„œ ๊ฐ€๊ณ„๋ถ€์— ๊ธฐ๋ก

2. ๐Ÿ“š ์ฑ… ์Šค์บ๋„ˆ
์ฑ… ํŽ˜์ด์ง€๋ฅผ ์ฐ์œผ๋ฉด ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ „์ž์ฑ… ๋งŒ๋“ค๊ธฐ

3. ๐Ÿš— ์ฃผ์ฐจ์žฅ ๋ฒˆํ˜ธํŒ ์ธ์‹
์ฐจ๋Ÿ‰ ๋ฒˆํ˜ธํŒ์„ ์ž๋™์œผ๋กœ ์ธ์‹ํ•ด์„œ ์ถœ์ž… ๊ด€๋ฆฌ

4. ๐Ÿ“‹ ๋ฌธ์„œ ๊ฒ€์ƒ‰ ์‹œ์Šคํ…œ
์Šค์บ”ํ•œ ๋ฌธ์„œ๋“ค์„ OCR ์ฒ˜๋ฆฌํ•ด์„œ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ

5. ๐ŸŒ ์‹ค์‹œ๊ฐ„ ๋ฒˆ์—ญ๊ธฐ
์นด๋ฉ”๋ผ๋กœ ์™ธ๊ตญ์–ด ๊ฐ„ํŒ์„ ์ฐ์œผ๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฒˆ์—ญ

6. ๐Ÿ“ ํ•„๊ธฐ ๋…ธํŠธ ๋””์ง€ํ„ธํ™”
์†์œผ๋กœ ์“ด ๋…ธํŠธ๋ฅผ ๋””์ง€ํ„ธ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜
๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ์šฐ๋ฆฌ๊ฐ€ ํ•จ๊ป˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด์ž๋ฉด: ๐ŸŽ‰

โœ… OCR์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๊ณผ ์›๋ฆฌ
โœ… Tesseract ์„ค์น˜ ๋ฐ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•
โœ… ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ๊ธฐ๋ฒ•๋“ค
โœ… PDF ๋ฌธ์„œ ์ฒ˜๋ฆฌ
โœ… ๊ณ ๊ธ‰ ์„ค์ • ๋ฐ ์ตœ์ ํ™”
โœ… ๋”ฅ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜ OCR (EasyOCR)
โœ… ์‹ค์ „ ํ”„๋กœ์ ํŠธ (๋ช…ํ•จ ์ธ์‹)
โœ… GUI ํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ
โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

์ด์ œ ๋„ˆ๋„ OCR ์ „๋ฌธ๊ฐ€์•ผ! ๐ŸŽŠ ์ฒ˜์Œ์—๋Š” ์–ด๋ ค์›Œ ๋ณด์˜€์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผํ•˜๋‹ค ๋ณด๋‹ˆ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์ง€?

์ค‘์š”ํ•œ ๊ฑด ๊ณ„์† ์—ฐ์Šตํ•˜๊ณ  ์‹คํ—˜ํ•ด๋ณด๋Š” ๊ฑฐ์•ผ. ๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€๋กœ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ณ , ์ „์ฒ˜๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์กฐ์ •ํ•ด๋ณด๊ณ , ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์‹œ๋„ํ•ด๋ด. ๊ทธ๋Ÿฌ๋‹ค ๋ณด๋ฉด ์ ์  ๋” ์ •ํ™•ํ•˜๊ณ  ๋น ๋ฅธ OCR ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿ’ช

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ๊ธฐ์ˆ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด ์ •๋ง ๋งŽ์€ ๊ณณ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ํšŒ์‚ฌ ์—…๋ฌด ์ž๋™ํ™”, ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ, ๋˜๋Š” ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ํ”„๋ฆฌ๋žœ์„œ๋กœ ํ™œ๋™ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์ง€!

๐ŸŒŸ ๋‹น์‹ ์˜ OCR ์—ฌ์ •์„ ์‘์›ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŒŸ

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋„์›€์ด ํ•„์š”ํ•˜๋ฉด ์–ธ์ œ๋“ ์ง€ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•ด๋ด.
ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ๊ฒŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๋งค๋ ฅ์ด๋‹ˆ๊นŒ! ๐Ÿ˜Š

Happy Coding! ๐Ÿš€

๐Ÿ’ผ ์ด ๊ธ€์ด ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
์žฌ๋Šฅ๋„ท์—์„œ ๋” ๋งŽ์€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ง€์‹์„ ๋งŒ๋‚˜๋ณด์„ธ์š”!

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.