smart-ocr

smart-ocr

熱門

>

299星標
68分支
更新於 2026/1/31
SKILL.md
readonlyread-only
name
smart-ocr
description

>

version
1.0

Smart OCR 技能

概述

本技能使用 PaddleOCR(支援 100 種以上語言的頂尖 OCR 引擎),從圖片與掃描文件中智慧擷取文字。可從照片、螢幕截圖、掃描 PDF 及手寫文件中準確提取文字。

使用方式

  1. 提供圖片或掃描文件
  2. 可選擇指定要偵測的語言
  3. 我會提取文字,並附上位置與信心度資料

範例提示:

  • 「從這張螢幕截圖中提取所有文字」
  • 「對這份掃描 PDF 文件進行 OCR」
  • 「讀取這張名片照片上的文字」
  • 「從這張圖片中提取中文與英文文字」

領域知識

PaddleOCR 基礎

from paddleocr import PaddleOCR

# 初始化 OCR 引擎
ocr = PaddleOCR(use_angle_cls=True, lang='en')

# 對圖片執行 OCR
result = ocr.ocr('image.png', cls=True)

# 結果結構:[[box, (text, confidence)], ...]
for line in result[0]:
    box = line[0]      # [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
    text = line[1][0]  # 提取的文字
    conf = line[1][1]  # 信心度分數
    print(f"{text} ({conf:.2f})")

支援的語言

# 常用語言代碼
languages = {
    'en': 'English',
    'ch': 'Chinese (Simplified)',
    'cht': 'Chinese (Traditional)',
    'japan': 'Japanese',
    'korean': 'Korean',
    'french': 'French',
    'german': 'German',
    'spanish': 'Spanish',
    'russian': 'Russian',
    'arabic': 'Arabic',
    'hindi': 'Hindi',
    'vi': 'Vietnamese',
    'th': 'Thai',
    # ... 支援 100 種以上語言
}

# 使用特定語言
ocr = PaddleOCR(lang='ch')  # 中文
ocr = PaddleOCR(lang='japan')  # 日文
ocr = PaddleOCR(lang='multilingual')  # 自動偵測

設定選項

from paddleocr import PaddleOCR

ocr = PaddleOCR(
    # 偵測設定
    det_model_dir=None,         # 自訂偵測模型
    det_limit_side_len=960,     # 偵測最大邊長
    det_db_thresh=0.3,          # 二值化閾值
    det_db_box_thresh=0.5,      # 邊界框分數閾值
    
    # 辨識設定
    rec_model_dir=None,         # 自訂辨識模型
    rec_char_dict_path=None,    # 自訂字元字典
    
    # 角度分類
    use_angle_cls=True,         # 啟用角度分類
    cls_model_dir=None,         # 自訂分類模型
    
    # 語言
    lang='en',                  # 語言代碼
    
    # 效能
    use_gpu=True,               # 使用 GPU(若可用)
    gpu_mem=500,                # GPU 記憶體限制(MB)
    enable_mkldnn=True,         # CPU 最佳化
    
    # 輸出
    show_log=False,             # 隱藏日誌
)

處理不同來源

圖片檔案
# 單張圖片
result = ocr.ocr('image.png')

# 多張圖片
images = ['img1.png', 'img2.png', 'img3.png']
for img in images:
    result = ocr.ocr(img)
    process_result(result)
PDF 檔案(掃描)
from pdf2image import convert_from_path

def ocr_pdf(pdf_path):
    """對掃描 PDF 進行 OCR。"""
    # 將 PDF 頁面轉換為圖片
    images = convert_from_path(pdf_path)
    
    all_text = []
    for i, img in enumerate(images):
        # 儲存暫存圖片
        temp_path = f'temp_page_{i}.png'
        img.save(temp_path)
        
        # 對圖片進行 OCR
        result = ocr.ocr(temp_path)
        
        # 提取文字
        page_text = '\n'.join([line[1][0] for line in result[0]])
        all_text.append(f"--- 第 {i+1} 頁 ---\n{page_text}")
        
        os.remove(temp_path)
    
    return '\n\n'.join(all_text)
