技能备份 - 2026-04-15 (40个技能)
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tesseract OCR wrapper with optimized configuration for mixed Chinese/English content
|
||||
"""
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
try:
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
print("[ERROR] Install dependencies: pip install opencv-python numpy pillow")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TesseractOCR:
|
||||
"""Tesseract OCR processor with preprocessing pipeline"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
verbose: bool = False,
|
||||
lang: str = 'chi_sim+eng',
|
||||
psm: int = 6,
|
||||
oem: int = 3
|
||||
):
|
||||
"""
|
||||
Initialize Tesseract processor.
|
||||
|
||||
Args:
|
||||
verbose: Enable detailed logging
|
||||
lang: Tesseract language code (e.g., 'eng', 'chi_sim', 'chi_sim+eng')
|
||||
psm: Page segmentation mode (default: 6, uniform block)
|
||||
oem: OCR engine mode (default: 3, LSTM only)
|
||||
"""
|
||||
self.verbose = verbose
|
||||
self.lang = lang
|
||||
self.psm = psm
|
||||
self.oem = oem
|
||||
|
||||
# Check tesseract availability
|
||||
self._check_tesseract()
|
||||
|
||||
def _check_tesseract(self) -> bool:
|
||||
"""Check if tesseract is installed"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['tesseract', '--version'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
if self.verbose:
|
||||
logger.info(f"Tesseract version: {result.stdout.split()[2]}")
|
||||
return True
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError, IndexError):
|
||||
logger.error("Tesseract not found. Install with:")
|
||||
logger.error(" macOS: brew install tesseract")
|
||||
logger.error(" Ubuntu: sudo apt install tesseract-ocr")
|
||||
logger.error(" Windows: Download from https://github.com/UB-Mannheim/tesseract/wiki")
|
||||
return False
|
||||
|
||||
def _preprocess_image(self, image_path: str) -> str:
|
||||
"""
|
||||
Apply preprocessing pipeline.
|
||||
|
||||
Returns:
|
||||
Path to processed image
|
||||
"""
|
||||
if self.verbose:
|
||||
logger.info("Preprocessing image...")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Read image
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise ValueError(f"Could not load image: {image_path}")
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Apply bilateral filter (noise reduction + edge preservation)
|
||||
bilateral = cv2.bilateralFilter(gray, 9, 75, 75)
|
||||
|
||||
# Adaptive thresholding for low contrast images
|
||||
min_val, max_val, _, _ = cv2.minMaxLoc(bilateral)
|
||||
contrast = max_val - min_val
|
||||
|
||||
if contrast < 100: # Low contrast
|
||||
processed = cv2.adaptiveThreshold(
|
||||
bilateral, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 11, 2
|
||||
)
|
||||
else:
|
||||
processed = bilateral
|
||||
|
||||
# Save processed image (temp file)
|
||||
input_path = Path(image_path)
|
||||
processed_path = input_path.parent / f"{input_path.stem}_tess_processed.png"
|
||||
cv2.imwrite(str(processed_path), processed)
|
||||
|
||||
if self.verbose:
|
||||
logger.info(f"Preprocessing completed in {time.time() - start_time:.2f}s")
|
||||
|
||||
return str(processed_path)
|
||||
|
||||
def _run_tesseract(
|
||||
self,
|
||||
image_path: str,
|
||||
lang: Optional[str] = None,
|
||||
psm: Optional[int] = None,
|
||||
oem: Optional[int] = None
|
||||
) -> Dict:
|
||||
"""
|
||||
Run Tesseract OCR.
|
||||
|
||||
Returns:
|
||||
Dict with text, confidence, timing
|
||||
"""
|
||||
if lang is None:
|
||||
lang = self.lang
|
||||
if psm is None:
|
||||
psm = self.psm
|
||||
if oem is None:
|
||||
oem = self.oem
|
||||
|
||||
# Run tesseract
|
||||
cmd = [
|
||||
'tesseract', image_path, 'stdout',
|
||||
'-l', lang,
|
||||
'--psm', str(psm),
|
||||
'--oem', str(oem),
|
||||
'_stdout'
|
||||
]
|
||||
|
||||
if self.verbose:
|
||||
logger.info(f"Running: {' '.join(cmd)}")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
processing_time = time.time() - start_time
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error(f"Tesseract error: {result.stderr}")
|
||||
return {
|
||||
'text': '',
|
||||
'confidence': 0.0,
|
||||
'error': result.stderr,
|
||||
'processing_time_ms': processing_time * 1000
|
||||
}
|
||||
|
||||
# Extract confidence if available
|
||||
text = result.stdout.strip()
|
||||
confidence = self._estimate_confidence(text)
|
||||
|
||||
return {
|
||||
'text': text,
|
||||
'confidence': confidence,
|
||||
'processing_time_ms': processing_time * 1000
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
'text': '',
|
||||
'confidence': 0.0,
|
||||
'error': 'Tesseract timeout',
|
||||
'processing_time_ms': 60000
|
||||
}
|
||||
|
||||
def _estimate_confidence(self, text: str) -> float:
|
||||
"""
|
||||
Estimate OCR confidence from output quality.
|
||||
|
||||
Simple heuristic: longer text with fewer garbage characters = higher confidence
|
||||
"""
|
||||
if not text:
|
||||
return 0.0
|
||||
|
||||
# Count printable characters
|
||||
printable = sum(1 for c in text if c.isprintable() or c in '\n\r\t')
|
||||
|
||||
# Length factor (more text = more reliable)
|
||||
length_factor = min(1.0, len(text) / 100)
|
||||
|
||||
# Quality factor
|
||||
quality = printable / max(len(text), 1)
|
||||
|
||||
# Combined score
|
||||
confidence = (0.6 * quality) + (0.4 * length_factor)
|
||||
|
||||
return round(confidence, 2)
|
||||
|
||||
def extract(self, image_path: str) -> Dict:
|
||||
"""
|
||||
Extract text from image using Tesseract.
|
||||
|
||||
Args:
|
||||
image_path: Path to input image
|
||||
|
||||
Returns:
|
||||
Dict with text, confidence, processing time
|
||||
"""
|
||||
if self.verbose:
|
||||
logger.info(f"Processing: {image_path}")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Preprocess
|
||||
processed_path = self._preprocess_image(image_path)
|
||||
|
||||
try:
|
||||
# Run OCR
|
||||
result = self._run_tesseract(
|
||||
processed_path,
|
||||
lang=self.lang,
|
||||
psm=self.psm,
|
||||
oem=self.oem
|
||||
)
|
||||
|
||||
# Clean up temp file
|
||||
Path(processed_path).unlink(missing_ok=True)
|
||||
|
||||
result['processing_time_ms'] = round(time.time() - start_time, 2)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
Path(processed_path).unlink(missing_ok=True)
|
||||
return {
|
||||
'text': '',
|
||||
'confidence': 0.0,
|
||||
'error': str(e),
|
||||
'processing_time_ms': (time.time() - start_time) * 1000
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Tesseract OCR wrapper')
|
||||
parser.add_argument('image', help='Image file to process')
|
||||
parser.add_argument('--lang', default='chi_sim+eng', help='Language code')
|
||||
parser.add_argument('--verbose', '-v', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
processor = TesseractOCR(verbose=args.verbose, lang=args.lang)
|
||||
result = processor.extract(args.image)
|
||||
|
||||
print(f"\nText:\n{result['text']}")
|
||||
print(f"\nConfidence: {result['confidence']:.2%}")
|
||||
print(f"Time: {result.get('processing_time_ms', 0):.2f}ms")
|
||||
Reference in New Issue
Block a user