URL 與位元組資料
import requests
from io import BytesIO

# 從 URL
response = requests.get('https://example.com/image.png')
result = ocr.ocr(BytesIO(response.content))

# 從位元組資料
with open('image.png', 'rb') as f:
    img_bytes = f.read()
result = ocr.ocr(BytesIO(img_bytes))

結果處理

def process_ocr_result(result):
    """將 OCR 結果處理為結構化資料。"""
    
    lines = []
    for line in result[0]:
        box = line[0]
        text = line[1][0]
        confidence = line[1][1]
        
        # 計算邊界框
        x_coords = [p[0] for p in box]
        y_coords = [p[1] for p in box]
        
        lines.append({
            'text': text,
            'confidence': confidence,
            'bbox': {
                'left': min(x_coords),
                'top': min(y_coords),
                'right': max(x_coords),
                'bottom': max(y_coords),
            },
            'raw_box': box
        })
    
    return lines

# 依位置排序(從上到下,從左到右)
def sort_by_position(lines):
    return sorted(lines, key=lambda x: (x['bbox']['top'], x['bbox']['left']))

文字版面重建

def reconstruct_layout(result, line_threshold=10):
    """從 OCR 結果重建文字版面。"""
    
    lines = process_ocr_result(result)
    lines = sort_by_position(lines)
    
    # 分組為邏輯行
    text_lines = []
    current_line = []
    current_y = None
    
    for line in lines:
        y = line['bbox']['top']
        
        if current_y is None or abs(y - current_y) < line_threshold:
            current_line.append(line)
            current_y = y
        else:
            # 新行
            text_lines.append(' '.join([l['text'] for l in current_line]))
            current_line = [line]
            current_y = y
    
    # 加入最後一行
    if current_line:
        text_lines.append(' '.join([l['text'] for l in current_line]))
    
    return '\n'.join(text_lines)

最佳實務

  1. 預處理圖片:在 OCR 前提升品質
  2. 選擇正確語言:指定語言以獲得更高準確度
  3. 處理多欄位:分別處理各欄位
  4. 過濾低信心度:跳過低於閾值的結果
  5. 批次處理:有效處理多張圖片

常見模式

圖片預處理

from PIL import Image, ImageEnhance, ImageFilter

def preprocess_image(image_path):
    """預處理圖片以獲得更好的 OCR 效果。"""
    img = Image.open(image_path)
    
    # 轉為灰階
    img = img.convert('L')
    
    # 增強對比度
    enhancer = ImageEnhance.Contrast(img)
    img = enhancer.enhance(2.0)
    
    # 銳化
    img = img.filter(ImageFilter.SHARPEN)
    
    # 儲存預處理結果
    preprocessed_path = 'preprocessed.png'
    img.save(preprocessed_path)
    
    return preprocessed_path

批次 OCR 含進度顯示

from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor

def batch_ocr(image_paths, max_workers=4):
    """並行 OCR 多張圖片。"""
    
    results = {}
    
    def process_single(img_path):
        result = ocr.ocr(img_path)
        return img_path, result
    
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(process_single, p) for p in image_paths]
        
        for future in tqdm(futures, desc="處理 OCR"):
            path, result = future.result()
            results[path] = result
    
    return results

範例

範例 1:名片讀取器

from paddleocr import PaddleOCR
import re

def read_business_card(image_path):
    """從名片中提取聯絡資訊。"""
    
    ocr = PaddleOCR(use_angle_cls=True, lang='en')
    result = ocr.ocr(image_path)
    
    # 提取所有文字
    all_text = []
    for line in result[0]:
        all_text.append(line[1][0])
    
    full_text = '\n'.join(all_text)
    
    # 解析聯絡資訊
    contact = {
        'name': None,
        'email': None,
        'phone': None,
        'company': None,
        'title': None,
        'raw_text': full_text
    }
    
    # Email 模式
    email_match = re.search(r'[\w\.-]+@[\w\.-]+\.\w+', full_text)
    if email_match:
        contact['email'] = email_match.group()
    
    # 電話模式
    phone_match = re.search(r'[\+\d][\d\s\-\(\)]{8,}', full_text)
    if phone_match:
        contact['phone'] = phone_match.group().strip()
    
    # 姓名通常是最大/第一個文字
    if all_text:
        contact['name'] = all_text[0]
    
    return contact

card_info = read_business_card('business_card.jpg')
print(f"姓名:{card_info['name']}")
print(f"Email:{card_info['email']}")
print(f"電話:{card_info['phone']}")

範例 2:收據掃描器

from paddleocr import PaddleOCR
import re

def scan_receipt(image_path):
    """從收據中提取品項與總額。"""
    
    ocr = PaddleOCR(use_angle_cls=True, lang='en')
    result = ocr.ocr(image_path)
    
    lines = []
    for line in result[0]:
        text = line[1][0]
        y_pos = line[0][0][1]
        lines.append({'text': text, 'y': y_pos})
    
    # 依垂直位置排序
    lines.sort(key=lambda x: x['y'])
    
    receipt = {
        'items': [],
        'subtotal': None,
        'tax': None,
        'total': None
    }
    
    for line in lines:
        text = line['text']
        
        # 尋找總額
        if 'total' in text.lower():
            amount = re.search(r'\$?([\d,]+\.?\d*)', text)
            if amount:
                if 'sub' in text.lower():
                    receipt['subtotal'] = float(amount.group(1).replace(',', ''))
                else:
                    receipt['total'] = float(amount.group(1).replace(',', ''))
        
        # 尋找稅金
        elif 'tax' in text.lower():
            amount = re.search(r'\$?([\d,]+\.?\d*)', text)
            if amount:
                receipt['tax'] = float(amount.group(1).replace(',', ''))
        
        # 尋找品項(含價格的行)
        else:
            item_match = re.search(r'(.+?)\s+\$?([\d,]+\.?\d+)$', text)
            if item_match:
                receipt['items'].append({
                    'name': item_match.group(1).strip(),
                    'price': float(item_match.group(2).replace(',', ''))
                })
    
    return receipt

receipt_data = scan_receipt('receipt.jpg')
print(f"品項數:{len(receipt_data['items'])}")
print(f"總額:${receipt_data['total']}")

範例 3:多語言文件

from paddleocr import PaddleOCR

def ocr_multilingual(image_path, languages=['en', 'ch']):
    """對多語言文件進行 OCR。"""
    
    all_results = {}
    
    for lang in languages:
        ocr = PaddleOCR(use_angle_cls=True, lang=lang)
        result = ocr.ocr(image_path)
        
        texts = []
        for line in result[0]:
            texts.append({
                'text': line[1][0],
                'confidence': line[1][1]
            })
        
        all_results[lang] = texts
    
    # 合併結果,保留最高信心度
    merged = {}
    for lang, texts in all_results.items():
        for item in texts:
            text = item['text']
            conf = item['confidence']
            
            if text not in merged or merged[text]['confidence'] < conf:
                merged[text] = {'confidence': conf, 'language': lang}
    
    return merged

result = ocr_multilingual('bilingual_document.png')
for text, info in result.items():
    print(f"[{info['language']}] {text} ({info['confidence']:.2f})")

限制

  • 手寫文字準確度因情況而異
  • 非常小的文字可能無法偵測
  • 複雜背景會降低準確度
  • 旋轉文字需要角度分類
  • 建議使用 GPU 以獲得最佳效能

安裝

# CPU 版本
pip install paddlepaddle paddleocr

# GPU 版本(CUDA 11.x)
pip install paddlepaddle-gpu paddleocr

# 額外相依套件
pip install pdf2image Pillow

資源